44import android .os .Build ;
55import android .util .Log ;
66import android .webkit .JavascriptInterface ;
7+ import android .widget .Button ;
78import android .widget .Toast ;
89
910import androidx .annotation .Keep ;
11+ import androidx .appcompat .app .AlertDialog ;
1012
1113import com .fox2code .mmm .BuildConfig ;
1214import com .fox2code .mmm .MainApplication ;
15+ import com .fox2code .mmm .R ;
16+ import com .fox2code .mmm .compat .CompatDisplay ;
1317import com .fox2code .mmm .installer .InstallerInitializer ;
1418import com .fox2code .mmm .manager .LocalModuleInfo ;
1519import com .fox2code .mmm .manager .ModuleInfo ;
1620import com .fox2code .mmm .manager .ModuleManager ;
21+ import com .fox2code .mmm .repo .RepoManager ;
22+ import com .fox2code .mmm .repo .RepoModule ;
1723import com .fox2code .mmm .utils .Files ;
1824import com .fox2code .mmm .utils .Hashes ;
1925import com .fox2code .mmm .utils .IntentHelper ;
26+ import com .fox2code .mmm .utils .PropUtils ;
27+ import com .google .android .material .dialog .MaterialAlertDialogBuilder ;
2028
2129import java .io .File ;
2230import java .io .IOException ;
2533@ Keep
2634public class AndroidacyWebAPI {
2735 private static final String TAG = "AndroidacyWebAPI" ;
36+ private static final int MAX_COMPAT_MODE = 1 ;
2837 private final AndroidacyActivity activity ;
2938 private final boolean allowInstall ;
3039 boolean consumedAction ;
40+ boolean downloadMode ;
41+ int effectiveCompatMode ;
42+ int notifiedCompatMode ;
3143
3244 public AndroidacyWebAPI (AndroidacyActivity activity , boolean allowInstall ) {
3345 this .activity = activity ;
3446 this .allowInstall = allowInstall ;
3547 }
3648
37- public void forceQuitRaw (String error ) {
49+ void forceQuitRaw (String error ) {
3850 Toast .makeText (this .activity , error , Toast .LENGTH_LONG ).show ();
3951 this .activity .runOnUiThread (this .activity ::forceBackPressed );
4052 this .activity .backOnResume = true ; // Set backOnResume just in case
53+ this .downloadMode = false ;
54+ }
55+
56+ void openNativeModuleDialogRaw (String moduleUrl , String installTitle ,
57+ String checksum , boolean canInstall ) {
58+ this .downloadMode = false ;
59+ RepoModule repoModule = RepoManager .getINSTANCE ()
60+ .getAndroidacyRepoData ().moduleHashMap .get (installTitle );
61+ String title , description ;
62+ if (repoModule != null ) {
63+ title = repoModule .moduleInfo .name ;
64+ description = repoModule .moduleInfo .description ;
65+ if (description == null || description .length () == 0 ) {
66+ description = this .activity .getString (R .string .no_desc_found );
67+ }
68+ } else {
69+ title = PropUtils .makeNameFromId (installTitle );
70+ String checkSumType = Hashes .checkSumName (checksum );
71+ if (checkSumType == null ) {
72+ description = "Checksum: " + ((
73+ checksum == null || checksum .isEmpty ()) ? "null" : checksum );
74+ } else {
75+ description = checkSumType + ": " + checksum ;
76+ }
77+ }
78+ final MaterialAlertDialogBuilder builder =
79+ new MaterialAlertDialogBuilder (this .activity );
80+ builder .setTitle (title ).setMessage (description ).setCancelable (true )
81+ .setIcon (R .drawable .ic_baseline_extension_24 );
82+ builder .setNegativeButton (R .string .download_module , (x , y ) -> {
83+ this .downloadMode = true ;
84+ this .activity .webView .loadUrl (moduleUrl );
85+ });
86+ if (canInstall ) {
87+ boolean hasUpdate = false ;
88+ String config = null ;
89+ if (repoModule != null ) {
90+ config = repoModule .moduleInfo .config ;
91+ LocalModuleInfo localModuleInfo =
92+ ModuleManager .getINSTANCE ().getModules ().get (repoModule .id );
93+ hasUpdate = localModuleInfo != null &&
94+ repoModule .moduleInfo .versionCode > localModuleInfo .versionCode ;
95+ }
96+ final String fModuleUrl = moduleUrl , fTitle = title ,
97+ fConfig = config , fChecksum = checksum ;
98+ builder .setPositiveButton (hasUpdate ?
99+ R .string .update_module : R .string .install_module , (x , y ) -> {
100+ IntentHelper .openInstaller (this .activity ,
101+ fModuleUrl , fTitle , fConfig , fChecksum );
102+ });
103+ }
104+ builder .setOnCancelListener (dialogInterface -> {
105+ if (!this .activity .backOnResume )
106+ this .consumedAction = false ;
107+ });
108+ final int dim5dp = CompatDisplay .dpToPixel (5 );
109+ builder .setBackgroundInsetStart (dim5dp ).setBackgroundInsetEnd (dim5dp );
110+ this .activity .runOnUiThread (() -> {
111+ AlertDialog alertDialog = builder .show ();
112+ for (int i = -3 ; i < 0 ; i ++) {
113+ Button alertButton = alertDialog .getButton (i );
114+ if (alertButton != null && alertButton .getPaddingStart () > dim5dp ) {
115+ alertButton .setPadding (dim5dp , dim5dp , dim5dp , dim5dp );
116+ }
117+ }
118+ });
119+ }
120+
121+ void notifyCompatModeRaw (int value ) {
122+ if (this .consumedAction ) return ;
123+ Log .d (TAG , "Androidacy Compat mode: " + value );
124+ this .notifiedCompatMode = value ;
125+ if (value < 0 ) {
126+ value = 0 ;
127+ } else if (value > MAX_COMPAT_MODE ) {
128+ value = MAX_COMPAT_MODE ;
129+ }
130+ this .effectiveCompatMode = value ;
41131 }
42132
43133 @ JavascriptInterface
44134 public void forceQuit (String error ) {
45- if (this .consumedAction ) return ;
135+ // Allow forceQuit and cancel in downloadMode
136+ if (this .consumedAction && !this .downloadMode ) return ;
46137 this .consumedAction = true ;
47138 this .forceQuitRaw (error );
48139 }
49140
50141 @ JavascriptInterface
51142 public void cancel () {
52- if (this .consumedAction ) return ;
143+ // Allow forceQuit and cancel in downloadMode
144+ if (this .consumedAction && !this .downloadMode ) return ;
53145 this .consumedAction = true ;
54146 this .activity .runOnUiThread (
55147 this .activity ::forceBackPressed );
@@ -62,12 +154,30 @@ public void cancel() {
62154 public void openUrl (String url ) {
63155 if (this .consumedAction ) return ;
64156 this .consumedAction = true ;
157+ this .downloadMode = false ;
65158 Log .d (TAG , "Received openUrl request: " + url );
66159 if (Uri .parse (url ).getScheme ().equals ("https" )) {
67160 IntentHelper .openUrl (this .activity , url );
68161 }
69162 }
70163
164+ /**
165+ * Open an url in a custom tab if possible.
166+ */
167+ @ JavascriptInterface
168+ public void openCustomTab (String url ) {
169+ if (this .consumedAction ) return ;
170+ this .consumedAction = true ;
171+ this .downloadMode = false ;
172+ Log .d (TAG , "Received openCustomTab request: " + url );
173+ if (Uri .parse (url ).getScheme ().equals ("https" )) {
174+ IntentHelper .openCustomTab (this .activity , url );
175+ }
176+ }
177+
178+ /**
179+ * Return if current theme is a light theme.
180+ */
71181 @ JavascriptInterface
72182 public boolean isLightTheme () {
73183 return MainApplication .getINSTANCE ().isLightTheme ();
@@ -97,14 +207,57 @@ public boolean canInstall() {
97207 */
98208 @ JavascriptInterface
99209 public void install (String moduleUrl , String installTitle ,String checksum ) {
100- if (this .consumedAction || !this .canInstall ()) {
210+ // If compat mode is 0, this means Androidacy didn't implemented a download mode yet
211+ if (this .consumedAction || (this .effectiveCompatMode >= 1 && !this .canInstall ())) {
101212 return ;
102213 }
103214 this .consumedAction = true ;
215+ this .downloadMode = false ;
104216 Log .d (TAG , "Received install request: " +
105217 moduleUrl + " " + installTitle + " " + checksum );
106- Uri uri = Uri .parse (moduleUrl );
107- if (!AndroidacyUtil .isAndroidacyLink (moduleUrl , uri )) {
218+ if (!AndroidacyUtil .isAndroidacyLink (moduleUrl )) {
219+ this .forceQuitRaw ("Non Androidacy module link used on Androidacy" );
220+ return ;
221+ }
222+ if (checksum != null ) checksum = checksum .trim ();
223+ if (checksum == null || checksum .isEmpty ()) {
224+ Log .w (TAG , "Androidacy WebView didn't provided a checksum!" );
225+ } else if (!Hashes .checkSumValid (checksum )) {
226+ this .forceQuitRaw ("Androidacy didn't provided a valid checksum" );
227+ return ;
228+ }
229+ // Let's handle download mode ourself if not implemented
230+ if (this .effectiveCompatMode < 1 ) {
231+ if (!this .canInstall ()) {
232+ this .downloadMode = true ;
233+ this .activity .runOnUiThread (() ->
234+ this .activity .webView .loadUrl (moduleUrl ));
235+ } else {
236+ this .openNativeModuleDialogRaw (moduleUrl , installTitle , checksum , true );
237+ }
238+ } else {
239+ RepoModule repoModule = RepoManager .getINSTANCE ()
240+ .getAndroidacyRepoData ().moduleHashMap .get (installTitle );
241+ String config = null ;
242+ if (repoModule != null && repoModule .moduleInfo .name .length () >= 3 ) {
243+ installTitle = repoModule .moduleInfo .name ; // Set title to module name
244+ config = repoModule .moduleInfo .config ;
245+ }
246+ this .activity .backOnResume = true ;
247+ IntentHelper .openInstaller (this .activity ,
248+ moduleUrl , installTitle , config , checksum );
249+ }
250+ }
251+
252+ /**
253+ * install a module via url, with the file checked with the md5 checksum value.
254+ */
255+ @ JavascriptInterface
256+ public void openNativeModuleDialog (String moduleUrl , String moduleId , String checksum ) {
257+ if (this .consumedAction ) return ;
258+ this .consumedAction = true ;
259+ this .downloadMode = false ;
260+ if (!AndroidacyUtil .isAndroidacyLink (moduleUrl )) {
108261 this .forceQuitRaw ("Non Androidacy module link used on Androidacy" );
109262 return ;
110263 }
@@ -115,9 +268,7 @@ public void install(String moduleUrl, String installTitle,String checksum) {
115268 this .forceQuitRaw ("Androidacy didn't provided a valid checksum" );
116269 return ;
117270 }
118- this .activity .backOnResume = true ;
119- IntentHelper .openInstaller (this .activity ,
120- moduleUrl , installTitle , null , checksum );
271+ this .openNativeModuleDialogRaw (moduleUrl , moduleId , checksum , this .canInstall ());
121272 }
122273
123274 /**
@@ -274,4 +425,25 @@ public int getAndroidVersionCode() {
274425 public int getNavigationBarHeight () {
275426 return this .activity .getNavigationBarHeight ();
276427 }
428+
429+ /**
430+ * Allow Androidacy backend to notify compat mode
431+ * return current effective compat mode
432+ */
433+ @ JavascriptInterface
434+ public int getEffectiveCompatMode () {
435+ return this .effectiveCompatMode ;
436+ }
437+
438+ // Androidacy feature level declaration method
439+
440+ @ JavascriptInterface
441+ public void notifyCompatUnsupported () {
442+ this .notifyCompatModeRaw (0 );
443+ }
444+
445+ @ JavascriptInterface
446+ public void notifyCompatDownloadButton () {
447+ this .notifyCompatModeRaw (1 );
448+ }
277449}
0 commit comments