From d30ff0c7aff7cceef4b4be906825c9ff0407f3a1 Mon Sep 17 00:00:00 2001 From: Juma Allan Date: Mon, 3 Oct 2022 19:38:51 +0300 Subject: [PATCH 01/26] updated gradle versions, and added sample app to test library --- app/build.gradle | 41 +++-- app/proguard-rules.pro | 21 +++ .../com/hover/app/ExampleInstrumentedTest.kt | 24 +++ app/src/main/AndroidManifest.xml | 19 +- .../drawable-v24/ic_launcher_foreground.xml | 30 ++++ .../res/drawable/ic_launcher_background.xml | 170 ++++++++++++++++++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + app/src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 0 -> 1404 bytes .../res/mipmap-hdpi/ic_launcher_round.webp | Bin 0 -> 2898 bytes app/src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 0 -> 982 bytes .../res/mipmap-mdpi/ic_launcher_round.webp | Bin 0 -> 1772 bytes .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin 0 -> 1900 bytes .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin 0 -> 3918 bytes .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 0 -> 2884 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 0 -> 5914 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 0 -> 3844 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 0 -> 7778 bytes app/src/main/res/values-night/themes.xml | 16 ++ app/src/main/res/values/colors.xml | 12 +- app/src/main/res/values/strings.xml | 4 +- app/src/main/res/values/themes.xml | 16 ++ .../java/com/hover/app/ExampleUnitTest.kt | 17 ++ build.gradle | 11 +- gradle/wrapper/gradle-wrapper.properties | 3 +- .../multisim/ExampleInstrumentedTest.java | 0 .../java/com/hover/multisim/DataSource.java | 0 .../com/hover/multisim/MultiSimWorker.java | 0 .../java/com/hover/multisim/SimContract.java | 0 .../com/hover/multisim/SimDataSource.java | 0 .../java/com/hover/multisim/SimDatabase.java | 0 .../main/java/com/hover/multisim/SimInfo.java | 0 .../java/com/hover/multisim/SlotManager.java | 0 .../main/java/com/hover/multisim/Utils.java | 0 .../src/main/res/values/styles.xml | 0 .../com/hover/multisim/ExampleUnitTest.java | 0 settings.gradle | 2 + 37 files changed, 361 insertions(+), 35 deletions(-) create mode 100644 app/proguard-rules.pro create mode 100644 app/src/androidTest/java/com/hover/app/ExampleInstrumentedTest.kt create mode 100644 app/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/values-night/themes.xml create mode 100644 app/src/main/res/values/themes.xml create mode 100644 app/src/test/java/com/hover/app/ExampleUnitTest.kt rename {app => multisim}/src/androidTest/java/com/hover/multisim/ExampleInstrumentedTest.java (100%) rename {app => multisim}/src/main/java/com/hover/multisim/DataSource.java (100%) rename {app => multisim}/src/main/java/com/hover/multisim/MultiSimWorker.java (100%) rename {app => multisim}/src/main/java/com/hover/multisim/SimContract.java (100%) rename {app => multisim}/src/main/java/com/hover/multisim/SimDataSource.java (100%) rename {app => multisim}/src/main/java/com/hover/multisim/SimDatabase.java (100%) rename {app => multisim}/src/main/java/com/hover/multisim/SimInfo.java (100%) rename {app => multisim}/src/main/java/com/hover/multisim/SlotManager.java (100%) rename {app => multisim}/src/main/java/com/hover/multisim/Utils.java (100%) rename {app => multisim}/src/main/res/values/styles.xml (100%) rename {app => multisim}/src/test/java/com/hover/multisim/ExampleUnitTest.java (100%) diff --git a/app/build.gradle b/app/build.gradle index b2d063e..0758add 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,31 +1,46 @@ -apply plugin: 'com.android.library' +apply plugin: 'com.android.application' +apply plugin: 'org.jetbrains.kotlin.android' android { - compileSdkVersion 28 + +// namespace 'com.hover.app' + compileSdkVersion 33 + defaultConfig { - minSdkVersion 18 - targetSdkVersion 28 + applicationId "com.hover.app" + minSdkVersion 21 + targetSdkVersion 33 versionCode 1 versionName "1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } + buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } } dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'androidx.appcompat:appcompat:1.1.0' - implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0' - implementation "androidx.work:work-runtime:2.2.0" - testImplementation 'junit:junit:4.12' - androidTestImplementation 'androidx.test.ext:junit:1.1.0' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' + implementation 'androidx.core:core-ktx:1.9.0' - implementation 'io.sentry:sentry-android:1.7.21' -} + implementation 'androidx.appcompat:appcompat:1.5.1' + implementation 'com.google.android.material:material:1.6.1' + + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/androidTest/java/com/hover/app/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/hover/app/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..aace88f --- /dev/null +++ b/app/src/androidTest/java/com/hover/app/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.hover.app + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.hover.app", appContext.packageName) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ec7315a..fb2a003 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,9 +1,12 @@ - + + - - + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..c209e78ecd372343283f4157dcfd918ec5165bb3 GIT binary patch literal 1404 zcmV-?1%vuhNk&F=1pok7MM6+kP&il$0000G0000-002h-06|PpNX!5L00Dqw+t%{r zzW2vH!KF=w&cMnnN@{whkTw+#mAh0SV?YL=)3MimFYCWp#fpdtz~8$hD5VPuQgtcN zXl<@<#Cme5f5yr2h%@8TWh?)bSK`O z^Z@d={gn7J{iyxL_y_%J|L>ep{dUxUP8a{byupH&!UNR*OutO~0{*T4q5R6@ApLF! z5{w?Z150gC7#>(VHFJZ-^6O@PYp{t!jH(_Z*nzTK4 zkc{fLE4Q3|mA2`CWQ3{8;gxGizgM!zccbdQoOLZc8hThi-IhN90RFT|zlxh3Ty&VG z?Fe{#9RrRnxzsu|Lg2ddugg7k%>0JeD+{XZ7>Z~{=|M+sh1MF7~ zz>To~`~LVQe1nNoR-gEzkpe{Ak^7{{ZBk2i_<+`Bq<^GB!RYG+z)h;Y3+<{zlMUYd zrd*W4w&jZ0%kBuDZ1EW&KLpyR7r2=}fF2%0VwHM4pUs}ZI2egi#DRMYZPek*^H9YK zay4Iy3WXFG(F14xYsoDA|KXgGc5%2DhmQ1gFCkrgHBm!lXG8I5h*uf{rn48Z!_@ z4Bk6TJAB2CKYqPjiX&mWoW>OPFGd$wqroa($ne7EUK;#3VYkXaew%Kh^3OrMhtjYN?XEoY`tRPQsAkH-DSL^QqyN0>^ zmC>{#F14jz4GeW{pJoRpLFa_*GI{?T93^rX7SPQgT@LbLqpNA}<@2wH;q493)G=1Y z#-sCiRNX~qf3KgiFzB3I>4Z%AfS(3$`-aMIBU+6?gbgDb!)L~A)je+;fR0jWLL-Fu z4)P{c7{B4Hp91&%??2$v9iRSFnuckHUm}or9seH6 z>%NbT+5*@L5(I9j@06@(!{ZI?U0=pKn8uwIg&L{JV14+8s2hnvbRrU|hZCd}IJu7*;;ECgO%8_*W Kmw_-CKmY()leWbG literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..b2dfe3d1ba5cf3ee31b3ecc1ced89044a1f3b7a9 GIT binary patch literal 2898 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..4f0f1d64e58ba64d180ce43ee13bf9a17835fbca GIT binary patch literal 982 zcmV;{11bDcNk&G_0{{S5MM6+kP&il$0000G0000l001ul06|PpNU8t;00Dqo+t#w^ z^1csucXz7-Qrhzl9HuHB%l>&>1tG2^vb*E&k^T3$FG1eQZ51g$uv4V+kI`0<^1Z@N zk?Jjh$olyC%l>)Xq;7!>{iBj&BjJ`P&$fsCfpve_epJOBkTF?nu-B7D!hO=2ZR}

C%4 zc_9eOXvPbC4kzU8YowIA8cW~Uv|eB&yYwAObSwL2vY~UYI7NXPvf3b+c^?wcs~_t{ ze_m66-0)^{JdOMKPwjpQ@Sna!*?$wTZ~su*tNv7o!gXT!GRgivP}ec?5>l1!7<(rT zds|8x(qGc673zrvYIz;J23FG{9nHMnAuP}NpAED^laz3mAN1sy+NXK)!6v1FxQ;lh zOBLA>$~P3r4b*NcqR;y6pwyhZ3_PiDb|%n1gGjl3ZU}ujInlP{eks-#oA6>rh&g+!f`hv#_%JrgYPu z(U^&XLW^QX7F9Z*SRPpQl{B%x)_AMp^}_v~?j7 zapvHMKxSf*Mtyx8I}-<*UGn3)oHd(nn=)BZ`d$lDBwq_GL($_TPaS{UeevT(AJ`p0 z9%+hQb6z)U9qjbuXjg|dExCLjpS8$VKQ55VsIC%@{N5t{NsW)=hNGI`J=x97_kbz@ E0Of=7!TQj4N+cqN`nQhxvX7dAV-`K|Ub$-q+H-5I?Tx0g9jWxd@A|?POE8`3b8fO$T))xP* z(X?&brZw({`)WU&rdAs1iTa0x6F@PIxJ&&L|dpySV!ID|iUhjCcKz(@mE z!x@~W#3H<)4Ae(4eQJRk`Iz3<1)6^m)0b_4_TRZ+cz#eD3f8V;2r-1fE!F}W zEi0MEkTTx}8i1{`l_6vo0(Vuh0HD$I4SjZ=?^?k82R51bC)2D_{y8mi_?X^=U?2|F{Vr7s!k(AZC$O#ZMyavHhlQ7 zUR~QXuH~#o#>(b$u4?s~HLF*3IcF7023AlwAYudn0FV~|odGH^05AYPEfR)8p`i{n zwg3zPVp{+wOsxKc>)(pMupKF!Y2HoUqQ3|Yu|8lwR=?5zZuhG6J?H`bSNk_wPoM{u zSL{c@pY7+c2kck>`^q1^^gR0QB7Y?KUD{vz-uVX~;V-rW)PDcI)$_UjgVV?S?=oLR zf4}zz{#*R_{LkiJ#0RdQLNC^2Vp%JPEUvG9ra2BVZ92(p9h7Ka@!yf9(lj#}>+|u* z;^_?KWdzkM`6gqPo9;;r6&JEa)}R3X{(CWv?NvgLeOTq$cZXqf7|sPImi-7cS8DCN zGf;DVt3Am`>hH3{4-WzH43Ftx)SofNe^-#|0HdCo<+8Qs!}TZP{HH8~z5n`ExcHuT zDL1m&|DVpIy=xsLO>8k92HcmfSKhflQ0H~9=^-{#!I1g(;+44xw~=* zxvNz35vfsQE)@)Zsp*6_GjYD};Squ83<_?^SbALb{a`j<0Gn%6JY!zhp=Fg}Ga2|8 z52e1WU%^L1}15Ex0fF$e@eCT(()_P zvV?CA%#Sy08_U6VPt4EtmVQraWJX` zh=N|WQ>LgrvF~R&qOfB$!%D3cGv?;Xh_z$z7k&s4N)$WYf*k=|*jCEkO19{h_(%W4 zPuOqbCw`SeAX*R}UUsbVsgtuG?xs(#Ikx9`JZoQFz0n*7ZG@Fv@kZk`gzO$HoA9kN z8U5{-yY zvV{`&WKU2$mZeoBmiJrEdzUZAv1sRxpePdg1)F*X^Y)zp^Y*R;;z~vOv-z&)&G)JQ{m!C9cmziu1^nHA z`#`0c>@PnQ9CJKgC5NjJD8HM3|KC(g5nnCq$n0Gsu_DXk36@ql%npEye|?%RmG)

FJ$wK}0tWNB{uH;AM~i literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..948a3070fe34c611c42c0d3ad3013a0dce358be0 GIT binary patch literal 1900 zcmV-y2b1_xNk&Fw2LJ$9MM6+kP&il$0000G0001A003VA06|PpNH75a00DqwTbm-~ zullQTcXxO9ki!OCRx^i?oR|n!<8G0=kI^!JSjFi-LL*`V;ET0H2IXfU0*i>o6o6Gy zRq6Ap5(_{XLdXcL-MzlN`ugSdZY_`jXhcENAu)N_0?GhF))9R;E`!bo9p?g?SRgw_ zEXHhFG$0{qYOqhdX<(wE4N@es3VIo$%il%6xP9gjiBri+2pI6aY4 zJbgh-Ud|V%3O!IcHKQx1FQH(_*TK;1>FQWbt^$K1zNn^cczkBs=QHCYZ8b&l!UV{K z{L0$KCf_&KR^}&2Fe|L&?1I7~pBENnCtCuH3sjcx6$c zwqkNkru);ie``q+_QI;IYLD9OV0ZxkuyBz|5<$1BH|vtey$> z5oto4=l-R-Aaq`Dk0}o9N0VrkqW_#;!u{!bJLDq%0092{Ghe=F;(kn} z+sQ@1=UlX30+2nWjkL$B^b!H2^QYO@iFc0{(-~yXj2TWz?VG{v`Jg zg}WyYnwGgn>{HFaG7E~pt=)sOO}*yd(UU-D(E&x{xKEl6OcU?pl)K%#U$dn1mDF19 zSw@l8G!GNFB3c3VVK0?uyqN&utT-D5%NM4g-3@Sii9tSXKtwce~uF zS&Jn746EW^wV~8zdQ1XC28~kXu8+Yo9p!<8h&(Q({J*4DBglPdpe4M_mD8AguZFn~ ztiuO~{6Bx?SfO~_ZV(GIboeR9~hAym{{fV|VM=77MxDrbW6`ujX z<3HF(>Zr;#*uCvC*bpoSr~C$h?_%nXps@A)=l_;({Fo#6Y1+Zv`!T5HB+)#^-Ud_; zBwftPN=d8Vx)*O1Mj+0oO=mZ+NVH*ptNDC-&zZ7Hwho6UQ#l-yNvc0Cm+2$$6YUk2D2t#vdZX-u3>-Be1u9gtTBiMB^xwWQ_rgvGpZ6(C@e23c!^K=>ai-Rqu zhqT`ZQof;9Bu!AD(i^PCbYV%yha9zuoKMp`U^z;3!+&d@Hud&_iy!O-$b9ZLcSRh? z)R|826w}TU!J#X6P%@Zh=La$I6zXa#h!B;{qfug}O%z@K{EZECu6zl)7CiNi%xti0 zB{OKfAj83~iJvmpTU|&q1^?^cIMn2RQ?jeSB95l}{DrEPTW{_gmU_pqTc)h@4T>~& zluq3)GM=xa(#^VU5}@FNqpc$?#SbVsX!~RH*5p0p@w z;~v{QMX0^bFT1!cXGM8K9FP+=9~-d~#TK#ZE{4umGT=;dfvWi?rYj;^l_Zxywze`W z^Cr{55U@*BalS}K%Czii_80e0#0#Zkhlij4-~I@}`-JFJ7$5{>LnoJSs??J8kWVl6|8A}RCGAu9^rAsfCE=2}tHwl93t0C?#+jMpvr7O3`2=tr{Hg$=HlnjVG^ewm|Js0J*kfPa6*GhtB>`fN!m#9J(sU!?(OSfzY*zS(FJ<-Vb zfAIg+`U)YaXv#sY(c--|X zEB+TVyZ%Ie4L$gi#Fc++`h6%vzsS$pjz9aLt+ZL(g;n$Dzy5=m=_TV(3H8^C{r0xd zp#a%}ht55dOq?yhwYPrtp-m1xXp;4X;)NhxxUpgP%XTLmO zcjaFva^}dP3$&sfFTIR_jC=2pHh9kpI@2(6V*GQo7Ws)`j)hd+tr@P~gR*2gO@+1? zG<`_tB+LJuF|SZ9tIec;h%}}6WClT`L>HSW?E{Hp1h^+mlbf_$9zA>!ug>NALJsO{ mU%z=YwVD?}XMya)Bp;vlyE5&E_6!fzx9pwrdz474!~g(M6R?N? literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..1b9a6956b3acdc11f40ce2bb3f6efbd845cc243f GIT binary patch literal 3918 zcmV-U53%r4Nk&FS4*&pHMM6+kP&il$0000G0001A003VA06|PpNSy@$00HoY|G(*G z+qV7x14$dSO^Re!iqt-AAIE9iwr$(CZQJL$blA4B`>;C3fBY6Q8_YSjb2%a=fc}4E zrSzssacq<^nmW|Rs93PJni30R<8w<(bK_$LO4L?!_OxLl$}K$MUEllnMK|rg=f3;y z*?;3j|Nh>)p0JQ3A~rf(MibH2r+)3cyV1qF&;8m{w-S*y+0mM){KTK^M5}ksc`qX3 zy>rf^b>~l>SSHds8(I@hz3&PD@LmEs4&prkT=BjsBCXTMhN$_)+kvnl0bLKW5rEsj z*d#KXGDB4P&>etx0X+`R19yC=LS)j!mgs5M0L~+o-T~Jl!p!AJxnGAhV%~rhYUL4hlWhgES3Kb5oA&X z{}?3OBSS-{!v$nCIGj->(-TAG)8LR{htr41^gxsT8yqt2@DEG6Yl`Uma3Nd4;YUoW zTbkYl3CMU5ypMF3EIkYmWL|*BknM`0+Kq6CpvO(y$#j94e+q{vI{Zp8cV_6RK!`&C zob$*5Q|$IZ09dW=L!V zw@#2wviu|<#3lgGE8GEhcx+zBt`} zOwP8j9X%^f7i_bth4PiJ$LYtFJSCN$3xwDN;8mr*B;CJwBP2G0TMq0uNt7S^DO_wE zepk!Wrn#Z#03j{`c*Rf~y3o7?J}w?tEELRUR2cgxB*Y{LzA#pxHgf}q?u5idu>077 zd^=p)`nA}6e`|@`p?u}YU66PP_MA}Zqqe!c{nK&z%Jwq1N4e_q<#4g^xaz=ao;u|6 zwpRcW2Lax=ZGbx=Q*HhlJ`Ns#Y*r0*%!T?P*TTiX;rb)$CGLz=rSUum$)3Qyv{BL2 zO*=OI2|%(Yz~`pNEOnLp>+?T@glq-DujlIp?hdJeZ7ctP4_OKx|5@EOps3rr(pWzg zK4d3&oN-X2qN(d_MkfwB4I)_)!I_6nj2iA9u^pQ{;GckGLxBGrJUM2Wdda!k)Y>lq zmjws>dVQ*vW9lvEMkiN3wE-__6OWD0txS&Qn0n22cyj4Q*8(nG4!G{6OOwNvsrPIL zCl-$W9UwkEUVuLwyD%|inbOF*xMODZ4VMEVAq_zUxZ+K#Gdqf!DW$5f)?7UNOFMz! zrB~tuu=6X2FE(p^iqgxr+?ZK;=yz`e;C$#_@D9Lj-+TDVOrva>(#*PVbaHO>A)mhl z07OJWCqYC60518$!&c`eNBcBW%GnfaQ*$eazV^2_AW?j)h;J1nUjN(I9=0+!RVx~% z3@Tf!P0TE+98jA?WceK-}A1% zW!K)lyKcGqy#M~})315-A#2NXQ`?6NR#Apo=S!oF=JfpX>iR*49ec{7AN$xxpK{D$ z2d%Fz&rdfSqourN$~Y^NFIMV1CZ?J*bMx~H3k&meGtH@q9ra2vZxmA$S(#jaaj-g4 ztJmxG+DLV<*q<|sDXPp$X>E)#S}Vm&sRaO5P&goh2><}FEdZSXDqsL$06sAkh(e+v zAsBhKSRexgwg6tIy~GFJzaTxXD(}|+0eOwFDA%rn`X;MVwDHT9=4=g%OaJ9s%3b9>9EUTnnp0t;2Zpa{*>mk~hZqItE_!dQ zOtC>8`$l|mV43Jbudf0N6&&X;{=z}Zi}d1`2qmJ}i|0*GsulD3>GgQXHN)pkR6sf1 z?5ZU%&xtL}oH;YiAA)d*^Ndw2T$+Mjuzyzz@-SM`9df7LqTxLuIwC~S0092~+=qYv z@*ja;?Wt!T!{U?c*Z0YtGe)XbI&y-?B&G2$`JDM)(dIV9G`Sc#6?sI60de6kv+)Qb zUW~2|WjvJq3TA8`0+sWA3zRhY9a~ow)O~&StBkG2{*{TGiY~S8ep{V&Vo2l<6LWsu z^#p0-v*t2?3&aA1)ozu|%efSR=XnpX$lvTeRdKlvM!@|pM5p2w3u-6 zU>}t2xiYLS+{|%C65AzX+23Mtlq?BS&YdYcYsVjoiE&rT>;Necn6l^K)T^lmE`5u{ zm1i+-a-gc;Z&v-{;8r)z6NYfBUv+=_L}ef}qa9FX01)+Aaf+;xj(mL6|JUzGJR1|fnanb%?BPPIp>SCjP|8qE5qJ{=n5ZGw?81z3(k;pzH%1CtlX50{E7h)$h{qGKfzC`e2o`*IqA#tjA z`Fz&^%$b9F*N`)U-#6>a)Z`55`$Dd0cfcs0$d13^ONrdCu9xcv_=n#WQo8stcz3jP9|2EvdI-RhJM3%Q%oM&!OlShM|0 z?gz?wHZSnm45njLtsz8PVT1S&jAlbKg5kVam$p16=EK@Sj4EP0OtH zmJDmdc^v)x>56Qg_wmYHz6h)>kl_h$>0@J!ypv%APmjZTAQVLy6Fu50RGY&JAVNhx zrF_qG6`x9MkT;1SFWo$)l{M$;3qUDn9JwE}z zRl#E_bDRJFii61kPgBybIgp8dNW!Cc1b*^YYk-#oWLJvtM_v^hQx~9?8LD4VFFxBF z3MlrsSC%f9Oupn*ctPL0U1fwfX?`tRhPD{PSLFPQOmIt$mDy0SgpNVvHS+f#Do>h1Gn?LZU9(KaN>Q_=Y*_T zvtD7%_u^^+{g`0VGzg(VZrpVQ6Ub5M=tI_p7T93R8@3Zulu3|#{iNcu!oiHxZ4Rf*( zfmiN$$ru(*_Zqn=`Gq#OuHRTSwp7uH_SokR&|)RuW5yo=Z|_4?qU-JU+tpt>!B&Is z@N(=SG;bpVc;AO@zbmMM zScqq1)b-ZQIrs={oD}|?6y{$HNB1U0^LsBh8JI&3!GBZxOXI<}&5-$lgkAaYqhOTb z?2vEnZ$-kk;*M_17(upJF3%+iH*s0-r{vttXVB2OUwI1s^+G(Ft(U8gYFXC}#P&E^ z>T@C^tS`Z7{6HT4_nF~n>JlZtk5&qDBl6r|^kzQYe`wq!C)n@$c>WOPA61NDFj<<6 zGW71NMMhwAl!U-yqrq2xrSFqRCI8acw7?}3j;ynxo*-b7Co;g5r%^j=H@9({PXXBf z@r>U>>N;E)81wx`B4f%{PB~MHka_);%kBCb(d|Jy5!MqJ%2p`t&@L)4$T2j&-WHvG zv3(uyA_gwqNu(k?jQTtv3dgPKRZoH8prxe7>pQBW5L&dpumS&5Ld2?(sCpJjvc4L5 zEnh&?91WVm)ZdTj=fjJ$pPDdgAttLXuke+?KdKxu*;kTC(r!tQk6;gxj4h%FdHAt(^M3YvYj(!tOeN)+Hvj6+< zzyJRG?^lZfWuR#t!tUKP&(?%3v&Zd$R2YN>lB(Lq`OInY48%4%yTv2 zYe1{G`3)(PDEio5Y@-I5tUf`c%%OCJMtSW56g3iEg%3`$7XSJJHyA z<|7&N)5Xrlgv~%BO24eFd;Hd;uiK%D`EdK|quUeRZDqbh9l)%j%J#0lfrZumvA<_w zu&=AVvdChf6}eqh(bUz`(`Ue*p01{fBAcTgKyDYLs_I+YyJEk+rM@avU~>fB$n)HS zM7pfJydu`i%gfS<{PF94kZDv$t>06sAkheDzu40NJ$5CMW%n^Lls?8^p^QGWURbKu3ZduZQZ((s2? zzE`}<{;Zt7<$C|9R8A~DJ~@%x>TfP zF>TX8)@v|t)q4GjRt<}5s6hLHwRel7>V@&r-O|Av(yh;Q1A{E>Ir>p+%dHD|=l+lT zpr(Dg&>#Nu=!)6bCLr-ZS%|;h)Ij$+e@r8_{qO19QvDe=&1tmpY*0lcA^Cc-#{9fQ z<~$*<&P$Q<_jy#<$40PMofM7aQ}C=jphI`4kLg}Z7CIN#26D{-4v-_CA-LiE@(%{y!BzsU%gG`Q?sjLUf%qFSl0y)2#ae*+EI>s|i`d^V$Dn)qmzqRq6VJRY|{4ujsIU%#bnqU6MR&-1I_43=|5(6Jr;Jvert) zE?S|Tmn}Tv<-??sxV5@9t}3D=>YZ0JrQe$CO~|EY=Lj9RM&4svQHPQL6%pV5fPFiH zfXDx;l@~et{*{U*#c#Dvzu)|znDO7$#CRx)Z&yp-}SrD{&|(MQtfUz~n35@RLfUy=aqrhCX0M}J_r5QsK~NmRCR|Nm&L z41UdsLjWxSUlL41r^0K&nCCK>fdR-!MYjFg(z9_mF^C|#ZQw?`)f6uVzF^`bRnVY& zo}@M06J&_+>w9@jpaO4snmU;0t-(zYW1qVBHtuD!d?%?AtN7Plp><-1Y8Rqb20ZaP zTCgn*-Sri4Q8Xn>=gNaWQ57%!D35UkA@ksOlPB*Dvw}t02ENAqw|kFhn%ZyyW%+t{ zNdM!uqEM^;2}f+tECHbwLmH*!nZVrb$-az%t50Y2pg(HqhvY-^-lb}>^6l{$jOI6} zo_kBzj%8aX|6H5M0Y<)7pzz_wLkIpRm!;PzY)9+24wk2&TT{w--phDGDCOz{cN_ca zpnm7`$oDy=HX%0i-`769*0M6(e5j-?(?24%)<)&46y0e&6@HCDZAm9W6Ib#Y#BF6- z=30crHGg+RRTe%VBC>T00OV6F+gQDAK38Ne3N9bm|62tPccBJi)5{B z4zc^Db72XiBd}v$CF|yU{Z=M|DZ%-(XarYNclODlb1Kz1_EKLy(NSLCN`eUl(rBCL zT*jx@wNvze0|TSqgE(QArOZU)_?qH(sj#TwzElLs9q)(0u!_P|R%Cy_0JFQxgGV>1 zz4?_uq<8_gM0`c*Hh|;UMz~vrg1gQXp{ufg`hM_qU;U>+zmvc5blCLSq@PrEBSGR# z&8=2Z4uXN`F3p73ueD1l{s{k$WipAvSh5W7ABe?4)t;r@V?y`bNB5FvBuE|0VRTb< zM1Hn^?DSsJY+sX@T5xW=#>T9VEV|?<(=6|ge$X6Sb05!LFdjDcoq*gM(Zq=t;_)Le&jyt(&9jzR73noru`a# zN*<`KwGa^gZU3-)MSLF0aFag#f0<>E(bYTeHmtdbns#|I)-$)mJ`q9ctQ8g0=ET?| zdO}eZ*b_p>ygRTtR^5Ggdam=Zb5wmd{}np+Jn1d_=M`~P=M67jj})fH4ztb5yQqQW z^C|C&^LHAK-u+ooIK)yM)QM?t;|<{P;;{`p=BclzAN#JzL4jCwXkQB1Dy{=^KR`=~ zTrr)y7eiYBzSNs_DvO=4A6#EgGS-zY%Vi)N*Yb`U;6o}KR}dq{r9pT5wqZ@3NOE8- z9-(}D|Nc5732CSYQbL)!gPQ#RbD8BhK3dl{sUuPvei0tkvnJBxDEAYTesU8H$)g(Plra{VH(v3u^CO1~(+ zU0O7#)jaS4{NcwA+LuSm&VBcX2#Im3xg)W}ySNw%->orn1taZ&+d)}8gJTqA!u|5P z{yv?zol_3|(1(%M(EVU=cp?L`{Pi|ixk{U)*guFML3P!OSlz;zGA#T+E@8@cgQ_mv1o7RSU=Zo_82F?&&2r;WE z@wk}JHYEZ9nYUc(Vv~iTCa3u8e4q(yq<29VoNbKk|`mq%I6u)My=gPIDuUb&lzf4`MEA9^g8u z)vp8|$$HE9m_BTV?lOosIGa4jud=jIbw)O2eCMfyw2*S8?hjWw^nqws$O*M$3I1)x zR0PWFb3$ySOcGTe1dz%N0l;RPc`x%05FtT^f^j{YCP}*Q=lvp4$ZXrTZQHhO+w%wJn3c8j%+5C3UAFD&%8dBl_qi9D5g8fry}6Ev z2_Q~)5^N$!IU`BPh1O|=BxQ#*C5*}`lluC515$lxc-vNC)IgW=K|=z7o%cWFpndn= zX}f{`!VK02_kU+Q5a3m37J;c} zTzbxteE{GNf?yLt5X=Bzc-mio^Up0nunMCgp*ZJ;%MJvPM3QK)BryP(_v@ei4UvHr z6+sbCifQaOkL6-;5fL8$W($zZ_;CZp305C;~$hhRquZr-r)jjd1z z31%ZK{-(`P#|Um_Sivn@p$-vz46uqT>QG0B1w9znfS9A8PB2LaHdzA|_)yjXVR*l{ zkcu3@vEf7bxH0nkh`q?8FmoO_Ucui*>_a~P?qQrlZ9@+D7%MTpSnztpylXrt5!-k8_QPB?YL8Kx_On8WD zgT+111d(Op$^$&KLAN5+@?>f7F4~wFi(8TL8+szgVmcMDTp5l&k6~=rA{Dt}!gb^r zSWY<)M7D|Z2P0cEodj6E42PV>&>DFmQpgt)E-|#sSUU@uKed+F680H@<;-x{p|nuH4!_mn85rx>wz;0mPi2ZkL#k6;sznu?cXh!T0S>{w6 zL^gvR05NY64l*<+_L>On$rjx9!US;l;LX6@z}yi#2XHh)F@Oo+l)h%fq$v}DNmF2> zfs^_t0)3N-W<9-N?uedVv{)-J0W5mh#29QM5R5h&KuiRM=0Zvnf#lF=K#WlCgc#9c zS;qvh(P$!_a8JwyhI^ZJV2k+B6Z^64?w|1?5gyo6y{}923CRZfYVe1#?F% z7h2SUiNO3;T#JUOyovSs@@C1GtwipycA=*x5{BpIZ_#GCMuV8XK=x;qCNy{d7?wA~ zC+=vjls;ci&zW=6$H~4^K%v{p}Ab?U%C6Z4p%eC<3ExqU$XR<}LLF67A$Sr20DR_pJ3yeBa~ z^sw{V0FI5;UpwXsScYuhbqGQ`YQ25;6p6W^+tgL&;Ml;>S3CGpSZ>VrTn0m1$y$HU z&65)I!c?oREz};c=nLCliriqQX->4uivHTgd${GqeAlf*!P^B|jkU|*IdNP(&6C>4 zqOW$)Nw9nvjy^&`?E|gotDV{JmJ9Q~vuhy<`^C4XIUDt|j4o6rK^e8_(=YqC zuaR6TRVf@tUFHB079o4MBIh{M~4>WwnGgesQH*3?w(RA%hCZ*7)b!aNV=yOQ%o_Y=Lt0Sl*(9^jfRnC210Om$=y>*o|3z} zAR&vAdrB#mWoaB0fJSw9xw|Am$fzK>rx-~R#7IFSAwdu_EI|SRfB*yl0w8oX09H^q zAjl2?0I)v*odGJ40FVGaF&2qJq9Gv`>V>2r0|c`GX8h>CX8eHcOy>S0@<;M3<_6UM z7yCEpug5NZL!H_0>Hg_HasQGxR`rY&Z{geOy?N92Z z{lER^um|$*?*G63*njwc(R?NT)Bei*3jVzR>FWUDb^gKhtL4A=kE_1p-%Fo2`!8M} z(0AjuCiS;G{?*^1tB-uY%=)SRx&D)pK4u@>f6@KPe3}2j_har$>HqzH;UCR^ssFD0 z7h+VLO4o@_Yt>>AeaZKUxqyvxWCAjKB>qjQ30UA)#w z&=RmdwlT`7a8J8Yae=7*c8XL|{@%wA8uvCqfsNX^?UZsS>wX}QD{K}ad4y~iO*p%4 z_cS{u7Ek%?WV6em2(U9#d8(&JDirb^u~7wK4+xP$iiI6IlD|a&S)6o=kG;59N|>K1 zn(0mUqbG3YIY7dQd+*4~)`!S9m7H6HP6YcKHhBc#b%1L}VIisp%;TckEkcu0>lo@u995$<*Em;XNodjTiCdC%R+TX|_ZR#|1`RR|`^@Teh zl#w@8fI1FTx2Dy+{blUT{`^kY*V-AZUd?ZZqCS4gW(kY5?retkLbF=>p=59Nl|=sf zo1Pc|{{N4>5nt#627ylGF`3n>X%`w%bw-Y~zWM_{Si$dc82|=YhISal{N7OY?O`C4 zD|qb}6nLWJ`hUyL+E>-;ricg9J@ZNYP(x(Sct&OI$Y!QWr*=^VN;G3#i>^1n4e#Je zOVhbFbLpXVu*16enDM+ic;97@R~u&kh__kgP#!R`*rQEnA+_dLkNP~L`0alC|J;c; zeiK=s8;BsLE)KbG3BD&Br@(Ha@SBT&$?xX`=$;eeel=|R_dIr6-Ro?=HEjnsJ_b`1 zK6Yg^-6;^2aW!xeTK)A~3Rm|L^FCHB_I>jIju7ZGo&N_1*QHkxH2!!%@o4iZ?vntS;&zJdPe1dH#04YD93A44o-MpfD zP{rn_aq>U%RDvC2+bp;xPlsOzauIi3*Lf42`jVKKZCRuKdYhi>FDuL2l=v{$BCN#Q6796s%r-AG$Q^t(3c@ zD?w0UhYr11@feiyl9kY_@H8~|xlmO<8PfQmj1!$@WieW@VxR@Psxfe-v9WCi1+f>F4VL?0O~K7T?m4-u|pSkBpUJZZe*16_wAp zSYZ@;k`3;W3UHKUWc8QeI}0jH5Ly=cGWQPw(Kr2fm=-5L(d`lcXofy8tJY3@Tuadz zYWXR{mW7XT!RF#RVCe%}=tM*O6!AD3^(!8un~opNI%Uko7$5t@<8+?; zTxDys(MyyGsUjtSu9$+|_-t!U3fVb1dkK?l`17<+jfl=hrBHnDSV>^R1=TnQeyqbW z>ov#l%!1|S!1>8UUxIdhQq`_klcHVx0{?#>K3#$4GlXncwldt!g17TcvKq-jo_996 z>oA=tH9CqRl6Yw?Uc`am!V?lHJbizOJaVaScf1UP5e7Dbgabq=b!B~T&_F6?ooU>w%x0A zH~&MHJ=q`fCH{U<7MDXE4SD32cDZA)WJeWkllJ`UspWaS#eDe^kg^oU_A14UE9zG-a^g{xaXf$})Wik>gT zl#dkzGr(;h0JZDuFn(+k8wNq?PZ5grQ<+sM?wBGt@JnH6v0#or-5wBQWKU~(S_> zkE!tc*ZJ1Y&*p(xX84POb3cClRMd!^qJ#CAZfIepEj-<`VURS_yCz0(?*Ixcj4 z-!zV1_QZhpm=0<;*(nm+F>T=)o?ep@CK5I%g^VAA+RB25ab?7)A~z~egru=I1S|@v zH7tXV!0wmGS^qj#e+MY;C5eUjEAp$Y?LDkS^QPZ}8WN85?r$u<-Epi;yZ1|J2J`se z$D6DpH~2F=eI0B&=UFAUnJvZAmClJlK)sutJ?M>xpZiWV&0=G4MZP+x+p>EX=HbCz zxls%Mw?*u^;LbHWIWCyq+yi)`GmFn9J112CZda_u@YIP%i;srFg_paU02Ifij*7}l z&CF-(3|>*a|+vbNR`^RP=9G?ymEJ0Z~)d&c*UE$UMepZ zcITr{0WqhxkjUnM15js_gW=e3Uh|y6ZReaXHIz-=p`x5VvB&rH9y>Amv@^WmXFEw) zQXYrk3feir=a{jMQ+wDIkkFnZ$k{sJakHn*?u za%4b!00ev8NVLM1TY=cl?KB&55BY_MU-sg?c>=Dbz_W{(Z~c?HJi*XpYL)C6Bd8WH zt+v-#0&o~@t4qESi*)+eW%@VD0|o^yF)n0hME$UtXF$*Lvh}7sso{`|pn*JDIy5^Fm3s$5*zEE=?u5<=l8FJc3r%+H} zdfoNl2J0^~!-*mOL5o-x32|e0Im*E!yY7F7E5N)W3>+v_LBydlEx?4$RL5f2oYRD# zaR0wv(-p~wO0eLDl3K=%`{5+0Gd$ktO=W)gWlGZJ0`K z$_RNA=ckrfa;H0KA~dR^p�(p-{x$&=IACIfoAR!za)F-^da-t3#0Dycnp zwO~NVXwXCl;jE<}>%@xz|=8fIJAB?>+E{7)|4l${4ngA3G|=r z2Dyv;VVWSgZx9Wj>qUjleGl3Ei9K4>h!(lPS%8VOG>Xu0%6VDz^O=bjJmuP7>DeUv zrbI}MlHB^^d?{zv6d=@_ZD2lg1&G7UjnVN{1}9WkaM3H~btX0GtSzB+tZ^qRgWo4m z!GmimlG$=wgXCnr6j@m<1gAL46#T~5Bnm=2{^@>|t&`9mkEPddj zAvG~@Tv~TAm2i%VW}R-g(Z0)z-Y|szHr@rk>4MAyG*Ma*7Yh#H7(!-5>DZ@8r;_dx z{prSe<>~099F8vsYd2xff7uAS%7{S)f(|@me3t2$iy&NEc7OUEchp@9A|X;;IA>8!oX+y(BKJ$EzV* znR$z;!L$s7uy@{OT~nG#B!NRraT8(X##Ho!0r_o@gg0CA-9H^;-uE&?$2$nHv_00o z%cbuUc-tCx$Uh&EZ4Nf4Zgqv)Y6>usG3>GeQnxx_Z6+PcbX-+ysbt1hQ`K1LDpOE? zrAhIZhSN9yVIAOa22gn577tbc&i3|3V8NWy&!tw##`}9*x}gtI^h1DzZRA>UuaJG) zaZ7j)dq!O}{?#8Y7~7i6fHh4{`pL?>-18|p!S75Y#^DM>-S3)vuZG+Q7l@ek zQP~#cBpWgg#mApc_sPYjpw8odQuRokmTkzcNl`^CcKB7e&;zViV;{Y{o^Y$%7i0m# z62%#1Lq!RC?}lK>%mp}T!3Xv;L*0v*>USLm``N%>w>@fwC+#T&Tx2bN4w(20JB}oU zuSa6v^kXi0xPs?pbaOHnyiqq6By1EZY9OZ^^QA>{q-Hsd&m`pbQ%8121aWG-F5xf zlZ%;B{;C>X19|`^_?dVyCq>n+41w7|!tUS!{9rHlbhX=SZO5CQ^;!Du_E7*`GiR^Q w)2!4MKjfSAeNo!9>IaV6aUZ*?W>} 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

UmY2RIYtNPVDpE$%vda+HD#3m z&VuXJ{BK&Qe+rBa7eq}Q(bq|tn(RrJAk|ztj2(i{d>nmQnM?;HF2k&9sA6up5tmjl z7lySlzMbifH17-m-Lwa_F&e7nOH?ESi3#ckR3tsM+jsck3`oG!uMS}|eAwVXv>}qxwq?QY%QJ0}r@^;fhuUA9W z*BVl>TGo&N004@xSiwDUXUvp51sVmqO3m)=B55aPwf@0=e}cN+$-BdKxY`YrT_4)0 z_d10#i44Q*rFr8MC>*)v$EJvz``(pb{e&*6k+b zsMz%($|1+8hn8c2?P(l@;Rb&CsZeYoCI3?2!LqjbwPXW3z4G$Qfj=cT5Yb%vY0(AX oeb?AaKtwrnc|$|zzw9vfvn^aJJ!zd)XFXqqy0000001=f@-~a#s literal 0 HcmV?d00001 diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml new file mode 100644 index 0000000..a7950b8 --- /dev/null +++ b/app/src/main/res/values-night/themes.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index a491964..f8c6127 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -1,6 +1,10 @@ - #008577 - #00574B - #D81B60 - + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 68e3975..4daeaed 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,3 +1,3 @@ - MultiSim - + Multisim + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..c00448c --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/app/src/test/java/com/hover/app/ExampleUnitTest.kt b/app/src/test/java/com/hover/app/ExampleUnitTest.kt new file mode 100644 index 0000000..9d29a1c --- /dev/null +++ b/app/src/test/java/com/hover/app/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.hover.app + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/build.gradle b/build.gradle index 48947c3..0a64348 100644 --- a/build.gradle +++ b/build.gradle @@ -3,12 +3,12 @@ buildscript { repositories { google() - jcenter() - + mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:3.5.1' - + classpath 'com.android.tools.build:gradle:3.5.4' + classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10' + // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } @@ -17,8 +17,7 @@ buildscript { allprojects { repositories { google() - jcenter() - + mavenCentral() } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index acb77f0..aa991fc 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Fri Nov 01 12:42:32 PDT 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip diff --git a/app/src/androidTest/java/com/hover/multisim/ExampleInstrumentedTest.java b/multisim/src/androidTest/java/com/hover/multisim/ExampleInstrumentedTest.java similarity index 100% rename from app/src/androidTest/java/com/hover/multisim/ExampleInstrumentedTest.java rename to multisim/src/androidTest/java/com/hover/multisim/ExampleInstrumentedTest.java diff --git a/app/src/main/java/com/hover/multisim/DataSource.java b/multisim/src/main/java/com/hover/multisim/DataSource.java similarity index 100% rename from app/src/main/java/com/hover/multisim/DataSource.java rename to multisim/src/main/java/com/hover/multisim/DataSource.java diff --git a/app/src/main/java/com/hover/multisim/MultiSimWorker.java b/multisim/src/main/java/com/hover/multisim/MultiSimWorker.java similarity index 100% rename from app/src/main/java/com/hover/multisim/MultiSimWorker.java rename to multisim/src/main/java/com/hover/multisim/MultiSimWorker.java diff --git a/app/src/main/java/com/hover/multisim/SimContract.java b/multisim/src/main/java/com/hover/multisim/SimContract.java similarity index 100% rename from app/src/main/java/com/hover/multisim/SimContract.java rename to multisim/src/main/java/com/hover/multisim/SimContract.java diff --git a/app/src/main/java/com/hover/multisim/SimDataSource.java b/multisim/src/main/java/com/hover/multisim/SimDataSource.java similarity index 100% rename from app/src/main/java/com/hover/multisim/SimDataSource.java rename to multisim/src/main/java/com/hover/multisim/SimDataSource.java diff --git a/app/src/main/java/com/hover/multisim/SimDatabase.java b/multisim/src/main/java/com/hover/multisim/SimDatabase.java similarity index 100% rename from app/src/main/java/com/hover/multisim/SimDatabase.java rename to multisim/src/main/java/com/hover/multisim/SimDatabase.java diff --git a/app/src/main/java/com/hover/multisim/SimInfo.java b/multisim/src/main/java/com/hover/multisim/SimInfo.java similarity index 100% rename from app/src/main/java/com/hover/multisim/SimInfo.java rename to multisim/src/main/java/com/hover/multisim/SimInfo.java diff --git a/app/src/main/java/com/hover/multisim/SlotManager.java b/multisim/src/main/java/com/hover/multisim/SlotManager.java similarity index 100% rename from app/src/main/java/com/hover/multisim/SlotManager.java rename to multisim/src/main/java/com/hover/multisim/SlotManager.java diff --git a/app/src/main/java/com/hover/multisim/Utils.java b/multisim/src/main/java/com/hover/multisim/Utils.java similarity index 100% rename from app/src/main/java/com/hover/multisim/Utils.java rename to multisim/src/main/java/com/hover/multisim/Utils.java diff --git a/app/src/main/res/values/styles.xml b/multisim/src/main/res/values/styles.xml similarity index 100% rename from app/src/main/res/values/styles.xml rename to multisim/src/main/res/values/styles.xml diff --git a/app/src/test/java/com/hover/multisim/ExampleUnitTest.java b/multisim/src/test/java/com/hover/multisim/ExampleUnitTest.java similarity index 100% rename from app/src/test/java/com/hover/multisim/ExampleUnitTest.java rename to multisim/src/test/java/com/hover/multisim/ExampleUnitTest.java diff --git a/settings.gradle b/settings.gradle index d9c5076..a3cea40 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,4 @@ include ':app' +include ':multisim' + rootProject.name='MultiSim' From 5d2942584902fca3191c80df8c787ec9b4199fbd Mon Sep 17 00:00:00 2001 From: Juma Allan Date: Mon, 3 Oct 2022 19:59:05 +0300 Subject: [PATCH 02/26] setting up sample app --- app/.gitignore | 2 +- app/build.gradle | 2 +- build.gradle | 2 +- multisim/.gitignore | 1 + multisim/build.gradle | 31 ++++++++++++++++++++++++ multisim/src/main/AndroidManifest.xml | 9 +++++++ multisim/src/main/res/values/colors.xml | 6 +++++ multisim/src/main/res/values/strings.xml | 3 +++ 8 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 multisim/.gitignore create mode 100644 multisim/build.gradle create mode 100644 multisim/src/main/AndroidManifest.xml create mode 100644 multisim/src/main/res/values/colors.xml create mode 100644 multisim/src/main/res/values/strings.xml diff --git a/app/.gitignore b/app/.gitignore index 796b96d..42afabf 100644 --- a/app/.gitignore +++ b/app/.gitignore @@ -1 +1 @@ -/build +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 0758add..d26efe5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,7 +3,7 @@ apply plugin: 'org.jetbrains.kotlin.android' android { -// namespace 'com.hover.app' + namespace 'com.hover.app' compileSdkVersion 33 defaultConfig { diff --git a/build.gradle b/build.gradle index 0a64348..6fabb3d 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:3.5.4' + classpath 'com.android.tools.build:gradle:4.2.2' classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10' // NOTE: Do not place your application dependencies here; they belong diff --git a/multisim/.gitignore b/multisim/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/multisim/.gitignore @@ -0,0 +1 @@ +/build diff --git a/multisim/build.gradle b/multisim/build.gradle new file mode 100644 index 0000000..e537ea3 --- /dev/null +++ b/multisim/build.gradle @@ -0,0 +1,31 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 33 + defaultConfig { + minSdkVersion 18 + targetSdkVersion 33 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0' + implementation "androidx.work:work-runtime:2.2.0" + + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test.ext:junit:1.1.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' + + implementation 'io.sentry:sentry-android:1.7.21' +} diff --git a/multisim/src/main/AndroidManifest.xml b/multisim/src/main/AndroidManifest.xml new file mode 100644 index 0000000..ec7315a --- /dev/null +++ b/multisim/src/main/AndroidManifest.xml @@ -0,0 +1,9 @@ + + + + diff --git a/multisim/src/main/res/values/colors.xml b/multisim/src/main/res/values/colors.xml new file mode 100644 index 0000000..a491964 --- /dev/null +++ b/multisim/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #008577 + #00574B + #D81B60 + diff --git a/multisim/src/main/res/values/strings.xml b/multisim/src/main/res/values/strings.xml new file mode 100644 index 0000000..68e3975 --- /dev/null +++ b/multisim/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + MultiSim + From def79b53ef01e76430e8bfb9251cbf635e756fb1 Mon Sep 17 00:00:00 2001 From: Juma Allan Date: Mon, 3 Oct 2022 20:05:51 +0300 Subject: [PATCH 03/26] updated deps, and fixed build warnings --- app/build.gradle | 2 + multisim/build.gradle | 14 +- multisim/src/main/AndroidManifest.xml | 11 +- .../com/hover/multisim/MultiSimWorker.java | 768 +++++++++--------- .../com/hover/multisim/SimDataSource.java | 2 +- .../main/java/com/hover/multisim/SimInfo.java | 498 ++++++------ .../java/com/hover/multisim/SlotManager.java | 8 +- .../main/java/com/hover/multisim/Utils.java | 2 +- multisim/src/main/res/values/styles.xml | 11 - 9 files changed, 677 insertions(+), 639 deletions(-) delete mode 100644 multisim/src/main/res/values/styles.xml diff --git a/app/build.gradle b/app/build.gradle index d26efe5..b187b55 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -35,6 +35,8 @@ android { dependencies { + implementation(project(":multisim")) + implementation 'androidx.core:core-ktx:1.9.0' implementation 'androidx.appcompat:appcompat:1.5.1' diff --git a/multisim/build.gradle b/multisim/build.gradle index e537ea3..879cefc 100644 --- a/multisim/build.gradle +++ b/multisim/build.gradle @@ -19,13 +19,13 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'androidx.appcompat:appcompat:1.1.0' - implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0' - implementation "androidx.work:work-runtime:2.2.0" + implementation 'androidx.appcompat:appcompat:1.5.1' + implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.1.0' + implementation "androidx.work:work-runtime:2.7.1" - testImplementation 'junit:junit:4.12' - androidTestImplementation 'androidx.test.ext:junit:1.1.0' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' - implementation 'io.sentry:sentry-android:1.7.21' + implementation 'io.sentry:sentry-android:6.4.2' } diff --git a/multisim/src/main/AndroidManifest.xml b/multisim/src/main/AndroidManifest.xml index ec7315a..1592c5d 100644 --- a/multisim/src/main/AndroidManifest.xml +++ b/multisim/src/main/AndroidManifest.xml @@ -1,9 +1,8 @@ + package="com.hover.multisim"> - + diff --git a/multisim/src/main/java/com/hover/multisim/MultiSimWorker.java b/multisim/src/main/java/com/hover/multisim/MultiSimWorker.java index 1fa151f..d028111 100644 --- a/multisim/src/main/java/com/hover/multisim/MultiSimWorker.java +++ b/multisim/src/main/java/com/hover/multisim/MultiSimWorker.java @@ -8,8 +8,6 @@ import android.content.IntentFilter; import android.os.Build; import android.os.SystemClock; -import androidx.annotation.NonNull; -import androidx.localbroadcastmanager.content.LocalBroadcastManager; import android.telephony.PhoneStateListener; import android.telephony.ServiceState; import android.telephony.SubscriptionInfo; @@ -17,6 +15,14 @@ import android.telephony.TelephonyManager; import android.util.Log; +import androidx.annotation.NonNull; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; +import androidx.work.ListenableWorker; +import androidx.work.OneTimeWorkRequest; +import androidx.work.PeriodicWorkRequest; +import androidx.work.WorkerParameters; +import androidx.work.impl.utils.futures.SettableFuture; + import com.google.common.util.concurrent.ListenableFuture; import java.lang.reflect.Field; @@ -27,406 +33,406 @@ import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; -import androidx.work.ListenableWorker; -import androidx.work.OneTimeWorkRequest; -import androidx.work.PeriodicWorkRequest; -import androidx.work.WorkerParameters; -import androidx.work.impl.utils.futures.SettableFuture; import io.sentry.Sentry; final public class MultiSimWorker extends ListenableWorker { - public static final String TAG = "MultiSimTeleMgr"; - private static final String NEW_SIM_INFO = "NEW_SIM_INFO_ACTION"; - static final int SLOT_COUNT = 3; // Need to check 0, 1, and 2. Some phones index from 1. - - private SettableFuture workerFuture; - private Result result = null; - - private final Semaphore slotSemaphore = new Semaphore(1, true); - private final Semaphore simSemaphore = new Semaphore(1, true); - - private SimStateReceiver simStateReceiver; - private SimStateListener simStateListener; - private ArrayList validClassNames; - - private final String[] POSS_CLASS_NAMES = new String[] { - null, - "android.telephony.TelephonyManager", - "android.telephony.MSimTelephonyManager", - "android.telephony.MultiSimTelephonyService", - "com.mediatek.telephony.TelephonyManagerEx", - "com.android.internal.telephony.Phone", - "com.android.internal.telephony.PhoneFactory" - }; - - public MultiSimWorker(@NonNull Context context, @NonNull WorkerParameters params) { - super(context, params); - } - - public static PeriodicWorkRequest makeToil() { - return new PeriodicWorkRequest.Builder(MultiSimWorker.class, 15, TimeUnit.MINUTES).build(); - } - - public static OneTimeWorkRequest makeWork() { - return new OneTimeWorkRequest.Builder(MultiSimWorker.class).build(); - } - - @Override - @SuppressLint("RestrictedApi") - public @NonNull ListenableFuture startWork() { - Log.v(TAG, "Starting new Multi SIM worker"); - workerFuture = SettableFuture.create(); - - if (Utils.hasPhonePerm(getApplicationContext())) - startListeners(); - else - workerFuture.set(Result.failure()); - return workerFuture; - } - - @SuppressLint("RestrictedApi") - private void startListeners() { - try { - registerSimStateReceiver(); - if (simStateListener == null) simStateListener = new SimStateListener(); - // TelephonyManager.listen() must take place on the main thread - ((TelephonyManager) getApplicationContext().getSystemService(Context.TELEPHONY_SERVICE)) - .listen(simStateListener, PhoneStateListener.LISTEN_SERVICE_STATE | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS); - } catch (Exception e) { - Log.d(TAG, "Failed to start SIM listeners, setting retry", e); - workerFuture.set(Result.retry()); - } - } - - @SuppressLint("RestrictedApi") - synchronized private void updateSimInfo() { - getBackgroundExecutor().execute(new Runnable() { - @Override - public void run() { - try { - simSemaphore.acquire(); - if (Utils.hasPhonePerm(getApplicationContext())) { - Log.v(TAG, "reviewing sim info"); - List oldList = getSaved(); - List newList = findUniqueSimInfo(); - - if (newList != null) { - compareNewAndOld(newList, oldList); - result = Result.success(); - } else - result = Result.failure(); - } else result = Result.failure(); - } catch (Exception e) { Log.w(TAG, "threw while attempting to update sim list", e); Sentry.capture(e); result = Result.failure(); - } finally { - simSemaphore.release(); + public static final String TAG = "MultiSimTeleMgr"; + private static final String NEW_SIM_INFO = "NEW_SIM_INFO_ACTION"; + static final int SLOT_COUNT = 3; // Need to check 0, 1, and 2. Some phones index from 1. + + private SettableFuture workerFuture; + private Result result = null; + + private final Semaphore slotSemaphore = new Semaphore(1, true); + private final Semaphore simSemaphore = new Semaphore(1, true); + + private SimStateReceiver simStateReceiver; + private SimStateListener simStateListener; + private ArrayList validClassNames; + + private final String[] POSS_CLASS_NAMES = new String[]{null, "android.telephony.TelephonyManager", "android.telephony.MSimTelephonyManager", "android.telephony.MultiSimTelephonyService", "com.mediatek.telephony.TelephonyManagerEx", "com.android.internal.telephony.Phone", "com.android.internal.telephony.PhoneFactory"}; + + public MultiSimWorker(@NonNull Context context, @NonNull WorkerParameters params) { + super(context, params); + } + + public static PeriodicWorkRequest makeToil() { + return new PeriodicWorkRequest.Builder(MultiSimWorker.class, 15, TimeUnit.MINUTES).build(); + } + + public static OneTimeWorkRequest makeWork() { + return new OneTimeWorkRequest.Builder(MultiSimWorker.class).build(); + } + + @Override + @SuppressLint("RestrictedApi") + public @NonNull + ListenableFuture startWork() { + Log.v(TAG, "Starting new Multi SIM worker"); + workerFuture = SettableFuture.create(); + + if (Utils.hasPhonePerm(getApplicationContext())) startListeners(); + else workerFuture.set(Result.failure()); + return workerFuture; + } + + @SuppressLint("RestrictedApi") + private void startListeners() { + try { + registerSimStateReceiver(); + if (simStateListener == null) simStateListener = new SimStateListener(); + // TelephonyManager.listen() must take place on the main thread + ((TelephonyManager) getApplicationContext().getSystemService(Context.TELEPHONY_SERVICE)).listen(simStateListener, PhoneStateListener.LISTEN_SERVICE_STATE | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS); + } catch (Exception e) { + Log.d(TAG, "Failed to start SIM listeners, setting retry", e); + workerFuture.set(Result.retry()); + } + } + + @SuppressLint("RestrictedApi") + synchronized private void updateSimInfo() { + getBackgroundExecutor().execute(new Runnable() { + @Override + public void run() { + try { + simSemaphore.acquire(); + if (Utils.hasPhonePerm(getApplicationContext())) { + Log.v(TAG, "reviewing sim info"); + List oldList = getSaved(); + List newList = findUniqueSimInfo(); + + if (newList != null) { + compareNewAndOld(newList, oldList); + result = Result.success(); + } else result = Result.failure(); + } else result = Result.failure(); + } catch (Exception e) { + Log.w(TAG, "threw while attempting to update sim list", e); + Sentry.captureException(e); + result = Result.failure(); + } finally { + simSemaphore.release(); // Give the listeners a chance to receive a few events - sometimes the first trigger isn't the needed info. 5s is long but this is a background thread anyway and the info has already been updated in the DB - SystemClock.sleep(5000); - if (!workerFuture.isDone()) { - Log.v(TAG, "Finishing Multi SIM worker"); - workerFuture.set(result); - } - } - } - }); - } - - private void compareNewAndOld(List newList, List oldList) { - if (oldList == null || oldList.size() != newList.size()) { - Log.v(TAG, "no old list or sizes differ. Old: " + (oldList != null ? oldList.size() : "null") + ", new: " + newList.size()); - onSimInfoUpdate(newList); - } else { - for (int i = 0; i < newList.size(); i++) - if (newList.get(i).isNotContainedInOrHasMoved(oldList)) { - Log.v(TAG, "some sim moved"); - onSimInfoUpdate(newList); - break; - } - } - } - - private ArrayList getSaved() { - ArrayList oldList = null; - for (int i = 0; i < SLOT_COUNT; i++) { - SimInfo si = new SimDataSource(getApplicationContext()).get(i); - if (si != null) { - if (oldList == null) oldList = new ArrayList<>(); - oldList.add(si); - } - } - Log.v(TAG, "Loaded old list from db. Size: " + (oldList != null ? oldList.size() : "null")); - return oldList; - } - - private void onSimInfoUpdate(List newList) { - updateDb(newList); - Log.v(TAG, "Saved. Firing broadcast"); - LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(new Intent(action(getApplicationContext()))); - } - - private void updateDb(List newList) { - for (SimInfo si: newList) { - Log.i(TAG, "Saving SIM in slot " + si.slotIdx + ": " + si.toString()); - si.save(getApplicationContext()); - } - - List dbInfos = new SimDataSource(getApplicationContext()).getAll(); - for (SimInfo dbSi: dbInfos) { - if (!findPhysicalSim(newList, dbSi)) { - Log.i(TAG, "Couldn't find SIM: " + dbSi.toString() + ", removing"); - dbSi.setSimRemoved(getApplicationContext()); - } - } - } - - private boolean findPhysicalSim(List newList, SimInfo savedSim) { - for (SimInfo si: newList) { - if (si.isSameSim(savedSim)) - return true; - } - return false; - } - - @SuppressWarnings({"MissingPermission"}) - synchronized private List findUniqueSimInfo() { - List newList = new ArrayList<>(); - try { - slotSemaphore.acquire(); - List slotMgrList = new ArrayList<>(); - List teleMgrInstances = listTeleMgrs(slotMgrList); - - List subInfos = new ArrayList<>(); - if (Build.VERSION.SDK_INT >= 22) - subInfos = getSubscriptions(teleMgrInstances, slotMgrList); - - if (subInfos != null && subInfos.size() > 0) - newList = createUniqueSimInfoList(subInfos, slotMgrList); - else - newList = createUniqueSimInfoList(slotMgrList); - } catch (Exception e) { Log.w(TAG, "Multi-SIM worker caught something", e); Sentry.capture(e); - } finally { slotSemaphore.release(); } - return newList; - } - - private List createUniqueSimInfoList(List subInfos, List slotMgrList) { - List newList = createUniqueSimInfoList(slotMgrList); - for (SubscriptionInfo subInfo: subInfos) - newList.add(new SimInfo(subInfo, getApplicationContext())); - return removeDuplicates(newList); - } - private List createUniqueSimInfoList(List slotMgrList) { - List newList = new ArrayList<>(); - for (SlotManager sm: slotMgrList) - newList.add(sm.createSimInfo()); - return removeDuplicates(newList); - } - private List removeDuplicates(List simInfos) { - List uniqueSimInfos = new ArrayList<>(); - for (SimInfo simInfo: simInfos) - if (simInfo.isNotContainedIn(uniqueSimInfos)) - uniqueSimInfos.add(simInfo); - return uniqueSimInfos; - } - - @TargetApi(22) - @SuppressWarnings({"MissingPermission"}) - private List getSubscriptions(List teleMgrInstances, List slotMgrList) throws Exception { - List subInfos = SubscriptionManager.from(getApplicationContext()).getActiveSubscriptionInfoList(); - if (teleMgrInstances != null) { - for (Object teleMgr : teleMgrInstances) { - if (subInfos != null) { - for (SubscriptionInfo subinfo : subInfos) - SlotManager.addValidReadySlots(slotMgrList, subinfo.getSimSlotIndex(), subinfo.getSubscriptionId(), teleMgr, validClassNames); - } - } - } - return subInfos; - } - - @SuppressWarnings("ResourceType") - private List listTeleMgrs(List slotMgrList) { - if (validClassNames == null || validClassNames.isEmpty()) validClassNames = new ArrayList<>(Arrays.asList(POSS_CLASS_NAMES)); - List teleMgrList = new ArrayList<>(); + SystemClock.sleep(5000); + if (!workerFuture.isDone()) { + Log.v(TAG, "Finishing Multi SIM worker"); + workerFuture.set(result); + } + } + } + }); + } + + private void compareNewAndOld(List newList, List oldList) { + if (oldList == null || oldList.size() != newList.size()) { + Log.v(TAG, "no old list or sizes differ. Old: " + (oldList != null ? oldList.size() : "null") + ", new: " + newList.size()); + onSimInfoUpdate(newList); + } else { + for (int i = 0; i < newList.size(); i++) + if (newList.get(i).isNotContainedInOrHasMoved(oldList)) { + Log.v(TAG, "some sim moved"); + onSimInfoUpdate(newList); + break; + } + } + } + + private ArrayList getSaved() { + ArrayList oldList = null; + for (int i = 0; i < SLOT_COUNT; i++) { + SimInfo si = new SimDataSource(getApplicationContext()).get(i); + if (si != null) { + if (oldList == null) oldList = new ArrayList<>(); + oldList.add(si); + } + } + Log.v(TAG, "Loaded old list from db. Size: " + (oldList != null ? oldList.size() : "null")); + return oldList; + } + + private void onSimInfoUpdate(List newList) { + updateDb(newList); + Log.v(TAG, "Saved. Firing broadcast"); + LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(new Intent(action(getApplicationContext()))); + } + + private void updateDb(List newList) { + for (SimInfo si : newList) { + Log.i(TAG, "Saving SIM in slot " + si.slotIdx + ": " + si.toString()); + si.save(getApplicationContext()); + } + + List dbInfos = new SimDataSource(getApplicationContext()).getAll(); + for (SimInfo dbSi : dbInfos) { + if (!findPhysicalSim(newList, dbSi)) { + Log.i(TAG, "Couldn't find SIM: " + dbSi.toString() + ", removing"); + dbSi.setSimRemoved(getApplicationContext()); + } + } + } + + private boolean findPhysicalSim(List newList, SimInfo savedSim) { + for (SimInfo si : newList) { + if (si.isSameSim(savedSim)) return true; + } + return false; + } + + @SuppressWarnings({"MissingPermission"}) + synchronized private List findUniqueSimInfo() { + List newList = new ArrayList<>(); + try { + slotSemaphore.acquire(); + List slotMgrList = new ArrayList<>(); + List teleMgrInstances = listTeleMgrs(slotMgrList); + + List subInfos = new ArrayList<>(); + if (Build.VERSION.SDK_INT >= 22) + subInfos = getSubscriptions(teleMgrInstances, slotMgrList); + + if (subInfos != null && subInfos.size() > 0) + newList = createUniqueSimInfoList(subInfos, slotMgrList); + else newList = createUniqueSimInfoList(slotMgrList); + } catch (Exception e) { + Log.w(TAG, "Multi-SIM worker caught something", e); + Sentry.captureException(e); + } finally { + slotSemaphore.release(); + } + return newList; + } + + private List createUniqueSimInfoList(List subInfos, List slotMgrList) { + List newList = createUniqueSimInfoList(slotMgrList); + for (SubscriptionInfo subInfo : subInfos) + newList.add(new SimInfo(subInfo, getApplicationContext())); + return removeDuplicates(newList); + } + + private List createUniqueSimInfoList(List slotMgrList) { + List newList = new ArrayList<>(); + for (SlotManager sm : slotMgrList) + newList.add(sm.createSimInfo()); + return removeDuplicates(newList); + } + + private List removeDuplicates(List simInfos) { + List uniqueSimInfos = new ArrayList<>(); + for (SimInfo simInfo : simInfos) + if (simInfo.isNotContainedIn(uniqueSimInfos)) uniqueSimInfos.add(simInfo); + return uniqueSimInfos; + } + + @TargetApi(22) + @SuppressWarnings({"MissingPermission"}) + private List getSubscriptions(List teleMgrInstances, List slotMgrList) throws Exception { + List subInfos = SubscriptionManager.from(getApplicationContext()).getActiveSubscriptionInfoList(); + if (teleMgrInstances != null) { + for (Object teleMgr : teleMgrInstances) { + if (subInfos != null) { + for (SubscriptionInfo subinfo : subInfos) + SlotManager.addValidReadySlots(slotMgrList, subinfo.getSimSlotIndex(), subinfo.getSubscriptionId(), teleMgr, validClassNames); + } + } + } + return subInfos; + } + + @SuppressWarnings("ResourceType") + private List listTeleMgrs(List slotMgrList) { + if (validClassNames == null || validClassNames.isEmpty()) + validClassNames = new ArrayList<>(Arrays.asList(POSS_CLASS_NAMES)); + List teleMgrList = new ArrayList<>(); // Log("Creating TeleMgrs List.............................................................."); - for (String className : POSS_CLASS_NAMES) { - if (className == null) continue; - addMgrFromReflection(className, teleMgrList, null, slotMgrList); - for (int i = 0; i < SLOT_COUNT; i++) - addMgrFromReflection(className, teleMgrList, i, slotMgrList); - } - - addMgrFromSystemService(Context.TELEPHONY_SERVICE, teleMgrList, null, slotMgrList); - addMgrFromSystemService("phone_msim", teleMgrList, null, slotMgrList); - for (int j = 0; j < SLOT_COUNT; j++) - addMgrFromSystemService("phone" + j, teleMgrList, j, slotMgrList); - teleMgrList.add(null); + for (String className : POSS_CLASS_NAMES) { + if (className == null) continue; + addMgrFromReflection(className, teleMgrList, null, slotMgrList); + for (int i = 0; i < SLOT_COUNT; i++) + addMgrFromReflection(className, teleMgrList, i, slotMgrList); + } + + addMgrFromSystemService(Context.TELEPHONY_SERVICE, teleMgrList, null, slotMgrList); + addMgrFromSystemService("phone_msim", teleMgrList, null, slotMgrList); + for (int j = 0; j < SLOT_COUNT; j++) + addMgrFromSystemService("phone" + j, teleMgrList, j, slotMgrList); + teleMgrList.add(null); // Log("Total TeleMgrInstances length including null entry: " + teleMgrList.size()); // Log("Valid class names were: " + validClassNames.toString()); // Log("...................................................................................."); - return teleMgrList; - } - private void addMgrFromReflection(String className, List teleMgrList, Object slotIdx, List slotMgrList) { - Object result = runMethodReflect(className, "getDefault", slotIdx == null ? null : new Object[]{ slotIdx }); - if (result != null && !teleMgrList.contains(result)) { - teleMgrList.add(result); - Log("Added Mgr using className: " + className + ", method: getDefault, and param: " + slotIdx); - if (Build.VERSION.SDK_INT < 22) - SlotManager.addValidReadySlots(slotMgrList, slotIdx, result, validClassNames); - } - } - private void addMgrFromSystemService(String serviceName, List teleMgrList, Object slotIdx, List slotMgrList) { - Object serv = getApplicationContext().getSystemService(serviceName); - if (serv != null && !teleMgrList.contains(serv)) { - teleMgrList.add(serv); - Log("Added Mgr using mContext.getSystemService('" + serviceName + "')"); - if (Build.VERSION.SDK_INT < 22) - SlotManager.addValidReadySlots(slotMgrList, slotIdx, serv, validClassNames); - } - } - - @SuppressWarnings("SameParameterValue") - private Object runMethodReflect(String className, String methodName, Object[] methodParams) { - try { - return runMethodReflect(null, Class.forName(className), methodName, methodParams); - } catch (ClassNotFoundException e) { - validClassNames.remove(className); + return teleMgrList; + } + + private void addMgrFromReflection(String className, List teleMgrList, Object slotIdx, List slotMgrList) { + Object result = runMethodReflect(className, "getDefault", slotIdx == null ? null : new Object[]{slotIdx}); + if (result != null && !teleMgrList.contains(result)) { + teleMgrList.add(result); + Log("Added Mgr using className: " + className + ", method: getDefault, and param: " + slotIdx); + if (Build.VERSION.SDK_INT < 22) + SlotManager.addValidReadySlots(slotMgrList, slotIdx, result, validClassNames); + } + } + + private void addMgrFromSystemService(String serviceName, List teleMgrList, Object slotIdx, List slotMgrList) { + Object serv = getApplicationContext().getSystemService(serviceName); + if (serv != null && !teleMgrList.contains(serv)) { + teleMgrList.add(serv); + Log("Added Mgr using mContext.getSystemService('" + serviceName + "')"); + if (Build.VERSION.SDK_INT < 22) + SlotManager.addValidReadySlots(slotMgrList, slotIdx, serv, validClassNames); + } + } + + @SuppressWarnings("SameParameterValue") + private Object runMethodReflect(String className, String methodName, Object[] methodParams) { + try { + return runMethodReflect(null, Class.forName(className), methodName, methodParams); + } catch (ClassNotFoundException e) { + validClassNames.remove(className); // Log("Class not found, removing: " + e); - } - return null; - } - private static Object runMethodReflect(Object actualInstance, String methodName, Object[] methodParams) { - return runMethodReflect(actualInstance, actualInstance.getClass(), methodName, methodParams); - } - public static Object runMethodReflect(Object actualInstance, Class classInstance, String methodName, Object[] methodParams) { - Object result = null; - try { - Method method = classInstance.getDeclaredMethod(methodName, getClassParams(methodParams)); - boolean accessible = method.isAccessible(); - method.setAccessible(true); - result = method.invoke(actualInstance != null ? actualInstance : classInstance, methodParams); - method.setAccessible(accessible); - } catch (Exception ignored) { /* Log("Method not found: " + ignored); */ } - return result; - } - - @SuppressWarnings("unused") - private static Object runFieldReflect(String className, String field) { - Object result = null; - try { - Class classInstance = Class.forName(className); - Field fieldReflect = classInstance.getField(field); - boolean accessible = fieldReflect.isAccessible(); - fieldReflect.setAccessible(true); - result = fieldReflect.get(null).toString(); - fieldReflect.setAccessible(accessible); - } catch (Exception ignored) { + } + return null; + } + + private static Object runMethodReflect(Object actualInstance, String methodName, Object[] methodParams) { + return runMethodReflect(actualInstance, actualInstance.getClass(), methodName, methodParams); + } + + public static Object runMethodReflect(Object actualInstance, Class classInstance, String methodName, Object[] methodParams) { + Object result = null; + try { + Method method = classInstance.getDeclaredMethod(methodName, getClassParams(methodParams)); + boolean accessible = method.isAccessible(); + method.setAccessible(true); + result = method.invoke(actualInstance != null ? actualInstance : classInstance, methodParams); + method.setAccessible(accessible); + } catch (Exception ignored) { /* Log("Method not found: " + ignored); */ } + return result; + } + + @SuppressWarnings("unused") + private static Object runFieldReflect(String className, String field) { + Object result = null; + try { + Class classInstance = Class.forName(className); + Field fieldReflect = classInstance.getField(field); + boolean accessible = fieldReflect.isAccessible(); + fieldReflect.setAccessible(true); + result = fieldReflect.get(null).toString(); + fieldReflect.setAccessible(accessible); + } catch (Exception ignored) { // Log("Error accessing reflected class: " + ignored); - } - return result; - } - - private static Class[] getClassParams(Object[] methodParams) { - Class[] classesParams = null; - if (methodParams != null) { - classesParams = new Class[methodParams.length]; - for (int i = 0; i < methodParams.length; i++) { - if (methodParams[i] instanceof Integer) - classesParams[i] = int.class; // logString += methodParams[i] + ","; - else if (methodParams[i] instanceof String) - classesParams[i] = String.class; // logString += "\"" + methodParams[i] + "\","; - else if (methodParams[i] instanceof Long) - classesParams[i] = long.class; // logString += methodParams[i] + ","; - else if (methodParams[i] instanceof Boolean) - classesParams[i] = boolean.class; // logString += methodParams[i] + ","; - else - classesParams[i] = methodParams[i].getClass(); // logString += "["+methodParams[i]+"]" + ","; - } - } - return classesParams; - } - - private void registerSimStateReceiver() { - if (simStateReceiver == null) { - simStateReceiver = new SimStateReceiver(); - IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction("android.intent.action.SIM_STATE_CHANGED"); - intentFilter.addAction("android.intent.action.ACTION_SIM_STATE_CHANGED"); - intentFilter.addAction("android.intent.action.PHONE_STATE"); - intentFilter.addAction("vivo.intent.action.ACTION_SIM_STATE_CHANGED"); - getApplicationContext().registerReceiver(simStateReceiver, intentFilter); - } - } - - private class SimStateListener extends PhoneStateListener { - public void onServiceStateChanged(ServiceState serviceState) { - updateSimInfo(); - } - } - - private class SimStateReceiver extends BroadcastReceiver { - @Override - public void onReceive(final Context context, final Intent intent) { - if (intent != null) updateSimInfo(); - } - } - - public static String action(Context c) { return Utils.getPackage(c) + "." + NEW_SIM_INFO; } - - @Override - public void onStopped() { - super.onStopped(); - try { - ((TelephonyManager) getApplicationContext().getSystemService(Context.TELEPHONY_SERVICE)) - .listen(simStateListener, PhoneStateListener.LISTEN_NONE); - simStateListener = null; - if (simStateReceiver != null) - getApplicationContext().unregisterReceiver(simStateReceiver); - simStateReceiver = null; - } catch(Exception ignored) {} - } - - @SuppressWarnings("unused") - private void goFish() { - printAllMethodsAndFields("android.telephony.TelephonyManager", -1); // all methods - printAllMethodsAndFields("android.telephony.MultiSimTelephonyService", -1); // all methods + } + return result; + } + + private static Class[] getClassParams(Object[] methodParams) { + Class[] classesParams = null; + if (methodParams != null) { + classesParams = new Class[methodParams.length]; + for (int i = 0; i < methodParams.length; i++) { + if (methodParams[i] instanceof Integer) + classesParams[i] = int.class; // logString += methodParams[i] + ","; + else if (methodParams[i] instanceof String) + classesParams[i] = String.class; // logString += "\"" + methodParams[i] + "\","; + else if (methodParams[i] instanceof Long) + classesParams[i] = long.class; // logString += methodParams[i] + ","; + else if (methodParams[i] instanceof Boolean) + classesParams[i] = boolean.class; // logString += methodParams[i] + ","; + else + classesParams[i] = methodParams[i].getClass(); // logString += "["+methodParams[i]+"]" + ","; + } + } + return classesParams; + } + + private void registerSimStateReceiver() { + if (simStateReceiver == null) { + simStateReceiver = new SimStateReceiver(); + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction("android.intent.action.SIM_STATE_CHANGED"); + intentFilter.addAction("android.intent.action.ACTION_SIM_STATE_CHANGED"); + intentFilter.addAction("android.intent.action.PHONE_STATE"); + intentFilter.addAction("vivo.intent.action.ACTION_SIM_STATE_CHANGED"); + getApplicationContext().registerReceiver(simStateReceiver, intentFilter); + } + } + + private class SimStateListener extends PhoneStateListener { + public void onServiceStateChanged(ServiceState serviceState) { + updateSimInfo(); + } + } + + private class SimStateReceiver extends BroadcastReceiver { + @Override + public void onReceive(final Context context, final Intent intent) { + if (intent != null) updateSimInfo(); + } + } + + public static String action(Context c) { + return Utils.getPackage(c) + "." + NEW_SIM_INFO; + } + + @Override + public void onStopped() { + super.onStopped(); + try { + ((TelephonyManager) getApplicationContext().getSystemService(Context.TELEPHONY_SERVICE)).listen(simStateListener, PhoneStateListener.LISTEN_NONE); + simStateListener = null; + if (simStateReceiver != null) + getApplicationContext().unregisterReceiver(simStateReceiver); + simStateReceiver = null; + } catch (Exception ignored) { + } + } + + @SuppressWarnings("unused") + private void goFish() { + printAllMethodsAndFields("android.telephony.TelephonyManager", -1); // all methods + printAllMethodsAndFields("android.telephony.MultiSimTelephonyService", -1); // all methods // printAllMethodsAndFields("android.telephony.MultiSimTelephonyService", 0); // methods with 0 params // printAllMethodsAndFields("android.telephony.MultiSimTelephonyService", 1); // methods with 1 params // printAllMethodsAndFields("android.telephony.MultiSimTelephonyService", 2); // methods with 2 params - printAllMethodsAndFields("android.telephony.MSimTelephonyManager", -1); // all methods - printAllMethodsAndFields("com.mediatek.telephony.TelephonyManager", -1); // all methods - printAllMethodsAndFields("com.mediatek.telephony.TelephonyManagerEx", -1); // all methods - printAllMethodsAndFields("com.android.internal.telephony.ITelephony", -1); // all methods - printAllMethodsAndFields("com.android.internal.telephony.ITelephony$Stub$Proxy", -1); // all methods - } - @SuppressWarnings("unused") - private void printAllMethodsAndFields(String className, int paramsCount) { - Log("===================================================================================="); - Log("Methods of " + className); - try { - Class MultiSimClass = Class.forName(className); - for (Method method : MultiSimClass.getMethods()) { + printAllMethodsAndFields("android.telephony.MSimTelephonyManager", -1); // all methods + printAllMethodsAndFields("com.mediatek.telephony.TelephonyManager", -1); // all methods + printAllMethodsAndFields("com.mediatek.telephony.TelephonyManagerEx", -1); // all methods + printAllMethodsAndFields("com.android.internal.telephony.ITelephony", -1); // all methods + printAllMethodsAndFields("com.android.internal.telephony.ITelephony$Stub$Proxy", -1); // all methods + } + + @SuppressWarnings("unused") + private void printAllMethodsAndFields(String className, int paramsCount) { + Log("===================================================================================="); + Log("Methods of " + className); + try { + Class MultiSimClass = Class.forName(className); + for (Method method : MultiSimClass.getMethods()) { // if (method.toGenericString().toLowerCase().contains("ussd")) { - Log(method.toGenericString()); - try { - if (method.getParameterTypes().length == 0) - Log((String) runMethodReflect(MultiSimClass, method.getName(), null)); - else if (method.getParameterTypes().length == 1) - Log(" " + runMethodReflect(MultiSimClass, method.getName(), new Object[]{0})); - } catch (Exception e) { Log("Failed. " + e); } + Log(method.toGenericString()); + try { + if (method.getParameterTypes().length == 0) + Log((String) runMethodReflect(MultiSimClass, method.getName(), null)); + else if (method.getParameterTypes().length == 1) + Log(" " + runMethodReflect(MultiSimClass, method.getName(), new Object[]{0})); + } catch (Exception e) { + Log("Failed. " + e); + } // } - } - } catch (Exception e) { - Log("Failed. " + e); - } + } + } catch (Exception e) { + Log("Failed. " + e); + } // for( Field field : MultiSimClass.getFields()) { // field.setAccessible(true); // Log.i(LOG, " f2 " + field.getName() + " " + field.getType() + " " + field.load(inst)); // } - } + } - @SuppressWarnings({"EmptyMethod", "unused"}) - private static void Log(String message) { + @SuppressWarnings({"EmptyMethod", "unused"}) + private static void Log(String message) { // Log.e(TAG, message); - } + } } diff --git a/multisim/src/main/java/com/hover/multisim/SimDataSource.java b/multisim/src/main/java/com/hover/multisim/SimDataSource.java index a54df6d..d5542f2 100644 --- a/multisim/src/main/java/com/hover/multisim/SimDataSource.java +++ b/multisim/src/main/java/com/hover/multisim/SimDataSource.java @@ -40,7 +40,7 @@ public List getAll() { } cursor.close(); close(); - } catch (Exception e) { Sentry.capture(e); } + } catch (Exception e) { Sentry.captureException(e); } return infos; } diff --git a/multisim/src/main/java/com/hover/multisim/SimInfo.java b/multisim/src/main/java/com/hover/multisim/SimInfo.java index 562e16f..daccbe2 100644 --- a/multisim/src/main/java/com/hover/multisim/SimInfo.java +++ b/multisim/src/main/java/com/hover/multisim/SimInfo.java @@ -17,120 +17,152 @@ import io.sentry.Sentry; public class SimInfo { - private final static String TAG = "SimInfo"; - private final static String KEY = "sim_info_"; - - /** - * The slot that the SIM is in, starting from 0. Can be -1 if the SIM has been removed - */ - public int slotIdx = -1; - /** - * The Subscription ID assigned by Android. The same SIM can be assigned a new ID if it is removed and re-inserted. Hover will forget the old ID and update a SIM to the newest - */ - public int subscriptionId = -1; - - String imei; - - protected String iccId; - /** - * The Hardware identifier for the SIM. Hover uses this to track a SIM regardless of whether it is removed or its slot changed - */ - public String getIccId() { return iccId; } - - protected String imsi; - /** - * The The International Mobile Subscriber Identity used by the network to identify the SIM. The value reported here may be only the begining 5-6 digits or the whole thing. - * If you are trying to determine which network a SIM is for use this. The first 3 digits will always be the MCC and the following 2 or 3 will be the MNC which you can use to definitively identify which network this SIM is for - * See https://en.wikipedia.org/wiki/Mobile_country_code - */ - public String getImsi() { return imsi; } - String mcc; - String mnc; - - int simState = -1; - - protected String hni; - /** - * The Home Network Identifier. This is the first 5-6 digits of the IMSI, however, we recomend against using this since some devices may not report it correctly. Use the first 5-6 digits of the imsi using getImsi() - * - * @see SimInfo#getImsi() - */ - public String getOSReportedHni() { return hni; } - - protected String operatorName; - /** - * The name of the operator which provisioned the SIM. May differ from SIM to SIM distributed by the same network provisioner - */ - public String getOperatorName() { return operatorName; } - - protected String countryIso; - /** - * The country ISO of the operator which provisioned the SIM - */ - public String getCountryIso() { return countryIso; } - - String networkOperator; - /** - * The network of the operator which the SIM is connected to - */ - public String getNetworkOperator() { return networkOperator; } - String networkOperatorName; - /** - * The network name of the operator which the SIM is connected to - */ - public String getNetworkOperatorName() { return networkOperatorName; } - String networkCountryIso; - /** - * The country ISO of the operator which the SIM is connected to - */ - public String getNetworkCountryIso() { return networkCountryIso; } - int networkType; - // careful find networkTypeName because it will be different with networkType on same devices - - protected boolean networkRoaming = false; - /** - * Whether the SIM is currently roaming. Not guaranteed to be accurate. - */ - public boolean isRoaming() { return networkRoaming; } - - public SimInfo() {} - - public SimInfo(SlotManager slotMgr) { - if (slotMgr.slotIndex != null) slotIdx = slotMgr.slotIndex; - subscriptionId = slotMgr.subscriptionId; - - imei = slotMgr.imei; - simState = setSimState(slotMgr.findSimState()); - iccId = setStandardIccId(slotMgr.findIccId()); - imsi = slotMgr.findImsi(); - mcc = setMcc(imsi); - - hni = slotMgr.findOperator(); - operatorName = slotMgr.findOperatorName(); - countryIso = slotMgr.findCountryIso(); - networkOperator = slotMgr.findNetworkOperator(); - networkOperatorName = slotMgr.findNetworkOperatorName(); - networkCountryIso = slotMgr.findNetworkCountryIso(); - networkType = setNetworkType(slotMgr.findNetworkType()); - networkRoaming = setNetworkRoaming(slotMgr.findNetworkRoaming()); + private final static String TAG = "SimInfo"; + private final static String KEY = "sim_info_"; + + /** + * The slot that the SIM is in, starting from 0. Can be -1 if the SIM has been removed + */ + public int slotIdx = -1; + /** + * The Subscription ID assigned by Android. The same SIM can be assigned a new ID if it is removed and re-inserted. Hover will forget the old ID and update a SIM to the newest + */ + public int subscriptionId = -1; + + String imei; + + protected String iccId; + + /** + * The Hardware identifier for the SIM. Hover uses this to track a SIM regardless of whether it is removed or its slot changed + */ + public String getIccId() { + return iccId; + } + + protected String imsi; + + /** + * The The International Mobile Subscriber Identity used by the network to identify the SIM. The value reported here may be only the begining 5-6 digits or the whole thing. + * If you are trying to determine which network a SIM is for use this. The first 3 digits will always be the MCC and the following 2 or 3 will be the MNC which you can use to definitively identify which network this SIM is for + * See https://en.wikipedia.org/wiki/Mobile_country_code + */ + public String getImsi() { + return imsi; + } + + String mcc; + String mnc; + + int simState = -1; + + protected String hni; + + /** + * The Home Network Identifier. This is the first 5-6 digits of the IMSI, however, we recomend against using this since some devices may not report it correctly. Use the first 5-6 digits of the imsi using getImsi() + * + * @see SimInfo#getImsi() + */ + public String getOSReportedHni() { + return hni; + } + + protected String operatorName; + + /** + * The name of the operator which provisioned the SIM. May differ from SIM to SIM distributed by the same network provisioner + */ + public String getOperatorName() { + return operatorName; + } + + protected String countryIso; + + /** + * The country ISO of the operator which provisioned the SIM + */ + public String getCountryIso() { + return countryIso; + } + + String networkOperator; + + /** + * The network of the operator which the SIM is connected to + */ + public String getNetworkOperator() { + return networkOperator; + } + + String networkOperatorName; + + /** + * The network name of the operator which the SIM is connected to + */ + public String getNetworkOperatorName() { + return networkOperatorName; + } + + String networkCountryIso; + + /** + * The country ISO of the operator which the SIM is connected to + */ + public String getNetworkCountryIso() { + return networkCountryIso; + } + + int networkType; + // careful find networkTypeName because it will be different with networkType on same devices + + protected boolean networkRoaming = false; + + /** + * Whether the SIM is currently roaming. Not guaranteed to be accurate. + */ + public boolean isRoaming() { + return networkRoaming; + } + + public SimInfo() { + } + + public SimInfo(SlotManager slotMgr) { + if (slotMgr.slotIndex != null) slotIdx = slotMgr.slotIndex; + subscriptionId = slotMgr.subscriptionId; + + imei = slotMgr.imei; + simState = setSimState(slotMgr.findSimState()); + iccId = setStandardIccId(slotMgr.findIccId()); + imsi = slotMgr.findImsi(); + mcc = setMcc(imsi); + + hni = slotMgr.findOperator(); + operatorName = slotMgr.findOperatorName(); + countryIso = slotMgr.findCountryIso(); + networkOperator = slotMgr.findNetworkOperator(); + networkOperatorName = slotMgr.findNetworkOperatorName(); + networkCountryIso = slotMgr.findNetworkCountryIso(); + networkType = setNetworkType(slotMgr.findNetworkType()); + networkRoaming = setNetworkRoaming(slotMgr.findNetworkRoaming()); // Log.i(TAG, "Created SIM representation using reflection: " + this.log()); - } + } - @TargetApi(22) - public SimInfo(SubscriptionInfo subInfo, Context c) { - subscriptionId = subInfo.getSubscriptionId(); - slotIdx = subInfo.getSimSlotIndex(); + @TargetApi(22) + public SimInfo(SubscriptionInfo subInfo, Context c) { + subscriptionId = subInfo.getSubscriptionId(); + slotIdx = subInfo.getSimSlotIndex(); - imsi = "" + subInfo.getMcc() + subInfo.getMnc(); - hni = "" + subInfo.getMcc() + subInfo.getMnc(); - mcc = "" + subInfo.getMcc(); - mnc = "" + subInfo.getMnc(); - iccId = setStandardIccId(subInfo.getIccId()); + imsi = "" + subInfo.getMcc() + subInfo.getMnc(); + hni = "" + subInfo.getMcc() + subInfo.getMnc(); + mcc = "" + subInfo.getMcc(); + mnc = "" + subInfo.getMnc(); + iccId = setStandardIccId(subInfo.getIccId()); - operatorName = (String) subInfo.getCarrierName(); // Is this Network Operator or Sim Operator? - countryIso = subInfo.getCountryIso(); - networkRoaming = SubscriptionManager.from(c).isNetworkRoaming(subscriptionId); + operatorName = (String) subInfo.getCarrierName(); // Is this Network Operator or Sim Operator? + countryIso = subInfo.getCountryIso(); + networkRoaming = SubscriptionManager.from(c).isNetworkRoaming(subscriptionId); // Can't load these until API 24 (Using TelephonyManager), but doesn't really matter: // hnis, networkOperator, networkOperatorName, networkCountryIso, networkType @@ -139,127 +171,137 @@ public SimInfo(SubscriptionInfo subInfo, Context c) { // Log.i(TAG, "SubInfo Number: " + si.getNumber()); // Log.i(TAG, "Created SIM representation using Subscription info: " + this.log()); - } - - public boolean isSameSim(SimInfo simInfo) { // FIXME: change so that if the SimInfo represents the same sim, the one with more/better info (and more accurate slotIdx?) is returned. It currently relies on order to do this, which is fragile - return simInfo != null && simInfo.iccId != null && iccId != null && iccId.equals(simInfo.iccId); - } - - private boolean isSameSimInSameSlot(SimInfo simInfo) { - return isSameSim(simInfo) && simInfo.slotIdx == slotIdx; - } - - public boolean isNotContainedIn(List simInfos) { - if (simInfos == null) return true; - for (SimInfo simInfo : simInfos) - if (this.isSameSim(simInfo)) - return false; - return true; - } - - public boolean isNotContainedInOrHasMoved(List simInfos) { - if (simInfos == null) return true; - for (SimInfo simInfo : simInfos) - if (this.isSameSimInSameSlot(simInfo)) - return false; - return true; - } - - public void save(Context c) { - new SimDataSource(c).saveToDb(this); - updateSubId(subscriptionId, c); - } - -// private void updateSlot(Context c) { + } + + public boolean isSameSim(SimInfo simInfo) { // FIXME: change so that if the SimInfo represents the same sim, the one with more/better info (and more accurate slotIdx?) is returned. It currently relies on order to do this, which is fragile + return simInfo != null && simInfo.iccId != null && iccId != null && iccId.equals(simInfo.iccId); + } + + private boolean isSameSimInSameSlot(SimInfo simInfo) { + return isSameSim(simInfo) && simInfo.slotIdx == slotIdx; + } + + public boolean isNotContainedIn(List simInfos) { + if (simInfos == null) return true; + for (SimInfo simInfo : simInfos) + if (this.isSameSim(simInfo)) + return false; + return true; + } + + public boolean isNotContainedInOrHasMoved(List simInfos) { + if (simInfos == null) return true; + for (SimInfo simInfo : simInfos) + if (this.isSameSimInSameSlot(simInfo)) + return false; + return true; + } + + public void save(Context c) { + new SimDataSource(c).saveToDb(this); + updateSubId(subscriptionId, c); + } + + // private void updateSlot(Context c) { // Log.i(TAG, "Updating sim slot to: " + slotIdx); // new SimDataSource(c).updateSlot(this, updatedInfo); // updateSubId(updatedInfo.subscriptionId, c); // } - public void setSimRemoved(Context c) { - Log.i(TAG, "Updating sim slot to: -1"); - new SimDataSource(c).remove(this); - } - - @SuppressLint("ApplySharedPref") - private void updateSubId(int subId, Context c) { - SharedPreferences.Editor editor = Utils.getSharedPrefs(c).edit(); - editor.putInt(KEY + SimContract.COLUMN_SUB_ID + iccId, subId); - editor.commit(); - } - public static int getSubId(String iccId, Context c) { - return Utils.getSharedPrefs(c).getInt(KEY + SimContract.COLUMN_SUB_ID + iccId, -1); - } - - public static List loadPresentByHni(JSONArray hniList, Context c) { - List simInfos = new ArrayList<>(); - for (int h = 0; h < hniList.length(); h++) { - try { - simInfos.addAll(new SimDataSource(c).getPresent(hniList.getString(h).substring(0, 3), hniList.getString(h).substring(3))); - } catch (JSONException | NullPointerException e) { Sentry.capture(e); } - } - return simInfos; - } - - public static SimInfo loadBySlot(int slotIdx, Context c) { - return new SimDataSource(c).get(slotIdx); - } - - public static List loadAll(Context c) { - return new SimDataSource(c).getAll(); - } - - String setStandardIccId(String iccId) { - if (iccId != null) - iccId = iccId.replaceAll("[a-zA-Z]", ""); - return iccId; - } - private String setMcc(String imsi) { return imsi != null ? imsi.substring(0, 3) : null; } - - boolean isMncMatch(int mncInt) { - return (imsi.length() == 4 && Integer.valueOf(imsi.substring(3)) == mncInt) || - (imsi.length() >= 5 && Integer.valueOf(imsi.substring(3, 5)) == mncInt) || - (imsi.length() >= 6 && Integer.valueOf(imsi.substring(3, 6)) == mncInt); - } - - public String getInterpretedHni(JSONArray actionHniList) { - for (int h = 0; h < actionHniList.length(); h++) { - int mncInt = Integer.valueOf(actionHniList.optString(h).substring(3)); - if (isMncMatch(mncInt)) { - return imsi.substring(0, 3) + mncInt; - } - } - return null; - } - - private int setSimState(Integer simState) { - if (simState == null) return -1; - return simState; - } - private int setNetworkType(Integer networkType) { - if (networkType == null) return 0; - return networkType; - } - private boolean setNetworkRoaming(Boolean networkRoaming) { return networkRoaming != null && networkRoaming; } - - - String log() { - return "slotIdx=[" + slotIdx + - "] subscriptionId=[" + subscriptionId + - "] imei=[" + imei + - "] imsi=[" + imsi + - "] simState=[" + simState + - "] simIccId=[" + iccId + - "] simHni=[" + hni + - "] simOperatorName=[" + operatorName + - "] simCountryIso=[" + countryIso + - "] networkOperator=[" + networkOperator + - "] networkOperatorName=[" + networkOperatorName + - "] networkCountryIso=[" + networkCountryIso + - "] networkType=[" + networkType + - "] networkRoaming=[" + networkRoaming + "]"; - } - - public String toString() { - return operatorName + " " + (countryIso != null ? countryIso.toUpperCase() : "") + " (SIM " + (slotIdx + 1) + ")"; - } + public void setSimRemoved(Context c) { + Log.i(TAG, "Updating sim slot to: -1"); + new SimDataSource(c).remove(this); + } + + @SuppressLint("ApplySharedPref") + private void updateSubId(int subId, Context c) { + SharedPreferences.Editor editor = Utils.getSharedPrefs(c).edit(); + editor.putInt(KEY + SimContract.COLUMN_SUB_ID + iccId, subId); + editor.commit(); + } + + public static int getSubId(String iccId, Context c) { + return Utils.getSharedPrefs(c).getInt(KEY + SimContract.COLUMN_SUB_ID + iccId, -1); + } + + public static List loadPresentByHni(JSONArray hniList, Context c) { + List simInfos = new ArrayList<>(); + for (int h = 0; h < hniList.length(); h++) { + try { + simInfos.addAll(new SimDataSource(c).getPresent(hniList.getString(h).substring(0, 3), hniList.getString(h).substring(3))); + } catch (JSONException | NullPointerException e) { + Sentry.captureException(e); + } + } + return simInfos; + } + + public static SimInfo loadBySlot(int slotIdx, Context c) { + return new SimDataSource(c).get(slotIdx); + } + + public static List loadAll(Context c) { + return new SimDataSource(c).getAll(); + } + + String setStandardIccId(String iccId) { + if (iccId != null) + iccId = iccId.replaceAll("[a-zA-Z]", ""); + return iccId; + } + + private String setMcc(String imsi) { + return imsi != null ? imsi.substring(0, 3) : null; + } + + boolean isMncMatch(int mncInt) { + return (imsi.length() == 4 && Integer.valueOf(imsi.substring(3)) == mncInt) || + (imsi.length() >= 5 && Integer.valueOf(imsi.substring(3, 5)) == mncInt) || + (imsi.length() >= 6 && Integer.valueOf(imsi.substring(3, 6)) == mncInt); + } + + public String getInterpretedHni(JSONArray actionHniList) { + for (int h = 0; h < actionHniList.length(); h++) { + int mncInt = Integer.valueOf(actionHniList.optString(h).substring(3)); + if (isMncMatch(mncInt)) { + return imsi.substring(0, 3) + mncInt; + } + } + return null; + } + + private int setSimState(Integer simState) { + if (simState == null) return -1; + return simState; + } + + private int setNetworkType(Integer networkType) { + if (networkType == null) return 0; + return networkType; + } + + private boolean setNetworkRoaming(Boolean networkRoaming) { + return networkRoaming != null && networkRoaming; + } + + + String log() { + return "slotIdx=[" + slotIdx + + "] subscriptionId=[" + subscriptionId + + "] imei=[" + imei + + "] imsi=[" + imsi + + "] simState=[" + simState + + "] simIccId=[" + iccId + + "] simHni=[" + hni + + "] simOperatorName=[" + operatorName + + "] simCountryIso=[" + countryIso + + "] networkOperator=[" + networkOperator + + "] networkOperatorName=[" + networkOperatorName + + "] networkCountryIso=[" + networkCountryIso + + "] networkType=[" + networkType + + "] networkRoaming=[" + networkRoaming + "]"; + } + + public String toString() { + return operatorName + " " + (countryIso != null ? countryIso.toUpperCase() : "") + " (SIM " + (slotIdx + 1) + ")"; + } } diff --git a/multisim/src/main/java/com/hover/multisim/SlotManager.java b/multisim/src/main/java/com/hover/multisim/SlotManager.java index 7c0ee12..aa119eb 100644 --- a/multisim/src/main/java/com/hover/multisim/SlotManager.java +++ b/multisim/src/main/java/com/hover/multisim/SlotManager.java @@ -1,5 +1,8 @@ package com.hover.multisim; +import static android.telephony.TelephonyManager.SIM_STATE_READY; +import static android.telephony.TelephonyManager.SIM_STATE_UNKNOWN; + import android.util.Log; import java.util.ArrayList; @@ -7,9 +10,6 @@ import io.sentry.Sentry; -import static android.telephony.TelephonyManager.SIM_STATE_READY; -import static android.telephony.TelephonyManager.SIM_STATE_UNKNOWN; - final class SlotManager { public final static String TAG = "SlotManager"; final Integer slotIndex; @@ -62,7 +62,7 @@ private boolean isUnique(List slotMgrList) { try { if (mgr != null && imei.equals(mgr.imei) && teleMgr == mgr.teleMgr && teleClass == mgr.teleClass) return false; - } catch (NullPointerException e) { Log.w(TAG, "something was null that shouldn't be", e); Sentry.capture(e); } + } catch (NullPointerException e) { Log.w(TAG, "something was null that shouldn't be", e); Sentry.captureException(e); } } // Log.i(TAG, "Slot Manager was unique with Imei: " + imei + ", teleMgr: " + teleMgr + ", and teleClass: " + teleClass); return true; diff --git a/multisim/src/main/java/com/hover/multisim/Utils.java b/multisim/src/main/java/com/hover/multisim/Utils.java index 47d97a4..c7ee29f 100644 --- a/multisim/src/main/java/com/hover/multisim/Utils.java +++ b/multisim/src/main/java/com/hover/multisim/Utils.java @@ -16,7 +16,7 @@ public static String getPackage(Context c) { try { return c.getApplicationContext().getPackageName(); } catch (NullPointerException e) { - Sentry.capture(e); + Sentry.captureException(e); return "fail"; } } diff --git a/multisim/src/main/res/values/styles.xml b/multisim/src/main/res/values/styles.xml deleted file mode 100644 index 79a66e2..0000000 --- a/multisim/src/main/res/values/styles.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - From febedf593650f1823edd72fd02786f2d9886cc03 Mon Sep 17 00:00:00 2001 From: Juma Allan Date: Mon, 3 Oct 2022 20:25:14 +0300 Subject: [PATCH 04/26] migrate to ktx, and added gradle plugins --- app/build.gradle | 48 -- app/build.gradle.kts | 55 ++ app/proguard-rules.pro | 2 +- .../com/hover/app/ExampleInstrumentedTest.kt | 5 +- build.gradle | 26 - build.gradle.kts | 43 ++ detekt.yml | 538 ++++++++++++++++++ gradle/libs.versions.toml | 5 + multisim/build.gradle | 31 - multisim/build.gradle.kts | 45 ++ settings.gradle | 4 - settings.gradle.kts | 26 + spotless/copyright.kt | 15 + 13 files changed, 730 insertions(+), 113 deletions(-) delete mode 100644 app/build.gradle create mode 100644 app/build.gradle.kts delete mode 100644 build.gradle create mode 100644 build.gradle.kts create mode 100644 detekt.yml create mode 100644 gradle/libs.versions.toml delete mode 100644 multisim/build.gradle create mode 100644 multisim/build.gradle.kts delete mode 100644 settings.gradle create mode 100644 settings.gradle.kts create mode 100644 spotless/copyright.kt diff --git a/app/build.gradle b/app/build.gradle deleted file mode 100644 index b187b55..0000000 --- a/app/build.gradle +++ /dev/null @@ -1,48 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'org.jetbrains.kotlin.android' - -android { - - namespace 'com.hover.app' - compileSdkVersion 33 - - defaultConfig { - applicationId "com.hover.app" - minSdkVersion 21 - targetSdkVersion 33 - versionCode 1 - versionName "1.0" - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - kotlinOptions { - jvmTarget = '1.8' - } -} - -dependencies { - - implementation(project(":multisim")) - - implementation 'androidx.core:core-ktx:1.9.0' - - implementation 'androidx.appcompat:appcompat:1.5.1' - implementation 'com.google.android.material:material:1.6.1' - - testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.ext:junit:1.1.3' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' -} \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..5fef725 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,55 @@ +plugins { + id("com.android.application") + id("org.jetbrains.kotlin.android") + id("org.jlleitschuh.gradle.ktlint") + id("io.gitlab.arturbosch.detekt") +} + +android { + + namespace = "com.hover.app" + compileSdk = 33 + + defaultConfig { + applicationId = "com.hover.app" + minSdk = 21 + targetSdk = 33 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = "11" + } + + buildTypes { + getByName("release") { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } +} + +dependencies { + + implementation(project(":multisim")) + + implementation("androidx.core:core-ktx:1.9.0") + + implementation("androidx.appcompat:appcompat:1.5.1") + implementation("com.google.android.material:material:1.6.1") + + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.3") + androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0") +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 481bb43..ff59496 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,6 +1,6 @@ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. +# proguardFiles setting in build.gradle.kts. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html diff --git a/app/src/androidTest/java/com/hover/app/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/hover/app/ExampleInstrumentedTest.kt index aace88f..ba818a7 100644 --- a/app/src/androidTest/java/com/hover/app/ExampleInstrumentedTest.kt +++ b/app/src/androidTest/java/com/hover/app/ExampleInstrumentedTest.kt @@ -1,12 +1,11 @@ package com.hover.app -import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 - +import androidx.test.platform.app.InstrumentationRegistry +import junit.framework.Assert.assertEquals import org.junit.Test import org.junit.runner.RunWith -import org.junit.Assert.* /** * Instrumented test, which will execute on an Android device. diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 6fabb3d..0000000 --- a/build.gradle +++ /dev/null @@ -1,26 +0,0 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. - -buildscript { - repositories { - google() - mavenCentral() - } - dependencies { - classpath 'com.android.tools.build:gradle:4.2.2' - classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10' - - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files - } -} - -allprojects { - repositories { - google() - mavenCentral() - } -} - -task clean(type: Delete) { - delete rootProject.buildDir -} diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..fcd802f --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,43 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +plugins { + id("com.android.application") version "7.2.2" apply false + id("com.android.library") version "7.2.2" apply false + id("org.jetbrains.kotlin.android") version "1.7.10" apply false + id("org.jlleitschuh.gradle.ktlint") version "10.2.0" + id("io.gitlab.arturbosch.detekt") version "1.18.0-RC2" + id("com.diffplug.spotless") version "6.0.0" + id("org.jetbrains.dokka") version "1.4.20" +} + +allprojects { + apply(plugin = "org.jetbrains.dokka") + apply(plugin = "org.jlleitschuh.gradle.ktlint") + ktlint { + android.set(true) + verbose.set(true) + filter { + exclude { element -> element.file.path.contains("generated/") } + } + } +} + +subprojects { + apply(plugin = "io.gitlab.arturbosch.detekt") + detekt { + config = files("${project.rootDir}/detekt.yml") + parallel = true + buildUponDefaultConfig = true + } + + apply(plugin = "com.diffplug.spotless") + spotless { + kotlin { + target("**/*.kt") + licenseHeaderFile( + rootProject.file("${project.rootDir}/spotless/copyright.kt"), + "^(package|object|import|interface)" + ) + } + } +} \ No newline at end of file diff --git a/detekt.yml b/detekt.yml new file mode 100644 index 0000000..53c990a --- /dev/null +++ b/detekt.yml @@ -0,0 +1,538 @@ +build: + maxIssues: 0 + weights: + # complexity: 2 + # LongParameterList: 1 + # style: 1 + # comments: 1 + +processors: + active: true + exclude: + # - 'FunctionCountProcessor' + # - 'PropertyCountProcessor' + # - 'ClassCountProcessor' + # - 'PackageCountProcessor' + # - 'KtFileCountProcessor' + +console-reports: + active: true + exclude: + # - 'ProjectStatisticsReport' + # - 'ComplexityReport' + # - 'NotificationReport' + # - 'FindingsReport' + # - 'BuildFailureReport' + +comments: + active: true + CommentOverPrivateFunction: + active: false + CommentOverPrivateProperty: + active: false + EndOfSentenceFormat: + active: false + endOfSentenceFormat: ([.?!][ \t\n\r\f<])|([.?!:]$) + UndocumentedPublicClass: + active: false + searchInNestedClass: true + searchInInnerClass: true + searchInInnerObject: true + searchInInnerInterface: true + UndocumentedPublicFunction: + active: false + +complexity: + active: true + ComplexCondition: + active: false + threshold: 4 + ComplexInterface: + active: true + threshold: 10 + includeStaticDeclarations: false + ComplexMethod: + active: false + threshold: 10 + ignoreSingleWhenExpression: false + ignoreSimpleWhenEntries: false + LabeledExpression: + active: false + ignoredLabels: "" + LargeClass: + active: true + threshold: 600 + LongMethod: + active: false + threshold: 60 + LongParameterList: + active: true + threshold: 6 + ignoreDefaultParameters: false + MethodOverloading: + active: true + threshold: 6 + NestedBlockDepth: + active: true + threshold: 4 + StringLiteralDuplication: + active: true + threshold: 3 + ignoreAnnotation: true + excludeStringsWithLessThan5Characters: true + ignoreStringsRegex: '$^' + TooManyFunctions: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + thresholdInFiles: 30 + thresholdInClasses: 30 + thresholdInInterfaces: 30 + thresholdInObjects: 15 + thresholdInEnums: 15 + ignoreDeprecated: false + ignorePrivate: false + ignoreOverridden: false + +empty-blocks: + active: true + EmptyCatchBlock: + active: true + allowedExceptionNameRegex: "^(_|(ignore|expected).*)" + EmptyClassBlock: + active: true + EmptyDefaultConstructor: + active: true + EmptyDoWhileBlock: + active: true + EmptyElseBlock: + active: true + EmptyFinallyBlock: + active: true + EmptyForBlock: + active: true + EmptyFunctionBlock: + active: false + ignoreOverridden: false + EmptyIfBlock: + active: true + EmptyInitBlock: + active: true + EmptyKtFile: + active: true + EmptySecondaryConstructor: + active: true + EmptyWhenBlock: + active: true + EmptyWhileBlock: + active: true + +exceptions: + active: true + ExceptionRaisedInUnexpectedLocation: + active: true + methodNames: 'toString,hashCode,equals,finalize' + InstanceOfCheckForException: + active: true + NotImplementedDeclaration: + active: false + PrintStackTrace: + active: false + RethrowCaughtException: + active: false + ReturnFromFinally: + active: true + SwallowedException: + active: false + ignoredExceptionTypes: 'InterruptedException,NumberFormatException,ParseException,MalformedURLException' + ThrowingExceptionFromFinally: + active: true + ThrowingExceptionInMain: + active: true + ThrowingExceptionsWithoutMessageOrCause: + active: true + exceptions: 'IllegalArgumentException,IllegalStateException,IOException' + ThrowingNewInstanceOfSameException: + active: true + TooGenericExceptionCaught: + active: false + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + exceptionNames: + - ArrayIndexOutOfBoundsException + - Error + - Exception + - IllegalMonitorStateException + - NullPointerException + - IndexOutOfBoundsException + - RuntimeException + - Throwable + allowedExceptionNameRegex: "^(_|(ignore|expected).*)" + TooGenericExceptionThrown: + active: true + exceptionNames: + - Error + - Exception + - Throwable + - RuntimeException + +formatting: + active: true + android: true + ChainWrapping: + active: true + autoCorrect: true + CommentSpacing: + active: true + autoCorrect: true + Filename: + active: true + FinalNewline: + active: true + autoCorrect: true + ImportOrdering: + active: true + Indentation: + active: true + indentSize: 4 + continuationIndentSize: 4 + MaximumLineLength: + active: true + maxLineLength: 120 + ModifierOrdering: + active: true + autoCorrect: true + MultiLineIfElse: + active: true + autoCorrect: true + NoBlankLineBeforeRbrace: + active: true + autoCorrect: true + NoConsecutiveBlankLines: + active: true + autoCorrect: true + NoEmptyClassBody: + active: true + autoCorrect: true + NoLineBreakAfterElse: + active: true + autoCorrect: true + NoLineBreakBeforeAssignment: + active: true + autoCorrect: true + NoMultipleSpaces: + active: true + autoCorrect: true + NoSemicolons: + active: true + autoCorrect: true + NoTrailingSpaces: + active: true + autoCorrect: true + NoUnitReturn: + active: true + autoCorrect: true + NoUnusedImports: + active: true + autoCorrect: true + NoWildcardImports: + active: true + autoCorrect: true + PackageName: + active: true + autoCorrect: true + ParameterListWrapping: + active: true + autoCorrect: true + indentSize: 4 + SpacingAroundColon: + active: true + autoCorrect: true + SpacingAroundComma: + active: true + autoCorrect: true + SpacingAroundCurly: + active: true + autoCorrect: true + SpacingAroundDot: + active: true + autoCorrect: true + SpacingAroundKeyword: + active: true + autoCorrect: true + SpacingAroundOperators: + active: true + autoCorrect: true + SpacingAroundParens: + active: true + autoCorrect: true + SpacingAroundRangeOperator: + active: true + autoCorrect: true + StringTemplate: + active: true + autoCorrect: true + +naming: + active: true + ClassNaming: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + classPattern: '[A-Z$][a-zA-Z0-9$]*' + ConstructorParameterNaming: + active: false + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + parameterPattern: '[a-z][A-Za-z0-9]*' + privateParameterPattern: '[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + EnumNaming: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + enumEntryPattern: '^[A-Z][_a-zA-Z0-9]*' + ForbiddenClassName: + active: false + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + forbiddenName: '' + FunctionMaxLength: + active: false + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + maximumFunctionNameLength: 30 + FunctionMinLength: + active: false + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + minimumFunctionNameLength: 3 + FunctionNaming: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + functionPattern: '^([a-z$][a-zA-Z$0-9]*)|(`.*`)$' + excludeClassPattern: '$^' + ignoreOverridden: true + FunctionParameterNaming: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + parameterPattern: '[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + ignoreOverridden: true + InvalidPackageDeclaration: + active: false + rootPackage: '' + MatchingDeclarationName: + active: true + MemberNameEqualsClassName: + active: false + ignoreOverridden: true + ObjectPropertyNaming: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + constantPattern: '[A-Za-z][_A-Za-z0-9]*' + propertyPattern: '[A-Za-z][_A-Za-z0-9]*' + privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*' + PackageNaming: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + packagePattern: '^[a-z]+(\.[a-z][A-Za-z0-9]*)*$' + TopLevelPropertyNaming: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + constantPattern: '[A-Z][_A-Z0-9]*' + propertyPattern: '[A-Za-z][_A-Za-z0-9]*' + privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*' + VariableMaxLength: + active: false + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + maximumVariableNameLength: 64 + VariableMinLength: + active: false + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + minimumVariableNameLength: 1 + VariableNaming: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + variablePattern: '[a-z][A-Za-z0-9]*' + privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + ignoreOverridden: true + +performance: + active: true + ArrayPrimitive: + active: true + ForEachOnRange: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + SpreadOperator: + active: false + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + UnnecessaryTemporaryInstantiation: + active: true + +potential-bugs: + active: true + DuplicateCaseInWhenExpression: + active: true + EqualsAlwaysReturnsTrueOrFalse: + active: true + EqualsWithHashCodeExist: + active: true + ExplicitGarbageCollectionCall: + active: true + InvalidRange: + active: true + IteratorHasNextCallsNextMethod: + active: true + IteratorNotThrowingNoSuchElementException: + active: true + LateinitUsage: + active: false + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + excludeAnnotatedProperties: "" + ignoreOnClassesPattern: "" + MissingWhenCase: + active: false + RedundantElseInWhen: + active: false + UnconditionalJumpStatementInLoop: + active: true + UnreachableCode: + active: true + UnsafeCallOnNullableType: + active: false + UnsafeCast: + active: true + UselessPostfixExpression: + active: true + WrongEqualsTypeParameter: + active: true + +style: + active: true + CollapsibleIfStatements: + active: true + DataClassContainsFunctions: + active: false + conversionFunctionPrefix: 'to' + DataClassShouldBeImmutable: + active: false + EqualsNullCall: + active: true + EqualsOnSignatureLine: + active: true + ExplicitItLambdaParameter: + active: true + ExpressionBodySyntax: + active: true + includeLineWrapping: false + ForbiddenComment: + active: true + values: 'FIXME:,STOPSHIP:' + ForbiddenImport: + active: false + imports: '' + ForbiddenVoid: + active: true + ignoreOverridden: false + FunctionOnlyReturningConstant: + active: true + ignoreOverridableFunction: true + excludedFunctions: 'describeContents' + LibraryCodeMustSpecifyReturnType: + active: true + LoopWithTooManyJumpStatements: + active: true + maxJumpCount: 1 + MagicNumber: + active: false + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + ignoreNumbers: '-1,0,1,2' + ignoreHashCodeFunction: true + ignorePropertyDeclaration: true + ignoreConstantDeclaration: true + ignoreCompanionObjectPropertyDeclaration: true + ignoreAnnotation: false + ignoreNamedArgument: true + ignoreEnums: false + ignoreRanges: false + ignoreLocalVariableDeclaration: true + MandatoryBracesIfStatements: + active: true + MaxLineLength: + active: false + maxLineLength: 120 + excludePackageStatements: true + excludeImportStatements: true + excludeCommentStatements: false + MayBeConst: + active: true + ModifierOrder: + active: true + NestedClassesVisibility: + active: true + NewLineAtEndOfFile: + active: false + NoTabs: + active: true + OptionalAbstractKeyword: + active: true + OptionalUnit: + active: true + OptionalWhenBraces: + active: false + PreferToOverPairSyntax: + active: true + ProtectedMemberInFinalClass: + active: true + RedundantVisibilityModifierRule: + active: true + ReturnCount: + active: false + max: 2 + excludedFunctions: "equals" + excludeLabeled: false + excludeReturnFromLambda: true + SafeCast: + active: true + SerialVersionUIDInSerializableClass: + active: true + SpacingBetweenPackageAndImports: + active: true + ThrowsCount: + active: true + max: 5 + TrailingWhitespace: + active: true + UnderscoresInNumericLiterals: + active: false + acceptableDecimalLength: 5 + UnnecessaryAbstractClass: + active: false + excludeAnnotatedClasses: "dagger.Module" + UnnecessaryApply: + active: false + UnnecessaryInheritance: + active: true + UnnecessaryLet: + active: true + UnnecessaryParentheses: + active: false + UntilInsteadOfRangeTo: + active: true + UnusedImports: + active: true + UnusedPrivateClass: + active: true + UnusedPrivateMember: + active: false + allowedNames: '(_|ignored|expected|serialVersionUID)' + UseDataClass: + active: false + excludeAnnotatedClasses: "" + UseRequire: + active: false + UselessCallOnNotNull: + active: false + UtilityClassWithPublicConstructor: + active: true + VarCouldBeVal: + active: true + WildcardImport: + active: false + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + excludeImports: 'java.util.*,kotlinx.android.synthetic.*' \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..140f294 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,5 @@ +[versions] +espresso = "3.5.0-alpha07" + +[libraries] +android-coreKtx = "androidx.core:core-ktx:1.8.0" diff --git a/multisim/build.gradle b/multisim/build.gradle deleted file mode 100644 index 879cefc..0000000 --- a/multisim/build.gradle +++ /dev/null @@ -1,31 +0,0 @@ -apply plugin: 'com.android.library' - -android { - compileSdkVersion 33 - defaultConfig { - minSdkVersion 18 - targetSdkVersion 33 - versionCode 1 - versionName "1.0" - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } -} - -dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'androidx.appcompat:appcompat:1.5.1' - implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.1.0' - implementation "androidx.work:work-runtime:2.7.1" - - testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.ext:junit:1.1.3' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' - - implementation 'io.sentry:sentry-android:6.4.2' -} diff --git a/multisim/build.gradle.kts b/multisim/build.gradle.kts new file mode 100644 index 0000000..65f42e3 --- /dev/null +++ b/multisim/build.gradle.kts @@ -0,0 +1,45 @@ +plugins { + id("com.android.library") + id("org.jetbrains.kotlin.android") +} + +android { + + compileSdk = 33 + + defaultConfig { + minSdk = 18 + targetSdk = 33 + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro") + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = "11" + } + + dependencies { + implementation("androidx.appcompat:appcompat:1.5.1") + implementation("androidx.localbroadcastmanager:localbroadcastmanager:1.1.0") + implementation("androidx.work:work-runtime:2.7.1") + + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.3") + androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0") + + implementation("io.sentry:sentry-android:6.4.2") + } +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index a3cea40..0000000 --- a/settings.gradle +++ /dev/null @@ -1,4 +0,0 @@ -include ':app' -include ':multisim' - -rootProject.name='MultiSim' diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..5511627 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,26 @@ +pluginManagement { + repositories { + gradlePluginPortal() + google() + mavenCentral() + } +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} + +mapOf( + "app" to "app", +).forEach { (projectName, projectPath) -> + include(":$projectName") + project(":$projectName").projectDir = File(projectPath) +} + +include(":multisim") + +rootProject.name = "MultiSim" diff --git a/spotless/copyright.kt b/spotless/copyright.kt new file mode 100644 index 0000000..2cf820d --- /dev/null +++ b/spotless/copyright.kt @@ -0,0 +1,15 @@ +/* + * Copyright $YEAR UseHover + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ \ No newline at end of file From 30e0c9c047fb011b0184cb0bad6eb57b404d6e8d Mon Sep 17 00:00:00 2001 From: Juma Allan Date: Mon, 3 Oct 2022 21:32:42 +0300 Subject: [PATCH 05/26] migrate to versions plugin and cleaning up --- app/build.gradle.kts | 12 ++++----- build.gradle.kts | 4 +-- gradle/libs.versions.toml | 12 ++++++++- multisim/build.gradle.kts | 14 +++++----- .../multisim/ExampleInstrumentedTest.java | 27 ------------------- multisim/src/main/AndroidManifest.xml | 3 +-- 6 files changed, 26 insertions(+), 46 deletions(-) delete mode 100644 multisim/src/androidTest/java/com/hover/multisim/ExampleInstrumentedTest.java diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5fef725..dcb6482 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -44,12 +44,12 @@ dependencies { implementation(project(":multisim")) - implementation("androidx.core:core-ktx:1.9.0") + implementation(libs.android.coreKtx) + implementation(libs.android.appcompat) + implementation(libs.android.material) - implementation("androidx.appcompat:appcompat:1.5.1") - implementation("com.google.android.material:material:1.6.1") + androidTestImplementation(libs.test.android.junit) + androidTestImplementation(libs.test.android.espresso) - testImplementation("junit:junit:4.13.2") - androidTestImplementation("androidx.test.ext:junit:1.1.3") - androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0") + testImplementation(libs.test.junit4) } \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index fcd802f..e03491c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "7.2.2" apply false - id("com.android.library") version "7.2.2" apply false + id("com.android.application") version "7.3.0" apply false + id("com.android.library") version "7.3.0" apply false id("org.jetbrains.kotlin.android") version "1.7.10" apply false id("org.jlleitschuh.gradle.ktlint") version "10.2.0" id("io.gitlab.arturbosch.detekt") version "1.18.0-RC2" diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 140f294..fbf1506 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,4 +2,14 @@ espresso = "3.5.0-alpha07" [libraries] -android-coreKtx = "androidx.core:core-ktx:1.8.0" +android-coreKtx = "androidx.core:core-ktx:1.9.0" +android-material = "com.google.android.material:material:1.6.1" +android-appcompat = "androidx.appcompat:appcompat:1.5.1" +android-localbroadcastmanager = "androidx.localbroadcastmanager:localbroadcastmanager:1.1.0" +android-work = "androidx.work:work-runtime:2.7.1" + +sentry = "io.sentry:sentry-android:6.4.2" + +test-junit4 = "junit:junit:4.13.2" +test-android-junit = "androidx.test.ext:junit:1.1.3" +test-android-espresso = "androidx.test.espresso:espresso-core:3.4.0" \ No newline at end of file diff --git a/multisim/build.gradle.kts b/multisim/build.gradle.kts index 65f42e3..6ebe587 100644 --- a/multisim/build.gradle.kts +++ b/multisim/build.gradle.kts @@ -30,16 +30,14 @@ android { kotlinOptions { jvmTarget = "11" } + namespace = "com.hover.multisim" dependencies { - implementation("androidx.appcompat:appcompat:1.5.1") - implementation("androidx.localbroadcastmanager:localbroadcastmanager:1.1.0") - implementation("androidx.work:work-runtime:2.7.1") + implementation(libs.android.appcompat) + implementation(libs.android.localbroadcastmanager) + implementation(libs.android.work) + implementation(libs.sentry) - testImplementation("junit:junit:4.13.2") - androidTestImplementation("androidx.test.ext:junit:1.1.3") - androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0") - - implementation("io.sentry:sentry-android:6.4.2") + testImplementation(libs.test.junit4) } } \ No newline at end of file diff --git a/multisim/src/androidTest/java/com/hover/multisim/ExampleInstrumentedTest.java b/multisim/src/androidTest/java/com/hover/multisim/ExampleInstrumentedTest.java deleted file mode 100644 index 5bfb06c..0000000 --- a/multisim/src/androidTest/java/com/hover/multisim/ExampleInstrumentedTest.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.hover.multisim; - -import android.content.Context; - -import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import static org.junit.Assert.*; - -/** - * Instrumented test, which will execute on an Android device. - * - * @see Testing documentation - */ -@RunWith(AndroidJUnit4.class) -public class ExampleInstrumentedTest { - @Test - public void useAppContext() { - // Context of the app under test. - Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); - - assertEquals("com.hover.multisim", appContext.getPackageName()); - } -} diff --git a/multisim/src/main/AndroidManifest.xml b/multisim/src/main/AndroidManifest.xml index 1592c5d..ad9cd11 100644 --- a/multisim/src/main/AndroidManifest.xml +++ b/multisim/src/main/AndroidManifest.xml @@ -1,5 +1,4 @@ - + Date: Mon, 3 Oct 2022 23:38:19 +0300 Subject: [PATCH 06/26] runt lint checks --- app/build.gradle.kts | 29 ++++++++- .../com/hover/app/ExampleInstrumentedTest.kt | 18 +++++- app/src/main/AndroidManifest.xml | 18 +++++- .../main/java/com/hover/app/MainActivity.kt | 59 +++++++++++++++++++ .../main/java/com/hover/app/ui/theme/Color.kt | 23 ++++++++ .../main/java/com/hover/app/ui/theme/Shape.kt | 26 ++++++++ .../main/java/com/hover/app/ui/theme/Theme.kt | 59 +++++++++++++++++++ .../main/java/com/hover/app/ui/theme/Type.kt | 35 +++++++++++ app/src/main/res/values/strings.xml | 1 + .../java/com/hover/app/ExampleUnitTest.kt | 20 ++++++- build.gradle.kts | 2 +- multisim/build.gradle.kts | 8 ++- 12 files changed, 287 insertions(+), 11 deletions(-) create mode 100644 app/src/main/java/com/hover/app/MainActivity.kt create mode 100644 app/src/main/java/com/hover/app/ui/theme/Color.kt create mode 100644 app/src/main/java/com/hover/app/ui/theme/Shape.kt create mode 100644 app/src/main/java/com/hover/app/ui/theme/Theme.kt create mode 100644 app/src/main/java/com/hover/app/ui/theme/Type.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index dcb6482..46e9126 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -18,6 +18,9 @@ android { versionName = "1.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables { + useSupportLibrary = true + } } compileOptions { @@ -38,6 +41,20 @@ android { ) } } + + buildFeatures { + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = "1.1.1" + } + + packagingOptions { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } } dependencies { @@ -48,8 +65,18 @@ dependencies { implementation(libs.android.appcompat) implementation(libs.android.material) + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.3.1") + implementation("androidx.activity:activity-compose:1.3.1") + implementation("androidx.compose.ui:ui:1.1.1") + implementation("androidx.compose.ui:ui-tooling-preview:1.1.1") + implementation("androidx.compose.material:material:1.1.1") + + androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.1.1") + debugImplementation("androidx.compose.ui:ui-tooling:1.1.1") + debugImplementation("androidx.compose.ui:ui-test-manifest:1.1.1") + androidTestImplementation(libs.test.android.junit) androidTestImplementation(libs.test.android.espresso) testImplementation(libs.test.junit4) -} \ No newline at end of file +} diff --git a/app/src/androidTest/java/com/hover/app/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/hover/app/ExampleInstrumentedTest.kt index ba818a7..93ba096 100644 --- a/app/src/androidTest/java/com/hover/app/ExampleInstrumentedTest.kt +++ b/app/src/androidTest/java/com/hover/app/ExampleInstrumentedTest.kt @@ -1,3 +1,18 @@ +/* + * Copyright 2022 UseHover + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.hover.app import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -6,7 +21,6 @@ import junit.framework.Assert.assertEquals import org.junit.Test import org.junit.runner.RunWith - /** * Instrumented test, which will execute on an Android device. * @@ -20,4 +34,4 @@ class ExampleInstrumentedTest { val appContext = InstrumentationRegistry.getInstrumentation().targetContext assertEquals("com.hover.app", appContext.packageName) } -} \ No newline at end of file +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index fb2a003..6610b74 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,22 @@ android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" - android:theme="@style/Theme.MultiSim" /> + android:theme="@style/Theme.MultiSim"> + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/hover/app/MainActivity.kt b/app/src/main/java/com/hover/app/MainActivity.kt new file mode 100644 index 0000000..cee7a36 --- /dev/null +++ b/app/src/main/java/com/hover/app/MainActivity.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2022 UseHover + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hover.app + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Surface +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import com.hover.app.ui.theme.MultiSimTheme + +class MainActivity : ComponentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + MultiSimTheme { + // A surface container using the 'background' color from the theme + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colors.background + ) { + Greeting("Android") + } + } + } + } +} + +@Composable +fun Greeting(name: String) { + Text(text = "Hello $name!") +} + +@Preview(showBackground = true) +@Composable +fun DefaultPreview() { + MultiSimTheme { + Greeting("Android") + } +} diff --git a/app/src/main/java/com/hover/app/ui/theme/Color.kt b/app/src/main/java/com/hover/app/ui/theme/Color.kt new file mode 100644 index 0000000..a8b19d8 --- /dev/null +++ b/app/src/main/java/com/hover/app/ui/theme/Color.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2022 UseHover + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hover.app.ui.theme + +import androidx.compose.ui.graphics.Color + +val Purple200 = Color(0xFFBB86FC) +val Purple500 = Color(0xFF6200EE) +val Purple700 = Color(0xFF3700B3) +val Teal200 = Color(0xFF03DAC5) diff --git a/app/src/main/java/com/hover/app/ui/theme/Shape.kt b/app/src/main/java/com/hover/app/ui/theme/Shape.kt new file mode 100644 index 0000000..5516a33 --- /dev/null +++ b/app/src/main/java/com/hover/app/ui/theme/Shape.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2022 UseHover + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hover.app.ui.theme + +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Shapes +import androidx.compose.ui.unit.dp + +val Shapes = Shapes( + small = RoundedCornerShape(4.dp), + medium = RoundedCornerShape(4.dp), + large = RoundedCornerShape(0.dp) +) diff --git a/app/src/main/java/com/hover/app/ui/theme/Theme.kt b/app/src/main/java/com/hover/app/ui/theme/Theme.kt new file mode 100644 index 0000000..5286226 --- /dev/null +++ b/app/src/main/java/com/hover/app/ui/theme/Theme.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2022 UseHover + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hover.app.ui.theme + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material.MaterialTheme +import androidx.compose.material.darkColors +import androidx.compose.material.lightColors +import androidx.compose.runtime.Composable + +private val DarkColorPalette = darkColors( + primary = Purple200, + primaryVariant = Purple700, + secondary = Teal200 +) + +private val LightColorPalette = lightColors( + primary = Purple500, + primaryVariant = Purple700, + secondary = Teal200 + + /* Other default colors to override + background = Color.White, + surface = Color.White, + onPrimary = Color.White, + onSecondary = Color.Black, + onBackground = Color.Black, + onSurface = Color.Black, + */ +) + +@Composable +fun MultiSimTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) { + val colors = if (darkTheme) { + DarkColorPalette + } else { + LightColorPalette + } + + MaterialTheme( + colors = colors, + typography = Typography, + shapes = Shapes, + content = content + ) +} diff --git a/app/src/main/java/com/hover/app/ui/theme/Type.kt b/app/src/main/java/com/hover/app/ui/theme/Type.kt new file mode 100644 index 0000000..81d8253 --- /dev/null +++ b/app/src/main/java/com/hover/app/ui/theme/Type.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2022 UseHover + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hover.app.ui.theme + +import androidx.compose.material.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( + body1 = TextStyle( + fontFamily = FontFamily.Default, fontWeight = FontWeight.Normal, fontSize = 16.sp + ), + button = TextStyle( + fontFamily = FontFamily.Default, fontWeight = FontWeight.W500, fontSize = 14.sp + ), + caption = TextStyle( + fontFamily = FontFamily.Default, fontWeight = FontWeight.Normal, fontSize = 12.sp + ) +) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4daeaed..7c9f826 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,3 +1,4 @@ Multisim + MainActivity \ No newline at end of file diff --git a/app/src/test/java/com/hover/app/ExampleUnitTest.kt b/app/src/test/java/com/hover/app/ExampleUnitTest.kt index 9d29a1c..fbc762c 100644 --- a/app/src/test/java/com/hover/app/ExampleUnitTest.kt +++ b/app/src/test/java/com/hover/app/ExampleUnitTest.kt @@ -1,9 +1,23 @@ +/* + * Copyright 2022 UseHover + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.hover.app +import junit.framework.Assert.assertEquals import org.junit.Test -import org.junit.Assert.* - /** * Example local unit test, which will execute on the development machine (host). * @@ -14,4 +28,4 @@ class ExampleUnitTest { fun addition_isCorrect() { assertEquals(4, 2 + 2) } -} \ No newline at end of file +} diff --git a/build.gradle.kts b/build.gradle.kts index e03491c..f6d7b35 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -40,4 +40,4 @@ subprojects { ) } } -} \ No newline at end of file +} diff --git a/multisim/build.gradle.kts b/multisim/build.gradle.kts index 6ebe587..671303d 100644 --- a/multisim/build.gradle.kts +++ b/multisim/build.gradle.kts @@ -17,8 +17,10 @@ android { buildTypes { release { isMinifyEnabled = false - proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro") + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) } } @@ -40,4 +42,4 @@ android { testImplementation(libs.test.junit4) } -} \ No newline at end of file +} From 8c153051f195c19a6b37ffc4317e026b45e7b200 Mon Sep 17 00:00:00 2001 From: Juma Allan Date: Mon, 3 Oct 2022 23:38:38 +0300 Subject: [PATCH 07/26] added code analysis bash script --- codeAnalysis.sh | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100755 codeAnalysis.sh diff --git a/codeAnalysis.sh b/codeAnalysis.sh new file mode 100755 index 0000000..59e163c --- /dev/null +++ b/codeAnalysis.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +./gradlew ktlintFormat && ./gradlew ktlintCheck && ./gradlew detekt && ./gradlew spotlessApply + +# Before use it, in the first time, you must guarantee some running permissions: +# chmod +x codeAnalysis.sh +# +# After that, you just need to run: +# ./codeAnalysis.sh \ No newline at end of file From a5a7b48d52864e2aac83863af9d6f6d34483a4e9 Mon Sep 17 00:00:00 2001 From: Juma Allan Date: Tue, 4 Oct 2022 01:36:53 +0300 Subject: [PATCH 08/26] adding hilt and room support --- app/build.gradle.kts | 25 ++++++++------- build.gradle.kts | 9 +++++- gradle/libs.versions.toml | 29 +++++++++++++++-- multisim/build.gradle.kts | 32 ++++++++++++++++++- .../com/hover/multisim/ExampleUnitTest.java | 17 ---------- 5 files changed, 79 insertions(+), 33 deletions(-) delete mode 100644 multisim/src/test/java/com/hover/multisim/ExampleUnitTest.java diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 46e9126..6879c65 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -8,6 +8,7 @@ plugins { android { namespace = "com.hover.app" + compileSdk = 33 defaultConfig { @@ -46,15 +47,15 @@ android { compose = true } - composeOptions { - kotlinCompilerExtensionVersion = "1.1.1" - } - packagingOptions { resources { excludes += "/META-INF/{AL2.0,LGPL2.1}" } } + + composeOptions { + kotlinCompilerExtensionVersion = "1.2.0" + } } dependencies { @@ -65,15 +66,15 @@ dependencies { implementation(libs.android.appcompat) implementation(libs.android.material) - implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.3.1") - implementation("androidx.activity:activity-compose:1.3.1") - implementation("androidx.compose.ui:ui:1.1.1") - implementation("androidx.compose.ui:ui-tooling-preview:1.1.1") - implementation("androidx.compose.material:material:1.1.1") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.5.1") + implementation("androidx.activity:activity-compose:1.6.0") + implementation("androidx.compose.ui:ui:1.2.1") + implementation("androidx.compose.ui:ui-tooling-preview:1.2.1") + implementation("androidx.compose.material:material:1.2.1") - androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.1.1") - debugImplementation("androidx.compose.ui:ui-tooling:1.1.1") - debugImplementation("androidx.compose.ui:ui-test-manifest:1.1.1") + androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.2.1") + debugImplementation("androidx.compose.ui:ui-tooling:1.2.1") + debugImplementation("androidx.compose.ui:ui-test-manifest:1.2.1") androidTestImplementation(libs.test.android.junit) androidTestImplementation(libs.test.android.espresso) diff --git a/build.gradle.kts b/build.gradle.kts index f6d7b35..ccd596d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,13 +3,20 @@ plugins { id("com.android.application") version "7.3.0" apply false id("com.android.library") version "7.3.0" apply false - id("org.jetbrains.kotlin.android") version "1.7.10" apply false + id("org.jetbrains.kotlin.android") version "1.7.0" apply false + id("com.google.devtools.ksp") version "1.7.0-1.0.6" apply true id("org.jlleitschuh.gradle.ktlint") version "10.2.0" id("io.gitlab.arturbosch.detekt") version "1.18.0-RC2" id("com.diffplug.spotless") version "6.0.0" id("org.jetbrains.dokka") version "1.4.20" } +buildscript { + dependencies { + classpath("com.google.dagger:hilt-android-gradle-plugin:2.42") + } +} + allprojects { apply(plugin = "org.jetbrains.dokka") apply(plugin = "org.jlleitschuh.gradle.ktlint") diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fbf1506..4609c61 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,15 +1,40 @@ [versions] -espresso = "3.5.0-alpha07" +hilt = "2.42" +room = "2.4.2" +coroutines = "1.6.0" [libraries] android-coreKtx = "androidx.core:core-ktx:1.9.0" + android-material = "com.google.android.material:material:1.6.1" + android-appcompat = "androidx.appcompat:appcompat:1.5.1" + android-localbroadcastmanager = "androidx.localbroadcastmanager:localbroadcastmanager:1.1.0" + android-work = "androidx.work:work-runtime:2.7.1" +android-hilt = { module = "com.google.dagger:hilt-android", version.ref = "hilt" } +android-hilt-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hilt" } +android-hilt-testing = { module = "com.google.dagger:hilt-android-testing", version.ref = "hilt" } + +room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" } +room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" } +room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" } + sentry = "io.sentry:sentry-android:6.4.2" +kotlin-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" } +kotlin-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" } + test-junit4 = "junit:junit:4.13.2" test-android-junit = "androidx.test.ext:junit:1.1.3" -test-android-espresso = "androidx.test.espresso:espresso-core:3.4.0" \ No newline at end of file +test-robolectric = "org.robolectric:robolectric:4.8.1" +test-mockk = "io.mockk:mockk:1.12.7" +test-fixture = "com.appmattus.fixture:fixture:1.2.0" + +test-android-espresso = "androidx.test.espresso:espresso-core:3.4.0" + +[bundles] +hilt = ["android-hilt", "android-hilt-compiler", "android-hilt-testing"] +room = ["room-ktx", "room-runtime"] \ No newline at end of file diff --git a/multisim/build.gradle.kts b/multisim/build.gradle.kts index 671303d..3b3fa05 100644 --- a/multisim/build.gradle.kts +++ b/multisim/build.gradle.kts @@ -1,10 +1,15 @@ plugins { id("com.android.library") id("org.jetbrains.kotlin.android") + id("dagger.hilt.android.plugin") + id("com.google.devtools.ksp") + kotlin("kapt") } android { + namespace = "com.hover.multisim" + compileSdk = 33 defaultConfig { @@ -24,6 +29,10 @@ android { } } + ksp { + arg("room.schemaLocation", "$projectDir/schemas") + } + compileOptions { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 @@ -32,14 +41,35 @@ android { kotlinOptions { jvmTarget = "11" } - namespace = "com.hover.multisim" dependencies { implementation(libs.android.appcompat) + implementation(libs.android.localbroadcastmanager) + implementation(libs.android.work) + implementation(libs.sentry) + implementation(libs.bundles.hilt) + + implementation(libs.bundles.room) + ksp(libs.room.compiler) + testImplementation(libs.test.junit4) + testImplementation(libs.kotlin.coroutines.test) + testImplementation(libs.test.robolectric) + testImplementation(libs.test.mockk) + testImplementation(libs.test.fixture) + } +} + +kotlin { + sourceSets { + all { + languageSettings.apply { + optIn("kotlinx.coroutines.ExperimentalCoroutinesApi") + } + } } } diff --git a/multisim/src/test/java/com/hover/multisim/ExampleUnitTest.java b/multisim/src/test/java/com/hover/multisim/ExampleUnitTest.java deleted file mode 100644 index 93abdd5..0000000 --- a/multisim/src/test/java/com/hover/multisim/ExampleUnitTest.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.hover.multisim; - -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * Example local unit test, which will execute on the development machine (host). - * - * @see Testing documentation - */ -public class ExampleUnitTest { - @Test - public void addition_isCorrect() { - assertEquals(4, 2 + 2); - } -} \ No newline at end of file From 3a0d32edc7f3487a8c6efa9232bbea3d47169cc3 Mon Sep 17 00:00:00 2001 From: Juma Allan Date: Tue, 4 Oct 2022 01:37:57 +0300 Subject: [PATCH 09/26] setting up room database and database test --- .../com.hover.multisim.db.Database/1.json | 140 ++++++++++++++++++ .../java/com/hover/multisim/db/Database.kt | 33 +++++ .../java/com/hover/multisim/db/dao/BaseDao.kt | 49 ++++++ .../java/com/hover/multisim/db/dao/SimDao.kt | 28 ++++ .../com/hover/multisim/db/model/HSDKSims.kt | 43 ++++++ .../java/com/hover/multisim/di/DaoModule.kt | 33 +++++ .../com/hover/multisim/di/DatabaseModule.kt | 41 +++++ .../com/hover/multisim/db/dao/SimDaoTest.kt | 64 ++++++++ 8 files changed, 431 insertions(+) create mode 100644 multisim/schemas/com.hover.multisim.db.Database/1.json create mode 100644 multisim/src/main/java/com/hover/multisim/db/Database.kt create mode 100644 multisim/src/main/java/com/hover/multisim/db/dao/BaseDao.kt create mode 100644 multisim/src/main/java/com/hover/multisim/db/dao/SimDao.kt create mode 100644 multisim/src/main/java/com/hover/multisim/db/model/HSDKSims.kt create mode 100644 multisim/src/main/java/com/hover/multisim/di/DaoModule.kt create mode 100644 multisim/src/main/java/com/hover/multisim/di/DatabaseModule.kt create mode 100644 multisim/src/test/java/com/hover/multisim/db/dao/SimDaoTest.kt diff --git a/multisim/schemas/com.hover.multisim.db.Database/1.json b/multisim/schemas/com.hover.multisim.db.Database/1.json new file mode 100644 index 0000000..e7ab190 --- /dev/null +++ b/multisim/schemas/com.hover.multisim.db.Database/1.json @@ -0,0 +1,140 @@ +{ + "formatVersion": 1, + "database": { + "version": 1, + "identityHash": "64772d7bc9ec08680b3c8ce4ced8e2e0", + "entities": [ + { + "tableName": "hsdk_sims", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `slot_idx` INTEGER NOT NULL, `sub_id` INTEGER NOT NULL, `imei` TEXT, `state` INTEGER NOT NULL, `imsi` TEXT NOT NULL, `mcc` TEXT NOT NULL, `mnc` TEXT, `iccid` TEXT NOT NULL, `operator` TEXT, `operator_name` TEXT, `country_iso` TEXT, `is_roaming` INTEGER NOT NULL, `network_code` TEXT, `network_name` TEXT, `network_country` TEXT, `network_type` INTEGER)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "slot_idx", + "columnName": "slot_idx", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sub_id", + "columnName": "sub_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "imei", + "columnName": "imei", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "state", + "columnName": "state", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "imsi", + "columnName": "imsi", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mcc", + "columnName": "mcc", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mnc", + "columnName": "mnc", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "iccid", + "columnName": "iccid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "operator", + "columnName": "operator", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "operator_name", + "columnName": "operator_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "country_iso", + "columnName": "country_iso", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "is_roaming", + "columnName": "is_roaming", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "network_code", + "columnName": "network_code", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "network_name", + "columnName": "network_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "network_country", + "columnName": "network_country", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "network_type", + "columnName": "network_type", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_hsdk_sims_iccid", + "unique": true, + "columnNames": [ + "iccid" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_hsdk_sims_iccid` ON `${TABLE_NAME}` (`iccid`)" + } + ], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '64772d7bc9ec08680b3c8ce4ced8e2e0')" + ] + } +} \ No newline at end of file diff --git a/multisim/src/main/java/com/hover/multisim/db/Database.kt b/multisim/src/main/java/com/hover/multisim/db/Database.kt new file mode 100644 index 0000000..a0061b1 --- /dev/null +++ b/multisim/src/main/java/com/hover/multisim/db/Database.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2022 UseHover + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hover.multisim.db + +import androidx.room.Database +import androidx.room.RoomDatabase +import com.hover.multisim.db.dao.SimDao +import com.hover.multisim.db.model.HSDKSims + +@Database( + entities = [ + HSDKSims::class + ], + version = 1, + exportSchema = true +) + +abstract class Database : RoomDatabase() { + abstract fun simDao(): SimDao +} diff --git a/multisim/src/main/java/com/hover/multisim/db/dao/BaseDao.kt b/multisim/src/main/java/com/hover/multisim/db/dao/BaseDao.kt new file mode 100644 index 0000000..4f44cae --- /dev/null +++ b/multisim/src/main/java/com/hover/multisim/db/dao/BaseDao.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2022 UseHover + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hover.multisim.db.dao + +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.OnConflictStrategy.REPLACE +import androidx.room.Update + +/** + * BaseDao + * + * This dao interface makes it easy to abstract commonly used room operations + * + * @param T takes in the data class + */ +interface BaseDao { + + @Insert(onConflict = REPLACE) + suspend fun insert(item: T): Long + + @Insert(onConflict = REPLACE) + suspend fun insert(vararg items: T) + + @Insert(onConflict = REPLACE) + suspend fun insert(items: List) + + @Update(onConflict = REPLACE) + suspend fun update(item: T): Int + + @Update(onConflict = REPLACE) + suspend fun update(items: List): Int + + @Delete + suspend fun delete(item: T) +} diff --git a/multisim/src/main/java/com/hover/multisim/db/dao/SimDao.kt b/multisim/src/main/java/com/hover/multisim/db/dao/SimDao.kt new file mode 100644 index 0000000..d644c6c --- /dev/null +++ b/multisim/src/main/java/com/hover/multisim/db/dao/SimDao.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2022 UseHover + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hover.multisim.db.dao + +import androidx.room.Dao +import androidx.room.Query +import com.hover.multisim.db.model.HSDKSims +import kotlinx.coroutines.flow.Flow + +@Dao +interface SimDao : BaseDao { + + @Query("SELECT * FROM hsdk_sims") + fun getAll(): Flow> +} diff --git a/multisim/src/main/java/com/hover/multisim/db/model/HSDKSims.kt b/multisim/src/main/java/com/hover/multisim/db/model/HSDKSims.kt new file mode 100644 index 0000000..226205b --- /dev/null +++ b/multisim/src/main/java/com/hover/multisim/db/model/HSDKSims.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2022 UseHover + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hover.multisim.db.model + +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey + +@Entity( + tableName = "hsdk_sims", indices = [Index(value = ["iccid"], unique = true)] +) +data class HSDKSims( + @PrimaryKey(autoGenerate = true) val id: Long, + val slot_idx: Int, + val sub_id: Int, + val imei: String?, + val state: Int, + val imsi: String, + val mcc: String, + val mnc: String?, + val iccid: String, + val operator: String?, + val operator_name: String?, + val country_iso: String?, + val is_roaming: Boolean, + val network_code: String?, + val network_name: String?, + val network_country: String?, + val network_type: Int?, +) diff --git a/multisim/src/main/java/com/hover/multisim/di/DaoModule.kt b/multisim/src/main/java/com/hover/multisim/di/DaoModule.kt new file mode 100644 index 0000000..5580e1d --- /dev/null +++ b/multisim/src/main/java/com/hover/multisim/di/DaoModule.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2022 UseHover + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hover.multisim.di + +import com.hover.multisim.db.Database +import com.hover.multisim.db.dao.SimDao +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent + +@Module +@InstallIn(SingletonComponent::class) +object DaoModule { + + @Provides + fun providesSimDao( + database: Database, + ): SimDao = database.simDao() +} diff --git a/multisim/src/main/java/com/hover/multisim/di/DatabaseModule.kt b/multisim/src/main/java/com/hover/multisim/di/DatabaseModule.kt new file mode 100644 index 0000000..2b73179 --- /dev/null +++ b/multisim/src/main/java/com/hover/multisim/di/DatabaseModule.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2022 UseHover + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hover.multisim.di + +import android.content.Context +import androidx.room.Room +import com.hover.multisim.db.Database +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object DatabaseModule { + + @Provides + @Singleton + fun providesDatabase( + @ApplicationContext context: Context, + ): Database = Room.databaseBuilder( + context, + Database::class.java, + "multisim" + ).build() +} diff --git a/multisim/src/test/java/com/hover/multisim/db/dao/SimDaoTest.kt b/multisim/src/test/java/com/hover/multisim/db/dao/SimDaoTest.kt new file mode 100644 index 0000000..ca4e537 --- /dev/null +++ b/multisim/src/test/java/com/hover/multisim/db/dao/SimDaoTest.kt @@ -0,0 +1,64 @@ +/* + * Copyright 2022 UseHover + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hover.multisim.db.dao + +import android.content.Context +import androidx.room.Room +import androidx.test.core.app.ApplicationProvider +import com.appmattus.kotlinfixture.kotlinFixture +import com.hover.multisim.db.Database +import com.hover.multisim.db.model.HSDKSims +import java.io.IOException +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.test.runTest +import org.hamcrest.CoreMatchers.`is` +import org.hamcrest.MatcherAssert +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +class SimDaoTest { + + private lateinit var simDao: SimDao + private lateinit var db: Database + private val fixture = kotlinFixture() + + @Before + fun setup() { + val context = ApplicationProvider.getApplicationContext() + db = Room.inMemoryDatabaseBuilder( + context, Database::class.java + ).build() + simDao = db.simDao() + } + + @After + @Throws(IOException::class) + fun tearDown() { + db.close() + } + + @Test + fun `test simDao fetches all sim data`() = runTest { + val sim: HSDKSims = fixture() + simDao.insert(sim) + val result = simDao.getAll().first() + MatcherAssert.assertThat(sim.imsi, `is`(result.first().imsi)) + } +} From 15ca5bd7ada319e1928f45cdfe29e229d9305d06 Mon Sep 17 00:00:00 2001 From: Juma Allan Date: Tue, 4 Oct 2022 02:23:49 +0300 Subject: [PATCH 10/26] migrating from SQLite to Room Database --- multisim/build.gradle.kts | 4 + .../java/com/hover/multisim/DataSource.java | 22 --- .../java/com/hover/multisim/SimContract.java | 48 ----- .../com/hover/multisim/SimDataSource.java | 168 ------------------ .../java/com/hover/multisim/SimDatabase.java | 58 ------ .../java/com/hover/multisim/db/dao/SimDao.kt | 11 +- .../multisim/{ => sim}/MultiSimWorker.java | 3 +- .../com/hover/multisim/{ => sim}/SimInfo.java | 2 +- .../hover/multisim/{ => sim}/SlotManager.java | 2 +- .../com/hover/multisim/{ => sim}/Utils.java | 2 +- 10 files changed, 19 insertions(+), 301 deletions(-) delete mode 100644 multisim/src/main/java/com/hover/multisim/DataSource.java delete mode 100644 multisim/src/main/java/com/hover/multisim/SimContract.java delete mode 100644 multisim/src/main/java/com/hover/multisim/SimDataSource.java delete mode 100644 multisim/src/main/java/com/hover/multisim/SimDatabase.java rename multisim/src/main/java/com/hover/multisim/{ => sim}/MultiSimWorker.java (99%) rename multisim/src/main/java/com/hover/multisim/{ => sim}/SimInfo.java (99%) rename multisim/src/main/java/com/hover/multisim/{ => sim}/SlotManager.java (99%) rename multisim/src/main/java/com/hover/multisim/{ => sim}/Utils.java (96%) diff --git a/multisim/build.gradle.kts b/multisim/build.gradle.kts index 3b3fa05..794d3c5 100644 --- a/multisim/build.gradle.kts +++ b/multisim/build.gradle.kts @@ -33,6 +33,10 @@ android { arg("room.schemaLocation", "$projectDir/schemas") } + kapt { + correctErrorTypes = true + } + compileOptions { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 diff --git a/multisim/src/main/java/com/hover/multisim/DataSource.java b/multisim/src/main/java/com/hover/multisim/DataSource.java deleted file mode 100644 index 3f94a3a..0000000 --- a/multisim/src/main/java/com/hover/multisim/DataSource.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.hover.multisim; - -import android.content.Context; -import android.database.SQLException; -import android.database.sqlite.SQLiteDatabase; - -public class DataSource { - public final static String TAG = "DataSource"; - - private Context context; - protected SQLiteDatabase database; - - protected DataSource(Context context) { context = context.getApplicationContext(); } - - protected void open() throws SQLException { - database = SimDatabase.getInstance(context).getWritableDatabase(); - } - @SuppressWarnings("EmptyMethod") - protected void close() throws SQLException { - // Apparently don't need to do this? Seems strange but: https://stackoverflow.com/questions/6608498/best-place-to-close-database-connection - } -} diff --git a/multisim/src/main/java/com/hover/multisim/SimContract.java b/multisim/src/main/java/com/hover/multisim/SimContract.java deleted file mode 100644 index f0ba92b..0000000 --- a/multisim/src/main/java/com/hover/multisim/SimContract.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.hover.multisim; - -final public class SimContract { - public static final String TABLE_NAME = "hsdk_sims"; - - public static final String COLUMN_ENTRY_ID = "_id"; - - public static final String COLUMN_SLOT_IDX = "slot_idx"; - public static final String COLUMN_SUB_ID = "sub_id"; - public static final String COLUMN_IMEI = "imei"; - public static final String COLUMN_STATE = "state"; - - public static final String COLUMN_IMSI = "imsi"; - public static final String COLUMN_MCC = "mcc"; - public static final String COLUMN_MNC = "mnc"; - public static final String COLUMN_ICCID = "iccid"; - public static final String COLUMN_OP = "operator"; - public static final String COLUMN_OP_NAME = "operator_name"; - public static final String COLUMN_COUNTRY_ISO = "country_iso"; - public static final String COLUMN_ROAMING = "is_roaming"; - - public static final String COLUMN_NETWORK_CODE = "network_code"; - public static final String COLUMN_NETWORK_NAME = "network_name"; - public static final String COLUMN_NETWORK_COUNTRY = "network_country"; - public static final String COLUMN_NETWORK_TYPE = "network_type"; - - public static final String[] allColumns = { - COLUMN_ENTRY_ID, - COLUMN_SLOT_IDX, - COLUMN_SUB_ID, - COLUMN_IMEI, - COLUMN_STATE, - - COLUMN_IMSI, - COLUMN_MCC, - COLUMN_MNC, - COLUMN_ICCID, - COLUMN_OP, - COLUMN_OP_NAME, - COLUMN_COUNTRY_ISO, - COLUMN_ROAMING, - - COLUMN_NETWORK_CODE, - COLUMN_NETWORK_NAME, - COLUMN_NETWORK_COUNTRY, - COLUMN_NETWORK_TYPE - }; -} diff --git a/multisim/src/main/java/com/hover/multisim/SimDataSource.java b/multisim/src/main/java/com/hover/multisim/SimDataSource.java deleted file mode 100644 index d5542f2..0000000 --- a/multisim/src/main/java/com/hover/multisim/SimDataSource.java +++ /dev/null @@ -1,168 +0,0 @@ -package com.hover.multisim; - -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.util.Log; - -import java.util.ArrayList; -import java.util.List; - -import io.sentry.Sentry; - -final public class SimDataSource extends DataSource { - private final static String TAG = "SimDataSource"; - private final String TABLE = SimContract.TABLE_NAME; - private final String[] COLUMNS = SimContract.allColumns; - - public SimDataSource(Context context) { super(context); } - - @SuppressWarnings("UnusedReturnValue") - long saveToDb(SimInfo simInfo) { - ContentValues cv = getContentValues(simInfo); - open(); - long insertId = database.insert(TABLE, null, cv); - close(); - Log.d(TAG, "Saved Sim with imsi: " + simInfo.imsi + ", iccid: " + simInfo.iccId + ". Id: " + insertId); - return insertId; - } - - public List getAll() { - List infos = new ArrayList<>(); - try { - open(); - Cursor cursor = database.query(TABLE, COLUMNS, null, null, null, null, null); - cursor.moveToFirst(); - while (!cursor.isAfterLast()) { - SimInfo si = cursorToSimInfo(cursor); - infos.add(si); - cursor.moveToNext(); - } - cursor.close(); - close(); - } catch (Exception e) { Sentry.captureException(e); } - return infos; - } - - public SimInfo get(int slotIdx) { - return load(SimContract.COLUMN_SLOT_IDX + " = " + slotIdx); - } - @SuppressWarnings("unused") - public SimInfo loadBy(String iccId) { - return load(SimContract.COLUMN_ICCID + " = '" + iccId + "'"); - } - private SimInfo load(String selection) { - open(); - SimInfo si = null; - Cursor cursor = database.query(TABLE, COLUMNS, selection, null, null, null, null); - cursor.moveToFirst(); - if (!cursor.isAfterLast()) - si = cursorToSimInfo(cursor); - else - Log.d(TAG, "didn't load cursor..."); - - cursor.close(); - close(); - return si; - } - - @SuppressWarnings("UnnecessaryUnboxing") - public List getPresent(String mcc, String mnc) { - List matchingSims = new ArrayList<>(); - open(); - Cursor cursor = database.query(TABLE, COLUMNS, SimContract.COLUMN_MCC + " = '" + mcc + "' AND " + SimContract.COLUMN_SLOT_IDX + " != -1", null, null, null, null); - cursor.moveToFirst(); - int mncInt = Integer.valueOf(mnc).intValue(); - while (!cursor.isAfterLast()) { - SimInfo si = cursorToSimInfo(cursor); - if (si.isMncMatch(mncInt)) - matchingSims.add(si); - cursor.moveToNext(); - } - cursor.close(); - close(); - return matchingSims; - } - - void remove(SimInfo si) { - ContentValues cv = new ContentValues(); - cv.put(SimContract.COLUMN_SLOT_IDX, -1); - update(cv, si.iccId); - } - -// void updateSlot(SimInfo si, SimInfo updatedInfo) { -// ContentValues cv = new ContentValues(); -// if (updatedInfo != null) { -// cv.put(SimContract.COLUMN_SLOT_IDX, updatedInfo.slotIdx); -// cv.put(SimContract.COLUMN_SUB_ID, updatedInfo.subscriptionId); -// cv.put(SimContract.COLUMN_IMEI, updatedInfo.imei); -// cv.put(SimContract.COLUMN_STATE, updatedInfo.simState); -// } else -// cv.put(SimContract.COLUMN_SLOT_IDX, -1); -// -// update(cv, si.iccId); -// } - -// void updateNetwork(SimInfo si, String operator, String name, String countryIso, int type, int roaming) { -// ContentValues cv = new ContentValues(); -// cv.put(SimContract.COLUMN_NETWORK_CODE, operator); -// cv.put(SimContract.COLUMN_NETWORK_NAME, name); -// cv.put(SimContract.COLUMN_NETWORK_COUNTRY, countryIso); -// cv.put(SimContract.COLUMN_NETWORK_TYPE, type); -// cv.put(SimContract.COLUMN_ROAMING, roaming); -// -// update(cv, si.iccId); -// } - - private void update(ContentValues cv, String iccId) { - open(); - database.update(TABLE, cv, SimContract.COLUMN_ICCID + " = '" + iccId + "'", null); - close(); - } - - private ContentValues getContentValues(SimInfo simInfo) { - ContentValues cv = new ContentValues(); - cv.put(SimContract.COLUMN_SLOT_IDX, simInfo.slotIdx); - cv.put(SimContract.COLUMN_SUB_ID, simInfo.subscriptionId); - cv.put(SimContract.COLUMN_IMEI, simInfo.imei); - cv.put(SimContract.COLUMN_STATE, simInfo.simState); - - cv.put(SimContract.COLUMN_IMSI, simInfo.imsi); - cv.put(SimContract.COLUMN_MCC, simInfo.mcc); - cv.put(SimContract.COLUMN_MNC, simInfo.mnc); - cv.put(SimContract.COLUMN_ICCID, simInfo.setStandardIccId(simInfo.iccId)); - cv.put(SimContract.COLUMN_OP, simInfo.hni); - cv.put(SimContract.COLUMN_OP_NAME, simInfo.operatorName); - cv.put(SimContract.COLUMN_COUNTRY_ISO, simInfo.countryIso); - cv.put(SimContract.COLUMN_ROAMING, simInfo.networkRoaming ? 1 : 0); - - cv.put(SimContract.COLUMN_NETWORK_CODE, simInfo.networkOperator); - cv.put(SimContract.COLUMN_NETWORK_NAME, simInfo.networkOperatorName); - cv.put(SimContract.COLUMN_NETWORK_COUNTRY, simInfo.networkCountryIso); - cv.put(SimContract.COLUMN_NETWORK_TYPE, simInfo.networkType); - return cv; - } - - private SimInfo cursorToSimInfo(Cursor c) { - SimInfo simInfo = new SimInfo(); - simInfo.slotIdx = c.getInt(c.getColumnIndex(SimContract.COLUMN_SLOT_IDX)); - simInfo.subscriptionId = c.getInt(c.getColumnIndex(SimContract.COLUMN_SUB_ID)); - simInfo.imei = c.getString(c.getColumnIndex(SimContract.COLUMN_IMEI)); - simInfo.simState = c.getInt(c.getColumnIndex(SimContract.COLUMN_STATE)); - - simInfo.imsi = c.getString(c.getColumnIndex(SimContract.COLUMN_IMSI)); - simInfo.mcc = c.getString(c.getColumnIndex(SimContract.COLUMN_MCC)); - simInfo.mnc = c.getString(c.getColumnIndex(SimContract.COLUMN_MNC)); - simInfo.iccId = c.getString(c.getColumnIndex(SimContract.COLUMN_ICCID)); - simInfo.hni = c.getString(c.getColumnIndex(SimContract.COLUMN_OP)); - simInfo.operatorName = c.getString(c.getColumnIndex(SimContract.COLUMN_OP_NAME)); - simInfo.countryIso = c.getString(c.getColumnIndex(SimContract.COLUMN_COUNTRY_ISO)); - simInfo.networkRoaming = c.getInt(c.getColumnIndex(SimContract.COLUMN_ROAMING)) == 1; - - simInfo.networkOperator = c.getString(c.getColumnIndex(SimContract.COLUMN_NETWORK_CODE)); - simInfo.networkOperatorName = c.getString(c.getColumnIndex(SimContract.COLUMN_NETWORK_NAME)); - simInfo.networkCountryIso = c.getString(c.getColumnIndex(SimContract.COLUMN_NETWORK_COUNTRY)); - simInfo.networkType = c.getInt(c.getColumnIndex(SimContract.COLUMN_NETWORK_TYPE)); - return simInfo; - } -} diff --git a/multisim/src/main/java/com/hover/multisim/SimDatabase.java b/multisim/src/main/java/com/hover/multisim/SimDatabase.java deleted file mode 100644 index 522c999..0000000 --- a/multisim/src/main/java/com/hover/multisim/SimDatabase.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.hover.multisim; - -import android.content.Context; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; - -public class SimDatabase extends SQLiteOpenHelper { - private static final int DATABASE_VERSION = 1; - private static final String DATABASE_NAME = "multisim.db"; - - private static SimDatabase instance = null; - - public static synchronized SimDatabase getInstance(Context ctx) { - if (instance == null) - instance = new SimDatabase(ctx.getApplicationContext()); - return instance; - } - - private SimDatabase(Context ctx) { - super(ctx, DATABASE_NAME, null, DATABASE_VERSION); - } - - private static final String SIM_TABLE_CREATE = "create table " - + SimContract.TABLE_NAME + "(" - + SimContract.COLUMN_ENTRY_ID + " integer primary key autoincrement, " - + SimContract.COLUMN_SLOT_IDX + " integer not null, " - + SimContract.COLUMN_SUB_ID + " integer not null, " - + SimContract.COLUMN_IMEI + " text, " - + SimContract.COLUMN_STATE + " integer default -1, " - + SimContract.COLUMN_IMSI + " text not null, " - + SimContract.COLUMN_MCC + " text not null, " - + SimContract.COLUMN_MNC + " text, " - + SimContract.COLUMN_ICCID + " text not null, " - + SimContract.COLUMN_OP + " text, " - + SimContract.COLUMN_OP_NAME + " text, " - + SimContract.COLUMN_COUNTRY_ISO + " text, " - + SimContract.COLUMN_ROAMING + " integer default 0 not null, " - + SimContract.COLUMN_NETWORK_CODE + " text, " - + SimContract.COLUMN_NETWORK_NAME + " text, " - + SimContract.COLUMN_NETWORK_COUNTRY + " text, " - + SimContract.COLUMN_NETWORK_TYPE + " integer, " - + "UNIQUE (" + SimContract.COLUMN_ICCID + ") ON CONFLICT REPLACE" - + ")"; - - public void onCreate(SQLiteDatabase db) { - db.execSQL(SIM_TABLE_CREATE); - } - - private static final String SQL_DELETE_SIMS = "DROP TABLE IF EXISTS " + SimContract.TABLE_NAME; - - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - db.execSQL(SQL_DELETE_SIMS); - onCreate(db); - } - public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { - onUpgrade(db, oldVersion, newVersion); - } -} diff --git a/multisim/src/main/java/com/hover/multisim/db/dao/SimDao.kt b/multisim/src/main/java/com/hover/multisim/db/dao/SimDao.kt index d644c6c..31a361b 100644 --- a/multisim/src/main/java/com/hover/multisim/db/dao/SimDao.kt +++ b/multisim/src/main/java/com/hover/multisim/db/dao/SimDao.kt @@ -24,5 +24,14 @@ import kotlinx.coroutines.flow.Flow interface SimDao : BaseDao { @Query("SELECT * FROM hsdk_sims") - fun getAll(): Flow> + fun getAll(): Flow> // return SimInfo + + @Query("SELECT * FROM hsdk_sims WHERE mcc =:mcc AND slot_idx != -1") + fun getPresent(mcc: String): Flow> // return SimInfo + + @Query("SELECT * FROM hsdk_sims WHERE slot_idx =:slotIdx LIMIT 1") + suspend fun get(slotIdx: Int): HSDKSims // return SimInfo + + @Query("SELECT * FROM hsdk_sims WHERE iccId =:iccId LIMIT 1") + suspend fun loadBy(iccId: String): HSDKSims // return SimInfo } diff --git a/multisim/src/main/java/com/hover/multisim/MultiSimWorker.java b/multisim/src/main/java/com/hover/multisim/sim/MultiSimWorker.java similarity index 99% rename from multisim/src/main/java/com/hover/multisim/MultiSimWorker.java rename to multisim/src/main/java/com/hover/multisim/sim/MultiSimWorker.java index d028111..1a57dcd 100644 --- a/multisim/src/main/java/com/hover/multisim/MultiSimWorker.java +++ b/multisim/src/main/java/com/hover/multisim/sim/MultiSimWorker.java @@ -1,4 +1,4 @@ -package com.hover.multisim; +package com.hover.multisim.sim; import android.annotation.SuppressLint; import android.annotation.TargetApi; @@ -24,6 +24,7 @@ import androidx.work.impl.utils.futures.SettableFuture; import com.google.common.util.concurrent.ListenableFuture; +import com.hover.multisim.SlotManager; import java.lang.reflect.Field; import java.lang.reflect.Method; diff --git a/multisim/src/main/java/com/hover/multisim/SimInfo.java b/multisim/src/main/java/com/hover/multisim/sim/SimInfo.java similarity index 99% rename from multisim/src/main/java/com/hover/multisim/SimInfo.java rename to multisim/src/main/java/com/hover/multisim/sim/SimInfo.java index daccbe2..d92fce9 100644 --- a/multisim/src/main/java/com/hover/multisim/SimInfo.java +++ b/multisim/src/main/java/com/hover/multisim/sim/SimInfo.java @@ -1,4 +1,4 @@ -package com.hover.multisim; +package com.hover.multisim.sim; import android.annotation.SuppressLint; import android.annotation.TargetApi; diff --git a/multisim/src/main/java/com/hover/multisim/SlotManager.java b/multisim/src/main/java/com/hover/multisim/sim/SlotManager.java similarity index 99% rename from multisim/src/main/java/com/hover/multisim/SlotManager.java rename to multisim/src/main/java/com/hover/multisim/sim/SlotManager.java index aa119eb..1efff51 100644 --- a/multisim/src/main/java/com/hover/multisim/SlotManager.java +++ b/multisim/src/main/java/com/hover/multisim/sim/SlotManager.java @@ -1,4 +1,4 @@ -package com.hover.multisim; +package com.hover.multisim.sim; import static android.telephony.TelephonyManager.SIM_STATE_READY; import static android.telephony.TelephonyManager.SIM_STATE_UNKNOWN; diff --git a/multisim/src/main/java/com/hover/multisim/Utils.java b/multisim/src/main/java/com/hover/multisim/sim/Utils.java similarity index 96% rename from multisim/src/main/java/com/hover/multisim/Utils.java rename to multisim/src/main/java/com/hover/multisim/sim/Utils.java index c7ee29f..3a9d316 100644 --- a/multisim/src/main/java/com/hover/multisim/Utils.java +++ b/multisim/src/main/java/com/hover/multisim/sim/Utils.java @@ -1,4 +1,4 @@ -package com.hover.multisim; +package com.hover.multisim.sim; import android.Manifest; import android.content.Context; From 58298b7ad53783fb5919200a251a09b05d8aeff2 Mon Sep 17 00:00:00 2001 From: Juma Allan Date: Wed, 5 Oct 2022 22:00:25 +0300 Subject: [PATCH 11/26] Rename .java to .kt --- .../hover/multisim/sim/{MultiSimWorker.java => MultiSimWorker.kt} | 0 .../main/java/com/hover/multisim/sim/{SimInfo.java => SimInfo.kt} | 0 .../com/hover/multisim/sim/{SlotManager.java => SlotManager.kt} | 0 .../src/main/java/com/hover/multisim/sim/{Utils.java => Utils.kt} | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename multisim/src/main/java/com/hover/multisim/sim/{MultiSimWorker.java => MultiSimWorker.kt} (100%) rename multisim/src/main/java/com/hover/multisim/sim/{SimInfo.java => SimInfo.kt} (100%) rename multisim/src/main/java/com/hover/multisim/sim/{SlotManager.java => SlotManager.kt} (100%) rename multisim/src/main/java/com/hover/multisim/sim/{Utils.java => Utils.kt} (100%) diff --git a/multisim/src/main/java/com/hover/multisim/sim/MultiSimWorker.java b/multisim/src/main/java/com/hover/multisim/sim/MultiSimWorker.kt similarity index 100% rename from multisim/src/main/java/com/hover/multisim/sim/MultiSimWorker.java rename to multisim/src/main/java/com/hover/multisim/sim/MultiSimWorker.kt diff --git a/multisim/src/main/java/com/hover/multisim/sim/SimInfo.java b/multisim/src/main/java/com/hover/multisim/sim/SimInfo.kt similarity index 100% rename from multisim/src/main/java/com/hover/multisim/sim/SimInfo.java rename to multisim/src/main/java/com/hover/multisim/sim/SimInfo.kt diff --git a/multisim/src/main/java/com/hover/multisim/sim/SlotManager.java b/multisim/src/main/java/com/hover/multisim/sim/SlotManager.kt similarity index 100% rename from multisim/src/main/java/com/hover/multisim/sim/SlotManager.java rename to multisim/src/main/java/com/hover/multisim/sim/SlotManager.kt diff --git a/multisim/src/main/java/com/hover/multisim/sim/Utils.java b/multisim/src/main/java/com/hover/multisim/sim/Utils.kt similarity index 100% rename from multisim/src/main/java/com/hover/multisim/sim/Utils.java rename to multisim/src/main/java/com/hover/multisim/sim/Utils.kt From 2f2a43658b0fcea410fae07802b562978e5b9ddf Mon Sep 17 00:00:00 2001 From: Juma Allan Date: Wed, 5 Oct 2022 22:00:25 +0300 Subject: [PATCH 12/26] breaking the api a bit --- multisim/src/main/AndroidManifest.xml | 2 + .../java/com/hover/multisim/db/dao/BaseDao.kt | 3 - .../com/hover/multisim/sim/MultiSimWorker.kt | 722 +++++++++--------- .../java/com/hover/multisim/sim/SimInfo.kt | 355 ++++----- .../com/hover/multisim/sim/SlotManager.kt | 328 ++++---- .../main/java/com/hover/multisim/sim/Utils.kt | 56 +- 6 files changed, 745 insertions(+), 721 deletions(-) diff --git a/multisim/src/main/AndroidManifest.xml b/multisim/src/main/AndroidManifest.xml index ad9cd11..47d3baf 100644 --- a/multisim/src/main/AndroidManifest.xml +++ b/multisim/src/main/AndroidManifest.xml @@ -1,5 +1,7 @@ + + { @Insert(onConflict = REPLACE) suspend fun insert(item: T): Long - @Insert(onConflict = REPLACE) - suspend fun insert(vararg items: T) - @Insert(onConflict = REPLACE) suspend fun insert(items: List) diff --git a/multisim/src/main/java/com/hover/multisim/sim/MultiSimWorker.kt b/multisim/src/main/java/com/hover/multisim/sim/MultiSimWorker.kt index 1a57dcd..6642ef3 100644 --- a/multisim/src/main/java/com/hover/multisim/sim/MultiSimWorker.kt +++ b/multisim/src/main/java/com/hover/multisim/sim/MultiSimWorker.kt @@ -1,439 +1,459 @@ -package com.hover.multisim.sim; - -import android.annotation.SuppressLint; -import android.annotation.TargetApi; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.Build; -import android.os.SystemClock; -import android.telephony.PhoneStateListener; -import android.telephony.ServiceState; -import android.telephony.SubscriptionInfo; -import android.telephony.SubscriptionManager; -import android.telephony.TelephonyManager; -import android.util.Log; - -import androidx.annotation.NonNull; -import androidx.localbroadcastmanager.content.LocalBroadcastManager; -import androidx.work.ListenableWorker; -import androidx.work.OneTimeWorkRequest; -import androidx.work.PeriodicWorkRequest; -import androidx.work.WorkerParameters; -import androidx.work.impl.utils.futures.SettableFuture; - -import com.google.common.util.concurrent.ListenableFuture; -import com.hover.multisim.SlotManager; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; - -import io.sentry.Sentry; - -final public class MultiSimWorker extends ListenableWorker { - public static final String TAG = "MultiSimTeleMgr"; - private static final String NEW_SIM_INFO = "NEW_SIM_INFO_ACTION"; - static final int SLOT_COUNT = 3; // Need to check 0, 1, and 2. Some phones index from 1. - - private SettableFuture workerFuture; - private Result result = null; - - private final Semaphore slotSemaphore = new Semaphore(1, true); - private final Semaphore simSemaphore = new Semaphore(1, true); - - private SimStateReceiver simStateReceiver; - private SimStateListener simStateListener; - private ArrayList validClassNames; - - private final String[] POSS_CLASS_NAMES = new String[]{null, "android.telephony.TelephonyManager", "android.telephony.MSimTelephonyManager", "android.telephony.MultiSimTelephonyService", "com.mediatek.telephony.TelephonyManagerEx", "com.android.internal.telephony.Phone", "com.android.internal.telephony.PhoneFactory"}; - - public MultiSimWorker(@NonNull Context context, @NonNull WorkerParameters params) { - super(context, params); - } - - public static PeriodicWorkRequest makeToil() { - return new PeriodicWorkRequest.Builder(MultiSimWorker.class, 15, TimeUnit.MINUTES).build(); - } +package com.hover.multisim.sim + +import android.annotation.SuppressLint +import android.annotation.TargetApi +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.os.Build +import android.os.SystemClock +import android.telephony.* +import androidx.localbroadcastmanager.content.LocalBroadcastManager +import androidx.work.ListenableWorker +import androidx.work.OneTimeWorkRequest +import androidx.work.PeriodicWorkRequest +import androidx.work.WorkerParameters +import androidx.work.impl.utils.futures.SettableFuture +import com.google.common.util.concurrent.ListenableFuture +import com.hover.multisim.sim.MultiSimWorker +import com.hover.multisim.sim.SlotManager.Companion.addValidReadySlots +import com.hover.multisim.sim.Utils.getPackage +import com.hover.multisim.sim.Utils.hasPhonePerm +import io.sentry.Sentry +import java.util.* +import java.util.concurrent.Semaphore +import java.util.concurrent.TimeUnit + +class MultiSimWorker(context: Context, params: WorkerParameters) : + ListenableWorker(context, params) { + + private lateinit var workerFuture: SettableFuture + private var result: Result? = null + private val slotSemaphore = Semaphore(1, true) + private val simSemaphore = Semaphore(1, true) + private var simStateReceiver: SimStateReceiver? = null + private var simStateListener: SimStateListener? = null + private var validClassNames: ArrayList? = null + private val POSS_CLASS_NAMES = arrayOf( + null, + "android.telephony.TelephonyManager", + "android.telephony.MSimTelephonyManager", + "android.telephony.MultiSimTelephonyService", + "com.mediatek.telephony.TelephonyManagerEx", + "com.android.internal.telephony.Phone", + "com.android.internal.telephony.PhoneFactory" + ) - public static OneTimeWorkRequest makeWork() { - return new OneTimeWorkRequest.Builder(MultiSimWorker.class).build(); - } - - @Override @SuppressLint("RestrictedApi") - public @NonNull - ListenableFuture startWork() { - Log.v(TAG, "Starting new Multi SIM worker"); - workerFuture = SettableFuture.create(); - - if (Utils.hasPhonePerm(getApplicationContext())) startListeners(); - else workerFuture.set(Result.failure()); - return workerFuture; + override fun startWork(): ListenableFuture { + android.util.Log.v(TAG, "Starting new Multi SIM worker") + workerFuture = SettableFuture.create() + if (hasPhonePerm(applicationContext)) startListeners() else workerFuture.set(Result.failure()) + return workerFuture } @SuppressLint("RestrictedApi") - private void startListeners() { + private fun startListeners() { try { - registerSimStateReceiver(); - if (simStateListener == null) simStateListener = new SimStateListener(); + registerSimStateReceiver() + if (simStateListener == null) simStateListener = SimStateListener() // TelephonyManager.listen() must take place on the main thread - ((TelephonyManager) getApplicationContext().getSystemService(Context.TELEPHONY_SERVICE)).listen(simStateListener, PhoneStateListener.LISTEN_SERVICE_STATE | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS); - } catch (Exception e) { - Log.d(TAG, "Failed to start SIM listeners, setting retry", e); - workerFuture.set(Result.retry()); + (applicationContext.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager).listen( + simStateListener, + PhoneStateListener.LISTEN_SERVICE_STATE or PhoneStateListener.LISTEN_SIGNAL_STRENGTHS + ) + } catch (e: Exception) { + android.util.Log.d(TAG, "Failed to start SIM listeners, setting retry", e) + workerFuture.set(Result.retry()) } } @SuppressLint("RestrictedApi") - synchronized private void updateSimInfo() { - getBackgroundExecutor().execute(new Runnable() { - @Override - public void run() { - try { - simSemaphore.acquire(); - if (Utils.hasPhonePerm(getApplicationContext())) { - Log.v(TAG, "reviewing sim info"); - List oldList = getSaved(); - List newList = findUniqueSimInfo(); - - if (newList != null) { - compareNewAndOld(newList, oldList); - result = Result.success(); - } else result = Result.failure(); - } else result = Result.failure(); - } catch (Exception e) { - Log.w(TAG, "threw while attempting to update sim list", e); - Sentry.captureException(e); - result = Result.failure(); - } finally { - simSemaphore.release(); -// Give the listeners a chance to receive a few events - sometimes the first trigger isn't the needed info. 5s is long but this is a background thread anyway and the info has already been updated in the DB - SystemClock.sleep(5000); - if (!workerFuture.isDone()) { - Log.v(TAG, "Finishing Multi SIM worker"); - workerFuture.set(result); + @Synchronized + private fun updateSimInfo() { + backgroundExecutor.execute { + result = try { + simSemaphore.acquire() + if (hasPhonePerm(applicationContext)) { + android.util.Log.v(TAG, "reviewing sim info") + val oldList: List? = saved + val newList = findUniqueSimInfo() + run { + compareNewAndOld(newList, oldList) + Result.success() } + } else Result.failure() + } catch (e: Exception) { + android.util.Log.w(TAG, "threw while attempting to update sim list", e) + Sentry.captureException(e) + Result.failure() + } finally { + simSemaphore.release() + // Give the listeners a chance to receive a few events - sometimes the first trigger isn't the needed info. 5s is long but this is a background thread anyway and the info has already been updated in the DB + SystemClock.sleep(5000) + if (!workerFuture.isDone) { + android.util.Log.v(TAG, "Finishing Multi SIM worker") + workerFuture.set(result) } } - }); + } } - private void compareNewAndOld(List newList, List oldList) { - if (oldList == null || oldList.size() != newList.size()) { - Log.v(TAG, "no old list or sizes differ. Old: " + (oldList != null ? oldList.size() : "null") + ", new: " + newList.size()); - onSimInfoUpdate(newList); + private fun compareNewAndOld(newList: List, oldList: List?) { + if (oldList == null || oldList.size != newList.size) { + android.util.Log.v( + TAG, + "no old list or sizes differ. Old: " + (oldList?.size + ?: "null") + ", new: " + newList.size + ) + onSimInfoUpdate(newList) } else { - for (int i = 0; i < newList.size(); i++) - if (newList.get(i).isNotContainedInOrHasMoved(oldList)) { - Log.v(TAG, "some sim moved"); - onSimInfoUpdate(newList); - break; - } + for (i in newList.indices) if (newList[i]!!.isNotContainedInOrHasMoved(oldList)) { + android.util.Log.v(TAG, "some sim moved") + onSimInfoUpdate(newList) + break + } } } - private ArrayList getSaved() { - ArrayList oldList = null; - for (int i = 0; i < SLOT_COUNT; i++) { - SimInfo si = new SimDataSource(getApplicationContext()).get(i); - if (si != null) { - if (oldList == null) oldList = new ArrayList<>(); - oldList.add(si); + private val saved: ArrayList? + get() { + var oldList: ArrayList? = null + for (i in 0 until SLOT_COUNT) { + val si: SimInfo = SimDataSource(applicationContext).get(i) + if (oldList == null) oldList = ArrayList() + oldList.add(si) } + android.util.Log.v(TAG, "Loaded old list from db. Size: " + (oldList?.size ?: "null")) + return oldList } - Log.v(TAG, "Loaded old list from db. Size: " + (oldList != null ? oldList.size() : "null")); - return oldList; - } - private void onSimInfoUpdate(List newList) { - updateDb(newList); - Log.v(TAG, "Saved. Firing broadcast"); - LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(new Intent(action(getApplicationContext()))); - } - - private void updateDb(List newList) { - for (SimInfo si : newList) { - Log.i(TAG, "Saving SIM in slot " + si.slotIdx + ": " + si.toString()); - si.save(getApplicationContext()); + private fun onSimInfoUpdate(newList: List) { + updateDb(newList) + android.util.Log.v(TAG, "Saved. Firing broadcast") + LocalBroadcastManager.getInstance(applicationContext).sendBroadcast( + Intent( + action( + applicationContext + ) + ) + ) + } + + private fun updateDb(newList: List) { + for (si in newList) { + android.util.Log.i(TAG, "Saving SIM in slot " + si!!.slotIdx + ": " + si.toString()) + si.save(applicationContext) } - - List dbInfos = new SimDataSource(getApplicationContext()).getAll(); - for (SimInfo dbSi : dbInfos) { + val dbInfos: List = SimDataSource(applicationContext).getAll() + for (dbSi in dbInfos) { if (!findPhysicalSim(newList, dbSi)) { - Log.i(TAG, "Couldn't find SIM: " + dbSi.toString() + ", removing"); - dbSi.setSimRemoved(getApplicationContext()); + android.util.Log.i(TAG, "Couldn't find SIM: $dbSi, removing") + dbSi.setSimRemoved(applicationContext) } } } - private boolean findPhysicalSim(List newList, SimInfo savedSim) { - for (SimInfo si : newList) { - if (si.isSameSim(savedSim)) return true; + private fun findPhysicalSim(newList: List, savedSim: SimInfo): Boolean { + for (si in newList) { + if (si!!.isSameSim(savedSim)) return true } - return false; + return false } - @SuppressWarnings({"MissingPermission"}) - synchronized private List findUniqueSimInfo() { - List newList = new ArrayList<>(); + @Synchronized + private fun findUniqueSimInfo(): List { + var newList: List = ArrayList() try { - slotSemaphore.acquire(); - List slotMgrList = new ArrayList<>(); - List teleMgrInstances = listTeleMgrs(slotMgrList); - - List subInfos = new ArrayList<>(); - if (Build.VERSION.SDK_INT >= 22) - subInfos = getSubscriptions(teleMgrInstances, slotMgrList); - - if (subInfos != null && subInfos.size() > 0) - newList = createUniqueSimInfoList(subInfos, slotMgrList); - else newList = createUniqueSimInfoList(slotMgrList); - } catch (Exception e) { - Log.w(TAG, "Multi-SIM worker caught something", e); - Sentry.captureException(e); + slotSemaphore.acquire() + val slotMgrList: List = ArrayList() + val teleMgrInstances = listTeleMgrs(slotMgrList) + var subInfos: List? = ArrayList() + if (Build.VERSION.SDK_INT >= 22) subInfos = + getSubscriptions(teleMgrInstances, slotMgrList) + newList = if (subInfos != null && subInfos.isNotEmpty()) createUniqueSimInfoList( + subInfos, slotMgrList + ) else createUniqueSimInfoList(slotMgrList) + } catch (e: Exception) { + android.util.Log.w(TAG, "Multi-SIM worker caught something", e) + Sentry.captureException(e) } finally { - slotSemaphore.release(); + slotSemaphore.release() } - return newList; + return newList } - private List createUniqueSimInfoList(List subInfos, List slotMgrList) { - List newList = createUniqueSimInfoList(slotMgrList); - for (SubscriptionInfo subInfo : subInfos) - newList.add(new SimInfo(subInfo, getApplicationContext())); - return removeDuplicates(newList); + private fun createUniqueSimInfoList( + subInfos: List, slotMgrList: List + ): List { + val newList = createUniqueSimInfoList(slotMgrList) + for (subInfo in subInfos) newList.add(SimInfo(subInfo, applicationContext)) + return removeDuplicates(newList) } - private List createUniqueSimInfoList(List slotMgrList) { - List newList = new ArrayList<>(); - for (SlotManager sm : slotMgrList) - newList.add(sm.createSimInfo()); - return removeDuplicates(newList); + private fun createUniqueSimInfoList(slotMgrList: List): MutableList { + val newList: MutableList = ArrayList() + for (sm in slotMgrList) newList.add(sm!!.createSimInfo()) + return removeDuplicates(newList) } - private List removeDuplicates(List simInfos) { - List uniqueSimInfos = new ArrayList<>(); - for (SimInfo simInfo : simInfos) - if (simInfo.isNotContainedIn(uniqueSimInfos)) uniqueSimInfos.add(simInfo); - return uniqueSimInfos; + private fun removeDuplicates(simInfos: List): MutableList { + val uniqueSimInfos: MutableList = ArrayList() + for (simInfo in simInfos) if (simInfo!!.isNotContainedIn(uniqueSimInfos)) uniqueSimInfos.add( + simInfo + ) + return uniqueSimInfos } @TargetApi(22) - @SuppressWarnings({"MissingPermission"}) - private List getSubscriptions(List teleMgrInstances, List slotMgrList) throws Exception { - List subInfos = SubscriptionManager.from(getApplicationContext()).getActiveSubscriptionInfoList(); + @Throws(Exception::class) + private fun getSubscriptions( + teleMgrInstances: List?, slotMgrList: List + ): List? { + val subInfos = SubscriptionManager.from(applicationContext).activeSubscriptionInfoList if (teleMgrInstances != null) { - for (Object teleMgr : teleMgrInstances) { + for (teleMgr in teleMgrInstances) { if (subInfos != null) { - for (SubscriptionInfo subinfo : subInfos) - SlotManager.addValidReadySlots(slotMgrList, subinfo.getSimSlotIndex(), subinfo.getSubscriptionId(), teleMgr, validClassNames); + for (subinfo in subInfos) addValidReadySlots( + slotMgrList, + subinfo.simSlotIndex, + subinfo.subscriptionId, + teleMgr, + validClassNames + ) } } } - return subInfos; - } - - @SuppressWarnings("ResourceType") - private List listTeleMgrs(List slotMgrList) { - if (validClassNames == null || validClassNames.isEmpty()) - validClassNames = new ArrayList<>(Arrays.asList(POSS_CLASS_NAMES)); - List teleMgrList = new ArrayList<>(); - -// Log("Creating TeleMgrs List.............................................................."); - for (String className : POSS_CLASS_NAMES) { - if (className == null) continue; - addMgrFromReflection(className, teleMgrList, null, slotMgrList); - for (int i = 0; i < SLOT_COUNT; i++) - addMgrFromReflection(className, teleMgrList, i, slotMgrList); + return subInfos + } + + private fun listTeleMgrs(slotMgrList: List): List { + if (validClassNames == null || validClassNames!!.isEmpty()) validClassNames = ArrayList( + listOf(*POSS_CLASS_NAMES) + ) + val teleMgrList: MutableList = ArrayList() + for (className in POSS_CLASS_NAMES) { + if (className == null) continue + addMgrFromReflection(className, teleMgrList, null, slotMgrList) + for (i in 0 until SLOT_COUNT) addMgrFromReflection( + className, teleMgrList, i, slotMgrList + ) } - - addMgrFromSystemService(Context.TELEPHONY_SERVICE, teleMgrList, null, slotMgrList); - addMgrFromSystemService("phone_msim", teleMgrList, null, slotMgrList); - for (int j = 0; j < SLOT_COUNT; j++) - addMgrFromSystemService("phone" + j, teleMgrList, j, slotMgrList); - teleMgrList.add(null); - -// Log("Total TeleMgrInstances length including null entry: " + teleMgrList.size()); -// Log("Valid class names were: " + validClassNames.toString()); -// Log("...................................................................................."); - return teleMgrList; - } - - private void addMgrFromReflection(String className, List teleMgrList, Object slotIdx, List slotMgrList) { - Object result = runMethodReflect(className, "getDefault", slotIdx == null ? null : new Object[]{slotIdx}); + addMgrFromSystemService(Context.TELEPHONY_SERVICE, teleMgrList, null, slotMgrList) + addMgrFromSystemService("phone_msim", teleMgrList, null, slotMgrList) + for (j in 0 until SLOT_COUNT) addMgrFromSystemService( + "phone$j", teleMgrList, j, slotMgrList + ) + teleMgrList.add(null) + return teleMgrList + } + + private fun addMgrFromReflection( + className: String, + teleMgrList: MutableList, + slotIdx: Any?, + slotMgrList: List + ) { + val result = runMethodReflect(className, "getDefault", slotIdx?.let { arrayOf(it) }) if (result != null && !teleMgrList.contains(result)) { - teleMgrList.add(result); - Log("Added Mgr using className: " + className + ", method: getDefault, and param: " + slotIdx); - if (Build.VERSION.SDK_INT < 22) - SlotManager.addValidReadySlots(slotMgrList, slotIdx, result, validClassNames); + teleMgrList.add(result) + Log("Added Mgr using className: $className, method: getDefault, and param: $slotIdx") + if (Build.VERSION.SDK_INT < 22) addValidReadySlots( + slotMgrList, slotIdx, result, validClassNames + ) } } - private void addMgrFromSystemService(String serviceName, List teleMgrList, Object slotIdx, List slotMgrList) { - Object serv = getApplicationContext().getSystemService(serviceName); + private fun addMgrFromSystemService( + serviceName: String, + teleMgrList: MutableList, + slotIdx: Any?, + slotMgrList: List + ) { + val serv = applicationContext.getSystemService(serviceName) if (serv != null && !teleMgrList.contains(serv)) { - teleMgrList.add(serv); - Log("Added Mgr using mContext.getSystemService('" + serviceName + "')"); - if (Build.VERSION.SDK_INT < 22) - SlotManager.addValidReadySlots(slotMgrList, slotIdx, serv, validClassNames); + teleMgrList.add(serv) + Log("Added Mgr using mContext.getSystemService('$serviceName')") + if (Build.VERSION.SDK_INT < 22) addValidReadySlots( + slotMgrList, slotIdx, serv, validClassNames + ) } } - @SuppressWarnings("SameParameterValue") - private Object runMethodReflect(String className, String methodName, Object[] methodParams) { + private fun runMethodReflect( + className: String, methodName: String, methodParams: Array? + ): Any? { try { - return runMethodReflect(null, Class.forName(className), methodName, methodParams); - } catch (ClassNotFoundException e) { - validClassNames.remove(className); -// Log("Class not found, removing: " + e); + return runMethodReflect(null, Class.forName(className), methodName, methodParams) + } catch (e: ClassNotFoundException) { + validClassNames!!.remove(className) } - return null; + return null } - private static Object runMethodReflect(Object actualInstance, String methodName, Object[] methodParams) { - return runMethodReflect(actualInstance, actualInstance.getClass(), methodName, methodParams); + private fun registerSimStateReceiver() { + if (simStateReceiver == null) { + simStateReceiver = SimStateReceiver() + val intentFilter = IntentFilter() + intentFilter.addAction("android.intent.action.SIM_STATE_CHANGED") + intentFilter.addAction("android.intent.action.ACTION_SIM_STATE_CHANGED") + intentFilter.addAction("android.intent.action.PHONE_STATE") + intentFilter.addAction("vivo.intent.action.ACTION_SIM_STATE_CHANGED") + applicationContext.registerReceiver(simStateReceiver, intentFilter) + } } - public static Object runMethodReflect(Object actualInstance, Class classInstance, String methodName, Object[] methodParams) { - Object result = null; - try { - Method method = classInstance.getDeclaredMethod(methodName, getClassParams(methodParams)); - boolean accessible = method.isAccessible(); - method.setAccessible(true); - result = method.invoke(actualInstance != null ? actualInstance : classInstance, methodParams); - method.setAccessible(accessible); - } catch (Exception ignored) { /* Log("Method not found: " + ignored); */ } - return result; + private inner class SimStateListener : PhoneStateListener() { + override fun onServiceStateChanged(serviceState: ServiceState) { + updateSimInfo() + } } - @SuppressWarnings("unused") - private static Object runFieldReflect(String className, String field) { - Object result = null; - try { - Class classInstance = Class.forName(className); - Field fieldReflect = classInstance.getField(field); - boolean accessible = fieldReflect.isAccessible(); - fieldReflect.setAccessible(true); - result = fieldReflect.get(null).toString(); - fieldReflect.setAccessible(accessible); - } catch (Exception ignored) { -// Log("Error accessing reflected class: " + ignored); + private inner class SimStateReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (intent != null) updateSimInfo() } - return result; } - private static Class[] getClassParams(Object[] methodParams) { - Class[] classesParams = null; - if (methodParams != null) { - classesParams = new Class[methodParams.length]; - for (int i = 0; i < methodParams.length; i++) { - if (methodParams[i] instanceof Integer) - classesParams[i] = int.class; // logString += methodParams[i] + ","; - else if (methodParams[i] instanceof String) - classesParams[i] = String.class; // logString += "\"" + methodParams[i] + "\","; - else if (methodParams[i] instanceof Long) - classesParams[i] = long.class; // logString += methodParams[i] + ","; - else if (methodParams[i] instanceof Boolean) - classesParams[i] = boolean.class; // logString += methodParams[i] + ","; - else - classesParams[i] = methodParams[i].getClass(); // logString += "["+methodParams[i]+"]" + ","; - } + override fun onStopped() { + super.onStopped() + try { + (applicationContext.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager).listen( + simStateListener, PhoneStateListener.LISTEN_NONE + ) + simStateListener = null + if (simStateReceiver != null) applicationContext.unregisterReceiver(simStateReceiver) + simStateReceiver = null + } catch (ignored: Exception) { } - return classesParams; } - private void registerSimStateReceiver() { - if (simStateReceiver == null) { - simStateReceiver = new SimStateReceiver(); - IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction("android.intent.action.SIM_STATE_CHANGED"); - intentFilter.addAction("android.intent.action.ACTION_SIM_STATE_CHANGED"); - intentFilter.addAction("android.intent.action.PHONE_STATE"); - intentFilter.addAction("vivo.intent.action.ACTION_SIM_STATE_CHANGED"); - getApplicationContext().registerReceiver(simStateReceiver, intentFilter); - } + private fun goFish() { + printAllMethodsAndFields("android.telephony.TelephonyManager", -1) // all methods + printAllMethodsAndFields("android.telephony.MultiSimTelephonyService", -1) // all methods + printAllMethodsAndFields("android.telephony.MSimTelephonyManager", -1) // all methods + printAllMethodsAndFields("com.mediatek.telephony.TelephonyManager", -1) // all methods + printAllMethodsAndFields("com.mediatek.telephony.TelephonyManagerEx", -1) // all methods + printAllMethodsAndFields("com.android.internal.telephony.ITelephony", -1) // all methods + printAllMethodsAndFields( + "com.android.internal.telephony.ITelephony\$Stub\$Proxy", -1 + ) // all methods } - private class SimStateListener extends PhoneStateListener { - public void onServiceStateChanged(ServiceState serviceState) { - updateSimInfo(); + private fun printAllMethodsAndFields(className: String, paramsCount: Int) { + Log("====================================================================================") + Log("Methods of $className") + try { + val MultiSimClass = Class.forName(className) + for (method in MultiSimClass.methods) { + Log(method.toGenericString()) + try { + if (method.parameterTypes.isEmpty()) Log( + runMethodReflect( + MultiSimClass, method.name, null + ) as String? + ) else if (method.parameterTypes.size == 1) Log( + " " + runMethodReflect( + MultiSimClass, method.name, arrayOf(0) + ) + ) + } catch (e: Exception) { + Log("Failed. $e") + } + } + } catch (e: Exception) { + Log("Failed. $e") } } - private class SimStateReceiver extends BroadcastReceiver { - @Override - public void onReceive(final Context context, final Intent intent) { - if (intent != null) updateSimInfo(); + companion object { + const val TAG = "MultiSimTeleMgr" + private const val NEW_SIM_INFO = "NEW_SIM_INFO_ACTION" + const val SLOT_COUNT = 3 // Need to check 0, 1, and 2. Some phones index from 1. + fun makeToil(): PeriodicWorkRequest { + return PeriodicWorkRequest.Builder(MultiSimWorker::class.java, 15, TimeUnit.MINUTES) + .build() } - } - public static String action(Context c) { - return Utils.getPackage(c) + "." + NEW_SIM_INFO; - } + fun makeWork(): OneTimeWorkRequest { + return OneTimeWorkRequest.Builder(MultiSimWorker::class.java).build() + } - @Override - public void onStopped() { - super.onStopped(); - try { - ((TelephonyManager) getApplicationContext().getSystemService(Context.TELEPHONY_SERVICE)).listen(simStateListener, PhoneStateListener.LISTEN_NONE); - simStateListener = null; - if (simStateReceiver != null) - getApplicationContext().unregisterReceiver(simStateReceiver); - simStateReceiver = null; - } catch (Exception ignored) { + private fun runMethodReflect( + actualInstance: Any, methodName: String, methodParams: Array? + ): Any? { + return runMethodReflect( + actualInstance, actualInstance.javaClass, methodName, methodParams + ) } - } - @SuppressWarnings("unused") - private void goFish() { - printAllMethodsAndFields("android.telephony.TelephonyManager", -1); // all methods - printAllMethodsAndFields("android.telephony.MultiSimTelephonyService", -1); // all methods -// printAllMethodsAndFields("android.telephony.MultiSimTelephonyService", 0); // methods with 0 params -// printAllMethodsAndFields("android.telephony.MultiSimTelephonyService", 1); // methods with 1 params -// printAllMethodsAndFields("android.telephony.MultiSimTelephonyService", 2); // methods with 2 params - printAllMethodsAndFields("android.telephony.MSimTelephonyManager", -1); // all methods - printAllMethodsAndFields("com.mediatek.telephony.TelephonyManager", -1); // all methods - printAllMethodsAndFields("com.mediatek.telephony.TelephonyManagerEx", -1); // all methods - printAllMethodsAndFields("com.android.internal.telephony.ITelephony", -1); // all methods - printAllMethodsAndFields("com.android.internal.telephony.ITelephony$Stub$Proxy", -1); // all methods - } + fun runMethodReflect( + actualInstance: Any?, + classInstance: Class<*>, + methodName: String?, + methodParams: Array? + ): Any? { + var result: Any? = null + try { + val method = methodName?.let { + classInstance.getDeclaredMethod( + it, *getClassParams(methodParams) + ) + } + val accessible = method?.isAccessible + if (method != null) { + method.isAccessible = true + } + if (method != null) { + result = method.invoke(actualInstance ?: classInstance, *methodParams) + } + if (accessible != null) { + method.isAccessible = accessible + } + } catch (ignored: Exception) { /* Log("Method not found: " + ignored); */ + } + return result + } - @SuppressWarnings("unused") - private void printAllMethodsAndFields(String className, int paramsCount) { - Log("===================================================================================="); - Log("Methods of " + className); - try { - Class MultiSimClass = Class.forName(className); - for (Method method : MultiSimClass.getMethods()) { -// if (method.toGenericString().toLowerCase().contains("ussd")) { - Log(method.toGenericString()); - try { - if (method.getParameterTypes().length == 0) - Log((String) runMethodReflect(MultiSimClass, method.getName(), null)); - else if (method.getParameterTypes().length == 1) - Log(" " + runMethodReflect(MultiSimClass, method.getName(), new Object[]{0})); - } catch (Exception e) { - Log("Failed. " + e); + private fun runFieldReflect(className: String, field: String): Any? { + var result: Any? = null + try { + val classInstance = Class.forName(className) + val fieldReflect = classInstance.getField(field) + val accessible = fieldReflect.isAccessible + fieldReflect.isAccessible = true + result = fieldReflect[null]?.toString() + fieldReflect.isAccessible = accessible + } catch (ignored: Exception) { + } + return result + } + + private fun getClassParams(methodParams: Array?): Array?>? { + var classesParams: Array?>? = null + if (methodParams != null) { + classesParams = arrayOfNulls?>(methodParams.size) + for (i in methodParams.indices) { + if (methodParams[i] is Int) classesParams[i] = + Int::class.javaPrimitiveType // logString += methodParams[i] + ","; + else if (methodParams[i] is String) classesParams[i] = + String::class.java // logString += "\"" + methodParams[i] + "\","; + else if (methodParams[i] is Long) classesParams[i] = + Long::class.javaPrimitiveType // logString += methodParams[i] + ","; + else if (methodParams[i] is Boolean) classesParams[i] = + Boolean::class.javaPrimitiveType // logString += methodParams[i] + ","; + else classesParams[i] = + methodParams[i].javaClass // logString += "["+methodParams[i]+"]" + ","; } -// } } - } catch (Exception e) { - Log("Failed. " + e); + return classesParams + } + + fun action(c: Context?): String { + return getPackage(c!!) + "." + NEW_SIM_INFO } -// for( Field field : MultiSimClass.getFields()) { -// field.setAccessible(true); -// Log.i(LOG, " f2 " + field.getName() + " " + field.getType() + " " + field.load(inst)); -// } - } - @SuppressWarnings({"EmptyMethod", "unused"}) - private static void Log(String message) { -// Log.e(TAG, message); + private fun log(message: String?) {} } -} +} \ No newline at end of file diff --git a/multisim/src/main/java/com/hover/multisim/sim/SimInfo.kt b/multisim/src/main/java/com/hover/multisim/sim/SimInfo.kt index d92fce9..5060074 100644 --- a/multisim/src/main/java/com/hover/multisim/sim/SimInfo.kt +++ b/multisim/src/main/java/com/hover/multisim/sim/SimInfo.kt @@ -1,168 +1,121 @@ -package com.hover.multisim.sim; - -import android.annotation.SuppressLint; -import android.annotation.TargetApi; -import android.content.Context; -import android.content.SharedPreferences; -import android.telephony.SubscriptionInfo; -import android.telephony.SubscriptionManager; -import android.util.Log; - -import org.json.JSONArray; -import org.json.JSONException; - -import java.util.ArrayList; -import java.util.List; - -import io.sentry.Sentry; - -public class SimInfo { - private final static String TAG = "SimInfo"; - private final static String KEY = "sim_info_"; - +package com.hover.multisim.sim + +import android.annotation.SuppressLint +import android.annotation.TargetApi +import android.content.Context +import android.telephony.SubscriptionInfo +import android.telephony.SubscriptionManager +import android.util.Log +import io.sentry.Sentry +import org.json.JSONArray +import org.json.JSONException +import java.util.* + +open class SimInfo { /** * The slot that the SIM is in, starting from 0. Can be -1 if the SIM has been removed */ - public int slotIdx = -1; + @JvmField + var slotIdx = -1 + /** * The Subscription ID assigned by Android. The same SIM can be assigned a new ID if it is removed and re-inserted. Hover will forget the old ID and update a SIM to the newest */ - public int subscriptionId = -1; - - String imei; - - protected String iccId; + var subscriptionId = -1 + var imei: String? = null /** * The Hardware identifier for the SIM. Hover uses this to track a SIM regardless of whether it is removed or its slot changed */ - public String getIccId() { - return iccId; - } - - protected String imsi; + var iccId: String? = null + protected set /** * The The International Mobile Subscriber Identity used by the network to identify the SIM. The value reported here may be only the begining 5-6 digits or the whole thing. * If you are trying to determine which network a SIM is for use this. The first 3 digits will always be the MCC and the following 2 or 3 will be the MNC which you can use to definitively identify which network this SIM is for * See https://en.wikipedia.org/wiki/Mobile_country_code */ - public String getImsi() { - return imsi; - } - - String mcc; - String mnc; - - int simState = -1; - - protected String hni; + var imsi: String? = null + protected set + var mcc: String? = null + var mnc: String? = null + var simState = -1 /** * The Home Network Identifier. This is the first 5-6 digits of the IMSI, however, we recomend against using this since some devices may not report it correctly. Use the first 5-6 digits of the imsi using getImsi() * - * @see SimInfo#getImsi() + * @see SimInfo.getImsi */ - public String getOSReportedHni() { - return hni; - } - - protected String operatorName; + var oSReportedHni: String? = null + protected set /** * The name of the operator which provisioned the SIM. May differ from SIM to SIM distributed by the same network provisioner */ - public String getOperatorName() { - return operatorName; - } - - protected String countryIso; + var operatorName: String? = null + protected set /** * The country ISO of the operator which provisioned the SIM */ - public String getCountryIso() { - return countryIso; - } - - String networkOperator; + var countryIso: String? = null + protected set /** * The network of the operator which the SIM is connected to */ - public String getNetworkOperator() { - return networkOperator; - } - - String networkOperatorName; + var networkOperator: String? = null /** * The network name of the operator which the SIM is connected to */ - public String getNetworkOperatorName() { - return networkOperatorName; - } - - String networkCountryIso; + var networkOperatorName: String? = null /** * The country ISO of the operator which the SIM is connected to */ - public String getNetworkCountryIso() { - return networkCountryIso; - } - - int networkType; - // careful find networkTypeName because it will be different with networkType on same devices - - protected boolean networkRoaming = false; + var networkCountryIso: String? = null + var networkType = 0 /** * Whether the SIM is currently roaming. Not guaranteed to be accurate. */ - public boolean isRoaming() { - return networkRoaming; - } - - public SimInfo() { - } - - public SimInfo(SlotManager slotMgr) { - if (slotMgr.slotIndex != null) slotIdx = slotMgr.slotIndex; - subscriptionId = slotMgr.subscriptionId; - - imei = slotMgr.imei; - simState = setSimState(slotMgr.findSimState()); - iccId = setStandardIccId(slotMgr.findIccId()); - imsi = slotMgr.findImsi(); - mcc = setMcc(imsi); - - hni = slotMgr.findOperator(); - operatorName = slotMgr.findOperatorName(); - countryIso = slotMgr.findCountryIso(); - networkOperator = slotMgr.findNetworkOperator(); - networkOperatorName = slotMgr.findNetworkOperatorName(); - networkCountryIso = slotMgr.findNetworkCountryIso(); - networkType = setNetworkType(slotMgr.findNetworkType()); - networkRoaming = setNetworkRoaming(slotMgr.findNetworkRoaming()); + // careful find networkTypeName because it will be different with networkType on same devices + var isRoaming = false + protected set + + private constructor(slotMgr: SlotManager) { + slotIdx = slotMgr.slotIndex + subscriptionId = slotMgr.subscriptionId + imei = slotMgr.imei + simState = setSimState(slotMgr.findSimState()) + iccId = setStandardIccId(slotMgr.findIccId()) + imsi = slotMgr.findImsi() + mcc = setMcc(imsi) + oSReportedHni = slotMgr.findOperator() + operatorName = slotMgr.findOperatorName() + countryIso = slotMgr.findCountryIso() + networkOperator = slotMgr.findNetworkOperator() + networkOperatorName = slotMgr.findNetworkOperatorName() + networkCountryIso = slotMgr.findNetworkCountryIso() + networkType = setNetworkType(slotMgr.findNetworkType()) + isRoaming = setNetworkRoaming(slotMgr.findNetworkRoaming()) // Log.i(TAG, "Created SIM representation using reflection: " + this.log()); } @TargetApi(22) - public SimInfo(SubscriptionInfo subInfo, Context c) { - subscriptionId = subInfo.getSubscriptionId(); - slotIdx = subInfo.getSimSlotIndex(); - - imsi = "" + subInfo.getMcc() + subInfo.getMnc(); - hni = "" + subInfo.getMcc() + subInfo.getMnc(); - mcc = "" + subInfo.getMcc(); - mnc = "" + subInfo.getMnc(); - iccId = setStandardIccId(subInfo.getIccId()); - - operatorName = (String) subInfo.getCarrierName(); // Is this Network Operator or Sim Operator? - countryIso = subInfo.getCountryIso(); - networkRoaming = SubscriptionManager.from(c).isNetworkRoaming(subscriptionId); + constructor(subInfo: SubscriptionInfo, c: Context?) { + subscriptionId = subInfo.subscriptionId + slotIdx = subInfo.simSlotIndex + imsi = "" + subInfo.mcc + subInfo.mnc + oSReportedHni = "" + subInfo.mcc + subInfo.mnc + mcc = "" + subInfo.mcc + mnc = "" + subInfo.mnc + iccId = setStandardIccId(subInfo.iccId) + operatorName = subInfo.carrierName as String // Is this Network Operator or Sim Operator? + countryIso = subInfo.countryIso + isRoaming = SubscriptionManager.from(c).isNetworkRoaming(subscriptionId) // Can't load these until API 24 (Using TelephonyManager), but doesn't really matter: // hnis, networkOperator, networkOperatorName, networkCountryIso, networkType @@ -173,135 +126,139 @@ public class SimInfo { // Log.i(TAG, "Created SIM representation using Subscription info: " + this.log()); } - public boolean isSameSim(SimInfo simInfo) { // FIXME: change so that if the SimInfo represents the same sim, the one with more/better info (and more accurate slotIdx?) is returned. It currently relies on order to do this, which is fragile - return simInfo != null && simInfo.iccId != null && iccId != null && iccId.equals(simInfo.iccId); + fun isSameSim(simInfo: SimInfo?): Boolean { // FIXME: change so that if the SimInfo represents the same sim, the one with more/better info (and more accurate slotIdx?) is returned. It currently relies on order to do this, which is fragile + return simInfo?.iccId != null && iccId != null && iccId == simInfo.iccId } - private boolean isSameSimInSameSlot(SimInfo simInfo) { - return isSameSim(simInfo) && simInfo.slotIdx == slotIdx; + private fun isSameSimInSameSlot(simInfo: SimInfo): Boolean { + return isSameSim(simInfo) && simInfo.slotIdx == slotIdx } - public boolean isNotContainedIn(List simInfos) { - if (simInfos == null) return true; - for (SimInfo simInfo : simInfos) - if (this.isSameSim(simInfo)) - return false; - return true; + fun isNotContainedIn(simInfos: List?): Boolean { + if (simInfos == null) return true + for (simInfo in simInfos) if (isSameSim(simInfo)) return false + return true } - public boolean isNotContainedInOrHasMoved(List simInfos) { - if (simInfos == null) return true; - for (SimInfo simInfo : simInfos) - if (this.isSameSimInSameSlot(simInfo)) - return false; - return true; + fun isNotContainedInOrHasMoved(simInfos: List?): Boolean { + if (simInfos == null) return true + for (simInfo in simInfos) if (isSameSimInSameSlot(simInfo)) return false + return true } - public void save(Context c) { - new SimDataSource(c).saveToDb(this); - updateSubId(subscriptionId, c); + fun save(c: Context) { + SimDataSource(c).saveToDb(this) + updateSubId(subscriptionId, c) } // private void updateSlot(Context c) { -// Log.i(TAG, "Updating sim slot to: " + slotIdx); -// new SimDataSource(c).updateSlot(this, updatedInfo); -// updateSubId(updatedInfo.subscriptionId, c); -// } - public void setSimRemoved(Context c) { - Log.i(TAG, "Updating sim slot to: -1"); - new SimDataSource(c).remove(this); + // Log.i(TAG, "Updating sim slot to: " + slotIdx); + // new SimDataSource(c).updateSlot(this, updatedInfo); + // updateSubId(updatedInfo.subscriptionId, c); + // } + fun setSimRemoved(c: Context?) { + Log.i(TAG, "Updating sim slot to: -1") + SimDataSource(c).remove(this) } @SuppressLint("ApplySharedPref") - private void updateSubId(int subId, Context c) { - SharedPreferences.Editor editor = Utils.getSharedPrefs(c).edit(); - editor.putInt(KEY + SimContract.COLUMN_SUB_ID + iccId, subId); - editor.commit(); - } - - public static int getSubId(String iccId, Context c) { - return Utils.getSharedPrefs(c).getInt(KEY + SimContract.COLUMN_SUB_ID + iccId, -1); - } - - public static List loadPresentByHni(JSONArray hniList, Context c) { - List simInfos = new ArrayList<>(); - for (int h = 0; h < hniList.length(); h++) { - try { - simInfos.addAll(new SimDataSource(c).getPresent(hniList.getString(h).substring(0, 3), hniList.getString(h).substring(3))); - } catch (JSONException | NullPointerException e) { - Sentry.captureException(e); - } - } - return simInfos; - } - - public static SimInfo loadBySlot(int slotIdx, Context c) { - return new SimDataSource(c).get(slotIdx); - } - - public static List loadAll(Context c) { - return new SimDataSource(c).getAll(); + private fun updateSubId(subId: Int, c: Context) { + val editor = Utils.getSharedPrefs(c).edit() + editor.putInt(KEY + SimContract.COLUMN_SUB_ID.toString() + iccId, subId) + editor.commit() } - String setStandardIccId(String iccId) { - if (iccId != null) - iccId = iccId.replaceAll("[a-zA-Z]", ""); - return iccId; + fun setStandardIccId(iccId: String?): String? { + var iccId = iccId + if (iccId != null) iccId = iccId.replace("[a-zA-Z]".toRegex(), "") + return iccId } - private String setMcc(String imsi) { - return imsi != null ? imsi.substring(0, 3) : null; + private fun setMcc(imsi: String?): String? { + return imsi?.substring(0, 3) } - boolean isMncMatch(int mncInt) { - return (imsi.length() == 4 && Integer.valueOf(imsi.substring(3)) == mncInt) || - (imsi.length() >= 5 && Integer.valueOf(imsi.substring(3, 5)) == mncInt) || - (imsi.length() >= 6 && Integer.valueOf(imsi.substring(3, 6)) == mncInt); + fun isMncMatch(mncInt: Int): Boolean { + return imsi!!.length == 4 && Integer.valueOf(imsi!!.substring(3)) == mncInt || + imsi!!.length >= 5 && Integer.valueOf(imsi!!.substring(3, 5)) == mncInt || + imsi!!.length >= 6 && Integer.valueOf(imsi!!.substring(3, 6)) == mncInt } - public String getInterpretedHni(JSONArray actionHniList) { - for (int h = 0; h < actionHniList.length(); h++) { - int mncInt = Integer.valueOf(actionHniList.optString(h).substring(3)); + fun getInterpretedHni(actionHniList: JSONArray): String? { + for (h in 0 until actionHniList.length()) { + val mncInt = Integer.valueOf(actionHniList.optString(h).substring(3)) if (isMncMatch(mncInt)) { - return imsi.substring(0, 3) + mncInt; + return imsi!!.substring(0, 3) + mncInt } } - return null; + return null } - private int setSimState(Integer simState) { - if (simState == null) return -1; - return simState; + private fun setSimState(simState: Int): Int { + return simState ?: -1 } - private int setNetworkType(Integer networkType) { - if (networkType == null) return 0; - return networkType; + private fun setNetworkType(networkType: Int): Int { + return networkType ?: 0 } - private boolean setNetworkRoaming(Boolean networkRoaming) { - return networkRoaming != null && networkRoaming; + private fun setNetworkRoaming(networkRoaming: Boolean?): Boolean { + return networkRoaming != null && networkRoaming } - - String log() { + fun log(): String { return "slotIdx=[" + slotIdx + "] subscriptionId=[" + subscriptionId + "] imei=[" + imei + "] imsi=[" + imsi + "] simState=[" + simState + "] simIccId=[" + iccId + - "] simHni=[" + hni + + "] simHni=[" + oSReportedHni + "] simOperatorName=[" + operatorName + "] simCountryIso=[" + countryIso + "] networkOperator=[" + networkOperator + "] networkOperatorName=[" + networkOperatorName + "] networkCountryIso=[" + networkCountryIso + "] networkType=[" + networkType + - "] networkRoaming=[" + networkRoaming + "]"; + "] networkRoaming=[" + isRoaming + "]" } - public String toString() { - return operatorName + " " + (countryIso != null ? countryIso.toUpperCase() : "") + " (SIM " + (slotIdx + 1) + ")"; + override fun toString(): String { + return operatorName + " " + (if (countryIso != null) countryIso!!.uppercase(Locale.getDefault()) else "") + " (SIM " + (slotIdx + 1) + ")" + } + + companion object { + private const val TAG = "SimInfo" + private const val KEY = "sim_info_" + fun getSubId(iccId: String, c: Context?): Int { + return Utils.getSharedPrefs(c) + .getInt(KEY + SimContract.COLUMN_SUB_ID.toString() + iccId, -1) + } + + fun loadPresentByHni(hniList: JSONArray, c: Context?): List { + val simInfos: MutableList = ArrayList() + for (h in 0 until hniList.length()) { + try { + simInfos.addAll( + SimDataSource(c).getPresent( + hniList.getString(h).substring(0, 3), hniList.getString(h).substring(3) + ) + ) + } catch (e: JSONException) { + Sentry.captureException(e) + } catch (e: NullPointerException) { + Sentry.captureException(e) + } + } + return simInfos + } + + fun loadBySlot(slotIdx: Int, c: Context?): SimInfo { + return SimDataSource(c).get(slotIdx) + } + + fun loadAll(c: Context?): List { + return SimDataSource(c).getAll() + } } -} +} \ No newline at end of file diff --git a/multisim/src/main/java/com/hover/multisim/sim/SlotManager.kt b/multisim/src/main/java/com/hover/multisim/sim/SlotManager.kt index 1efff51..30291bb 100644 --- a/multisim/src/main/java/com/hover/multisim/sim/SlotManager.kt +++ b/multisim/src/main/java/com/hover/multisim/sim/SlotManager.kt @@ -1,143 +1,187 @@ -package com.hover.multisim.sim; - -import static android.telephony.TelephonyManager.SIM_STATE_READY; -import static android.telephony.TelephonyManager.SIM_STATE_UNKNOWN; - -import android.util.Log; - -import java.util.ArrayList; -import java.util.List; - -import io.sentry.Sentry; - -final class SlotManager { - public final static String TAG = "SlotManager"; - final Integer slotIndex; - final Integer subscriptionId; - final String imei; - private final Object teleMgr; - private final Class teleClass; - - private final static String[] METHOD_SUFFIXES = new String[] { - "", - "Gemini", - "Ext", - "Ds", - "ForSubscription", - "ForPhone" - }; - - @SuppressWarnings("unused") - private SlotManager(int slotIdx, int subscriptionId, Object teleMgr, Class teleClass, Integer simState, String imei, String iccId) { -// Log.i(TAG, "Creating slotMgr. SlotIdx: " + slotIdx + " Mgr: " + teleMgr + " Class + " + teleClass + " IMEI: " + imei + " ICCID: " + iccId); - slotIndex = slotIdx; - this.subscriptionId = subscriptionId; - this.imei = imei; - this.teleMgr = teleMgr; - this.teleClass = teleClass; - } - - SimInfo createSimInfo() { - return new SimInfo(this); - } - - Object getValue(String methodName, Object subscriptionId) { - return getValue(methodName, (int) subscriptionId, teleMgr, teleClass); - } - - Integer findSimState() { return (Integer) getValue("getSimState", slotIndex); } - String findIccId() { return (String) getValue("getSimSerialNumber", subscriptionId); } - String findImsi() { return (String) getValue("getSubscriberId", subscriptionId); } - String findOperator() { return (String) getValue("getSimOperator", subscriptionId); } - String findOperatorName() { return (String) getValue("getSimOperatorName", subscriptionId); } - String findCountryIso() { return (String) getValue("getSimCountryIso", subscriptionId); } - String findNetworkOperator() { return (String) getValue("getNetworkOperator", subscriptionId); } - String findNetworkOperatorName() { return (String) getValue("getNetworkOperatorName", subscriptionId); } - String findNetworkCountryIso() { return (String) getValue("getNetworkCountryIso", subscriptionId); } - Integer findNetworkType() { return (Integer) getValue("getNetworkType", subscriptionId); } - boolean findNetworkRoaming() { return (boolean) getValue("isNetworkRoaming", subscriptionId); } - - private boolean isUnique(List slotMgrList) { - for (SlotManager mgr: slotMgrList) { - try { - if (mgr != null && imei.equals(mgr.imei) && teleMgr == mgr.teleMgr && teleClass == mgr.teleClass) - return false; - } catch (NullPointerException e) { Log.w(TAG, "something was null that shouldn't be", e); Sentry.captureException(e); } - } -// Log.i(TAG, "Slot Manager was unique with Imei: " + imei + ", teleMgr: " + teleMgr + ", and teleClass: " + teleClass); - return true; - } - - public static void addValidReadySlots(List slotMgrList, Object slotIdx, Object teleMgrInstance, ArrayList validClassNames) { - if (slotIdx == null) - for (int i = 0; i < MultiSimWorker.SLOT_COUNT - 1; i++) - addValidReadySlots(slotMgrList, i, i, teleMgrInstance, validClassNames); - else - addValidReadySlots(slotMgrList, (int) slotIdx, (int) slotIdx, teleMgrInstance, validClassNames); - } - public static void addValidReadySlots(List slotMgrList, int slotIdx, int subscriptionId, Object teleMgrInstance, ArrayList validClassNames) { - if (validClassNames == null || validClassNames.size() <= 0) { return; } - for (String className : validClassNames) - if (teleMgrInstance != null || className != null) { - SlotManager sm = findValidReadySlot(slotIdx, subscriptionId, teleMgrInstance, className); - if (sm != null && (slotMgrList.size() == 0 || sm.isUnique(slotMgrList))) { - slotMgrList.add(sm); -// Log.e(TAG, "Added slotMgr. SlotIdx: " + slotIdx + " subId: " + subscriptionId + " Mgr: " + teleMgrInstance + " Class + " + sm.teleClass + " IMEI: " + sm.imei); // + " ICCID: " + getSimIccId(subscriptionId, teleMgr, sm.teleClass)); - } - } - } - private static SlotManager findValidReadySlot(int slotIdx, int subscriptionId, Object teleMgr, String className) { - Class teleClass = getTeleClass(teleMgr, className); - Integer simState = getSimState(slotIdx, teleMgr, teleClass); - String imei = getDeviceId(slotIdx, teleMgr, teleClass); - String iccId = getSimIccId(subscriptionId, teleMgr, teleClass); -// Log.i(TAG, "Got slot mgr with slotIdx: " + slotIdx + " subId: " + subscriptionId + " teleClass: " + teleClass + " simState: " + simState + " IMEI: " + imei + " ICCID: " + iccId + " IMSI: " + getSimImsi(subscriptionId, teleMgr, teleClass)); - if (simState != null && simState == SIM_STATE_READY && imei != null && iccId != null) - return new SlotManager(slotIdx, subscriptionId, teleMgr, teleClass, simState, imei, iccId); - return null; - } - - private static Integer getSimState(int slotIndex, Object teleMgr, Class teleClass) { - try { - return (Integer) getValue("getSimState", slotIndex, teleMgr, teleClass); - } catch (Exception e) { Log.d(TAG, "Couldn't get sim state"); return SIM_STATE_UNKNOWN; } - } - private static String getDeviceId(int slotIndex, Object teleMgr, Class teleClass) { - String imei = (String) getValue("getDeviceId", slotIndex, teleMgr, teleClass); - if (imei == null) imei = (String) getValue("getImei", slotIndex, teleMgr, teleClass); - return imei; - } - private static String getSimIccId(int subscriptionId, Object teleMgr, Class teleClass) { - return (String) getValue("getSimSerialNumber", subscriptionId, teleMgr, teleClass); - } - static String getSimImsi(int subscriptionId, Object teleMgr, Class teleClass) { - return (String) getValue("getSubscriberId", subscriptionId, teleMgr, teleClass); - } - - private static Class getTeleClass(Object teleMgr, String className) { - try { - if (className != null) return Class.forName(className); - } catch (ClassNotFoundException ignored) { } - if (teleMgr != null) return teleMgr.getClass(); - return null; - } - - private static Object getValue(String methodName, int slotIndex, Object teleMgr, Class teleClass) { - Object result = spamTeleMgr(methodName, teleMgr, teleClass, slotIndex); - if (result == null) result = spamTeleMgr(methodName, teleMgr, teleClass, null); - if (result == null) return null; - return result; - } - private static Object spamTeleMgr(String methodName, Object teleMgr, Class teleClass, Object subscriptionId) { - if (methodName == null || methodName.length() <= 0) return null; - Object result; - for (String methodSuffix : METHOD_SUFFIXES) { - result = MultiSimWorker.runMethodReflect(teleMgr, teleClass, methodName + methodSuffix, subscriptionId == null ? null : new Object[]{ subscriptionId }); - if (result != null) { -// Log.i(TAG, "Got result for method: " + methodName + methodSuffix + ". Result: " + result.toString()); - return result; - } - } - return null; - } +package com.hover.multisim.sim + +import android.telephony.TelephonyManager +import android.util.Log +import io.sentry.Sentry + +internal class SlotManager // Log.i(TAG, "Creating slotMgr. SlotIdx: " + slotIdx + " Mgr: " + teleMgr + " Class + " + teleClass + " IMEI: " + imei + " ICCID: " + iccId); +private constructor( + val slotIndex: Int, + val subscriptionId: Int, + private val teleMgr: Any?, + private val teleClass: Class<*>?, + simState: Int, + val imei: String, + iccId: String +) { + fun createSimInfo(): SimInfo { + return SimInfo(this) + } + + operator fun getValue(methodName: String, subscriptionId: Any): Any? { + return getValue(methodName, subscriptionId as Int, teleMgr, teleClass) + } + + fun findSimState(): Int? { + return getValue("getSimState", slotIndex) as Int? + } + + fun findIccId(): String? { + return getValue("getSimSerialNumber", subscriptionId) as String? + } + + fun findImsi(): String? { + return getValue("getSubscriberId", subscriptionId) as String? + } + + fun findOperator(): String? { + return getValue("getSimOperator", subscriptionId) as String? + } + + fun findOperatorName(): String? { + return getValue("getSimOperatorName", subscriptionId) as String? + } + + fun findCountryIso(): String? { + return getValue("getSimCountryIso", subscriptionId) as String? + } + + fun findNetworkOperator(): String? { + return getValue("getNetworkOperator", subscriptionId) as String? + } + + fun findNetworkOperatorName(): String? { + return getValue("getNetworkOperatorName", subscriptionId) as String? + } + + fun findNetworkCountryIso(): String? { + return getValue("getNetworkCountryIso", subscriptionId) as String? + } + + fun findNetworkType(): Int? { + return getValue("getNetworkType", subscriptionId) as Int? + } + + fun findNetworkRoaming(): Boolean { + return getValue("isNetworkRoaming", subscriptionId) as Boolean + } + + private fun isUnique(slotMgrList: List): Boolean { + for (mgr in slotMgrList) { + try { + if (mgr != null && imei == mgr.imei && teleMgr === mgr.teleMgr && teleClass == mgr.teleClass) return false + } catch (e: NullPointerException) { + Log.w(TAG, "something was null that shouldn't be", e) + Sentry.captureException(e) + } + } + return true + } + + companion object { + const val TAG = "SlotManager" + private val METHOD_SUFFIXES = arrayOf( + "", "Gemini", "Ext", "Ds", "ForSubscription", "ForPhone" + ) + + fun addValidReadySlots( + slotMgrList: MutableList, + slotIdx: Any?, + teleMgrInstance: Any?, + validClassNames: ArrayList? + ) { + if (slotIdx == null) for (i in 0 until MultiSimWorker.SLOT_COUNT - 1) addValidReadySlots( + slotMgrList, i, i, teleMgrInstance, validClassNames + ) else addValidReadySlots( + slotMgrList, slotIdx as Int, slotIdx, teleMgrInstance, validClassNames + ) + } + + @JvmStatic + fun addValidReadySlots( + slotMgrList: MutableList, + slotIdx: Int, + subscriptionId: Int, + teleMgrInstance: Any?, + validClassNames: ArrayList? + ) { + if (validClassNames == null || validClassNames.size <= 0) { + return + } + for (className in validClassNames) if (teleMgrInstance != null || className != null) { + val sm = findValidReadySlot(slotIdx, subscriptionId, teleMgrInstance, className) + if (sm != null && (slotMgrList.size == 0 || sm.isUnique(slotMgrList))) { + slotMgrList.add(sm) + } + } + } + + private fun findValidReadySlot( + slotIdx: Int, subscriptionId: Int, teleMgr: Any?, className: String? + ): SlotManager? { + val teleClass = getTeleClass(teleMgr, className) + val simState = getSimState(slotIdx, teleMgr, teleClass) + val imei = getDeviceId(slotIdx, teleMgr, teleClass) + val iccId = getSimIccId(subscriptionId, teleMgr, teleClass) + return if (simState != null && simState == TelephonyManager.SIM_STATE_READY && imei != null && iccId != null) SlotManager( + slotIdx, subscriptionId, teleMgr, teleClass, simState, imei, iccId + ) else null + } + + private fun getSimState(slotIndex: Int, teleMgr: Any?, teleClass: Class<*>?): Int? { + return try { + getValue("getSimState", slotIndex, teleMgr, teleClass) as Int? + } catch (e: Exception) { + Log.d(TAG, "Couldn't get sim state") + TelephonyManager.SIM_STATE_UNKNOWN + } + } + + private fun getDeviceId(slotIndex: Int, teleMgr: Any?, teleClass: Class<*>?): String? { + var imei = getValue("getDeviceId", slotIndex, teleMgr, teleClass) as String? + if (imei == null) imei = getValue("getImei", slotIndex, teleMgr, teleClass) as String? + return imei + } + + private fun getSimIccId(subscriptionId: Int, teleMgr: Any?, teleClass: Class<*>?): String? { + return getValue("getSimSerialNumber", subscriptionId, teleMgr, teleClass) as String? + } + + fun getSimImsi(subscriptionId: Int, teleMgr: Any?, teleClass: Class<*>?): String? { + return getValue("getSubscriberId", subscriptionId, teleMgr, teleClass) as String? + } + + private fun getTeleClass(teleMgr: Any?, className: String?): Class<*>? { + try { + if (className != null) return Class.forName(className) + } catch (ignored: ClassNotFoundException) { + } + return teleMgr?.javaClass + } + + private fun getValue( + methodName: String, slotIndex: Int, teleMgr: Any?, teleClass: Class<*>? + ): Any? { + var result = spamTeleMgr(methodName, teleMgr, teleClass, slotIndex) + if (result == null) result = spamTeleMgr(methodName, teleMgr, teleClass, null) + return result + } + + private fun spamTeleMgr( + methodName: String?, teleMgr: Any?, teleClass: Class<*>?, subscriptionId: Any? + ): Any? { + if (methodName == null || methodName.isEmpty()) return null + var result: Any? + for (methodSuffix in METHOD_SUFFIXES) { + result = MultiSimWorker.runMethodReflect(teleMgr, + teleClass, + methodName + methodSuffix, + subscriptionId?.let { arrayOf(it) }) + if (result != null) { + return result + } + } + return null + } + } } \ No newline at end of file diff --git a/multisim/src/main/java/com/hover/multisim/sim/Utils.kt b/multisim/src/main/java/com/hover/multisim/sim/Utils.kt index 3a9d316..7b19337 100644 --- a/multisim/src/main/java/com/hover/multisim/sim/Utils.kt +++ b/multisim/src/main/java/com/hover/multisim/sim/Utils.kt @@ -1,32 +1,36 @@ -package com.hover.multisim.sim; +package com.hover.multisim.sim -import android.Manifest; -import android.content.Context; -import android.content.SharedPreferences; -import android.content.pm.PackageManager; -import android.os.Build; +import android.Manifest +import android.content.Context +import android.content.SharedPreferences +import android.content.pm.PackageManager +import android.os.Build +import io.sentry.Sentry -import io.sentry.Sentry; +object Utils { -public class Utils { - private static final String TAG = "Utils"; - private static final String SHARED_PREFS = "_multisim"; + private const val SHARED_PREFS = "_multisim" - public static String getPackage(Context c) { - try { - return c.getApplicationContext().getPackageName(); - } catch (NullPointerException e) { - Sentry.captureException(e); - return "fail"; - } - } + @JvmStatic + fun getPackage(context: Context): String? { + return try { + context.applicationContext.packageName + } catch (e: NullPointerException) { + Sentry.captureException(e) + null + } + } - public static boolean hasPhonePerm(Context c) { - return Build.VERSION.SDK_INT < 23 || (c.checkSelfPermission(Manifest.permission.CALL_PHONE) == PackageManager.PERMISSION_GRANTED && - c.checkSelfPermission(Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED); - } + @JvmStatic + fun hasPhonePerm(context: Context): Boolean { + return Build.VERSION.SDK_INT < 23 || context.checkSelfPermission(Manifest.permission.CALL_PHONE) == PackageManager.PERMISSION_GRANTED && context.checkSelfPermission( + Manifest.permission.READ_PHONE_STATE + ) == PackageManager.PERMISSION_GRANTED + } - public static SharedPreferences getSharedPrefs(Context context) { - return context.getSharedPreferences(getPackage(context) + SHARED_PREFS, Context.MODE_PRIVATE); - } -} + fun getSharedPrefs(context: Context): SharedPreferences { + return context.getSharedPreferences( + getPackage(context) + SHARED_PREFS, Context.MODE_PRIVATE + ) + } +} \ No newline at end of file From 4a9350b81de09886bc2fddc6e14ce62ec2c66599 Mon Sep 17 00:00:00 2001 From: Juma Allan Date: Wed, 5 Oct 2022 22:43:08 +0300 Subject: [PATCH 13/26] setting up github workflows --- .github/CODEOWNERS | 4 ++++ .github/pull_request_template.md | 18 +++++++++++++++++ .github/workflows/build_and_test.yml | 30 ++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+) create mode 100644 .github/CODEOWNERS create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/build_and_test.yml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..c68d202 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,4 @@ +@davkutalek @jumaallan + +## code changes will send PR to following users +* @davkutalek @jumaallan diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..916d456 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,18 @@ +# Scope + +_Please explain what your feature does in a short paragraph._ please check the below boxes +- [ ] I have followed the coding conventions +- [ ] I have added/updated necessary tests +- [ ] I have tested the changes added on a physical device +- [ ] I have run `./codeAnalysis.sh` to make sure all lint/formatting checks have been done. + +## Closes/Fixes Issues +_Declare any issues by typing `fixes #1` or `closes #1` for example so that the automation can kick +in when this is merged_ + +## Other testing QA Notes +_What have you tested specifically and what possible impacts/areas there are that may need retesting +by others._ + +Please add a screenshot (if necessary) + diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml new file mode 100644 index 0000000..a176d97 --- /dev/null +++ b/.github/workflows/build_and_test.yml @@ -0,0 +1,30 @@ +name: Build and test + +on: + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + build_and_test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Bundle install + run: bundle install + + - name: Static check + run: bundle exec fastlane staticCheck + + - name: Unit tests + run: bundle exec fastlane unitTest + + - name: Upload test report + uses: actions/upload-artifact@v3 + if: failure() + with: + name: junit_test_report_${{ env.GITHUB_REF }}_${{ env.GITHUB_RUN_ID }} + path: multisim/build/reports/tests From be3d207627d1d1ba3f01df0c37d7515b8173e6a7 Mon Sep 17 00:00:00 2001 From: Juma Allan Date: Wed, 5 Oct 2022 22:59:09 +0300 Subject: [PATCH 14/26] setting up Fastlane --- Gemfile | 3 + Gemfile.lock | 218 ++++++++++++++++++++++++++++++++++++++++++++ fastlane/Appfile | 2 + fastlane/Fastfile | 30 ++++++ fastlane/README.md | 40 ++++++++ fastlane/report.xml | 20 ++++ 6 files changed, 313 insertions(+) create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 fastlane/Appfile create mode 100644 fastlane/Fastfile create mode 100644 fastlane/README.md create mode 100644 fastlane/report.xml diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..7a118b4 --- /dev/null +++ b/Gemfile @@ -0,0 +1,3 @@ +source "https://rubygems.org" + +gem "fastlane" diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..22e4779 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,218 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.5) + rexml + addressable (2.8.1) + public_suffix (>= 2.0.2, < 6.0) + artifactory (3.0.15) + atomos (0.1.3) + aws-eventstream (1.2.0) + aws-partitions (1.642.0) + aws-sdk-core (3.158.0) + aws-eventstream (~> 1, >= 1.0.2) + aws-partitions (~> 1, >= 1.525.0) + aws-sigv4 (~> 1.1) + jmespath (~> 1, >= 1.6.1) + aws-sdk-kms (1.58.0) + aws-sdk-core (~> 3, >= 3.127.0) + aws-sigv4 (~> 1.1) + aws-sdk-s3 (1.114.0) + aws-sdk-core (~> 3, >= 3.127.0) + aws-sdk-kms (~> 1) + aws-sigv4 (~> 1.4) + aws-sigv4 (1.5.2) + aws-eventstream (~> 1, >= 1.0.2) + babosa (1.0.4) + claide (1.1.0) + colored (1.2) + colored2 (3.1.2) + commander (4.6.0) + highline (~> 2.0.0) + declarative (0.0.20) + digest-crc (0.6.4) + rake (>= 12.0.0, < 14.0.0) + domain_name (0.5.20190701) + unf (>= 0.0.5, < 1.0.0) + dotenv (2.8.1) + emoji_regex (3.2.3) + excon (0.93.0) + faraday (1.10.2) + faraday-em_http (~> 1.0) + faraday-em_synchrony (~> 1.0) + faraday-excon (~> 1.1) + faraday-httpclient (~> 1.0) + faraday-multipart (~> 1.0) + faraday-net_http (~> 1.0) + faraday-net_http_persistent (~> 1.0) + faraday-patron (~> 1.0) + faraday-rack (~> 1.0) + faraday-retry (~> 1.0) + ruby2_keywords (>= 0.0.4) + faraday-cookie_jar (0.0.7) + faraday (>= 0.8.0) + http-cookie (~> 1.0.0) + faraday-em_http (1.0.0) + faraday-em_synchrony (1.0.0) + faraday-excon (1.1.0) + faraday-httpclient (1.0.1) + faraday-multipart (1.0.4) + multipart-post (~> 2) + faraday-net_http (1.0.1) + faraday-net_http_persistent (1.2.0) + faraday-patron (1.0.0) + faraday-rack (1.0.0) + faraday-retry (1.0.3) + faraday_middleware (1.2.0) + faraday (~> 1.0) + fastimage (2.2.6) + fastlane (2.210.1) + CFPropertyList (>= 2.3, < 4.0.0) + addressable (>= 2.8, < 3.0.0) + artifactory (~> 3.0) + aws-sdk-s3 (~> 1.0) + babosa (>= 1.0.3, < 2.0.0) + bundler (>= 1.12.0, < 3.0.0) + colored + commander (~> 4.6) + dotenv (>= 2.1.1, < 3.0.0) + emoji_regex (>= 0.1, < 4.0) + excon (>= 0.71.0, < 1.0.0) + faraday (~> 1.0) + faraday-cookie_jar (~> 0.0.6) + faraday_middleware (~> 1.0) + fastimage (>= 2.1.0, < 3.0.0) + gh_inspector (>= 1.1.2, < 2.0.0) + google-apis-androidpublisher_v3 (~> 0.3) + google-apis-playcustomapp_v1 (~> 0.1) + google-cloud-storage (~> 1.31) + highline (~> 2.0) + json (< 3.0.0) + jwt (>= 2.1.0, < 3) + mini_magick (>= 4.9.4, < 5.0.0) + multipart-post (~> 2.0.0) + naturally (~> 2.2) + optparse (~> 0.1.1) + plist (>= 3.1.0, < 4.0.0) + rubyzip (>= 2.0.0, < 3.0.0) + security (= 0.1.3) + simctl (~> 1.6.3) + terminal-notifier (>= 2.0.0, < 3.0.0) + terminal-table (>= 1.4.5, < 2.0.0) + tty-screen (>= 0.6.3, < 1.0.0) + tty-spinner (>= 0.8.0, < 1.0.0) + word_wrap (~> 1.0.0) + xcodeproj (>= 1.13.0, < 2.0.0) + xcpretty (~> 0.3.0) + xcpretty-travis-formatter (>= 0.0.3) + gh_inspector (1.1.3) + google-apis-androidpublisher_v3 (0.29.0) + google-apis-core (>= 0.9.0, < 2.a) + google-apis-core (0.9.0) + addressable (~> 2.5, >= 2.5.1) + googleauth (>= 0.16.2, < 2.a) + httpclient (>= 2.8.1, < 3.a) + mini_mime (~> 1.0) + representable (~> 3.0) + retriable (>= 2.0, < 4.a) + rexml + webrick + google-apis-iamcredentials_v1 (0.15.0) + google-apis-core (>= 0.9.0, < 2.a) + google-apis-playcustomapp_v1 (0.11.0) + google-apis-core (>= 0.9.0, < 2.a) + google-apis-storage_v1 (0.19.0) + google-apis-core (>= 0.9.0, < 2.a) + google-cloud-core (1.6.0) + google-cloud-env (~> 1.0) + google-cloud-errors (~> 1.0) + google-cloud-env (1.6.0) + faraday (>= 0.17.3, < 3.0) + google-cloud-errors (1.3.0) + google-cloud-storage (1.43.0) + addressable (~> 2.8) + digest-crc (~> 0.4) + google-apis-iamcredentials_v1 (~> 0.1) + google-apis-storage_v1 (~> 0.19.0) + google-cloud-core (~> 1.6) + googleauth (>= 0.16.2, < 2.a) + mini_mime (~> 1.0) + googleauth (1.2.0) + faraday (>= 0.17.3, < 3.a) + jwt (>= 1.4, < 3.0) + memoist (~> 0.16) + multi_json (~> 1.11) + os (>= 0.9, < 2.0) + signet (>= 0.16, < 2.a) + highline (2.0.3) + http-cookie (1.0.5) + domain_name (~> 0.5) + httpclient (2.8.3) + jmespath (1.6.1) + json (2.6.2) + jwt (2.5.0) + memoist (0.16.2) + mini_magick (4.11.0) + mini_mime (1.1.2) + multi_json (1.15.0) + multipart-post (2.0.0) + nanaimo (0.3.0) + naturally (2.2.1) + optparse (0.1.1) + os (1.1.4) + plist (3.6.0) + public_suffix (5.0.0) + rake (13.0.6) + representable (3.2.0) + declarative (< 0.1.0) + trailblazer-option (>= 0.1.1, < 0.2.0) + uber (< 0.2.0) + retriable (3.1.2) + rexml (3.2.5) + rouge (2.0.7) + ruby2_keywords (0.0.5) + rubyzip (2.3.2) + security (0.1.3) + signet (0.17.0) + addressable (~> 2.8) + faraday (>= 0.17.5, < 3.a) + jwt (>= 1.5, < 3.0) + multi_json (~> 1.10) + simctl (1.6.8) + CFPropertyList + naturally + terminal-notifier (2.0.0) + terminal-table (1.8.0) + unicode-display_width (~> 1.1, >= 1.1.1) + trailblazer-option (0.1.2) + tty-cursor (0.7.1) + tty-screen (0.8.1) + tty-spinner (0.9.3) + tty-cursor (~> 0.7) + uber (0.1.0) + unf (0.1.4) + unf_ext + unf_ext (0.0.8.2) + unicode-display_width (1.8.0) + webrick (1.7.0) + word_wrap (1.0.0) + xcodeproj (1.22.0) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.3.0) + rexml (~> 3.2.4) + xcpretty (0.3.0) + rouge (~> 2.0.7) + xcpretty-travis-formatter (1.0.1) + xcpretty (~> 0.2, >= 0.0.7) + +PLATFORMS + arm64-darwin-21 + +DEPENDENCIES + fastlane + +BUNDLED WITH + 2.3.11 diff --git a/fastlane/Appfile b/fastlane/Appfile new file mode 100644 index 0000000..64b7e15 --- /dev/null +++ b/fastlane/Appfile @@ -0,0 +1,2 @@ +json_key_file("") # Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one +package_name("com.hover.app") # e.g. com.krausefx.app diff --git a/fastlane/Fastfile b/fastlane/Fastfile new file mode 100644 index 0000000..c1d7d49 --- /dev/null +++ b/fastlane/Fastfile @@ -0,0 +1,30 @@ +# This file contains the fastlane.tools configuration +# You can find the documentation at https://docs.fastlane.tools +# +# For a list of all available actions, check out +# +# https://docs.fastlane.tools/actions +# +# For a list of all available plugins, check out +# +# https://docs.fastlane.tools/plugins/available-plugins +# + +# Uncomment the line if you want fastlane to automatically update itself +# update_fastlane + +default_platform(:android) + +platform :android do + + desc "Runs static checks on the codebase" + lane :staticCheck do + gradle(task: "detekt") + end + + desc "Runs all unit tests" + lane :unitTest do + gradle(task: "test") + end + +end diff --git a/fastlane/README.md b/fastlane/README.md new file mode 100644 index 0000000..ac1849b --- /dev/null +++ b/fastlane/README.md @@ -0,0 +1,40 @@ +fastlane documentation +---- + +# Installation + +Make sure you have the latest version of the Xcode command line tools installed: + +```sh +xcode-select --install +``` + +For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane) + +# Available Actions + +## Android + +### android staticCheck + +```sh +[bundle exec] fastlane android staticCheck +``` + +Runs static checks on the codebase + +### android unitTest + +```sh +[bundle exec] fastlane android unitTest +``` + +Runs all unit tests + +---- + +This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run. + +More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools). + +The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools). diff --git a/fastlane/report.xml b/fastlane/report.xml new file mode 100644 index 0000000..7218776 --- /dev/null +++ b/fastlane/report.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + From e19f86bb56dc1019cb969a801e2db6a061d082b1 Mon Sep 17 00:00:00 2001 From: Juma Allan Date: Wed, 5 Oct 2022 23:35:52 +0300 Subject: [PATCH 15/26] setting up maven central publishing --- multisim/build.gradle.kts | 127 ++++++++++++++++++ multisim/gradle.properties | 21 +++ .../com/hover/multisim/sim/MultiSimWorker.kt | 23 ++-- 3 files changed, 163 insertions(+), 8 deletions(-) create mode 100644 multisim/gradle.properties diff --git a/multisim/build.gradle.kts b/multisim/build.gradle.kts index 794d3c5..9c02679 100644 --- a/multisim/build.gradle.kts +++ b/multisim/build.gradle.kts @@ -3,6 +3,8 @@ plugins { id("org.jetbrains.kotlin.android") id("dagger.hilt.android.plugin") id("com.google.devtools.ksp") + id("maven-publish") + id("signing") kotlin("kapt") } @@ -77,3 +79,128 @@ kotlin { } } } + +tasks { + val sourceFiles = android.sourceSets.getByName("main").java.srcDirs + + register("withJavadoc") { + isFailOnError = false + dependsOn(android.libraryVariants.toList().last().javaCompileProvider) + + if (! project.plugins.hasPlugin("org.jetbrains.kotlin.android")) { + setSource(sourceFiles) + } + android.bootClasspath.forEach { classpath += project.fileTree(it) } + android.libraryVariants.forEach { variant -> + variant.javaCompileProvider.get().classpath.files.forEach { file -> + classpath += project.fileTree(file) + } + } + exclude("**/internal/*") + val options = options as StandardJavadocDocletOptions + options.links("https://developer.android.com/reference") + options.links("https://docs.oracle.com/javase/8/docs/api/") + + // Workaround for the following error when running on on JDK 9+ + // "The code being documented uses modules but the packages defined in ... are in the unnamed module." + if (JavaVersion.current() >= JavaVersion.VERSION_1_9) { + options.addStringOption("-release", "8") + } + } + + register("withJavadocJar") { + archiveClassifier.set("javadoc") + dependsOn(named("withJavadoc")) + val destination = named("withJavadoc").get().destinationDir + from(destination) + } + + register("withSourcesJar") { + archiveClassifier.set("sources") + from(sourceFiles) + } +} + +afterEvaluate { + fun Project.get(name: String, def: String = "$name not found") = + properties[name]?.toString() ?: System.getenv(name) ?: def + + fun Project.getRepositoryUrl(): java.net.URI { + val isReleaseBuild = !get("POM_VERSION_NAME").contains("SNAPSHOT") + val releaseRepoUrl = get( + "RELEASE_REPOSITORY_URL", + "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" + ) + val snapshotRepoUrl = get( + "SNAPSHOT_REPOSITORY_URL", + "https://s01.oss.sonatype.org/content/repositories/snapshots/" + ) + return uri(if (isReleaseBuild) releaseRepoUrl else snapshotRepoUrl) + } + + publishing { + publications { + val props = project.properties + repositories { + maven { + url = getRepositoryUrl() + credentials { + username = project.get("ossUsername") + password = project.get("ossPassword") + } + } + } + val publicationName = props["POM_NAME"]?.toString() ?: "publication" + create(publicationName) { + from(project.components["release"]) + artifact(tasks.named("withJavadocJar")) + artifact(tasks.named("withSourcesJar")) + + pom { + groupId = project.get("GROUP") + artifactId = project.get("POM_ARTIFACT_ID") + version = project.get("VERSION_NAME") + + name.set(project.get("POM_NAME")) + description.set(project.get("POM_DESCRIPTION")) + url.set(project.get("POM_URL")) + packaging = project.get("POM_PACKAGING") + + scm { + url.set(project.get("POM_SCM_URL")) + connection.set(project.get("POM_SCM_CONNECTION")) + developerConnection.set(project.get("POM_SCM_DEV_CONNECTION")) + } + + organization { + name.set(project.get("POM_DEVELOPER_NAME")) + url.set(project.get("POM_DEVELOPER_URL")) + } + + developers { + developer { + id.set(project.get("POM_DEVELOPER_ID")) + name.set(project.get("POM_DEVELOPER_NAME")) + } + } + + licenses { + license { + name.set(project.get("POM_LICENCE_NAME")) + url.set(project.get("POM_LICENCE_URL")) + distribution.set(project.get("POM_LICENCE_DIST")) + } + } + } + } + + signing { + val signingKeyId = project.get("signingKeyId") + val signingKeyPassword = project.get("signingKeyPassword") + val signingKey = project.get("signingKey") + useInMemoryPgpKeys(signingKeyId, signingKey, signingKeyPassword) + sign(publishing.publications.getByName(publicationName)) + } + } + } +} diff --git a/multisim/gradle.properties b/multisim/gradle.properties new file mode 100644 index 0000000..388b97b --- /dev/null +++ b/multisim/gradle.properties @@ -0,0 +1,21 @@ +GROUP= +POM_ARTIFACT_ID= +VERSION_NAME=0.0.1 +POM_PACKAGING=aar + +POM_NAME= +POM_DESCRIPTION= +POM_INCEPTION_YEAR= +POM_URL=https://github.com/UseHover/MultiSim + +POM_LICENSE_NAME= +POM_LICENSE_URL= +POM_LICENSE_DIST= + +POM_SCM_URL= +POM_SCM_CONNECTION= +POM_SCM_DEV_CONNECTION= + +POM_DEVELOPER_ID= +POM_DEVELOPER_NAME= +POM_DEVELOPER_URL=https://github.com/UseHover \ No newline at end of file diff --git a/multisim/src/main/java/com/hover/multisim/sim/MultiSimWorker.kt b/multisim/src/main/java/com/hover/multisim/sim/MultiSimWorker.kt index 6642ef3..f23998d 100644 --- a/multisim/src/main/java/com/hover/multisim/sim/MultiSimWorker.kt +++ b/multisim/src/main/java/com/hover/multisim/sim/MultiSimWorker.kt @@ -16,7 +16,6 @@ import androidx.work.PeriodicWorkRequest import androidx.work.WorkerParameters import androidx.work.impl.utils.futures.SettableFuture import com.google.common.util.concurrent.ListenableFuture -import com.hover.multisim.sim.MultiSimWorker import com.hover.multisim.sim.SlotManager.Companion.addValidReadySlots import com.hover.multisim.sim.Utils.getPackage import com.hover.multisim.sim.Utils.hasPhonePerm @@ -49,7 +48,11 @@ class MultiSimWorker(context: Context, params: WorkerParameters) : override fun startWork(): ListenableFuture { android.util.Log.v(TAG, "Starting new Multi SIM worker") workerFuture = SettableFuture.create() - if (hasPhonePerm(applicationContext)) startListeners() else workerFuture.set(Result.failure()) + if (hasPhonePerm(applicationContext)) { + startListeners() + } else { + workerFuture.set(Result.failure()) + } return workerFuture } @@ -57,7 +60,9 @@ class MultiSimWorker(context: Context, params: WorkerParameters) : private fun startListeners() { try { registerSimStateReceiver() - if (simStateListener == null) simStateListener = SimStateListener() + if (simStateListener == null) { + simStateListener = SimStateListener() + } // TelephonyManager.listen() must take place on the main thread (applicationContext.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager).listen( simStateListener, @@ -122,8 +127,10 @@ class MultiSimWorker(context: Context, params: WorkerParameters) : var oldList: ArrayList? = null for (i in 0 until SLOT_COUNT) { val si: SimInfo = SimDataSource(applicationContext).get(i) - if (oldList == null) oldList = ArrayList() - oldList.add(si) + if (oldList == null) { + oldList = ArrayList() + oldList.add(si) + } } android.util.Log.v(TAG, "Loaded old list from db. Size: " + (oldList?.size ?: "null")) return oldList @@ -157,7 +164,9 @@ class MultiSimWorker(context: Context, params: WorkerParameters) : private fun findPhysicalSim(newList: List, savedSim: SimInfo): Boolean { for (si in newList) { - if (si!!.isSameSim(savedSim)) return true + if (si!!.isSameSim(savedSim)){ + return true + } } return false } @@ -453,7 +462,5 @@ class MultiSimWorker(context: Context, params: WorkerParameters) : fun action(c: Context?): String { return getPackage(c!!) + "." + NEW_SIM_INFO } - - private fun log(message: String?) {} } } \ No newline at end of file From 408291c929858f317ba008f19826c403bb856658 Mon Sep 17 00:00:00 2001 From: Juma Allan Date: Wed, 12 Oct 2022 20:42:24 +0300 Subject: [PATCH 16/26] reworking multisim internals --- .../com/hover/multisim/sim/MultiSimWorker.kt | 62 ++++++++++--------- .../java/com/hover/multisim/sim/SimInfo.kt | 42 ++++--------- .../com/hover/multisim/sim/SlotManager.kt | 8 +-- 3 files changed, 49 insertions(+), 63 deletions(-) diff --git a/multisim/src/main/java/com/hover/multisim/sim/MultiSimWorker.kt b/multisim/src/main/java/com/hover/multisim/sim/MultiSimWorker.kt index f23998d..6804330 100644 --- a/multisim/src/main/java/com/hover/multisim/sim/MultiSimWorker.kt +++ b/multisim/src/main/java/com/hover/multisim/sim/MultiSimWorker.kt @@ -9,6 +9,7 @@ import android.content.IntentFilter import android.os.Build import android.os.SystemClock import android.telephony.* +import android.util.Log import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.work.ListenableWorker import androidx.work.OneTimeWorkRequest @@ -16,6 +17,7 @@ import androidx.work.PeriodicWorkRequest import androidx.work.WorkerParameters import androidx.work.impl.utils.futures.SettableFuture import com.google.common.util.concurrent.ListenableFuture +import com.hover.multisim.db.dao.SimDao import com.hover.multisim.sim.SlotManager.Companion.addValidReadySlots import com.hover.multisim.sim.Utils.getPackage import com.hover.multisim.sim.Utils.hasPhonePerm @@ -23,8 +25,13 @@ import io.sentry.Sentry import java.util.* import java.util.concurrent.Semaphore import java.util.concurrent.TimeUnit +import javax.inject.Inject -class MultiSimWorker(context: Context, params: WorkerParameters) : +class MultiSimWorker @Inject constructor( + context: Context, + params: WorkerParameters, + private val simDao: SimDao +) : ListenableWorker(context, params) { private lateinit var workerFuture: SettableFuture @@ -90,7 +97,7 @@ class MultiSimWorker(context: Context, params: WorkerParameters) : } } else Result.failure() } catch (e: Exception) { - android.util.Log.w(TAG, "threw while attempting to update sim list", e) + Log.w(TAG, "threw while attempting to update sim list", e) Sentry.captureException(e) Result.failure() } finally { @@ -126,7 +133,7 @@ class MultiSimWorker(context: Context, params: WorkerParameters) : get() { var oldList: ArrayList? = null for (i in 0 until SLOT_COUNT) { - val si: SimInfo = SimDataSource(applicationContext).get(i) + val si: SimInfo = simDao.get(i) if (oldList == null) { oldList = ArrayList() oldList.add(si) @@ -150,13 +157,13 @@ class MultiSimWorker(context: Context, params: WorkerParameters) : private fun updateDb(newList: List) { for (si in newList) { - android.util.Log.i(TAG, "Saving SIM in slot " + si!!.slotIdx + ": " + si.toString()) + Log.i(TAG, "Saving SIM in slot " + si!!.slotIdx + ": " + si.toString()) si.save(applicationContext) } - val dbInfos: List = SimDataSource(applicationContext).getAll() + val dbInfos: List = simDao.getAll() for (dbSi in dbInfos) { if (!findPhysicalSim(newList, dbSi)) { - android.util.Log.i(TAG, "Couldn't find SIM: $dbSi, removing") + Log.i(TAG, "Couldn't find SIM: $dbSi, removing") dbSi.setSimRemoved(applicationContext) } } @@ -164,7 +171,7 @@ class MultiSimWorker(context: Context, params: WorkerParameters) : private fun findPhysicalSim(newList: List, savedSim: SimInfo): Boolean { for (si in newList) { - if (si!!.isSameSim(savedSim)){ + if (si!!.isSameSim(savedSim)) { return true } } @@ -185,7 +192,7 @@ class MultiSimWorker(context: Context, params: WorkerParameters) : subInfos, slotMgrList ) else createUniqueSimInfoList(slotMgrList) } catch (e: Exception) { - android.util.Log.w(TAG, "Multi-SIM worker caught something", e) + Log.w(TAG, "Multi-SIM worker caught something", e) Sentry.captureException(e) } finally { slotSemaphore.release() @@ -215,6 +222,7 @@ class MultiSimWorker(context: Context, params: WorkerParameters) : return uniqueSimInfos } + @SuppressLint("MissingPermission") @TargetApi(22) @Throws(Exception::class) private fun getSubscriptions( @@ -225,7 +233,7 @@ class MultiSimWorker(context: Context, params: WorkerParameters) : for (teleMgr in teleMgrInstances) { if (subInfos != null) { for (subinfo in subInfos) addValidReadySlots( - slotMgrList, + slotMgrList.toMutableList(), subinfo.simSlotIndex, subinfo.subscriptionId, teleMgr, @@ -267,9 +275,9 @@ class MultiSimWorker(context: Context, params: WorkerParameters) : val result = runMethodReflect(className, "getDefault", slotIdx?.let { arrayOf(it) }) if (result != null && !teleMgrList.contains(result)) { teleMgrList.add(result) - Log("Added Mgr using className: $className, method: getDefault, and param: $slotIdx") + Log.d("", "Added Mgr using className: $className, method: getDefault, and param: $slotIdx") if (Build.VERSION.SDK_INT < 22) addValidReadySlots( - slotMgrList, slotIdx, result, validClassNames + slotMgrList.toMutableList(), slotIdx, result, validClassNames ) } } @@ -283,9 +291,9 @@ class MultiSimWorker(context: Context, params: WorkerParameters) : val serv = applicationContext.getSystemService(serviceName) if (serv != null && !teleMgrList.contains(serv)) { teleMgrList.add(serv) - Log("Added Mgr using mContext.getSystemService('$serviceName')") + Log.d("", "Added Mgr using mContext.getSystemService('$serviceName')") if (Build.VERSION.SDK_INT < 22) addValidReadySlots( - slotMgrList, slotIdx, serv, validClassNames + slotMgrList.toMutableList(), slotIdx, serv, validClassNames ) } } @@ -321,7 +329,7 @@ class MultiSimWorker(context: Context, params: WorkerParameters) : private inner class SimStateReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { - if (intent != null) updateSimInfo() + updateSimInfo() } } @@ -351,28 +359,22 @@ class MultiSimWorker(context: Context, params: WorkerParameters) : } private fun printAllMethodsAndFields(className: String, paramsCount: Int) { - Log("====================================================================================") - Log("Methods of $className") try { - val MultiSimClass = Class.forName(className) - for (method in MultiSimClass.methods) { - Log(method.toGenericString()) + val multiSimClass = Class.forName(className) + for (method in multiSimClass.methods) { + Log.d("", method.toGenericString()) try { - if (method.parameterTypes.isEmpty()) Log( - runMethodReflect( - MultiSimClass, method.name, null - ) as String? - ) else if (method.parameterTypes.size == 1) Log( - " " + runMethodReflect( - MultiSimClass, method.name, arrayOf(0) - ) - ) + if (method.parameterTypes.isEmpty()) { + Log.d("", (runMethodReflect(multiSimClass, method.name, null) as String?).toString()) + } else if (method.parameterTypes.size == 1) { + Log.d(" ", runMethodReflect(multiSimClass, method.name, arrayOf(0)).toString()) + } } catch (e: Exception) { - Log("Failed. $e") + Log.e("Failed", e.localizedMessage.toString()) } } } catch (e: Exception) { - Log("Failed. $e") + Log.e("Failed", e.localizedMessage.toString()) } } diff --git a/multisim/src/main/java/com/hover/multisim/sim/SimInfo.kt b/multisim/src/main/java/com/hover/multisim/sim/SimInfo.kt index 5060074..9e3cc0e 100644 --- a/multisim/src/main/java/com/hover/multisim/sim/SimInfo.kt +++ b/multisim/src/main/java/com/hover/multisim/sim/SimInfo.kt @@ -6,12 +6,16 @@ import android.content.Context import android.telephony.SubscriptionInfo import android.telephony.SubscriptionManager import android.util.Log +import com.hover.multisim.db.dao.SimDao import io.sentry.Sentry import org.json.JSONArray import org.json.JSONException import java.util.* +import javax.inject.Inject -open class SimInfo { +open class SimInfo @Inject constructor( + val simDao: SimDao +) { /** * The slot that the SIM is in, starting from 0. Can be -1 if the SIM has been removed */ @@ -100,8 +104,6 @@ open class SimInfo { networkCountryIso = slotMgr.findNetworkCountryIso() networkType = setNetworkType(slotMgr.findNetworkType()) isRoaming = setNetworkRoaming(slotMgr.findNetworkRoaming()) - -// Log.i(TAG, "Created SIM representation using reflection: " + this.log()); } @TargetApi(22) @@ -147,18 +149,13 @@ open class SimInfo { } fun save(c: Context) { - SimDataSource(c).saveToDb(this) + simDao.insert(this) updateSubId(subscriptionId, c) } - // private void updateSlot(Context c) { - // Log.i(TAG, "Updating sim slot to: " + slotIdx); - // new SimDataSource(c).updateSlot(this, updatedInfo); - // updateSubId(updatedInfo.subscriptionId, c); - // } fun setSimRemoved(c: Context?) { Log.i(TAG, "Updating sim slot to: -1") - SimDataSource(c).remove(this) + simDao.delete(this) } @SuppressLint("ApplySharedPref") @@ -179,9 +176,9 @@ open class SimInfo { } fun isMncMatch(mncInt: Int): Boolean { - return imsi!!.length == 4 && Integer.valueOf(imsi!!.substring(3)) == mncInt || - imsi!!.length >= 5 && Integer.valueOf(imsi!!.substring(3, 5)) == mncInt || - imsi!!.length >= 6 && Integer.valueOf(imsi!!.substring(3, 6)) == mncInt + return imsi!!.length == 4 && Integer.valueOf(imsi!!.substring(3)) == mncInt || imsi!!.length >= 5 && Integer.valueOf( + imsi!!.substring(3, 5) + ) == mncInt || imsi!!.length >= 6 && Integer.valueOf(imsi!!.substring(3, 6)) == mncInt } fun getInterpretedHni(actionHniList: JSONArray): String? { @@ -195,11 +192,11 @@ open class SimInfo { } private fun setSimState(simState: Int): Int { - return simState ?: -1 + return simState } private fun setNetworkType(networkType: Int): Int { - return networkType ?: 0 + return networkType } private fun setNetworkRoaming(networkRoaming: Boolean?): Boolean { @@ -207,20 +204,7 @@ open class SimInfo { } fun log(): String { - return "slotIdx=[" + slotIdx + - "] subscriptionId=[" + subscriptionId + - "] imei=[" + imei + - "] imsi=[" + imsi + - "] simState=[" + simState + - "] simIccId=[" + iccId + - "] simHni=[" + oSReportedHni + - "] simOperatorName=[" + operatorName + - "] simCountryIso=[" + countryIso + - "] networkOperator=[" + networkOperator + - "] networkOperatorName=[" + networkOperatorName + - "] networkCountryIso=[" + networkCountryIso + - "] networkType=[" + networkType + - "] networkRoaming=[" + isRoaming + "]" + return "slotIdx=[$slotIdx] subscriptionId=[$subscriptionId] imei=[$imei] imsi=[$imsi] simState=[$simState] simIccId=[$iccId] simHni=[$oSReportedHni] simOperatorName=[$operatorName] simCountryIso=[$countryIso] networkOperator=[$networkOperator] networkOperatorName=[$networkOperatorName] networkCountryIso=[$networkCountryIso] networkType=[$networkType] networkRoaming=[$isRoaming]" } override fun toString(): String { diff --git a/multisim/src/main/java/com/hover/multisim/sim/SlotManager.kt b/multisim/src/main/java/com/hover/multisim/sim/SlotManager.kt index 30291bb..e58de98 100644 --- a/multisim/src/main/java/com/hover/multisim/sim/SlotManager.kt +++ b/multisim/src/main/java/com/hover/multisim/sim/SlotManager.kt @@ -22,8 +22,8 @@ private constructor( return getValue(methodName, subscriptionId as Int, teleMgr, teleClass) } - fun findSimState(): Int? { - return getValue("getSimState", slotIndex) as Int? + fun findSimState(): Int { + return getValue("getSimState", slotIndex) as Int } fun findIccId(): String? { @@ -58,8 +58,8 @@ private constructor( return getValue("getNetworkCountryIso", subscriptionId) as String? } - fun findNetworkType(): Int? { - return getValue("getNetworkType", subscriptionId) as Int? + fun findNetworkType(): Int{ + return getValue("getNetworkType", subscriptionId) as Int } fun findNetworkRoaming(): Boolean { From 9a2dde1bc75bdded3f49673700bbbfd06995de6d Mon Sep 17 00:00:00 2001 From: Juma Allan Date: Thu, 13 Oct 2022 00:28:36 +0300 Subject: [PATCH 17/26] re writting sim info --- .../java/com/hover/multisim/sim/SimInfo.kt | 263 ++---------------- 1 file changed, 21 insertions(+), 242 deletions(-) diff --git a/multisim/src/main/java/com/hover/multisim/sim/SimInfo.kt b/multisim/src/main/java/com/hover/multisim/sim/SimInfo.kt index 9e3cc0e..6821679 100644 --- a/multisim/src/main/java/com/hover/multisim/sim/SimInfo.kt +++ b/multisim/src/main/java/com/hover/multisim/sim/SimInfo.kt @@ -1,248 +1,27 @@ package com.hover.multisim.sim -import android.annotation.SuppressLint -import android.annotation.TargetApi -import android.content.Context -import android.telephony.SubscriptionInfo -import android.telephony.SubscriptionManager -import android.util.Log -import com.hover.multisim.db.dao.SimDao -import io.sentry.Sentry -import org.json.JSONArray -import org.json.JSONException -import java.util.* -import javax.inject.Inject - -open class SimInfo @Inject constructor( - val simDao: SimDao +data class SimInfo( + val slotIdx: Int, + val subscriptionId: Int, + val imei: String?, + val simState: Int, + val imsi: String, + val mcc: String, + val mnc: String?, + val iccId: String, + val hni: String?, + val operatorName: String?, + val countryIso: String?, + val networkRoaming: Boolean, + val networkOperator: String?, + val networkOperatorName: String?, + val networkCountryIso: String?, + val networkType: Int? ) { - /** - * The slot that the SIM is in, starting from 0. Can be -1 if the SIM has been removed - */ - @JvmField - var slotIdx = -1 - - /** - * The Subscription ID assigned by Android. The same SIM can be assigned a new ID if it is removed and re-inserted. Hover will forget the old ID and update a SIM to the newest - */ - var subscriptionId = -1 - var imei: String? = null - - /** - * The Hardware identifier for the SIM. Hover uses this to track a SIM regardless of whether it is removed or its slot changed - */ - var iccId: String? = null - protected set - - /** - * The The International Mobile Subscriber Identity used by the network to identify the SIM. The value reported here may be only the begining 5-6 digits or the whole thing. - * If you are trying to determine which network a SIM is for use this. The first 3 digits will always be the MCC and the following 2 or 3 will be the MNC which you can use to definitively identify which network this SIM is for - * See https://en.wikipedia.org/wiki/Mobile_country_code - */ - var imsi: String? = null - protected set - var mcc: String? = null - var mnc: String? = null - var simState = -1 - - /** - * The Home Network Identifier. This is the first 5-6 digits of the IMSI, however, we recomend against using this since some devices may not report it correctly. Use the first 5-6 digits of the imsi using getImsi() - * - * @see SimInfo.getImsi - */ - var oSReportedHni: String? = null - protected set - - /** - * The name of the operator which provisioned the SIM. May differ from SIM to SIM distributed by the same network provisioner - */ - var operatorName: String? = null - protected set - - /** - * The country ISO of the operator which provisioned the SIM - */ - var countryIso: String? = null - protected set - - /** - * The network of the operator which the SIM is connected to - */ - var networkOperator: String? = null - - /** - * The network name of the operator which the SIM is connected to - */ - var networkOperatorName: String? = null - - /** - * The country ISO of the operator which the SIM is connected to - */ - var networkCountryIso: String? = null - var networkType = 0 - - /** - * Whether the SIM is currently roaming. Not guaranteed to be accurate. - */ - // careful find networkTypeName because it will be different with networkType on same devices - var isRoaming = false - protected set - - private constructor(slotMgr: SlotManager) { - slotIdx = slotMgr.slotIndex - subscriptionId = slotMgr.subscriptionId - imei = slotMgr.imei - simState = setSimState(slotMgr.findSimState()) - iccId = setStandardIccId(slotMgr.findIccId()) - imsi = slotMgr.findImsi() - mcc = setMcc(imsi) - oSReportedHni = slotMgr.findOperator() - operatorName = slotMgr.findOperatorName() - countryIso = slotMgr.findCountryIso() - networkOperator = slotMgr.findNetworkOperator() - networkOperatorName = slotMgr.findNetworkOperatorName() - networkCountryIso = slotMgr.findNetworkCountryIso() - networkType = setNetworkType(slotMgr.findNetworkType()) - isRoaming = setNetworkRoaming(slotMgr.findNetworkRoaming()) - } - - @TargetApi(22) - constructor(subInfo: SubscriptionInfo, c: Context?) { - subscriptionId = subInfo.subscriptionId - slotIdx = subInfo.simSlotIndex - imsi = "" + subInfo.mcc + subInfo.mnc - oSReportedHni = "" + subInfo.mcc + subInfo.mnc - mcc = "" + subInfo.mcc - mnc = "" + subInfo.mnc - iccId = setStandardIccId(subInfo.iccId) - operatorName = subInfo.carrierName as String // Is this Network Operator or Sim Operator? - countryIso = subInfo.countryIso - isRoaming = SubscriptionManager.from(c).isNetworkRoaming(subscriptionId) - -// Can't load these until API 24 (Using TelephonyManager), but doesn't really matter: -// hnis, networkOperator, networkOperatorName, networkCountryIso, networkType -// These are not useful/inconsistent: -// Log.i(TAG, "SubInfo Display Name: " + si.getDisplayName()); -// Log.i(TAG, "SubInfo Number: " + si.getNumber()); - -// Log.i(TAG, "Created SIM representation using Subscription info: " + this.log()); - } - - fun isSameSim(simInfo: SimInfo?): Boolean { // FIXME: change so that if the SimInfo represents the same sim, the one with more/better info (and more accurate slotIdx?) is returned. It currently relies on order to do this, which is fragile - return simInfo?.iccId != null && iccId != null && iccId == simInfo.iccId - } - - private fun isSameSimInSameSlot(simInfo: SimInfo): Boolean { - return isSameSim(simInfo) && simInfo.slotIdx == slotIdx - } - - fun isNotContainedIn(simInfos: List?): Boolean { - if (simInfos == null) return true - for (simInfo in simInfos) if (isSameSim(simInfo)) return false - return true - } - - fun isNotContainedInOrHasMoved(simInfos: List?): Boolean { - if (simInfos == null) return true - for (simInfo in simInfos) if (isSameSimInSameSlot(simInfo)) return false - return true - } - - fun save(c: Context) { - simDao.insert(this) - updateSubId(subscriptionId, c) - } - - fun setSimRemoved(c: Context?) { - Log.i(TAG, "Updating sim slot to: -1") - simDao.delete(this) - } - - @SuppressLint("ApplySharedPref") - private fun updateSubId(subId: Int, c: Context) { - val editor = Utils.getSharedPrefs(c).edit() - editor.putInt(KEY + SimContract.COLUMN_SUB_ID.toString() + iccId, subId) - editor.commit() - } - - fun setStandardIccId(iccId: String?): String? { - var iccId = iccId - if (iccId != null) iccId = iccId.replace("[a-zA-Z]".toRegex(), "") - return iccId - } - - private fun setMcc(imsi: String?): String? { - return imsi?.substring(0, 3) - } fun isMncMatch(mncInt: Int): Boolean { - return imsi!!.length == 4 && Integer.valueOf(imsi!!.substring(3)) == mncInt || imsi!!.length >= 5 && Integer.valueOf( - imsi!!.substring(3, 5) - ) == mncInt || imsi!!.length >= 6 && Integer.valueOf(imsi!!.substring(3, 6)) == mncInt - } - - fun getInterpretedHni(actionHniList: JSONArray): String? { - for (h in 0 until actionHniList.length()) { - val mncInt = Integer.valueOf(actionHniList.optString(h).substring(3)) - if (isMncMatch(mncInt)) { - return imsi!!.substring(0, 3) + mncInt - } - } - return null - } - - private fun setSimState(simState: Int): Int { - return simState - } - - private fun setNetworkType(networkType: Int): Int { - return networkType - } - - private fun setNetworkRoaming(networkRoaming: Boolean?): Boolean { - return networkRoaming != null && networkRoaming - } - - fun log(): String { - return "slotIdx=[$slotIdx] subscriptionId=[$subscriptionId] imei=[$imei] imsi=[$imsi] simState=[$simState] simIccId=[$iccId] simHni=[$oSReportedHni] simOperatorName=[$operatorName] simCountryIso=[$countryIso] networkOperator=[$networkOperator] networkOperatorName=[$networkOperatorName] networkCountryIso=[$networkCountryIso] networkType=[$networkType] networkRoaming=[$isRoaming]" - } - - override fun toString(): String { - return operatorName + " " + (if (countryIso != null) countryIso!!.uppercase(Locale.getDefault()) else "") + " (SIM " + (slotIdx + 1) + ")" - } - - companion object { - private const val TAG = "SimInfo" - private const val KEY = "sim_info_" - fun getSubId(iccId: String, c: Context?): Int { - return Utils.getSharedPrefs(c) - .getInt(KEY + SimContract.COLUMN_SUB_ID.toString() + iccId, -1) - } - - fun loadPresentByHni(hniList: JSONArray, c: Context?): List { - val simInfos: MutableList = ArrayList() - for (h in 0 until hniList.length()) { - try { - simInfos.addAll( - SimDataSource(c).getPresent( - hniList.getString(h).substring(0, 3), hniList.getString(h).substring(3) - ) - ) - } catch (e: JSONException) { - Sentry.captureException(e) - } catch (e: NullPointerException) { - Sentry.captureException(e) - } - } - return simInfos - } - - fun loadBySlot(slotIdx: Int, c: Context?): SimInfo { - return SimDataSource(c).get(slotIdx) - } - - fun loadAll(c: Context?): List { - return SimDataSource(c).getAll() - } + return imsi.length == 4 && imsi.substring(3).toInt() == mncInt || + imsi.length >= 5 && imsi.substring(3, 5).toInt() == mncInt || + imsi.length >= 6 && imsi.substring(3, 6).toInt() == mncInt } -} \ No newline at end of file +} From 00fecc64b8ed4d527281ff076491dbcba4024ae1 Mon Sep 17 00:00:00 2001 From: Juma Allan Date: Thu, 13 Oct 2022 00:28:59 +0300 Subject: [PATCH 18/26] fixed slotmanager class --- .../com/hover/multisim/sim/SlotManager.kt | 244 +++++++++--------- 1 file changed, 128 insertions(+), 116 deletions(-) diff --git a/multisim/src/main/java/com/hover/multisim/sim/SlotManager.kt b/multisim/src/main/java/com/hover/multisim/sim/SlotManager.kt index e58de98..4b8a23d 100644 --- a/multisim/src/main/java/com/hover/multisim/sim/SlotManager.kt +++ b/multisim/src/main/java/com/hover/multisim/sim/SlotManager.kt @@ -1,187 +1,199 @@ package com.hover.multisim.sim import android.telephony.TelephonyManager -import android.util.Log import io.sentry.Sentry -internal class SlotManager // Log.i(TAG, "Creating slotMgr. SlotIdx: " + slotIdx + " Mgr: " + teleMgr + " Class + " + teleClass + " IMEI: " + imei + " ICCID: " + iccId); -private constructor( - val slotIndex: Int, - val subscriptionId: Int, - private val teleMgr: Any?, - private val teleClass: Class<*>?, +class SlotManager( + slotIdx: Int, + subscriptionId: Int, + teleMgr: Any?, + teleClass: Class<*>?, simState: Int, - val imei: String, - iccId: String + imei: String?, + iccId: String? ) { - fun createSimInfo(): SimInfo { - return SimInfo(this) - } + + private var slotIndex: Int? = slotIdx + private var subscriptionId: Int? = subscriptionId + private var imei: String? = imei + private var teleMgr: Any? = teleMgr + private var teleClass: Class<*>? = teleClass + + private val METHOD_SUFFIXES = arrayOf( + "", "Gemini", "Ext", "Ds", "ForSubscription", "ForPhone" + ) operator fun getValue(methodName: String, subscriptionId: Any): Any? { return getValue(methodName, subscriptionId as Int, teleMgr, teleClass) } - fun findSimState(): Int { - return getValue("getSimState", slotIndex) as Int + fun findSimState(): Int? { + return getValue("getSimState", slotIndex!!) as Int? } fun findIccId(): String? { - return getValue("getSimSerialNumber", subscriptionId) as String? + return getValue("getSimSerialNumber", subscriptionId!!) as String? } fun findImsi(): String? { - return getValue("getSubscriberId", subscriptionId) as String? + return getValue("getSubscriberId", subscriptionId!!) as String? } fun findOperator(): String? { - return getValue("getSimOperator", subscriptionId) as String? + return getValue("getSimOperator", subscriptionId!!) as String? } fun findOperatorName(): String? { - return getValue("getSimOperatorName", subscriptionId) as String? + return getValue("getSimOperatorName", subscriptionId!!) as String? } fun findCountryIso(): String? { - return getValue("getSimCountryIso", subscriptionId) as String? + return getValue("getSimCountryIso", subscriptionId!!) as String? } fun findNetworkOperator(): String? { - return getValue("getNetworkOperator", subscriptionId) as String? + return getValue("getNetworkOperator", subscriptionId!!) as String? } fun findNetworkOperatorName(): String? { - return getValue("getNetworkOperatorName", subscriptionId) as String? + return getValue("getNetworkOperatorName", subscriptionId!!) as String? } fun findNetworkCountryIso(): String? { - return getValue("getNetworkCountryIso", subscriptionId) as String? + return getValue("getNetworkCountryIso", subscriptionId!!) as String? } - fun findNetworkType(): Int{ - return getValue("getNetworkType", subscriptionId) as Int + fun findNetworkType(): Int? { + return getValue("getNetworkType", subscriptionId!!) as Int? } fun findNetworkRoaming(): Boolean { - return getValue("isNetworkRoaming", subscriptionId) as Boolean + return getValue("isNetworkRoaming", subscriptionId!!) as Boolean } - private fun isUnique(slotMgrList: List): Boolean { + private fun isUnique(slotMgrList: List): Boolean { for (mgr in slotMgrList) { try { - if (mgr != null && imei == mgr.imei && teleMgr === mgr.teleMgr && teleClass == mgr.teleClass) return false + if (imei == mgr.imei && teleMgr === mgr.teleMgr && teleClass == mgr.teleClass) return false } catch (e: NullPointerException) { - Log.w(TAG, "something was null that shouldn't be", e) Sentry.captureException(e) } } return true } - companion object { - const val TAG = "SlotManager" - private val METHOD_SUFFIXES = arrayOf( - "", "Gemini", "Ext", "Ds", "ForSubscription", "ForPhone" + fun addValidReadySlots( + slotMgrList: List, + slotIdx: Any?, + teleMgrInstance: Any?, + validClassNames: ArrayList? + ) { + if (slotIdx == null) for (i in 0 until MultiSimWorker.SLOT_COUNT - 1) addValidReadySlots( + slotMgrList, i, i, teleMgrInstance, validClassNames + ) else addValidReadySlots( + slotMgrList, slotIdx as Int, slotIdx, teleMgrInstance, validClassNames ) + } - fun addValidReadySlots( - slotMgrList: MutableList, - slotIdx: Any?, - teleMgrInstance: Any?, - validClassNames: ArrayList? - ) { - if (slotIdx == null) for (i in 0 until MultiSimWorker.SLOT_COUNT - 1) addValidReadySlots( - slotMgrList, i, i, teleMgrInstance, validClassNames - ) else addValidReadySlots( - slotMgrList, slotIdx as Int, slotIdx, teleMgrInstance, validClassNames - ) + fun addValidReadySlots( + slotMgrList: List, + slotIdx: Int, + subscriptionId: Int, + teleMgrInstance: Any?, + validClassNames: java.util.ArrayList? + ) { + if (validClassNames == null || validClassNames.size <= 0) { + return } - - @JvmStatic - fun addValidReadySlots( - slotMgrList: MutableList, - slotIdx: Int, - subscriptionId: Int, - teleMgrInstance: Any?, - validClassNames: ArrayList? - ) { - if (validClassNames == null || validClassNames.size <= 0) { - return - } - for (className in validClassNames) if (teleMgrInstance != null || className != null) { - val sm = findValidReadySlot(slotIdx, subscriptionId, teleMgrInstance, className) - if (sm != null && (slotMgrList.size == 0 || sm.isUnique(slotMgrList))) { - slotMgrList.add(sm) + for (className in validClassNames) if (teleMgrInstance != null || className != null) { + val sm: SlotManager? = findValidReadySlot(slotIdx, subscriptionId, teleMgrInstance, className) + if (sm != null) { + if (slotMgrList.isEmpty() || sm.isUnique(slotMgrList)) { + slotMgrList + sm } } } + } - private fun findValidReadySlot( - slotIdx: Int, subscriptionId: Int, teleMgr: Any?, className: String? - ): SlotManager? { - val teleClass = getTeleClass(teleMgr, className) - val simState = getSimState(slotIdx, teleMgr, teleClass) - val imei = getDeviceId(slotIdx, teleMgr, teleClass) - val iccId = getSimIccId(subscriptionId, teleMgr, teleClass) - return if (simState != null && simState == TelephonyManager.SIM_STATE_READY && imei != null && iccId != null) SlotManager( - slotIdx, subscriptionId, teleMgr, teleClass, simState, imei, iccId - ) else null - } + private fun findValidReadySlot( + slotIdx: Int, + subscriptionId: Int, + teleMgr: Any?, + className: String? + ): SlotManager? { + val teleClass: Class<*>? = getTeleClass(teleMgr, className) + val simState: Int? = getSimState(slotIdx, teleMgr, teleClass) + val imei: String? = getDeviceId(slotIdx, teleMgr, teleClass) + val iccId: String? = getSimIccId(subscriptionId, teleMgr, teleClass) + return if (simState == TelephonyManager.SIM_STATE_READY) SlotManager( + slotIdx, subscriptionId, teleMgr, teleClass, simState, imei, iccId + ) else null + } - private fun getSimState(slotIndex: Int, teleMgr: Any?, teleClass: Class<*>?): Int? { - return try { - getValue("getSimState", slotIndex, teleMgr, teleClass) as Int? - } catch (e: Exception) { - Log.d(TAG, "Couldn't get sim state") - TelephonyManager.SIM_STATE_UNKNOWN - } + private fun getSimState(slotIndex: Int, teleMgr: Any?, teleClass: Class<*>?): Int? { + return try { + getValue("getSimState", slotIndex, teleMgr, teleClass) as Int? + } catch (e: Exception) { + TelephonyManager.SIM_STATE_UNKNOWN } + } - private fun getDeviceId(slotIndex: Int, teleMgr: Any?, teleClass: Class<*>?): String? { - var imei = getValue("getDeviceId", slotIndex, teleMgr, teleClass) as String? - if (imei == null) imei = getValue("getImei", slotIndex, teleMgr, teleClass) as String? - return imei - } + private fun getDeviceId(slotIndex: Int, teleMgr: Any?, teleClass: Class<*>?): String? { + var imei = getValue("getDeviceId", slotIndex, teleMgr, teleClass) as String? + if (imei == null) imei = getValue("getImei", slotIndex, teleMgr, teleClass) as String? + return imei + } - private fun getSimIccId(subscriptionId: Int, teleMgr: Any?, teleClass: Class<*>?): String? { - return getValue("getSimSerialNumber", subscriptionId, teleMgr, teleClass) as String? - } + private fun getSimIccId(subscriptionId: Int, teleMgr: Any?, teleClass: Class<*>?): String? { + return getValue( + "getSimSerialNumber", subscriptionId, teleMgr, teleClass + ) as String? + } - fun getSimImsi(subscriptionId: Int, teleMgr: Any?, teleClass: Class<*>?): String? { - return getValue("getSubscriberId", subscriptionId, teleMgr, teleClass) as String? - } + fun getSimImsi(subscriptionId: Int, teleMgr: Any, teleClass: Class<*>): String? { + return getValue( + "getSubscriberId", subscriptionId, teleMgr, teleClass + ) as String? + } - private fun getTeleClass(teleMgr: Any?, className: String?): Class<*>? { - try { - if (className != null) return Class.forName(className) - } catch (ignored: ClassNotFoundException) { - } - return teleMgr?.javaClass + private fun getTeleClass(teleMgr: Any?, className: String?): Class<*>? { + try { + if (className != null) return Class.forName(className) + } catch (ignored: ClassNotFoundException) { } + return teleMgr?.javaClass + } - private fun getValue( - methodName: String, slotIndex: Int, teleMgr: Any?, teleClass: Class<*>? - ): Any? { - var result = spamTeleMgr(methodName, teleMgr, teleClass, slotIndex) - if (result == null) result = spamTeleMgr(methodName, teleMgr, teleClass, null) - return result - } + private fun getValue( + methodName: String, + slotIndex: Int, + teleMgr: Any?, + teleClass: Class<*>? + ): Any? { + var result: Any? = spamTeleMgr(methodName, teleMgr, teleClass, slotIndex) + if (result == null) result = spamTeleMgr(methodName, teleMgr, teleClass, null) + return result + } - private fun spamTeleMgr( - methodName: String?, teleMgr: Any?, teleClass: Class<*>?, subscriptionId: Any? - ): Any? { - if (methodName == null || methodName.isEmpty()) return null - var result: Any? - for (methodSuffix in METHOD_SUFFIXES) { - result = MultiSimWorker.runMethodReflect(teleMgr, - teleClass, - methodName + methodSuffix, - subscriptionId?.let { arrayOf(it) }) - if (result != null) { - return result - } + private fun spamTeleMgr( + methodName: String?, + teleMgr: Any?, + teleClass: Class<*>?, + subscriptionId: Any? + ): Any? { + if (methodName == null || methodName.isEmpty()) return null + var result: Any? + for (methodSuffix in METHOD_SUFFIXES) { + result = MultiSimWorker.runMethodReflect( + teleMgr, + teleClass, + methodName + methodSuffix, + subscriptionId?.let { arrayOf(it) } + ) + if (result != null) { + return result } - return null } + return null } -} \ No newline at end of file +} From 6e651a8b45c5ea4b39add2279e797f2f4a90cf36 Mon Sep 17 00:00:00 2001 From: Juma Allan Date: Thu, 13 Oct 2022 00:29:29 +0300 Subject: [PATCH 19/26] HSDKSims to SimInfo mapper --- .../main/java/com/hover/multisim/sim/Utils.kt | 2 +- .../java/com/hover/multisim/sim/mapper.kt | 42 +++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 multisim/src/main/java/com/hover/multisim/sim/mapper.kt diff --git a/multisim/src/main/java/com/hover/multisim/sim/Utils.kt b/multisim/src/main/java/com/hover/multisim/sim/Utils.kt index 7b19337..e385ebf 100644 --- a/multisim/src/main/java/com/hover/multisim/sim/Utils.kt +++ b/multisim/src/main/java/com/hover/multisim/sim/Utils.kt @@ -33,4 +33,4 @@ object Utils { getPackage(context) + SHARED_PREFS, Context.MODE_PRIVATE ) } -} \ No newline at end of file +} diff --git a/multisim/src/main/java/com/hover/multisim/sim/mapper.kt b/multisim/src/main/java/com/hover/multisim/sim/mapper.kt new file mode 100644 index 0000000..01c20f6 --- /dev/null +++ b/multisim/src/main/java/com/hover/multisim/sim/mapper.kt @@ -0,0 +1,42 @@ +package com.hover.multisim.sim + +import com.hover.multisim.db.model.HSDKSims + +fun HSDKSims.toSimInfo() = SimInfo( + slotIdx = this.slot_idx, + subscriptionId = this.sub_id, + imei = this.imei, + simState = this.state, + imsi = this.imsi, + mcc = this.mcc, + mnc = this.mnc, + iccId = this.iccid, + hni = this.operator, + operatorName = this.operator_name, + countryIso = this.country_iso, + networkRoaming = this.is_roaming, + networkOperator = this.network_code, + networkOperatorName = this.network_name, + networkCountryIso = this.network_country, + networkType = this.network_type +) + +fun SimInfo.toSimInfo() = HSDKSims( + id = 0, + slot_idx = this.slotIdx, + sub_id = this.subscriptionId, + imei = this.imei, + state = this.simState, + imsi = this.imsi, + mcc = this.mcc, + mnc = this.mnc, + iccid = this.iccId, + operator = this.hni, + operator_name = this.operatorName, + country_iso = this.countryIso, + is_roaming = this.networkRoaming, + network_code = this.networkOperator, + network_name = this.networkOperatorName, + network_country = this.networkCountryIso, + network_type = this.networkType +) From fe99afe0db51c0314ae83b749e67150b4994226c Mon Sep 17 00:00:00 2001 From: Juma Allan Date: Thu, 13 Oct 2022 01:56:06 +0300 Subject: [PATCH 20/26] migrate worker to kotlin --- .../com/hover/multisim/sim/MultiSimWorker.kt | 416 ++++++++---------- .../com/hover/multisim/sim/SlotManager.kt | 2 +- 2 files changed, 187 insertions(+), 231 deletions(-) diff --git a/multisim/src/main/java/com/hover/multisim/sim/MultiSimWorker.kt b/multisim/src/main/java/com/hover/multisim/sim/MultiSimWorker.kt index 6804330..d50d5e8 100644 --- a/multisim/src/main/java/com/hover/multisim/sim/MultiSimWorker.kt +++ b/multisim/src/main/java/com/hover/multisim/sim/MultiSimWorker.kt @@ -8,7 +8,11 @@ import android.content.Intent import android.content.IntentFilter import android.os.Build import android.os.SystemClock -import android.telephony.* +import android.telephony.PhoneStateListener +import android.telephony.ServiceState +import android.telephony.SubscriptionInfo +import android.telephony.SubscriptionManager +import android.telephony.TelephonyManager import android.util.Log import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.work.ListenableWorker @@ -17,30 +21,29 @@ import androidx.work.PeriodicWorkRequest import androidx.work.WorkerParameters import androidx.work.impl.utils.futures.SettableFuture import com.google.common.util.concurrent.ListenableFuture -import com.hover.multisim.db.dao.SimDao -import com.hover.multisim.sim.SlotManager.Companion.addValidReadySlots -import com.hover.multisim.sim.Utils.getPackage -import com.hover.multisim.sim.Utils.hasPhonePerm +import com.hover.multisim.sim.MultiSimWorkerfff.Companion.action import io.sentry.Sentry -import java.util.* import java.util.concurrent.Semaphore import java.util.concurrent.TimeUnit -import javax.inject.Inject -class MultiSimWorker @Inject constructor( +class MultiSimWorker( context: Context, params: WorkerParameters, - private val simDao: SimDao -) : - ListenableWorker(context, params) { +) : ListenableWorker(context, params) { + + private val NEW_SIM_INFO = "NEW_SIM_INFO_ACTION" + val SLOT_COUNT = 3 // Need to check 0, 1, and 2. Some phones index from 1. private lateinit var workerFuture: SettableFuture private var result: Result? = null + private val slotSemaphore = Semaphore(1, true) private val simSemaphore = Semaphore(1, true) + private var simStateReceiver: SimStateReceiver? = null private var simStateListener: SimStateListener? = null - private var validClassNames: ArrayList? = null + private var validClassNames: ArrayList? = null + private val POSS_CLASS_NAMES = arrayOf( null, "android.telephony.TelephonyManager", @@ -51,11 +54,19 @@ class MultiSimWorker @Inject constructor( "com.android.internal.telephony.PhoneFactory" ) + fun makeToil(): PeriodicWorkRequest { + return PeriodicWorkRequest.Builder(MultiSimWorker::class.java, 15, TimeUnit.MINUTES).build() + } + + fun makeWork(): OneTimeWorkRequest { + return OneTimeWorkRequest.Builder(MultiSimWorker::class.java).build() + } + @SuppressLint("RestrictedApi") override fun startWork(): ListenableFuture { - android.util.Log.v(TAG, "Starting new Multi SIM worker") + Log.v(MultiSimWorkerfff.TAG, "Starting new Multi SIM worker") workerFuture = SettableFuture.create() - if (hasPhonePerm(applicationContext)) { + if (Utils.hasPhonePerm(applicationContext)) { startListeners() } else { workerFuture.set(Result.failure()) @@ -67,17 +78,15 @@ class MultiSimWorker @Inject constructor( private fun startListeners() { try { registerSimStateReceiver() - if (simStateListener == null) { - simStateListener = SimStateListener() - } + if (simStateListener == null) simStateListener = SimStateListener() // TelephonyManager.listen() must take place on the main thread - (applicationContext.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager).listen( - simStateListener, - PhoneStateListener.LISTEN_SERVICE_STATE or PhoneStateListener.LISTEN_SIGNAL_STRENGTHS - ) - } catch (e: Exception) { - android.util.Log.d(TAG, "Failed to start SIM listeners, setting retry", e) - workerFuture.set(Result.retry()) + (applicationContext.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager) + .listen( + simStateListener, + PhoneStateListener.LISTEN_SERVICE_STATE or PhoneStateListener.LISTEN_SIGNAL_STRENGTHS + ) + } catch (e: java.lang.Exception) { + workerFuture!!.set(Result.retry()) } } @@ -87,112 +96,96 @@ class MultiSimWorker @Inject constructor( backgroundExecutor.execute { result = try { simSemaphore.acquire() - if (hasPhonePerm(applicationContext)) { - android.util.Log.v(TAG, "reviewing sim info") - val oldList: List? = saved + if (Utils.hasPhonePerm(applicationContext)) { + val oldList: List? = getSaved() val newList = findUniqueSimInfo() - run { + if (newList != null) { compareNewAndOld(newList, oldList) Result.success() - } + } else Result.failure() } else Result.failure() - } catch (e: Exception) { - Log.w(TAG, "threw while attempting to update sim list", e) + } catch (e: java.lang.Exception) { Sentry.captureException(e) Result.failure() } finally { simSemaphore.release() // Give the listeners a chance to receive a few events - sometimes the first trigger isn't the needed info. 5s is long but this is a background thread anyway and the info has already been updated in the DB SystemClock.sleep(5000) - if (!workerFuture.isDone) { - android.util.Log.v(TAG, "Finishing Multi SIM worker") + if (!workerFuture!!.isDone) { workerFuture.set(result) } } } } - private fun compareNewAndOld(newList: List, oldList: List?) { + private fun compareNewAndOld(newList: List, oldList: List?) { if (oldList == null || oldList.size != newList.size) { - android.util.Log.v( - TAG, - "no old list or sizes differ. Old: " + (oldList?.size - ?: "null") + ", new: " + newList.size - ) onSimInfoUpdate(newList) } else { - for (i in newList.indices) if (newList[i]!!.isNotContainedInOrHasMoved(oldList)) { - android.util.Log.v(TAG, "some sim moved") + for (i in newList.indices) if (newList[i].isNotContainedInOrHasMoved(oldList)) { onSimInfoUpdate(newList) break } } } - private val saved: ArrayList? - get() { - var oldList: ArrayList? = null - for (i in 0 until SLOT_COUNT) { - val si: SimInfo = simDao.get(i) - if (oldList == null) { - oldList = ArrayList() - oldList.add(si) - } - } - android.util.Log.v(TAG, "Loaded old list from db. Size: " + (oldList?.size ?: "null")) - return oldList + private fun getSaved(): java.util.ArrayList? { + var oldList: java.util.ArrayList? = null + for (i in 0 until SLOT_COUNT) { + val si: SimInfo = SimDataSource(applicationContext).get(i) + if (oldList == null) oldList = java.util.ArrayList() + oldList.add(si) } + return oldList + } - private fun onSimInfoUpdate(newList: List) { + private fun onSimInfoUpdate(newList: List) { updateDb(newList) - android.util.Log.v(TAG, "Saved. Firing broadcast") LocalBroadcastManager.getInstance(applicationContext).sendBroadcast( Intent( - action( + MultiSimWorker.action( applicationContext ) ) ) } - private fun updateDb(newList: List) { + private fun updateDb(newList: List) { for (si in newList) { - Log.i(TAG, "Saving SIM in slot " + si!!.slotIdx + ": " + si.toString()) si.save(applicationContext) } - val dbInfos: List = simDao.getAll() + val dbInfos: List = SimDataSource(applicationContext).getAll() for (dbSi in dbInfos) { if (!findPhysicalSim(newList, dbSi)) { - Log.i(TAG, "Couldn't find SIM: $dbSi, removing") dbSi.setSimRemoved(applicationContext) } } } - private fun findPhysicalSim(newList: List, savedSim: SimInfo): Boolean { + private fun findPhysicalSim(newList: List, savedSim: SimInfo): Boolean { for (si in newList) { - if (si!!.isSameSim(savedSim)) { - return true - } + if (si.isSameSim(savedSim)) return true } return false } @Synchronized - private fun findUniqueSimInfo(): List { - var newList: List = ArrayList() + @SuppressWarnings("MissingPermission") + private fun findUniqueSimInfo(): List? { + var newList: List = java.util.ArrayList() try { slotSemaphore.acquire() - val slotMgrList: List = ArrayList() + val slotMgrList: List = java.util.ArrayList() val teleMgrInstances = listTeleMgrs(slotMgrList) - var subInfos: List? = ArrayList() + var subInfos: List? = java.util.ArrayList() if (Build.VERSION.SDK_INT >= 22) subInfos = getSubscriptions(teleMgrInstances, slotMgrList) - newList = if (subInfos != null && subInfos.isNotEmpty()) createUniqueSimInfoList( - subInfos, slotMgrList - ) else createUniqueSimInfoList(slotMgrList) - } catch (e: Exception) { - Log.w(TAG, "Multi-SIM worker caught something", e) + newList = + if (subInfos != null && subInfos.isNotEmpty()) createUniqueSimInfoList( + subInfos, + slotMgrList + ) else createUniqueSimInfoList(slotMgrList) + } catch (e: java.lang.Exception) { Sentry.captureException(e) } finally { slotSemaphore.release() @@ -201,39 +194,42 @@ class MultiSimWorker @Inject constructor( } private fun createUniqueSimInfoList( - subInfos: List, slotMgrList: List - ): List { + subInfos: List, + slotMgrList: List + ): List { val newList = createUniqueSimInfoList(slotMgrList) for (subInfo in subInfos) newList.add(SimInfo(subInfo, applicationContext)) return removeDuplicates(newList) } - private fun createUniqueSimInfoList(slotMgrList: List): MutableList { - val newList: MutableList = ArrayList() - for (sm in slotMgrList) newList.add(sm!!.createSimInfo()) + private fun createUniqueSimInfoList(slotMgrList: List): MutableList { + val newList: MutableList = java.util.ArrayList() + for (sm in slotMgrList) newList.add(sm.createSimInfo()) return removeDuplicates(newList) } - private fun removeDuplicates(simInfos: List): MutableList { - val uniqueSimInfos: MutableList = ArrayList() - for (simInfo in simInfos) if (simInfo!!.isNotContainedIn(uniqueSimInfos)) uniqueSimInfos.add( + private fun removeDuplicates(simInfos: List): MutableList { + val uniqueSimInfos: MutableList = java.util.ArrayList() + for (simInfo in simInfos) if (simInfo.isNotContainedIn(uniqueSimInfos)) uniqueSimInfos.add( simInfo ) return uniqueSimInfos } - @SuppressLint("MissingPermission") @TargetApi(22) - @Throws(Exception::class) + @SuppressWarnings("MissingPermission") private fun getSubscriptions( - teleMgrInstances: List?, slotMgrList: List + teleMgrInstances: List?, + slotMgrList: List ): List? { - val subInfos = SubscriptionManager.from(applicationContext).activeSubscriptionInfoList + val subInfos = SubscriptionManager.from( + applicationContext + ).activeSubscriptionInfoList if (teleMgrInstances != null) { for (teleMgr in teleMgrInstances) { if (subInfos != null) { - for (subinfo in subInfos) addValidReadySlots( - slotMgrList.toMutableList(), + for (subinfo in subInfos) SlotManager.addValidReadySlots( + slotMgrList, subinfo.simSlotIndex, subinfo.subscriptionId, teleMgr, @@ -245,22 +241,30 @@ class MultiSimWorker @Inject constructor( return subInfos } - private fun listTeleMgrs(slotMgrList: List): List { - if (validClassNames == null || validClassNames!!.isEmpty()) validClassNames = ArrayList( - listOf(*POSS_CLASS_NAMES) - ) - val teleMgrList: MutableList = ArrayList() + private fun listTeleMgrs(slotMgrList: List): List { + if (validClassNames == null || validClassNames!!.isEmpty()) validClassNames = + java.util.ArrayList( + listOf(*POSS_CLASS_NAMES) + ) + val teleMgrList: MutableList = java.util.ArrayList() + for (className in POSS_CLASS_NAMES) { if (className == null) continue addMgrFromReflection(className, teleMgrList, null, slotMgrList) for (i in 0 until SLOT_COUNT) addMgrFromReflection( - className, teleMgrList, i, slotMgrList + className, + teleMgrList, + i, + slotMgrList ) } addMgrFromSystemService(Context.TELEPHONY_SERVICE, teleMgrList, null, slotMgrList) addMgrFromSystemService("phone_msim", teleMgrList, null, slotMgrList) for (j in 0 until SLOT_COUNT) addMgrFromSystemService( - "phone$j", teleMgrList, j, slotMgrList + "phone$j", + teleMgrList, + j, + slotMgrList ) teleMgrList.add(null) return teleMgrList @@ -268,38 +272,44 @@ class MultiSimWorker @Inject constructor( private fun addMgrFromReflection( className: String, - teleMgrList: MutableList, + teleMgrList: MutableList, slotIdx: Any?, - slotMgrList: List + slotMgrList: List ) { val result = runMethodReflect(className, "getDefault", slotIdx?.let { arrayOf(it) }) if (result != null && !teleMgrList.contains(result)) { teleMgrList.add(result) - Log.d("", "Added Mgr using className: $className, method: getDefault, and param: $slotIdx") - if (Build.VERSION.SDK_INT < 22) addValidReadySlots( - slotMgrList.toMutableList(), slotIdx, result, validClassNames + if (Build.VERSION.SDK_INT < 22) SlotManager.addValidReadySlots( + slotMgrList, + slotIdx, + result, + validClassNames ) } } private fun addMgrFromSystemService( serviceName: String, - teleMgrList: MutableList, - slotIdx: Any?, - slotMgrList: List + teleMgrList: MutableList, + slotIdx: Any, + slotMgrList: List ) { val serv = applicationContext.getSystemService(serviceName) if (serv != null && !teleMgrList.contains(serv)) { teleMgrList.add(serv) - Log.d("", "Added Mgr using mContext.getSystemService('$serviceName')") - if (Build.VERSION.SDK_INT < 22) addValidReadySlots( - slotMgrList.toMutableList(), slotIdx, serv, validClassNames + if (Build.VERSION.SDK_INT < 22) SlotManager.addValidReadySlots( + slotMgrList, + slotIdx, + serv, + validClassNames ) } } private fun runMethodReflect( - className: String, methodName: String, methodParams: Array? + className: String, + methodName: String, + methodParams: Array ): Any? { try { return runMethodReflect(null, Class.forName(className), methodName, methodParams) @@ -309,6 +319,68 @@ class MultiSimWorker @Inject constructor( return null } + private fun runMethodReflect( + actualInstance: Any, + methodName: String, + methodParams: Array + ): Any? { + return runMethodReflect(actualInstance, actualInstance.javaClass, methodName, methodParams) + } + + fun runMethodReflect( + actualInstance: Any?, + classInstance: Class<*>, + methodName: String, + methodParams: Array + ): Any? { + var result: Any? = null + try { + val method = classInstance.getDeclaredMethod( + methodName, *getClassParams(methodParams) + ) + val accessible = method.isAccessible + method.isAccessible = true + result = method.invoke(actualInstance ?: classInstance, *methodParams) + method.isAccessible = accessible + } catch (ignored: java.lang.Exception) { + } + return result + } + + private fun runFieldReflect(className: String, field: String): Any? { + var result: Any? = null + try { + val classInstance = Class.forName(className) + val fieldReflect = classInstance.getField(field) + val accessible = fieldReflect.isAccessible + fieldReflect.isAccessible = true + result = fieldReflect[null]?.toString() + fieldReflect.isAccessible = accessible + } catch (ignored: java.lang.Exception) { + } + return result + } + + private fun getClassParams(methodParams: Array?): Array?>? { + var classesParams: Array?>? = null + if (methodParams != null) { + classesParams = arrayOfNulls?>(methodParams.size) + for (i in methodParams.indices) { + if (methodParams[i] is Int) classesParams[i] = + Int::class.javaPrimitiveType // logString += methodParams[i] + ","; + else if (methodParams[i] is String) classesParams[i] = + String::class.java // logString += "\"" + methodParams[i] + "\","; + else if (methodParams[i] is Long) classesParams[i] = + Long::class.javaPrimitiveType // logString += methodParams[i] + ","; + else if (methodParams[i] is Boolean) classesParams[i] = + Boolean::class.javaPrimitiveType // logString += methodParams[i] + ","; + else classesParams[i] = + methodParams[i].javaClass // logString += "["+methodParams[i]+"]" + ","; + } + } + return classesParams + } + private fun registerSimStateReceiver() { if (simStateReceiver == null) { simStateReceiver = SimStateReceiver() @@ -321,24 +393,27 @@ class MultiSimWorker @Inject constructor( } } - private inner class SimStateListener : PhoneStateListener() { + private class SimStateListener : PhoneStateListener() { override fun onServiceStateChanged(serviceState: ServiceState) { updateSimInfo() } } - private inner class SimStateReceiver : BroadcastReceiver() { + private class SimStateReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { updateSimInfo() } } + fun action(c: Context?): String { + return Utils.getPackage(c!!) + "." + NEW_SIM_INFO + } + override fun onStopped() { super.onStopped() try { - (applicationContext.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager).listen( - simStateListener, PhoneStateListener.LISTEN_NONE - ) + (applicationContext.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager) + .listen(simStateListener, PhoneStateListener.LISTEN_NONE) simStateListener = null if (simStateReceiver != null) applicationContext.unregisterReceiver(simStateReceiver) simStateReceiver = null @@ -346,123 +421,4 @@ class MultiSimWorker @Inject constructor( } } - private fun goFish() { - printAllMethodsAndFields("android.telephony.TelephonyManager", -1) // all methods - printAllMethodsAndFields("android.telephony.MultiSimTelephonyService", -1) // all methods - printAllMethodsAndFields("android.telephony.MSimTelephonyManager", -1) // all methods - printAllMethodsAndFields("com.mediatek.telephony.TelephonyManager", -1) // all methods - printAllMethodsAndFields("com.mediatek.telephony.TelephonyManagerEx", -1) // all methods - printAllMethodsAndFields("com.android.internal.telephony.ITelephony", -1) // all methods - printAllMethodsAndFields( - "com.android.internal.telephony.ITelephony\$Stub\$Proxy", -1 - ) // all methods - } - - private fun printAllMethodsAndFields(className: String, paramsCount: Int) { - try { - val multiSimClass = Class.forName(className) - for (method in multiSimClass.methods) { - Log.d("", method.toGenericString()) - try { - if (method.parameterTypes.isEmpty()) { - Log.d("", (runMethodReflect(multiSimClass, method.name, null) as String?).toString()) - } else if (method.parameterTypes.size == 1) { - Log.d(" ", runMethodReflect(multiSimClass, method.name, arrayOf(0)).toString()) - } - } catch (e: Exception) { - Log.e("Failed", e.localizedMessage.toString()) - } - } - } catch (e: Exception) { - Log.e("Failed", e.localizedMessage.toString()) - } - } - - companion object { - const val TAG = "MultiSimTeleMgr" - private const val NEW_SIM_INFO = "NEW_SIM_INFO_ACTION" - const val SLOT_COUNT = 3 // Need to check 0, 1, and 2. Some phones index from 1. - fun makeToil(): PeriodicWorkRequest { - return PeriodicWorkRequest.Builder(MultiSimWorker::class.java, 15, TimeUnit.MINUTES) - .build() - } - - fun makeWork(): OneTimeWorkRequest { - return OneTimeWorkRequest.Builder(MultiSimWorker::class.java).build() - } - - private fun runMethodReflect( - actualInstance: Any, methodName: String, methodParams: Array? - ): Any? { - return runMethodReflect( - actualInstance, actualInstance.javaClass, methodName, methodParams - ) - } - - fun runMethodReflect( - actualInstance: Any?, - classInstance: Class<*>, - methodName: String?, - methodParams: Array? - ): Any? { - var result: Any? = null - try { - val method = methodName?.let { - classInstance.getDeclaredMethod( - it, *getClassParams(methodParams) - ) - } - val accessible = method?.isAccessible - if (method != null) { - method.isAccessible = true - } - if (method != null) { - result = method.invoke(actualInstance ?: classInstance, *methodParams) - } - if (accessible != null) { - method.isAccessible = accessible - } - } catch (ignored: Exception) { /* Log("Method not found: " + ignored); */ - } - return result - } - - private fun runFieldReflect(className: String, field: String): Any? { - var result: Any? = null - try { - val classInstance = Class.forName(className) - val fieldReflect = classInstance.getField(field) - val accessible = fieldReflect.isAccessible - fieldReflect.isAccessible = true - result = fieldReflect[null]?.toString() - fieldReflect.isAccessible = accessible - } catch (ignored: Exception) { - } - return result - } - - private fun getClassParams(methodParams: Array?): Array?>? { - var classesParams: Array?>? = null - if (methodParams != null) { - classesParams = arrayOfNulls?>(methodParams.size) - for (i in methodParams.indices) { - if (methodParams[i] is Int) classesParams[i] = - Int::class.javaPrimitiveType // logString += methodParams[i] + ","; - else if (methodParams[i] is String) classesParams[i] = - String::class.java // logString += "\"" + methodParams[i] + "\","; - else if (methodParams[i] is Long) classesParams[i] = - Long::class.javaPrimitiveType // logString += methodParams[i] + ","; - else if (methodParams[i] is Boolean) classesParams[i] = - Boolean::class.javaPrimitiveType // logString += methodParams[i] + ","; - else classesParams[i] = - methodParams[i].javaClass // logString += "["+methodParams[i]+"]" + ","; - } - } - return classesParams - } - - fun action(c: Context?): String { - return getPackage(c!!) + "." + NEW_SIM_INFO - } - } } \ No newline at end of file diff --git a/multisim/src/main/java/com/hover/multisim/sim/SlotManager.kt b/multisim/src/main/java/com/hover/multisim/sim/SlotManager.kt index 4b8a23d..3a79e00 100644 --- a/multisim/src/main/java/com/hover/multisim/sim/SlotManager.kt +++ b/multisim/src/main/java/com/hover/multisim/sim/SlotManager.kt @@ -184,7 +184,7 @@ class SlotManager( if (methodName == null || methodName.isEmpty()) return null var result: Any? for (methodSuffix in METHOD_SUFFIXES) { - result = MultiSimWorker.runMethodReflect( + result = MultiSimWorkerfff.runMethodReflect( teleMgr, teleClass, methodName + methodSuffix, From 99e1490f689a14ef6df201bc2e2b4ec82a3b89c3 Mon Sep 17 00:00:00 2001 From: Juma Allan Date: Thu, 13 Oct 2022 02:00:31 +0300 Subject: [PATCH 21/26] housekeeping - need to fix database --- .../java/com/hover/multisim/sim/MultiSimWorker.kt | 12 +++--------- .../main/java/com/hover/multisim/sim/SlotManager.kt | 4 ++-- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/multisim/src/main/java/com/hover/multisim/sim/MultiSimWorker.kt b/multisim/src/main/java/com/hover/multisim/sim/MultiSimWorker.kt index d50d5e8..865f375 100644 --- a/multisim/src/main/java/com/hover/multisim/sim/MultiSimWorker.kt +++ b/multisim/src/main/java/com/hover/multisim/sim/MultiSimWorker.kt @@ -21,7 +21,6 @@ import androidx.work.PeriodicWorkRequest import androidx.work.WorkerParameters import androidx.work.impl.utils.futures.SettableFuture import com.google.common.util.concurrent.ListenableFuture -import com.hover.multisim.sim.MultiSimWorkerfff.Companion.action import io.sentry.Sentry import java.util.concurrent.Semaphore import java.util.concurrent.TimeUnit @@ -64,7 +63,6 @@ class MultiSimWorker( @SuppressLint("RestrictedApi") override fun startWork(): ListenableFuture { - Log.v(MultiSimWorkerfff.TAG, "Starting new Multi SIM worker") workerFuture = SettableFuture.create() if (Utils.hasPhonePerm(applicationContext)) { startListeners() @@ -86,7 +84,7 @@ class MultiSimWorker( PhoneStateListener.LISTEN_SERVICE_STATE or PhoneStateListener.LISTEN_SIGNAL_STRENGTHS ) } catch (e: java.lang.Exception) { - workerFuture!!.set(Result.retry()) + workerFuture.set(Result.retry()) } } @@ -111,7 +109,7 @@ class MultiSimWorker( simSemaphore.release() // Give the listeners a chance to receive a few events - sometimes the first trigger isn't the needed info. 5s is long but this is a background thread anyway and the info has already been updated in the DB SystemClock.sleep(5000) - if (!workerFuture!!.isDone) { + if (!workerFuture.isDone) { workerFuture.set(result) } } @@ -327,7 +325,7 @@ class MultiSimWorker( return runMethodReflect(actualInstance, actualInstance.javaClass, methodName, methodParams) } - fun runMethodReflect( + private fun runMethodReflect( actualInstance: Any?, classInstance: Class<*>, methodName: String, @@ -405,10 +403,6 @@ class MultiSimWorker( } } - fun action(c: Context?): String { - return Utils.getPackage(c!!) + "." + NEW_SIM_INFO - } - override fun onStopped() { super.onStopped() try { diff --git a/multisim/src/main/java/com/hover/multisim/sim/SlotManager.kt b/multisim/src/main/java/com/hover/multisim/sim/SlotManager.kt index 3a79e00..9bc8290 100644 --- a/multisim/src/main/java/com/hover/multisim/sim/SlotManager.kt +++ b/multisim/src/main/java/com/hover/multisim/sim/SlotManager.kt @@ -95,7 +95,7 @@ class SlotManager( ) } - fun addValidReadySlots( + private fun addValidReadySlots( slotMgrList: List, slotIdx: Int, subscriptionId: Int, @@ -184,7 +184,7 @@ class SlotManager( if (methodName == null || methodName.isEmpty()) return null var result: Any? for (methodSuffix in METHOD_SUFFIXES) { - result = MultiSimWorkerfff.runMethodReflect( + result = MultiSimWorker.runMethodReflect( teleMgr, teleClass, methodName + methodSuffix, From 17ad84b8d8aa20a4b2b49cbc6a9ce9f3f4b6227a Mon Sep 17 00:00:00 2001 From: Juma Allan Date: Tue, 18 Oct 2022 12:48:25 +0300 Subject: [PATCH 22/26] moved database in data package --- .../hover/multisim/{db => data/database}/Database.kt | 6 +++--- .../multisim/{db => data/database}/dao/BaseDao.kt | 2 +- .../multisim/{db => data/database}/dao/SimDao.kt | 12 ++++++------ .../multisim/{db => data/database}/model/HSDKSims.kt | 2 +- .../multisim/{sim => data/database/util}/mapper.kt | 2 +- .../src/main/java/com/hover/multisim/di/DaoModule.kt | 4 ++-- .../java/com/hover/multisim/di/DatabaseModule.kt | 2 +- .../multisim/{db => data/database}/dao/SimDaoTest.kt | 7 ++++--- 8 files changed, 19 insertions(+), 18 deletions(-) rename multisim/src/main/java/com/hover/multisim/{db => data/database}/Database.kt (85%) rename multisim/src/main/java/com/hover/multisim/{db => data/database}/dao/BaseDao.kt (96%) rename multisim/src/main/java/com/hover/multisim/{db => data/database}/dao/SimDao.kt (72%) rename multisim/src/main/java/com/hover/multisim/{db => data/database}/model/HSDKSims.kt (96%) rename multisim/src/main/java/com/hover/multisim/{sim => data/database/util}/mapper.kt (95%) rename multisim/src/test/java/com/hover/multisim/{db => data/database}/dao/SimDaoTest.kt (90%) diff --git a/multisim/src/main/java/com/hover/multisim/db/Database.kt b/multisim/src/main/java/com/hover/multisim/data/database/Database.kt similarity index 85% rename from multisim/src/main/java/com/hover/multisim/db/Database.kt rename to multisim/src/main/java/com/hover/multisim/data/database/Database.kt index a0061b1..e8ef828 100644 --- a/multisim/src/main/java/com/hover/multisim/db/Database.kt +++ b/multisim/src/main/java/com/hover/multisim/data/database/Database.kt @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.hover.multisim.db +package com.hover.multisim.data.database import androidx.room.Database import androidx.room.RoomDatabase -import com.hover.multisim.db.dao.SimDao -import com.hover.multisim.db.model.HSDKSims +import com.hover.multisim.data.database.dao.SimDao +import com.hover.multisim.data.database.model.HSDKSims @Database( entities = [ diff --git a/multisim/src/main/java/com/hover/multisim/db/dao/BaseDao.kt b/multisim/src/main/java/com/hover/multisim/data/database/dao/BaseDao.kt similarity index 96% rename from multisim/src/main/java/com/hover/multisim/db/dao/BaseDao.kt rename to multisim/src/main/java/com/hover/multisim/data/database/dao/BaseDao.kt index 7115fd4..f38bc30 100644 --- a/multisim/src/main/java/com/hover/multisim/db/dao/BaseDao.kt +++ b/multisim/src/main/java/com/hover/multisim/data/database/dao/BaseDao.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.hover.multisim.db.dao +package com.hover.multisim.data.database.dao import androidx.room.Delete import androidx.room.Insert diff --git a/multisim/src/main/java/com/hover/multisim/db/dao/SimDao.kt b/multisim/src/main/java/com/hover/multisim/data/database/dao/SimDao.kt similarity index 72% rename from multisim/src/main/java/com/hover/multisim/db/dao/SimDao.kt rename to multisim/src/main/java/com/hover/multisim/data/database/dao/SimDao.kt index 31a361b..129cc45 100644 --- a/multisim/src/main/java/com/hover/multisim/db/dao/SimDao.kt +++ b/multisim/src/main/java/com/hover/multisim/data/database/dao/SimDao.kt @@ -13,25 +13,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.hover.multisim.db.dao +package com.hover.multisim.data.database.dao import androidx.room.Dao import androidx.room.Query -import com.hover.multisim.db.model.HSDKSims +import com.hover.multisim.data.database.model.HSDKSims import kotlinx.coroutines.flow.Flow @Dao interface SimDao : BaseDao { @Query("SELECT * FROM hsdk_sims") - fun getAll(): Flow> // return SimInfo + fun getAllSims(): Flow> // return SimInfo @Query("SELECT * FROM hsdk_sims WHERE mcc =:mcc AND slot_idx != -1") - fun getPresent(mcc: String): Flow> // return SimInfo + fun getPresentSims(mcc: String): Flow> // return SimInfo @Query("SELECT * FROM hsdk_sims WHERE slot_idx =:slotIdx LIMIT 1") - suspend fun get(slotIdx: Int): HSDKSims // return SimInfo + suspend fun getSim(slotIdx: Int): HSDKSims // return SimInfo @Query("SELECT * FROM hsdk_sims WHERE iccId =:iccId LIMIT 1") - suspend fun loadBy(iccId: String): HSDKSims // return SimInfo + suspend fun loadBySim(iccId: String): HSDKSims // return SimInfo } diff --git a/multisim/src/main/java/com/hover/multisim/db/model/HSDKSims.kt b/multisim/src/main/java/com/hover/multisim/data/database/model/HSDKSims.kt similarity index 96% rename from multisim/src/main/java/com/hover/multisim/db/model/HSDKSims.kt rename to multisim/src/main/java/com/hover/multisim/data/database/model/HSDKSims.kt index 226205b..83ce9ed 100644 --- a/multisim/src/main/java/com/hover/multisim/db/model/HSDKSims.kt +++ b/multisim/src/main/java/com/hover/multisim/data/database/model/HSDKSims.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.hover.multisim.db.model +package com.hover.multisim.data.database.model import androidx.room.Entity import androidx.room.Index diff --git a/multisim/src/main/java/com/hover/multisim/sim/mapper.kt b/multisim/src/main/java/com/hover/multisim/data/database/util/mapper.kt similarity index 95% rename from multisim/src/main/java/com/hover/multisim/sim/mapper.kt rename to multisim/src/main/java/com/hover/multisim/data/database/util/mapper.kt index 01c20f6..b5d8e0b 100644 --- a/multisim/src/main/java/com/hover/multisim/sim/mapper.kt +++ b/multisim/src/main/java/com/hover/multisim/data/database/util/mapper.kt @@ -1,6 +1,6 @@ package com.hover.multisim.sim -import com.hover.multisim.db.model.HSDKSims +import com.hover.multisim.data.database.model.HSDKSims fun HSDKSims.toSimInfo() = SimInfo( slotIdx = this.slot_idx, diff --git a/multisim/src/main/java/com/hover/multisim/di/DaoModule.kt b/multisim/src/main/java/com/hover/multisim/di/DaoModule.kt index 5580e1d..c672d03 100644 --- a/multisim/src/main/java/com/hover/multisim/di/DaoModule.kt +++ b/multisim/src/main/java/com/hover/multisim/di/DaoModule.kt @@ -15,8 +15,8 @@ */ package com.hover.multisim.di -import com.hover.multisim.db.Database -import com.hover.multisim.db.dao.SimDao +import com.hover.multisim.data.database.Database +import com.hover.multisim.data.database.dao.SimDao import dagger.Module import dagger.Provides import dagger.hilt.InstallIn diff --git a/multisim/src/main/java/com/hover/multisim/di/DatabaseModule.kt b/multisim/src/main/java/com/hover/multisim/di/DatabaseModule.kt index 2b73179..cf11068 100644 --- a/multisim/src/main/java/com/hover/multisim/di/DatabaseModule.kt +++ b/multisim/src/main/java/com/hover/multisim/di/DatabaseModule.kt @@ -17,7 +17,7 @@ package com.hover.multisim.di import android.content.Context import androidx.room.Room -import com.hover.multisim.db.Database +import com.hover.multisim.data.database.Database import dagger.Module import dagger.Provides import dagger.hilt.InstallIn diff --git a/multisim/src/test/java/com/hover/multisim/db/dao/SimDaoTest.kt b/multisim/src/test/java/com/hover/multisim/data/database/dao/SimDaoTest.kt similarity index 90% rename from multisim/src/test/java/com/hover/multisim/db/dao/SimDaoTest.kt rename to multisim/src/test/java/com/hover/multisim/data/database/dao/SimDaoTest.kt index ca4e537..37bdf90 100644 --- a/multisim/src/test/java/com/hover/multisim/db/dao/SimDaoTest.kt +++ b/multisim/src/test/java/com/hover/multisim/data/database/dao/SimDaoTest.kt @@ -13,14 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.hover.multisim.db.dao +package com.hover.multisim.data.database.dao import android.content.Context import androidx.room.Room import androidx.test.core.app.ApplicationProvider import com.appmattus.kotlinfixture.kotlinFixture -import com.hover.multisim.db.Database -import com.hover.multisim.db.model.HSDKSims +import com.hover.multisim.data.database.Database +import com.hover.multisim.data.database.dao.SimDao +import com.hover.multisim.data.database.model.HSDKSims import java.io.IOException import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest From 00bf3a55c9cd4319d02a89154c6d182a90a5079e Mon Sep 17 00:00:00 2001 From: Juma Allan Date: Tue, 18 Oct 2022 13:45:52 +0300 Subject: [PATCH 23/26] working on simworker --- gradle/libs.versions.toml | 3 +- .../com/hover/multisim/sim/MultiSimWorker.kt | 104 +++++++---------- .../java/com/hover/multisim/sim/SimInfo.kt | 19 +++- .../com/hover/multisim/sim/SlotManager.kt | 105 ++++++------------ .../main/java/com/hover/multisim/sim/Utils.kt | 1 + .../multisim/data/database/dao/SimDaoTest.kt | 1 - 6 files changed, 98 insertions(+), 135 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4609c61..f276b12 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -15,6 +15,7 @@ android-localbroadcastmanager = "androidx.localbroadcastmanager:localbroadcastma android-work = "androidx.work:work-runtime:2.7.1" android-hilt = { module = "com.google.dagger:hilt-android", version.ref = "hilt" } +android-hilt-common = "androidx.hilt:hilt-common:1.0.0" android-hilt-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hilt" } android-hilt-testing = { module = "com.google.dagger:hilt-android-testing", version.ref = "hilt" } @@ -36,5 +37,5 @@ test-fixture = "com.appmattus.fixture:fixture:1.2.0" test-android-espresso = "androidx.test.espresso:espresso-core:3.4.0" [bundles] -hilt = ["android-hilt", "android-hilt-compiler", "android-hilt-testing"] +hilt = ["android-hilt", "android-hilt-common", "android-hilt-compiler", "android-hilt-testing"] room = ["room-ktx", "room-runtime"] \ No newline at end of file diff --git a/multisim/src/main/java/com/hover/multisim/sim/MultiSimWorker.kt b/multisim/src/main/java/com/hover/multisim/sim/MultiSimWorker.kt index 865f375..dc3f696 100644 --- a/multisim/src/main/java/com/hover/multisim/sim/MultiSimWorker.kt +++ b/multisim/src/main/java/com/hover/multisim/sim/MultiSimWorker.kt @@ -13,7 +13,7 @@ import android.telephony.ServiceState import android.telephony.SubscriptionInfo import android.telephony.SubscriptionManager import android.telephony.TelephonyManager -import android.util.Log +import androidx.hilt.work.HiltWorker import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.work.ListenableWorker import androidx.work.OneTimeWorkRequest @@ -21,17 +21,22 @@ import androidx.work.PeriodicWorkRequest import androidx.work.WorkerParameters import androidx.work.impl.utils.futures.SettableFuture import com.google.common.util.concurrent.ListenableFuture +import dagger.assisted.Assisted import io.sentry.Sentry import java.util.concurrent.Semaphore import java.util.concurrent.TimeUnit +@HiltWorker class MultiSimWorker( - context: Context, - params: WorkerParameters, + @Assisted context: Context, + @Assisted params: WorkerParameters, ) : ListenableWorker(context, params) { + companion object { + const val SLOT_COUNT = 3 // Need to check 0, 1, and 2. Some phones index from 1. + } + private val NEW_SIM_INFO = "NEW_SIM_INFO_ACTION" - val SLOT_COUNT = 3 // Need to check 0, 1, and 2. Some phones index from 1. private lateinit var workerFuture: SettableFuture private var result: Result? = null @@ -72,17 +77,17 @@ class MultiSimWorker( return workerFuture } + /* ktlint-disable max-line-length */ @SuppressLint("RestrictedApi") private fun startListeners() { try { registerSimStateReceiver() if (simStateListener == null) simStateListener = SimStateListener() // TelephonyManager.listen() must take place on the main thread - (applicationContext.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager) - .listen( - simStateListener, - PhoneStateListener.LISTEN_SERVICE_STATE or PhoneStateListener.LISTEN_SIGNAL_STRENGTHS - ) + (applicationContext.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager).listen( + simStateListener, + PhoneStateListener.LISTEN_SERVICE_STATE or PhoneStateListener.LISTEN_SIGNAL_STRENGTHS + ) } catch (e: java.lang.Exception) { workerFuture.set(Result.retry()) } @@ -116,7 +121,7 @@ class MultiSimWorker( } } - private fun compareNewAndOld(newList: List, oldList: List?) { + private fun compareNewAndOld(newList: List, oldList: List?) { if (oldList == null || oldList.size != newList.size) { onSimInfoUpdate(newList) } else { @@ -169,8 +174,8 @@ class MultiSimWorker( @Synchronized @SuppressWarnings("MissingPermission") - private fun findUniqueSimInfo(): List? { - var newList: List = java.util.ArrayList() + private fun findUniqueSimInfo(): List { + var newList: List = java.util.ArrayList() try { slotSemaphore.acquire() val slotMgrList: List = java.util.ArrayList() @@ -178,11 +183,9 @@ class MultiSimWorker( var subInfos: List? = java.util.ArrayList() if (Build.VERSION.SDK_INT >= 22) subInfos = getSubscriptions(teleMgrInstances, slotMgrList) - newList = - if (subInfos != null && subInfos.isNotEmpty()) createUniqueSimInfoList( - subInfos, - slotMgrList - ) else createUniqueSimInfoList(slotMgrList) + newList = if (subInfos != null && subInfos.isNotEmpty()) createUniqueSimInfoList( + subInfos, slotMgrList + ) else createUniqueSimInfoList(slotMgrList) } catch (e: java.lang.Exception) { Sentry.captureException(e) } finally { @@ -192,8 +195,7 @@ class MultiSimWorker( } private fun createUniqueSimInfoList( - subInfos: List, - slotMgrList: List + subInfos: List, slotMgrList: List ): List { val newList = createUniqueSimInfoList(slotMgrList) for (subInfo in subInfos) newList.add(SimInfo(subInfo, applicationContext)) @@ -217,8 +219,7 @@ class MultiSimWorker( @TargetApi(22) @SuppressWarnings("MissingPermission") private fun getSubscriptions( - teleMgrInstances: List?, - slotMgrList: List + teleMgrInstances: List?, slotMgrList: List ): List? { val subInfos = SubscriptionManager.from( applicationContext @@ -250,19 +251,13 @@ class MultiSimWorker( if (className == null) continue addMgrFromReflection(className, teleMgrList, null, slotMgrList) for (i in 0 until SLOT_COUNT) addMgrFromReflection( - className, - teleMgrList, - i, - slotMgrList + className, teleMgrList, i, slotMgrList ) } addMgrFromSystemService(Context.TELEPHONY_SERVICE, teleMgrList, null, slotMgrList) addMgrFromSystemService("phone_msim", teleMgrList, null, slotMgrList) for (j in 0 until SLOT_COUNT) addMgrFromSystemService( - "phone$j", - teleMgrList, - j, - slotMgrList + "phone$j", teleMgrList, j, slotMgrList ) teleMgrList.add(null) return teleMgrList @@ -278,10 +273,7 @@ class MultiSimWorker( if (result != null && !teleMgrList.contains(result)) { teleMgrList.add(result) if (Build.VERSION.SDK_INT < 22) SlotManager.addValidReadySlots( - slotMgrList, - slotIdx, - result, - validClassNames + slotMgrList, slotIdx, result, validClassNames ) } } @@ -296,40 +288,19 @@ class MultiSimWorker( if (serv != null && !teleMgrList.contains(serv)) { teleMgrList.add(serv) if (Build.VERSION.SDK_INT < 22) SlotManager.addValidReadySlots( - slotMgrList, - slotIdx, - serv, - validClassNames + slotMgrList, slotIdx, serv, validClassNames ) } } private fun runMethodReflect( - className: String, - methodName: String, - methodParams: Array - ): Any? { - try { - return runMethodReflect(null, Class.forName(className), methodName, methodParams) - } catch (e: ClassNotFoundException) { - validClassNames!!.remove(className) - } - return null - } - - private fun runMethodReflect( - actualInstance: Any, - methodName: String, - methodParams: Array + actualInstance: Any, methodName: String, methodParams: Array ): Any? { return runMethodReflect(actualInstance, actualInstance.javaClass, methodName, methodParams) } private fun runMethodReflect( - actualInstance: Any?, - classInstance: Class<*>, - methodName: String, - methodParams: Array + actualInstance: Any?, classInstance: Class<*>, methodName: String, methodParams: Array ): Any? { var result: Any? = null try { @@ -345,6 +316,17 @@ class MultiSimWorker( return result } + fun runMethodReflect( + className: String, methodName: String, methodParams: Array + ): Any? { + try { + return runMethodReflect(null, Class.forName(className), methodName, methodParams) + } catch (e: ClassNotFoundException) { + validClassNames!!.remove(className) + } + return null + } + private fun runFieldReflect(className: String, field: String): Any? { var result: Any? = null try { @@ -406,13 +388,13 @@ class MultiSimWorker( override fun onStopped() { super.onStopped() try { - (applicationContext.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager) - .listen(simStateListener, PhoneStateListener.LISTEN_NONE) + (applicationContext.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager).listen( + simStateListener, PhoneStateListener.LISTEN_NONE + ) simStateListener = null if (simStateReceiver != null) applicationContext.unregisterReceiver(simStateReceiver) simStateReceiver = null } catch (ignored: Exception) { } } - -} \ No newline at end of file +} diff --git a/multisim/src/main/java/com/hover/multisim/sim/SimInfo.kt b/multisim/src/main/java/com/hover/multisim/sim/SimInfo.kt index 6821679..68031c2 100644 --- a/multisim/src/main/java/com/hover/multisim/sim/SimInfo.kt +++ b/multisim/src/main/java/com/hover/multisim/sim/SimInfo.kt @@ -20,8 +20,21 @@ data class SimInfo( ) { fun isMncMatch(mncInt: Int): Boolean { - return imsi.length == 4 && imsi.substring(3).toInt() == mncInt || - imsi.length >= 5 && imsi.substring(3, 5).toInt() == mncInt || - imsi.length >= 6 && imsi.substring(3, 6).toInt() == mncInt + return imsi.length == 4 && imsi.substring(3) + .toInt() == mncInt || imsi.length >= 5 && imsi.substring(3, 5) + .toInt() == mncInt || imsi.length >= 6 && imsi.substring(3, 6).toInt() == mncInt } + + fun isNotContainedInOrHasMoved(simInfos: List?): Boolean { + if (simInfos == null) return true + for (simInfo in simInfos) if (simInfo.let { this.isSameSimInSameSlot(it) }) return false + return true + } + + private fun isSameSimInSameSlot(simInfo: SimInfo): Boolean = + isSameSim(simInfo) && simInfo.slotIdx == slotIdx + + // FIXME: change so that if the SimInfo represents the same sim, the one with more/better info (and more accurate slotIdx?) is returned. It currently relies on order to do this, which is fragile + fun isSameSim(simInfo: SimInfo?): Boolean = simInfo?.iccId != null && iccId == simInfo.iccId + } diff --git a/multisim/src/main/java/com/hover/multisim/sim/SlotManager.kt b/multisim/src/main/java/com/hover/multisim/sim/SlotManager.kt index 9bc8290..0130714 100644 --- a/multisim/src/main/java/com/hover/multisim/sim/SlotManager.kt +++ b/multisim/src/main/java/com/hover/multisim/sim/SlotManager.kt @@ -6,71 +6,52 @@ import io.sentry.Sentry class SlotManager( slotIdx: Int, subscriptionId: Int, - teleMgr: Any?, - teleClass: Class<*>?, + private var teleMgr: Any?, + private var teleClass: Class<*>?, simState: Int, - imei: String?, + private var imei: String?, iccId: String? ) { private var slotIndex: Int? = slotIdx private var subscriptionId: Int? = subscriptionId - private var imei: String? = imei - private var teleMgr: Any? = teleMgr - private var teleClass: Class<*>? = teleClass private val METHOD_SUFFIXES = arrayOf( "", "Gemini", "Ext", "Ds", "ForSubscription", "ForPhone" ) - operator fun getValue(methodName: String, subscriptionId: Any): Any? { - return getValue(methodName, subscriptionId as Int, teleMgr, teleClass) - } + operator fun getValue(methodName: String, subscriptionId: Any): Any? = + getValue(methodName, subscriptionId as Int, teleMgr, teleClass) - fun findSimState(): Int? { - return getValue("getSimState", slotIndex!!) as Int? - } + fun findSimState(): Int? = slotIndex?.let { getValue("getSimState", it) } as Int? - fun findIccId(): String? { - return getValue("getSimSerialNumber", subscriptionId!!) as String? - } + fun findIccId(): String? = subscriptionId?.let { getValue("getSimSerialNumber", it) } as String? - fun findImsi(): String? { - return getValue("getSubscriberId", subscriptionId!!) as String? - } + fun findImsi(): String? = subscriptionId?.let { getValue("getSubscriberId", it) } as String? - fun findOperator(): String? { - return getValue("getSimOperator", subscriptionId!!) as String? - } + fun findOperator(): String? = subscriptionId?.let { getValue("getSimOperator", it) } as String? - fun findOperatorName(): String? { - return getValue("getSimOperatorName", subscriptionId!!) as String? - } + fun findOperatorName(): String? = + subscriptionId?.let { getValue("getSimOperatorName", it) } as String? - fun findCountryIso(): String? { - return getValue("getSimCountryIso", subscriptionId!!) as String? - } + fun findCountryIso(): String? = + subscriptionId?.let { getValue("getSimCountryIso", it) } as String? - fun findNetworkOperator(): String? { - return getValue("getNetworkOperator", subscriptionId!!) as String? - } + fun findNetworkOperator(): String? = + subscriptionId?.let { getValue("getNetworkOperator", it) } as String? - fun findNetworkOperatorName(): String? { - return getValue("getNetworkOperatorName", subscriptionId!!) as String? - } + fun findNetworkOperatorName(): String? = + subscriptionId?.let { getValue("getNetworkOperatorName", it) } as String? - fun findNetworkCountryIso(): String? { - return getValue("getNetworkCountryIso", subscriptionId!!) as String? - } + fun findNetworkCountryIso(): String? = + subscriptionId?.let { getValue("getNetworkCountryIso", it) } as String? - fun findNetworkType(): Int? { - return getValue("getNetworkType", subscriptionId!!) as Int? - } + fun findNetworkType(): Int? = subscriptionId?.let { getValue("getNetworkType", it) } as Int? - fun findNetworkRoaming(): Boolean { - return getValue("isNetworkRoaming", subscriptionId!!) as Boolean - } + fun findNetworkRoaming(): Boolean = + subscriptionId?.let { getValue("isNetworkRoaming", it) } as Boolean + /* ktlint-disable max-line-length */ private fun isUnique(slotMgrList: List): Boolean { for (mgr in slotMgrList) { try { @@ -95,6 +76,7 @@ class SlotManager( ) } + /* ktlint-disable max-line-length */ private fun addValidReadySlots( slotMgrList: List, slotIdx: Int, @@ -106,7 +88,8 @@ class SlotManager( return } for (className in validClassNames) if (teleMgrInstance != null || className != null) { - val sm: SlotManager? = findValidReadySlot(slotIdx, subscriptionId, teleMgrInstance, className) + val sm: SlotManager? = + findValidReadySlot(slotIdx, subscriptionId, teleMgrInstance, className) if (sm != null) { if (slotMgrList.isEmpty() || sm.isUnique(slotMgrList)) { slotMgrList + sm @@ -116,10 +99,7 @@ class SlotManager( } private fun findValidReadySlot( - slotIdx: Int, - subscriptionId: Int, - teleMgr: Any?, - className: String? + slotIdx: Int, subscriptionId: Int, teleMgr: Any?, className: String? ): SlotManager? { val teleClass: Class<*>? = getTeleClass(teleMgr, className) val simState: Int? = getSimState(slotIdx, teleMgr, teleClass) @@ -144,17 +124,11 @@ class SlotManager( return imei } - private fun getSimIccId(subscriptionId: Int, teleMgr: Any?, teleClass: Class<*>?): String? { - return getValue( - "getSimSerialNumber", subscriptionId, teleMgr, teleClass - ) as String? - } + private fun getSimIccId(subscriptionId: Int, teleMgr: Any?, teleClass: Class<*>?): String? = + getValue("getSimSerialNumber", subscriptionId, teleMgr, teleClass) as String? - fun getSimImsi(subscriptionId: Int, teleMgr: Any, teleClass: Class<*>): String? { - return getValue( - "getSubscriberId", subscriptionId, teleMgr, teleClass - ) as String? - } + fun getSimImsi(subscriptionId: Int, teleMgr: Any, teleClass: Class<*>): String? = + getValue("getSubscriberId", subscriptionId, teleMgr, teleClass) as String? private fun getTeleClass(teleMgr: Any?, className: String?): Class<*>? { try { @@ -165,10 +139,7 @@ class SlotManager( } private fun getValue( - methodName: String, - slotIndex: Int, - teleMgr: Any?, - teleClass: Class<*>? + methodName: String, slotIndex: Int, teleMgr: Any?, teleClass: Class<*>? ): Any? { var result: Any? = spamTeleMgr(methodName, teleMgr, teleClass, slotIndex) if (result == null) result = spamTeleMgr(methodName, teleMgr, teleClass, null) @@ -176,20 +147,16 @@ class SlotManager( } private fun spamTeleMgr( - methodName: String?, - teleMgr: Any?, - teleClass: Class<*>?, - subscriptionId: Any? + methodName: String?, teleMgr: Any?, teleClass: Class<*>?, subscriptionId: Any? ): Any? { if (methodName == null || methodName.isEmpty()) return null var result: Any? for (methodSuffix in METHOD_SUFFIXES) { - result = MultiSimWorker.runMethodReflect( - teleMgr, + result = MultiSimWorker().runMethodReflect( +// teleMgr, teleClass, methodName + methodSuffix, - subscriptionId?.let { arrayOf(it) } - ) + subscriptionId?.let { arrayOf(it) }) if (result != null) { return result } diff --git a/multisim/src/main/java/com/hover/multisim/sim/Utils.kt b/multisim/src/main/java/com/hover/multisim/sim/Utils.kt index e385ebf..f65b80d 100644 --- a/multisim/src/main/java/com/hover/multisim/sim/Utils.kt +++ b/multisim/src/main/java/com/hover/multisim/sim/Utils.kt @@ -21,6 +21,7 @@ object Utils { } } + /* ktlint-disable max-line-length */ @JvmStatic fun hasPhonePerm(context: Context): Boolean { return Build.VERSION.SDK_INT < 23 || context.checkSelfPermission(Manifest.permission.CALL_PHONE) == PackageManager.PERMISSION_GRANTED && context.checkSelfPermission( diff --git a/multisim/src/test/java/com/hover/multisim/data/database/dao/SimDaoTest.kt b/multisim/src/test/java/com/hover/multisim/data/database/dao/SimDaoTest.kt index 37bdf90..e5072e7 100644 --- a/multisim/src/test/java/com/hover/multisim/data/database/dao/SimDaoTest.kt +++ b/multisim/src/test/java/com/hover/multisim/data/database/dao/SimDaoTest.kt @@ -20,7 +20,6 @@ import androidx.room.Room import androidx.test.core.app.ApplicationProvider import com.appmattus.kotlinfixture.kotlinFixture import com.hover.multisim.data.database.Database -import com.hover.multisim.data.database.dao.SimDao import com.hover.multisim.data.database.model.HSDKSims import java.io.IOException import kotlinx.coroutines.flow.first From 6aa983d7ec82fbd6d5fc3cdf276bb842c911858a Mon Sep 17 00:00:00 2001 From: Juma Allan Date: Tue, 18 Oct 2022 13:46:17 +0300 Subject: [PATCH 24/26] migrated db schema path --- .../1.json | 140 ++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 multisim/schemas/com.hover.multisim.data.database.Database/1.json diff --git a/multisim/schemas/com.hover.multisim.data.database.Database/1.json b/multisim/schemas/com.hover.multisim.data.database.Database/1.json new file mode 100644 index 0000000..e7ab190 --- /dev/null +++ b/multisim/schemas/com.hover.multisim.data.database.Database/1.json @@ -0,0 +1,140 @@ +{ + "formatVersion": 1, + "database": { + "version": 1, + "identityHash": "64772d7bc9ec08680b3c8ce4ced8e2e0", + "entities": [ + { + "tableName": "hsdk_sims", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `slot_idx` INTEGER NOT NULL, `sub_id` INTEGER NOT NULL, `imei` TEXT, `state` INTEGER NOT NULL, `imsi` TEXT NOT NULL, `mcc` TEXT NOT NULL, `mnc` TEXT, `iccid` TEXT NOT NULL, `operator` TEXT, `operator_name` TEXT, `country_iso` TEXT, `is_roaming` INTEGER NOT NULL, `network_code` TEXT, `network_name` TEXT, `network_country` TEXT, `network_type` INTEGER)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "slot_idx", + "columnName": "slot_idx", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sub_id", + "columnName": "sub_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "imei", + "columnName": "imei", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "state", + "columnName": "state", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "imsi", + "columnName": "imsi", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mcc", + "columnName": "mcc", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mnc", + "columnName": "mnc", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "iccid", + "columnName": "iccid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "operator", + "columnName": "operator", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "operator_name", + "columnName": "operator_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "country_iso", + "columnName": "country_iso", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "is_roaming", + "columnName": "is_roaming", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "network_code", + "columnName": "network_code", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "network_name", + "columnName": "network_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "network_country", + "columnName": "network_country", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "network_type", + "columnName": "network_type", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_hsdk_sims_iccid", + "unique": true, + "columnNames": [ + "iccid" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_hsdk_sims_iccid` ON `${TABLE_NAME}` (`iccid`)" + } + ], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '64772d7bc9ec08680b3c8ce4ced8e2e0')" + ] + } +} \ No newline at end of file From 97750bc1a388fa02fc35b284df23da3ef3f2d153 Mon Sep 17 00:00:00 2001 From: Juma Allan Date: Tue, 18 Oct 2022 13:46:38 +0300 Subject: [PATCH 25/26] added sim repo implementation --- .../multisim/data/repository/SimRepository.kt | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 multisim/src/main/java/com/hover/multisim/data/repository/SimRepository.kt diff --git a/multisim/src/main/java/com/hover/multisim/data/repository/SimRepository.kt b/multisim/src/main/java/com/hover/multisim/data/repository/SimRepository.kt new file mode 100644 index 0000000..81b7326 --- /dev/null +++ b/multisim/src/main/java/com/hover/multisim/data/repository/SimRepository.kt @@ -0,0 +1,30 @@ +package com.hover.multisim.data.repository + +import com.hover.multisim.data.database.dao.SimDao +import com.hover.multisim.sim.SimInfo +import com.hover.multisim.sim.toSimInfo +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +interface SimRepository { + fun getAllSims(): Flow> + fun getPresentSims(mcc: String): Flow> + suspend fun getSim(slotIdx: Int): SimInfo + suspend fun loadBySim(iccId: String): SimInfo +} + +class SimRepositoryImpl @Inject constructor( + private val simDao: SimDao +) : SimRepository { + + override fun getAllSims(): Flow> = + simDao.getAllSims().map { hsdkSimsList -> hsdkSimsList.map { it.toSimInfo() } } + + override fun getPresentSims(mcc: String): Flow> = + simDao.getPresentSims(mcc).map { hsdkSimsList -> hsdkSimsList.map { it.toSimInfo() } } + + override suspend fun getSim(slotIdx: Int): SimInfo = simDao.getSim(slotIdx).toSimInfo() + + override suspend fun loadBySim(iccId: String): SimInfo = simDao.loadBySim(iccId).toSimInfo() +} From c63a45f19164c276fc5b56eb0dc2d4cc7230b404 Mon Sep 17 00:00:00 2001 From: Juma Allan Date: Tue, 18 Oct 2022 14:03:42 +0300 Subject: [PATCH 26/26] some more refactors --- .../com/hover/multisim/sim/MultiSimWorker.kt | 9 ++++---- .../java/com/hover/multisim/sim/SimInfo.kt | 5 +++++ .../com/hover/multisim/sim/SlotManager.kt | 21 ++++++++++--------- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/multisim/src/main/java/com/hover/multisim/sim/MultiSimWorker.kt b/multisim/src/main/java/com/hover/multisim/sim/MultiSimWorker.kt index dc3f696..346317a 100644 --- a/multisim/src/main/java/com/hover/multisim/sim/MultiSimWorker.kt +++ b/multisim/src/main/java/com/hover/multisim/sim/MultiSimWorker.kt @@ -13,6 +13,7 @@ import android.telephony.ServiceState import android.telephony.SubscriptionInfo import android.telephony.SubscriptionManager import android.telephony.TelephonyManager +import android.util.Log import androidx.hilt.work.HiltWorker import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.work.ListenableWorker @@ -26,6 +27,7 @@ import io.sentry.Sentry import java.util.concurrent.Semaphore import java.util.concurrent.TimeUnit +// TODO - Maybe rewrite this implementation somehow to simplify it? @HiltWorker class MultiSimWorker( @Assisted context: Context, @@ -102,10 +104,10 @@ class MultiSimWorker( if (Utils.hasPhonePerm(applicationContext)) { val oldList: List? = getSaved() val newList = findUniqueSimInfo() - if (newList != null) { + run { compareNewAndOld(newList, oldList) Result.success() - } else Result.failure() + } } else Result.failure() } catch (e: java.lang.Exception) { Sentry.captureException(e) @@ -394,7 +396,6 @@ class MultiSimWorker( simStateListener = null if (simStateReceiver != null) applicationContext.unregisterReceiver(simStateReceiver) simStateReceiver = null - } catch (ignored: Exception) { - } + } catch (ignored: Exception) { } } } diff --git a/multisim/src/main/java/com/hover/multisim/sim/SimInfo.kt b/multisim/src/main/java/com/hover/multisim/sim/SimInfo.kt index 68031c2..8090d9e 100644 --- a/multisim/src/main/java/com/hover/multisim/sim/SimInfo.kt +++ b/multisim/src/main/java/com/hover/multisim/sim/SimInfo.kt @@ -37,4 +37,9 @@ data class SimInfo( // FIXME: change so that if the SimInfo represents the same sim, the one with more/better info (and more accurate slotIdx?) is returned. It currently relies on order to do this, which is fragile fun isSameSim(simInfo: SimInfo?): Boolean = simInfo?.iccId != null && iccId == simInfo.iccId + fun isNotContainedIn(simInfos: List?): Boolean { + if (simInfos == null) return true + for (simInfo in simInfos) if (isSameSim(simInfo)) return false + return true + } } diff --git a/multisim/src/main/java/com/hover/multisim/sim/SlotManager.kt b/multisim/src/main/java/com/hover/multisim/sim/SlotManager.kt index 0130714..a409c1d 100644 --- a/multisim/src/main/java/com/hover/multisim/sim/SlotManager.kt +++ b/multisim/src/main/java/com/hover/multisim/sim/SlotManager.kt @@ -151,16 +151,17 @@ class SlotManager( ): Any? { if (methodName == null || methodName.isEmpty()) return null var result: Any? - for (methodSuffix in METHOD_SUFFIXES) { - result = MultiSimWorker().runMethodReflect( -// teleMgr, - teleClass, - methodName + methodSuffix, - subscriptionId?.let { arrayOf(it) }) - if (result != null) { - return result - } - } + //TODO - FIX ME +// for (methodSuffix in METHOD_SUFFIXES) { +// result = MultiSimWorker().runMethodReflect( +//// teleMgr, +// teleClass, +// methodName + methodSuffix, +// subscriptionId?.let { arrayOf(it) }) +// if (result != null) { +// return result +// } +// } return null } }