├── plugins └── .keep ├── app ├── .gitignore ├── src │ ├── main │ │ ├── assets │ │ │ ├── custom-icons.json │ │ │ ├── fonts │ │ │ │ └── custom-icons.ttf │ │ │ ├── BlobDownloader.js │ │ │ └── offline.html │ │ ├── res │ │ │ ├── drawable-hdpi │ │ │ │ ├── splash.9.png │ │ │ │ ├── ic_actionbar.png │ │ │ │ ├── drawer_shadow.9.png │ │ │ │ ├── ic_notification.png │ │ │ │ ├── ic_chevron_up_light.png │ │ │ │ ├── ic_search_black_24dp.png │ │ │ │ ├── ic_search_white_24dp.png │ │ │ │ ├── ic_share_black_24dp.png │ │ │ │ ├── ic_share_white_24dp.png │ │ │ │ ├── ic_chevron_down_light.png │ │ │ │ ├── ic_refresh_black_24dp.png │ │ │ │ └── ic_refresh_white_24dp.png │ │ │ ├── drawable-mdpi │ │ │ │ ├── splash.9.png │ │ │ │ ├── ic_actionbar.png │ │ │ │ ├── drawer_shadow.9.png │ │ │ │ ├── ic_notification.png │ │ │ │ ├── ic_chevron_up_light.png │ │ │ │ ├── ic_search_black_24dp.png │ │ │ │ ├── ic_search_white_24dp.png │ │ │ │ ├── ic_share_black_24dp.png │ │ │ │ ├── ic_share_white_24dp.png │ │ │ │ ├── ic_chevron_down_light.png │ │ │ │ ├── ic_refresh_black_24dp.png │ │ │ │ └── ic_refresh_white_24dp.png │ │ │ ├── drawable-xhdpi │ │ │ │ ├── splash.9.png │ │ │ │ ├── ic_actionbar.png │ │ │ │ ├── drawer_shadow.9.png │ │ │ │ ├── ic_notification.png │ │ │ │ ├── ic_chevron_up_light.png │ │ │ │ ├── ic_share_black_24dp.png │ │ │ │ ├── ic_share_white_24dp.png │ │ │ │ ├── ic_chevron_down_light.png │ │ │ │ ├── ic_refresh_black_24dp.png │ │ │ │ ├── ic_refresh_white_24dp.png │ │ │ │ ├── ic_search_black_24dp.png │ │ │ │ └── ic_search_white_24dp.png │ │ │ ├── drawable-xxhdpi │ │ │ │ ├── splash.9.png │ │ │ │ ├── ic_actionbar.png │ │ │ │ ├── drawer_shadow.9.png │ │ │ │ ├── ic_notification.png │ │ │ │ ├── ic_chevron_up_light.png │ │ │ │ ├── ic_search_black_24dp.png │ │ │ │ ├── ic_search_white_24dp.png │ │ │ │ ├── ic_share_black_24dp.png │ │ │ │ ├── ic_share_white_24dp.png │ │ │ │ ├── ic_chevron_down_light.png │ │ │ │ ├── ic_refresh_black_24dp.png │ │ │ │ └── ic_refresh_white_24dp.png │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_sidebar_logo.png │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_sidebar_logo.png │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_sidebar_logo.png │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── values-sw600dp │ │ │ │ ├── attr.xml │ │ │ │ └── dimens.xml │ │ │ ├── drawable-xxxhdpi │ │ │ │ ├── splash.9.png │ │ │ │ ├── ic_actionbar.png │ │ │ │ ├── ic_notification.png │ │ │ │ ├── ic_share_black_24dp.png │ │ │ │ ├── ic_share_white_24dp.png │ │ │ │ ├── ic_refresh_black_24dp.png │ │ │ │ ├── ic_refresh_white_24dp.png │ │ │ │ ├── ic_search_black_24dp.png │ │ │ │ └── ic_search_white_24dp.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_sidebar_logo.png │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_sidebar_logo.png │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── drawable-night-hdpi │ │ │ │ ├── splash.9.png │ │ │ │ └── ic_actionbar.png │ │ │ ├── drawable-night-mdpi │ │ │ │ ├── splash.9.png │ │ │ │ └── ic_actionbar.png │ │ │ ├── drawable-night-xhdpi │ │ │ │ ├── splash.9.png │ │ │ │ └── ic_actionbar.png │ │ │ ├── drawable-night-xxhdpi │ │ │ │ ├── splash.9.png │ │ │ │ └── ic_actionbar.png │ │ │ ├── drawable-night-xxxhdpi │ │ │ │ ├── splash.9.png │ │ │ │ └── ic_actionbar.png │ │ │ ├── mipmap-night-hdpi │ │ │ │ └── ic_sidebar_logo.png │ │ │ ├── mipmap-night-mdpi │ │ │ │ └── ic_sidebar_logo.png │ │ │ ├── mipmap-night-xhdpi │ │ │ │ └── ic_sidebar_logo.png │ │ │ ├── menu │ │ │ │ └── topmenu.xml │ │ │ ├── mipmap-night-xxhdpi │ │ │ │ └── ic_sidebar_logo.png │ │ │ ├── mipmap-night-xxxhdpi │ │ │ │ └── ic_sidebar_logo.png │ │ │ ├── values │ │ │ │ ├── ic_launcher_background.xml │ │ │ │ ├── integers.xml │ │ │ │ ├── dimens.xml │ │ │ │ ├── colors.xml │ │ │ │ ├── attrs.xml │ │ │ │ ├── strings.xml │ │ │ │ └── styles.xml │ │ │ ├── layout │ │ │ │ ├── empty.xml │ │ │ │ ├── tab.xml │ │ │ │ ├── profile_picker_dropdown.xml │ │ │ │ ├── button_menu.xml │ │ │ │ ├── splash_screen.xml │ │ │ │ ├── menu_child_noicon.xml │ │ │ │ ├── actionbar_title.xml │ │ │ │ ├── activity_subscriptions.xml │ │ │ │ ├── menu_group_noicon.xml │ │ │ │ ├── view_handle.xml │ │ │ │ ├── menu_child_icon.xml │ │ │ │ └── menu_group_icon.xml │ │ │ ├── anim │ │ │ │ └── fast_fade_out.xml │ │ │ ├── xml │ │ │ │ ├── network_security_config.xml │ │ │ │ └── filepaths.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── drawable │ │ │ │ ├── ic_stat_onesignal_default.xml │ │ │ │ ├── bg_nav_icon.xml │ │ │ │ ├── ic_go_back.xml │ │ │ │ ├── ic_go_forward.xml │ │ │ │ ├── ic_baseline_arrow_forward_24.xml │ │ │ │ ├── ic_baseline_arrow_back_24.xml │ │ │ │ └── shape_rounded.xml │ │ │ ├── values-sw720dp-land │ │ │ │ └── dimens.xml │ │ │ ├── values-large │ │ │ │ └── styles.xml │ │ │ ├── values-ko │ │ │ │ └── strings.xml │ │ │ ├── values-v21 │ │ │ │ └── styles.xml │ │ │ ├── values-v29 │ │ │ │ └── styles.xml │ │ │ ├── values-night-v29 │ │ │ │ └── styles.xml │ │ │ └── values-night │ │ │ │ ├── styles.xml │ │ │ │ └── colors.xml │ │ ├── java │ │ │ └── io │ │ │ │ └── gonative │ │ │ │ └── android │ │ │ │ ├── JsResultBridge.java │ │ │ │ ├── WebViewPoolDisownPolicy.java │ │ │ │ ├── AppLinksActivity.java │ │ │ │ ├── IOUtils.java │ │ │ │ ├── MySwipeRefreshLayout.java │ │ │ │ ├── ConfigPreferences.java │ │ │ │ ├── widget │ │ │ │ ├── GoNativeDrawerLayout.java │ │ │ │ ├── WebViewContainerView.java │ │ │ │ ├── CircleImageView.java │ │ │ │ └── HandleView.kt │ │ │ │ ├── ShakeDialogFragment.java │ │ │ │ ├── UrlInspector.java │ │ │ │ ├── files │ │ │ │ └── CapturedImageSaver.kt │ │ │ │ ├── JsCustomCodeExecutor.java │ │ │ │ ├── CustomHeaders.java │ │ │ │ ├── ConfigUpdater.java │ │ │ │ ├── KeyboardManager.kt │ │ │ │ ├── SplashActivity.java │ │ │ │ ├── GoNativeApplication.java │ │ │ │ ├── SegmentedController.java │ │ │ │ ├── ProfilePicker.java │ │ │ │ ├── LoginManager.java │ │ │ │ ├── GoNativeWindowManager.java │ │ │ │ ├── Installation.java │ │ │ │ └── RegistrationManager.java │ │ └── AndroidManifest.xml │ ├── androidTest │ │ ├── assets │ │ │ ├── HelperClass.java │ │ │ └── appConfig.json │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── gonative │ │ │ └── testFiles │ │ │ └── FirstTestClass.java │ └── normal │ │ └── java │ │ └── io │ │ └── gonative │ │ └── android │ │ ├── PoolWebViewClient.java │ │ ├── GoNativeWebviewClient.java │ │ ├── WebkitCookieManagerProxy.java │ │ └── WebViewSetup.java ├── proguard-project.txt └── build.gradle ├── AppIcon ├── HeaderImage ├── NotificationIcon ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── gradle.properties ├── settings.gradle ├── README.md ├── CHANGELOG.md ├── generate-tinted-icons.sh ├── generate-header-images.sh ├── gradlew.bat ├── generate-plugin-icons.sh ├── .github └── workflows │ └── firebase-testing.yml ├── plugins.gradle └── gradlew /plugins/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | -------------------------------------------------------------------------------- /AppIcon: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/AppIcon -------------------------------------------------------------------------------- /app/src/main/assets/custom-icons.json: -------------------------------------------------------------------------------- 1 | { 2 | "gonative-icon": 59392 3 | } 4 | -------------------------------------------------------------------------------- /HeaderImage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/HeaderImage -------------------------------------------------------------------------------- /NotificationIcon: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/NotificationIcon -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/assets/fonts/custom-icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/assets/fonts/custom-icons.ttf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .idea/ 3 | .gradle/ 4 | *.iml 5 | local.properties 6 | app/build/ 7 | build/ 8 | captures/ 9 | .DS_Store 10 | plugins/ 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/splash.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-hdpi/splash.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/splash.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-mdpi/splash.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/splash.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-xhdpi/splash.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/splash.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-xxhdpi/splash.9.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-sw600dp/attr.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_actionbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-hdpi/ic_actionbar.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_actionbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-mdpi/ic_actionbar.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/splash.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-xxxhdpi/splash.9.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m 2 | android.enableJetifier=true 3 | android.useAndroidX=true 4 | enableLogsInRelease=false -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/drawer_shadow.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-hdpi/drawer_shadow.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-hdpi/ic_notification.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/drawer_shadow.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-mdpi/drawer_shadow.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-mdpi/ic_notification.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-night-hdpi/splash.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-night-hdpi/splash.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-night-mdpi/splash.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-night-mdpi/splash.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-night-xhdpi/splash.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-night-xhdpi/splash.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_actionbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-xhdpi/ic_actionbar.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_actionbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-xxhdpi/ic_actionbar.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_actionbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-xxxhdpi/ic_actionbar.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_sidebar_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/mipmap-hdpi/ic_sidebar_logo.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_sidebar_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/mipmap-mdpi/ic_sidebar_logo.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_sidebar_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/mipmap-xhdpi/ic_sidebar_logo.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_sidebar_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/mipmap-xxhdpi/ic_sidebar_logo.png -------------------------------------------------------------------------------- /app/src/androidTest/assets/HelperClass.java: -------------------------------------------------------------------------------- 1 | package io.gonative.android; 2 | 3 | public class HelperClass { 4 | public volatile static int newLoad = 0; 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-night-xxhdpi/splash.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-night-xxhdpi/splash.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-night-xxxhdpi/splash.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-night-xxxhdpi/splash.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/drawer_shadow.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-xhdpi/drawer_shadow.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-xhdpi/ic_notification.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/drawer_shadow.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-xxhdpi/drawer_shadow.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-xxhdpi/ic_notification.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_sidebar_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_sidebar_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_chevron_up_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-hdpi/ic_chevron_up_light.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_search_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-hdpi/ic_search_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_search_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-hdpi/ic_search_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_share_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-hdpi/ic_share_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_share_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-hdpi/ic_share_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_chevron_up_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-mdpi/ic_chevron_up_light.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_search_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-mdpi/ic_search_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_search_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-mdpi/ic_search_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_share_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-mdpi/ic_share_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_share_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-mdpi/ic_share_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-night-hdpi/ic_actionbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-night-hdpi/ic_actionbar.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-night-mdpi/ic_actionbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-night-mdpi/ic_actionbar.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-night-xhdpi/ic_actionbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-night-xhdpi/ic_actionbar.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-night-xxhdpi/ic_actionbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-night-xxhdpi/ic_actionbar.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_chevron_up_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-xhdpi/ic_chevron_up_light.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_share_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-xhdpi/ic_share_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_share_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-xhdpi/ic_share_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-xxxhdpi/ic_notification.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-night-hdpi/ic_sidebar_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/mipmap-night-hdpi/ic_sidebar_logo.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-night-mdpi/ic_sidebar_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/mipmap-night-mdpi/ic_sidebar_logo.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-night-xhdpi/ic_sidebar_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/mipmap-night-xhdpi/ic_sidebar_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_chevron_down_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-hdpi/ic_chevron_down_light.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_refresh_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-hdpi/ic_refresh_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_refresh_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-hdpi/ic_refresh_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_chevron_down_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-mdpi/ic_chevron_down_light.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_refresh_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-mdpi/ic_refresh_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_refresh_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-mdpi/ic_refresh_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-night-xxxhdpi/ic_actionbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-night-xxxhdpi/ic_actionbar.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_chevron_down_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-xhdpi/ic_chevron_down_light.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_refresh_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-xhdpi/ic_refresh_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_refresh_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-xhdpi/ic_refresh_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_search_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-xhdpi/ic_search_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_search_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-xhdpi/ic_search_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_chevron_up_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-xxhdpi/ic_chevron_up_light.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_search_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-xxhdpi/ic_search_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_search_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-xxhdpi/ic_search_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_share_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-xxhdpi/ic_share_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_share_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-xxhdpi/ic_share_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_share_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-xxxhdpi/ic_share_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_share_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-xxxhdpi/ic_share_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/menu/topmenu.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-night-xxhdpi/ic_sidebar_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/mipmap-night-xxhdpi/ic_sidebar_logo.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-night-xxxhdpi/ic_sidebar_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/mipmap-night-xxxhdpi/ic_sidebar_logo.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/java/io/gonative/android/JsResultBridge.java: -------------------------------------------------------------------------------- 1 | package io.gonative.android; 2 | 3 | public class JsResultBridge { 4 | public static String jsResult = ""; 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_chevron_down_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-xxhdpi/ic_chevron_down_light.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_refresh_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-xxhdpi/ic_refresh_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_refresh_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-xxhdpi/ic_refresh_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_refresh_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-xxxhdpi/ic_refresh_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_refresh_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-xxxhdpi/ic_refresh_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_search_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-xxxhdpi/ic_search_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_search_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/drawable-xxxhdpi/ic_search_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'GoNative' 2 | System.setProperty("user.dir", rootProject.projectDir.toString()) 3 | apply from: file("./plugins.gradle"); applyModulesSettingsGradle(settings) 4 | 5 | include ':app' 6 | -------------------------------------------------------------------------------- /app/src/main/res/layout/empty.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | -------------------------------------------------------------------------------- /app/src/androidTest/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values-sw600dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/anim/fast_fade_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Feb 18 14:21:02 EST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip 7 | -------------------------------------------------------------------------------- /app/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_stat_onesignal_default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/java/io/gonative/android/WebViewPoolDisownPolicy.java: -------------------------------------------------------------------------------- 1 | package io.gonative.android; 2 | 3 | /** 4 | * Created by weiyin on 9/3/14. 5 | */ 6 | public enum WebViewPoolDisownPolicy { 7 | Always, Reload, Never; 8 | 9 | public static WebViewPoolDisownPolicy defaultPolicy = WebViewPoolDisownPolicy.Reload; 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_nav_icon.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/values-sw720dp-land/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 128dp 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/tab.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /app/src/main/res/values-large/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_go_back.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_go_forward.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/xml/filepaths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/integers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 4 | 20 5 | 2 6 | 22 7 | 22 8 | 48 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_arrow_forward_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_arrow_back_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/layout/profile_picker_dropdown.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_rounded.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Archive Notice 2 | 3 | This repository is archived and is no longer being maintained. You can now build an app using our online App Studio at https://median.co/app and download custom iOS source code for free. 4 | 5 | 6 | gonative-android 7 | ================ 8 | 9 | This is the native Android code previously used by [GoNative](https://median.co). 10 | 11 | It allows the creation of apps from existing mobile-optimized websites. 12 | 13 | How to use 14 | ------------ 15 | Import into Android Studio. Edit appConfig.json as appropriate. 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/button_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 25 | 26 | 48 | 49 | -------------------------------------------------------------------------------- /app/src/main/java/io/gonative/android/SplashActivity.java: -------------------------------------------------------------------------------- 1 | package io.gonative.android; 2 | 3 | import android.Manifest; 4 | import android.content.Intent; 5 | import android.content.pm.PackageManager; 6 | import android.graphics.Color; 7 | import android.os.Bundle; 8 | 9 | import android.os.Handler; 10 | import android.os.Looper; 11 | import android.view.View; 12 | 13 | import androidx.annotation.NonNull; 14 | import androidx.core.app.ActivityCompat; 15 | import androidx.appcompat.app.AppCompatActivity; 16 | 17 | import java.util.HashSet; 18 | 19 | import androidx.core.content.ContextCompat; 20 | import androidx.core.splashscreen.SplashScreen; 21 | 22 | import io.gonative.gonative_core.AppConfig; 23 | 24 | public class SplashActivity extends AppCompatActivity { 25 | private static final int REQUEST_STARTUP_PERMISSIONS = 100; 26 | 27 | @Override 28 | protected void onCreate(Bundle savedInstanceState) { 29 | SplashScreen.installSplashScreen(this); 30 | super.onCreate(savedInstanceState); 31 | AppConfig config = AppConfig.getInstance(this); 32 | 33 | getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); 34 | getWindow().setStatusBarColor(Color.TRANSPARENT); 35 | getWindow().setNavigationBarColor(Color.TRANSPARENT); 36 | setContentView(R.layout.splash_screen); 37 | 38 | HashSet permissionsToRequest = new HashSet<>(); 39 | 40 | if (LeanWebView.isCrosswalk()) { 41 | if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED){ 42 | permissionsToRequest.add(Manifest.permission.READ_CONTACTS); 43 | } 44 | if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_CONTACTS) != PackageManager.PERMISSION_GRANTED){ 45 | permissionsToRequest.add(Manifest.permission.WRITE_CONTACTS); 46 | } 47 | } 48 | 49 | if (permissionsToRequest.isEmpty()) { 50 | handleSplash(config); 51 | } else { 52 | ActivityCompat.requestPermissions(this, 53 | permissionsToRequest.toArray(new String[]{}), 54 | REQUEST_STARTUP_PERMISSIONS); 55 | } 56 | } 57 | 58 | @Override 59 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 60 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 61 | startMainActivity(); 62 | } 63 | 64 | private void handleSplash(AppConfig config){ 65 | // Check app if unlicensed and show banner 66 | if (!config.isLicensed()) { 67 | findViewById(R.id.banner_text).setVisibility(View.VISIBLE); 68 | } 69 | 70 | Handler handler = new Handler(Looper.getMainLooper()); 71 | 72 | // handle splash 73 | double delay = 1.5; // in seconds 74 | double forceTime = config.showSplashForceTime; 75 | double maxTime = config.showSplashMaxTime; 76 | if (forceTime > 0) { 77 | delay = forceTime; 78 | } else if (maxTime > 0) { 79 | delay = maxTime; 80 | } 81 | handler.postDelayed(this::startMainActivity, (long)delay * 1000); 82 | } 83 | 84 | private void startMainActivity() { 85 | Intent intent = new Intent(this, MainActivity.class); 86 | // Make MainActivity think it was started from launcher 87 | intent.addCategory(Intent.CATEGORY_LAUNCHER); 88 | intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); 89 | startActivity(intent); 90 | finish(); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /app/src/main/java/io/gonative/android/GoNativeApplication.java: -------------------------------------------------------------------------------- 1 | package io.gonative.android; 2 | 3 | import android.os.Message; 4 | import android.webkit.ValueCallback; 5 | import android.widget.Toast; 6 | 7 | import androidx.appcompat.app.AppCompatDelegate; 8 | import androidx.multidex.MultiDexApplication; 9 | 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | import io.gonative.gonative_core.AppConfig; 14 | import io.gonative.gonative_core.Bridge; 15 | import io.gonative.gonative_core.BridgeModule; 16 | import io.gonative.gonative_core.GNLog; 17 | 18 | /** 19 | * Created by weiyin on 9/2/15. 20 | * Copyright 2014 GoNative.io LLC 21 | */ 22 | public class GoNativeApplication extends MultiDexApplication { 23 | 24 | private LoginManager loginManager; 25 | private RegistrationManager registrationManager; 26 | private WebViewPool webViewPool; 27 | private Message webviewMessage; 28 | private ValueCallback webviewValueCallback; 29 | private GoNativeWindowManager goNativeWindowManager; 30 | private List plugins; 31 | private final static String TAG = GoNativeApplication.class.getSimpleName(); 32 | public final Bridge mBridge = new Bridge(this) { 33 | @Override 34 | protected List getPlugins() { 35 | if (GoNativeApplication.this.plugins == null) { 36 | GoNativeApplication.this.plugins = new PackageList(GoNativeApplication.this).getPackages(); 37 | } 38 | 39 | return GoNativeApplication.this.plugins; 40 | } 41 | }; 42 | 43 | @Override 44 | public void onCreate() { 45 | super.onCreate(); 46 | AppCompatDelegate.setCompatVectorFromResourcesEnabled(true); 47 | 48 | mBridge.onApplicationCreate(this); 49 | 50 | AppConfig appConfig = AppConfig.getInstance(this); 51 | if (appConfig.configError != null) { 52 | Toast.makeText(this, "Invalid appConfig json", Toast.LENGTH_LONG).show(); 53 | GNLog.getInstance().logError(TAG, "AppConfig error", appConfig.configError); 54 | } 55 | 56 | this.loginManager = new LoginManager(this); 57 | 58 | if (appConfig.registrationEndpoints != null) { 59 | this.registrationManager = new RegistrationManager(this); 60 | registrationManager.processConfig(appConfig.registrationEndpoints); 61 | } 62 | 63 | // some global webview setup 64 | WebViewSetup.setupWebviewGlobals(this); 65 | 66 | webViewPool = new WebViewPool(); 67 | 68 | goNativeWindowManager = new GoNativeWindowManager(); 69 | } 70 | 71 | public LoginManager getLoginManager() { 72 | return loginManager; 73 | } 74 | 75 | public RegistrationManager getRegistrationManager() { 76 | return registrationManager; 77 | } 78 | 79 | public WebViewPool getWebViewPool() { 80 | return webViewPool; 81 | } 82 | 83 | public Message getWebviewMessage() { 84 | return webviewMessage; 85 | } 86 | 87 | public void setWebviewMessage(Message webviewMessage) { 88 | this.webviewMessage = webviewMessage; 89 | } 90 | 91 | public Map getAnalyticsProviderInfo() { 92 | return mBridge.getAnalyticsProviderInfo(); 93 | } 94 | 95 | // Needed for Crosswalk 96 | @SuppressWarnings("unused") 97 | public ValueCallback getWebviewValueCallback() { 98 | return webviewValueCallback; 99 | } 100 | 101 | public void setWebviewValueCallback(ValueCallback webviewValueCallback) { 102 | this.webviewValueCallback = webviewValueCallback; 103 | } 104 | 105 | public GoNativeWindowManager getWindowManager() { 106 | return goNativeWindowManager; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | 16 | 20 | 21 | 22 | 23 | 24 | 25 | 32 | 33 | 40 | 41 | 46 | 47 | 49 | 50 | 53 | 54 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/gonative/testFiles/FirstTestClass.java: -------------------------------------------------------------------------------- 1 | package com.gonative.testFiles; 2 | 3 | import android.webkit.WebView; 4 | import androidx.test.ext.junit.rules.ActivityScenarioRule; 5 | import androidx.test.ext.junit.runners.AndroidJUnit4; 6 | import androidx.test.filters.LargeTest; 7 | import androidx.test.filters.SdkSuppress; 8 | import androidx.test.uiautomator.UiDevice; 9 | import org.json.JSONException; 10 | import org.junit.Before; 11 | import org.junit.Rule; 12 | import org.junit.Test; 13 | import org.junit.runner.RunWith; 14 | import io.gonative.android.MainActivity; 15 | import io.gonative.android.R; 16 | import io.gonative.gonative_core.AppConfig; 17 | import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; 18 | 19 | @RunWith(AndroidJUnit4.class) 20 | @SdkSuppress(minSdkVersion = 18) 21 | @LargeTest 22 | public class FirstTestClass{ 23 | TestMethods testMethods; 24 | AppConfig appConfig; 25 | WebView webView; 26 | private UiDevice uiDevice; 27 | 28 | @Rule 29 | public ActivityScenarioRule activityScenarioRule = new ActivityScenarioRule<>(MainActivity.class); 30 | 31 | @Before 32 | public void initMethod() throws InterruptedException { 33 | for(int i = 0; i < 10; i++){ 34 | try{ 35 | uiDevice = UiDevice.getInstance(getInstrumentation()); 36 | }catch (RuntimeException runtimeException){ 37 | Thread.sleep(2000); 38 | continue; 39 | } 40 | Thread.sleep(1000); 41 | break; 42 | } 43 | activityScenarioRule.getScenario().onActivity(activity -> { 44 | appConfig = AppConfig.getInstance(activity); 45 | webView = activity.findViewById(R.id.webview); 46 | testMethods = new TestMethods(activity, webView); 47 | }); 48 | } 49 | 50 | //Sidebar Navigation Test 51 | @Test 52 | public void testSidebarNavigation() throws InterruptedException, JSONException { 53 | if(appConfig.showNavigationMenu && (appConfig.menus.get("default") != null)){ 54 | if(appConfig.menus.get("default") == null) throw new RuntimeException("Navigation drawer list not found."); 55 | else { 56 | testMethods.waitForPageLoaded(); 57 | testMethods.testNavigation(appConfig.menus.get("default")); 58 | } 59 | } 60 | } 61 | 62 | //Tab Menu Navigation Test 63 | @Test 64 | public void testTabMenuNavigation() throws JSONException, InterruptedException { 65 | if(appConfig.tabMenuRegexes.size() == 0) throw new RuntimeException("No Tab Menus found."); 66 | else{ 67 | testMethods.waitForPageLoaded(); 68 | testMethods.m_testTabNavigation(appConfig.tabMenus, appConfig.tabMenuRegexes); 69 | } 70 | } 71 | 72 | //Internal vs External Links Test 73 | @Test 74 | public void testIvE() throws InterruptedException { 75 | testMethods.waitForPageLoaded(); 76 | testMethods.testInternalvExternalLinks(uiDevice); 77 | } 78 | 79 | //Pull to Refresh Test 80 | @Test 81 | public void pullToRefresh() throws InterruptedException { 82 | if(appConfig.pullToRefresh){ 83 | testMethods.waitForPageLoaded(); 84 | testMethods.testPullToRefresh(); 85 | } 86 | } 87 | 88 | //Search Button Test 89 | @Test 90 | public void testSearch() throws InterruptedException { 91 | if(appConfig.searchTemplateUrl != null && !appConfig.searchTemplateUrl.isEmpty()){ 92 | testMethods.waitForPageLoaded(); 93 | testMethods.testSearchButton(); 94 | } 95 | } 96 | 97 | //Refresh Button Test 98 | @Test 99 | public void testRefreshButton() throws InterruptedException { 100 | if(appConfig.showRefreshButton){ 101 | testMethods.waitForPageLoaded(); 102 | testMethods.testRefreshButton(); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /.github/workflows/firebase-testing.yml: -------------------------------------------------------------------------------- 1 | name: firebase-testing 2 | on: [push, workflow_dispatch] 3 | jobs: 4 | test-app: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v2 8 | - name: Set up gcloud Cloud SDK environment 9 | # You may pin to the exact commit or the version. 10 | # uses: google-github-actions/setup-gcloud@94337306dda8180d967a56932ceb4ddcf01edae7 11 | uses: google-github-actions/setup-gcloud@v0.2.0 12 | with: 13 | # Version of the gcloud SDK to install. If unspecified or set to "latest", the latest available gcloud SDK version for the target platform will be installed. Example: "290.0.1". 14 | version: latest 15 | # Service account email address to use for authentication. This is required for legacy .p12 keys but can be omitted for .json keys. This is usually of the format @.iam.gserviceaccount.com. 16 | 17 | # Service account key to use for authentication. This should be the JSON formatted private key which can be exported from the Cloud Console. The value can be raw or base64-encoded. 18 | service_account_key: ${{ secrets.GCLOUD_KEY }} 19 | # ID of the Google Cloud project. If provided, this will configure gcloud to use this project ID by default for commands. Individual commands can still override the project using the --project flag which takes precedence. 20 | project_id: gn-test-firebase-test-lab 21 | # Export the provided credentials as Google Default Application Credentials. This will make the credentials available to later steps via the GOOGLE_APPLICATION_CREDENTIALS environment variable. Future steps that consume Default Application Credentials will automatically detect and use these credentials. 22 | export_default_credentials: true 23 | 24 | - name: Make gradlew executable 25 | run: chmod +x ./gradlew 26 | 27 | - name: Copying the appConfig file from androidTest to main 28 | run: cp -f ./app/src/androidTest/assets/appConfig.json ./app/src/main/assets/appConfig.json 29 | 30 | - name: Add dependencies in app/build.gradle 31 | run: sed -i "s/dependencies *\n*{/dependencies {\nandroidTestImplementation 'androidx.test.ext:junit:1.1.2'\nandroidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'\nandroidTestImplementation 'androidx.test.espresso:espresso-contrib:3.3.0'\nandroidTestImplementation 'androidx.test.espresso:espresso-web:3.3.0'\nandroidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'\n/" ./app/build.gradle 32 | 33 | - name: Add test runner in defaultConfig in app/build.gradle 34 | run: sed -i "s/defaultConfig *\n*{/defaultConfig {\ntestInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'/" ./app/build.gradle 35 | 36 | - name: Adding Helper method in GoNativeWebviewClient.java 37 | run: sed -i "s/super.onPageFinished *( *view *, *url *) *;/super.onPageFinished(view, url);\nHelperClass.newLoad++;/" ./app/src/normal/java/io/gonative/android/GoNativeWebviewClient.java 38 | 39 | - name: Copying HelperClass.java from androidTest/assets to normal/java/io/gonative/android/ 40 | run: cp -f ./app/src/androidTest/assets/HelperClass.java ./app/src/normal/java/io/gonative/android/HelperClass.java 41 | 42 | - name: Build the App 43 | run: ./gradlew assembleDebug assembleAndroidTest 44 | 45 | - name: Testing the App 46 | run: gcloud firebase test android run --type instrumentation --app ./app/build/outputs/apk/normal/debug/app-normal-debug.apk --test ./app/build/outputs/apk/androidTest/normal/debug/app-normal-debug-androidTest.apk --device model=flo,version=21,locale=en,orientation=portrait --device model=hammerhead,version=23,locale=en,orientation=portrait --device model=griffin,version=24,locale=en,orientation=portrait --device model=G8142,version=25,locale=en,orientation=portrait --device model=star2qlteue,version=26,locale=en,orientation=portrait --device model=walleye,version=27,locale=en,orientation=portrait --device model=OnePlus5T,version=28,locale=en,orientation=portrait --device model=x1q,version=29,locale=en,orientation=portrait --device model=flame,version=30,locale=en,orientation=portrait --results-bucket cloud-test-gn-test-firebase-test-lab --timeout 300s 47 | -------------------------------------------------------------------------------- /app/src/main/java/io/gonative/android/SegmentedController.java: -------------------------------------------------------------------------------- 1 | package io.gonative.android; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.content.IntentFilter; 7 | import androidx.localbroadcastmanager.content.LocalBroadcastManager; 8 | import android.view.View; 9 | import android.widget.AdapterView; 10 | import android.widget.ArrayAdapter; 11 | import android.widget.Spinner; 12 | 13 | import org.json.JSONObject; 14 | 15 | import java.util.ArrayList; 16 | 17 | import io.gonative.gonative_core.AppConfig; 18 | 19 | /** 20 | * Created by weiyin on 12/20/15. 21 | * Copyright 2014 GoNative.io LLC 22 | */ 23 | public class SegmentedController implements AdapterView.OnItemSelectedListener { 24 | private MainActivity mainActivity; 25 | private ArrayList labels; 26 | private ArrayList urls; 27 | private int selectedIndex; 28 | 29 | private ArrayAdapter adapter; 30 | private Spinner spinner; 31 | 32 | SegmentedController(MainActivity mainActivity, Spinner spinner) { 33 | this.mainActivity = mainActivity; 34 | this.spinner = spinner; 35 | 36 | this.labels = new ArrayList<>(); 37 | this.urls = new ArrayList<>(); 38 | 39 | this.spinner.setAdapter(getAdapter()); 40 | this.spinner.setOnItemSelectedListener(this); 41 | 42 | BroadcastReceiver messageReceiver = new BroadcastReceiver() { 43 | @Override 44 | public void onReceive(Context context, Intent intent) { 45 | if (intent == null || intent.getAction() == null) return; 46 | 47 | if (intent.getAction().equals(AppConfig.PROCESSED_SEGMENTED_CONTROL)) { 48 | updateSegmentedControl(); 49 | } 50 | } 51 | }; 52 | LocalBroadcastManager.getInstance(this.mainActivity).registerReceiver( 53 | messageReceiver, new IntentFilter(AppConfig.PROCESSED_SEGMENTED_CONTROL)); 54 | 55 | updateSegmentedControl(); 56 | } 57 | 58 | private void updateSegmentedControl() { 59 | this.labels.clear(); 60 | this.urls.clear(); 61 | this.selectedIndex = -1; 62 | 63 | AppConfig appConfig = AppConfig.getInstance(mainActivity); 64 | if (appConfig.segmentedControl == null) return; 65 | 66 | for (int i = 0; i < appConfig.segmentedControl.size(); i++) { 67 | JSONObject item = appConfig.segmentedControl.get(i); 68 | 69 | String label = item.optString("label", "Invalid"); 70 | String url = item.optString("url", ""); 71 | Boolean selected = item.optBoolean("selected"); 72 | 73 | this.labels.add(i, label); 74 | this.urls.add(i, url); 75 | if (selected) this.selectedIndex = i; 76 | } 77 | 78 | mainActivity.runOnUiThread(new Runnable() { 79 | @Override 80 | public void run() { 81 | if (selectedIndex > -1) { 82 | spinner.setSelection(selectedIndex); 83 | } 84 | 85 | if (labels.size() > 0) { 86 | spinner.setVisibility(View.VISIBLE); 87 | } else { 88 | spinner.setVisibility(View.GONE); 89 | } 90 | 91 | adapter.notifyDataSetChanged(); 92 | } 93 | }); 94 | 95 | } 96 | 97 | private ArrayAdapter getAdapter() { 98 | if (this.adapter != null) { 99 | return this.adapter; 100 | } 101 | 102 | ArrayAdapter adapter = new ArrayAdapter<>(mainActivity, 103 | android.R.layout.simple_spinner_item, labels); 104 | adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 105 | this.adapter = adapter; 106 | return adapter; 107 | } 108 | 109 | @Override 110 | public void onItemSelected(AdapterView parent, View view, int position, long id) { 111 | // only load if selection has changed 112 | if (position != selectedIndex) { 113 | String url = urls.get(position); 114 | 115 | if (url != null && url.length() > 0) { 116 | mainActivity.loadUrl(url); 117 | } 118 | 119 | mainActivity.closeDrawers(); 120 | selectedIndex = position; 121 | } 122 | } 123 | 124 | @Override 125 | public void onNothingSelected(AdapterView parent) { 126 | // do nothing 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /app/src/main/java/io/gonative/android/ProfilePicker.java: -------------------------------------------------------------------------------- 1 | package io.gonative.android; 2 | 3 | import androidx.annotation.NonNull; 4 | import android.view.View; 5 | import android.view.ViewGroup; 6 | import android.webkit.JavascriptInterface; 7 | import android.widget.AdapterView; 8 | import android.widget.ArrayAdapter; 9 | import android.widget.Spinner; 10 | import android.widget.TextView; 11 | 12 | import org.json.JSONArray; 13 | import org.json.JSONException; 14 | import org.json.JSONObject; 15 | 16 | import java.util.ArrayList; 17 | 18 | import io.gonative.gonative_core.GNLog; 19 | 20 | /** 21 | * Created by weiyin on 5/9/14. 22 | */ 23 | public class ProfilePicker implements AdapterView.OnItemSelectedListener { 24 | private static final String TAG = ProfilePicker.class.getName(); 25 | 26 | private MainActivity mainActivity; 27 | private JSONArray json; 28 | private ArrayList names; 29 | private ArrayList links; 30 | private int selectedIndex; 31 | 32 | private ArrayAdapter adapter; 33 | private Spinner spinner; 34 | private ProfileJsBridge profileJsBridge; 35 | 36 | public ProfilePicker(MainActivity mainActivity, Spinner spinner) { 37 | this.mainActivity = mainActivity; 38 | this.spinner = spinner; 39 | this.names = new ArrayList<>(); 40 | this.links = new ArrayList<>(); 41 | this.spinner.setAdapter(getAdapter()); 42 | this.spinner.setOnItemSelectedListener(this); 43 | this.profileJsBridge = new ProfileJsBridge(); 44 | } 45 | 46 | private void parseJson(String s){ 47 | try { 48 | json = new JSONArray(s); 49 | this.names.clear(); 50 | this.links.clear(); 51 | 52 | for (int i = 0; i < json.length(); i++) { 53 | JSONObject item = json.getJSONObject(i); 54 | 55 | this.names.add(item.optString("name", "")); 56 | this.links.add(item.optString("link", "")); 57 | 58 | if (item.optBoolean("selected", false)){ 59 | selectedIndex = i; 60 | } 61 | } 62 | 63 | mainActivity.runOnUiThread(new Runnable() { 64 | public void run() { 65 | if (selectedIndex < ProfilePicker.this.names.size()) { 66 | ProfilePicker.this.spinner.setSelection(selectedIndex); 67 | } 68 | if (ProfilePicker.this.json != null && 69 | ProfilePicker.this.json.length() > 0) 70 | ProfilePicker.this.spinner.setVisibility(View.VISIBLE); 71 | else 72 | ProfilePicker.this.spinner.setVisibility(View.GONE); 73 | getAdapter().notifyDataSetChanged(); 74 | } 75 | }); 76 | 77 | } catch (JSONException e) { 78 | GNLog.getInstance().logError(TAG, e.getMessage(), e); 79 | } 80 | } 81 | 82 | private ArrayAdapter getAdapter(){ 83 | if (adapter == null) { 84 | 85 | adapter = new ArrayAdapter(mainActivity, R.layout.profile_picker_dropdown, names) { 86 | @NonNull 87 | @Override 88 | public View getView(int position, View convertView, @NonNull ViewGroup parent) { 89 | TextView view = (TextView) super.getView(position, convertView, parent); 90 | view.setTextColor(mainActivity.getResources().getColor(R.color.sidebarForeground)); 91 | return view; 92 | } 93 | 94 | @Override 95 | public View getDropDownView(int position, View convertView, @NonNull ViewGroup parent) { 96 | TextView view = (TextView) super.getDropDownView(position, convertView, parent); 97 | view.setTextColor(mainActivity.getResources().getColor(R.color.sidebarForeground)); 98 | return view; 99 | } 100 | }; 101 | } 102 | 103 | return adapter; 104 | } 105 | 106 | public void onItemSelected(AdapterView parent, View view, int position, long id) { 107 | // only load if selection has changed 108 | if (position != selectedIndex) { 109 | mainActivity.loadUrl(links.get(position)); 110 | mainActivity.closeDrawers(); 111 | selectedIndex = position; 112 | } 113 | } 114 | 115 | public void onNothingSelected(AdapterView parent) { 116 | // do nothing 117 | } 118 | 119 | public ProfileJsBridge getProfileJsBridge() { 120 | return profileJsBridge; 121 | } 122 | 123 | public class ProfileJsBridge { 124 | @JavascriptInterface 125 | public void parseJson(String s) { 126 | ProfilePicker.this.parseJson(s); 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /app/src/androidTest/assets/appConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "general": { 3 | "userAgentAdd": "gonative", 4 | "initialUrl": "https://gonative.io", 5 | "appName": "GoNative.io" 6 | }, 7 | "navigation": { 8 | "androidPullToRefresh": true, 9 | "sidebarNavigation": { 10 | "sidebarEnabledRegex": null, 11 | "menus": [{ 12 | "name": "default", 13 | "items": [{ 14 | "url": "https://gonative.io", 15 | "label": "Home", 16 | "subLinks": [] 17 | }, { 18 | "url": "https://gonative.io/about", 19 | "label": "About", 20 | "subLinks": [] 21 | }, { 22 | "url": "https://gonative.io/examples", 23 | "label": "Examples", 24 | "subLinks": [] 25 | }], 26 | "active": true 27 | }] 28 | }, 29 | "tabNavigation": { 30 | "tabSelectionConfig": [{ 31 | "id": "1", 32 | "regex": ".*about.*" 33 | }], 34 | "tabMenus": [{ 35 | "id": "1", 36 | "items": [{ 37 | "icon": "fa-cloud", 38 | "label": "Tab 1", 39 | "url": "https://www.gonative.io/pricing" 40 | }, { 41 | "icon": "fa-globe", 42 | "label": "Tab 2", 43 | "url": "https://www.gonative.io/examples" 44 | }, { 45 | "icon": "fa-users", 46 | "label": "Tab 3", 47 | "url": "javascript:alert('You selected tab 3. These tabs are only shown on the about page')" 48 | }] 49 | }], 50 | "active": true 51 | }, 52 | "actionConfig": { 53 | "active": true, 54 | "actions": [{ 55 | "id": "exampleActions", 56 | "items": [{ 57 | "label": "Globe", 58 | "icon": "fa-globe", 59 | "url": "javascript:alert('You tapped the globe! It only appears on the Examples page')" 60 | }] 61 | }], 62 | "actionSelection": [{ 63 | "regex": ".*/examples.*", 64 | "id": "exampleActions" 65 | }] 66 | }, 67 | "regexInternalExternal": { 68 | "rules": [{ 69 | "regex": "https?://([-\\w]+\\.)*facebook\\.com/login.php.*", 70 | "internal": true 71 | }, { 72 | "regex": "https?://([-\\w]+\\.)*facebook\\.com/pages/.*", 73 | "internal": false 74 | }, { 75 | "regex": "https?://([-\\w]+\\.)*facebook\\.com/sharer\\.php.*", 76 | "internal": false 77 | }, { 78 | "regex": "https?://([-\\w]+\\.)*plus\\.google\\.com/share.*", 79 | "internal": false 80 | }, { 81 | "regex": "https?://([-\\w]+\\.)*twitter\\.com/intent/.*", 82 | "internal": false 83 | }, { 84 | "regex": "https?://([-\\w]+\\.)*gonative\\.io/?.*", 85 | "internal": true 86 | }, { 87 | "regex": "https?://([-\\w]+\\.)*google\\.com/?.*", 88 | "internal": true 89 | }, { 90 | "regex": "https://gonative-test-web.web.app/.*", 91 | "internal": true 92 | }, 93 | { 94 | "regex": "https://us-central1-gn-test-firebase-test-lab.cloudfunctions.net/.*", 95 | "internal": true 96 | }], 97 | "active": true 98 | }, 99 | "redirects": [{ 100 | "from": "https://example.com/from/", 101 | "to": "https://example.com/to/" 102 | }] 103 | }, 104 | "forms": { 105 | "search": { 106 | "active": true, 107 | "searchTemplateURL": "https://us-central1-gn-test-firebase-test-lab.cloudfunctions.net/gnTestSearch?q=" 108 | } 109 | }, 110 | "styling": { 111 | "showActionBar": true, 112 | "showNavigationBar": true, 113 | "iosTitleColor": "#333333", 114 | "iosTintColor": "#0091fe", 115 | "androidTheme": "Light.DarkActionBar", 116 | "androidSidebarBackgroundColor": "#111111", 117 | "androidSidebarForegroundColor": "#d0d0d0", 118 | "androidHideTitleInActionBar": false, 119 | "androidPullToRefreshColor": "#333333", 120 | "androidTabBarBackgroundColor": "#fefefe", 121 | "androidTabBarTextColor": "#747474", 122 | "androidTabBarIndicatorColorx": "#2f79fe", 123 | "androidShowSplash": true, 124 | "androidShowSplashMaxTime": null, 125 | "androidShowSplashForceTime": null, 126 | "disableAnimations": false, 127 | "menuAnimationDuration": 0.15, 128 | "transitionInteractiveDelayMax": 0.2 129 | }, 130 | "permissions": { 131 | "usesGeolocation": false, 132 | "androidDownloadToPublicStorage": false 133 | }, 134 | "services": { 135 | "oneSignal": { 136 | "active": false, 137 | "applicationId": "" 138 | }, 139 | "facebook": { 140 | "active": false, 141 | "appId": "", 142 | "displayName": "" 143 | }, 144 | "registration": { 145 | "active": false, 146 | "endpoints": [{ 147 | "url": "https://gonative.io/example_push_endpoint", 148 | "dataType": "onesignal", 149 | "urlRegex": ".*/loginfinished" 150 | }] 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /app/src/normal/java/io/gonative/android/GoNativeWebviewClient.java: -------------------------------------------------------------------------------- 1 | package io.gonative.android; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.graphics.Bitmap; 6 | import android.net.Uri; 7 | import android.net.http.SslError; 8 | import android.os.Build; 9 | import android.os.Message; 10 | import android.webkit.ClientCertRequest; 11 | import android.webkit.SslErrorHandler; 12 | import android.webkit.WebResourceError; 13 | import android.webkit.WebResourceRequest; 14 | import android.webkit.WebResourceResponse; 15 | import android.webkit.WebView; 16 | import android.webkit.WebViewClient; 17 | 18 | import androidx.annotation.RequiresApi; 19 | 20 | import io.gonative.gonative_core.GoNativeWebviewInterface; 21 | 22 | /** 23 | * Created by weiyin on 9/9/15. 24 | */ 25 | public class GoNativeWebviewClient extends WebViewClient{ 26 | private static final String TAG = GoNativeWebviewClient.class.getName(); 27 | private UrlNavigation urlNavigation; 28 | private Context context; 29 | 30 | public GoNativeWebviewClient(MainActivity mainActivity, UrlNavigation urlNavigation) { 31 | this.urlNavigation = urlNavigation; 32 | this.context = mainActivity; 33 | } 34 | 35 | @Override 36 | public boolean shouldOverrideUrlLoading(WebView view, String url) { 37 | return urlNavigation.shouldOverrideUrlLoading((GoNativeWebviewInterface)view, url); 38 | } 39 | 40 | public boolean shouldOverrideUrlLoading(WebView view, String url, boolean isReload) { 41 | return urlNavigation.shouldOverrideUrlLoading((GoNativeWebviewInterface)view, url, isReload, false); 42 | } 43 | 44 | @Override 45 | public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { 46 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 47 | Uri uri = request.getUrl(); 48 | return urlNavigation.shouldOverrideUrlLoading((GoNativeWebviewInterface)view, uri.toString(), false, request.isRedirect()); 49 | } 50 | return super.shouldOverrideUrlLoading(view, request); 51 | } 52 | 53 | @Override 54 | public void onPageStarted(WebView view, String url, Bitmap favicon) { 55 | super.onPageStarted(view, url, favicon); 56 | 57 | urlNavigation.onPageStarted(url); 58 | } 59 | 60 | @Override 61 | public void onPageFinished(WebView view, String url) { 62 | super.onPageFinished(view, url); 63 | 64 | urlNavigation.onPageFinished((GoNativeWebviewInterface)view, url); 65 | } 66 | 67 | @Override 68 | public void onFormResubmission(WebView view, Message dontResend, Message resend) { 69 | urlNavigation.onFormResubmission((GoNativeWebviewInterface)view, dontResend, resend); 70 | } 71 | 72 | @Override 73 | public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) { 74 | urlNavigation.doUpdateVisitedHistory((GoNativeWebviewInterface)view, url, isReload); 75 | } 76 | 77 | @Override 78 | public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { 79 | urlNavigation.onReceivedError((GoNativeWebviewInterface) view, errorCode, description, failingUrl); 80 | } 81 | 82 | @TargetApi(Build.VERSION_CODES.M) 83 | @Override 84 | public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) { 85 | urlNavigation.onReceivedError((GoNativeWebviewInterface) view, error.getErrorCode(), 86 | error.getDescription().toString(), request.getUrl().toString()); 87 | } 88 | 89 | @Override 90 | public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { 91 | handler.cancel(); 92 | urlNavigation.onReceivedSslError(error, view.getUrl()); 93 | } 94 | 95 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) 96 | @Override 97 | public void onReceivedClientCertRequest(WebView view, ClientCertRequest request) { 98 | urlNavigation.onReceivedClientCertRequest(view.getUrl(), request); 99 | } 100 | 101 | @Override 102 | public WebResourceResponse shouldInterceptRequest(WebView view, String url) { 103 | return urlNavigation.interceptHtml((LeanWebView)view, url); 104 | } 105 | 106 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 107 | @Override 108 | public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { 109 | 110 | WebResourceResponse wr = ((GoNativeApplication) context.getApplicationContext()).mBridge.interceptHtml((MainActivity) context, request); 111 | if (wr != null) { 112 | return wr; 113 | } 114 | 115 | String method = request.getMethod(); 116 | if (method == null || !method.equalsIgnoreCase("GET")) return null; 117 | 118 | android.net.Uri uri = request.getUrl(); 119 | if (uri == null || !uri.getScheme().startsWith("http")) return null; 120 | 121 | return shouldInterceptRequest(view, uri.toString()); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /app/src/main/java/io/gonative/android/LoginManager.java: -------------------------------------------------------------------------------- 1 | package io.gonative.android; 2 | 3 | import android.content.Context; 4 | import android.os.AsyncTask; 5 | 6 | import org.json.JSONObject; 7 | 8 | import java.lang.ref.WeakReference; 9 | import java.net.HttpURLConnection; 10 | import java.net.URL; 11 | import java.util.List; 12 | import java.util.Observable; 13 | import java.util.regex.Pattern; 14 | 15 | import io.gonative.gonative_core.AppConfig; 16 | import io.gonative.gonative_core.GNLog; 17 | 18 | /** 19 | * Created by weiyin on 3/16/14. 20 | */ 21 | public class LoginManager extends Observable { 22 | private static final String TAG = LoginManager.class.getName(); 23 | 24 | private Context context; 25 | private CheckRedirectionTask task = null; 26 | 27 | private boolean loggedIn = false; 28 | 29 | LoginManager(Context context) { 30 | this.context = context; 31 | checkLogin(); 32 | } 33 | 34 | public void checkLogin() { 35 | if (task != null) 36 | task.cancel(true); 37 | 38 | String loginDetectionUrl = AppConfig.getInstance(context).loginDetectionUrl; 39 | if (loginDetectionUrl == null) { 40 | return; 41 | } 42 | 43 | task = new CheckRedirectionTask(this); 44 | task.execute(AppConfig.getInstance(context).loginDetectionUrl); 45 | } 46 | 47 | public boolean isLoggedIn() { 48 | return loggedIn; 49 | } 50 | 51 | 52 | private static class CheckRedirectionTask extends AsyncTask { 53 | private WeakReference loginManagerReference; 54 | 55 | public CheckRedirectionTask(LoginManager loginManager) { 56 | this.loginManagerReference = new WeakReference<>(loginManager); 57 | } 58 | 59 | @Override 60 | protected String doInBackground(String... urls){ 61 | LoginManager loginManager = loginManagerReference.get(); 62 | if (loginManager == null) return null; 63 | 64 | try { 65 | URL parsedUrl = new URL(urls[0]); 66 | HttpURLConnection connection = null; 67 | boolean wasRedirected; 68 | int numRedirects = 0; 69 | do { 70 | if (connection != null) 71 | connection.disconnect(); 72 | 73 | connection = (HttpURLConnection) parsedUrl.openConnection(); 74 | connection.setInstanceFollowRedirects(true); 75 | connection.setRequestProperty("User-Agent", AppConfig.getInstance(loginManager.context).userAgent); 76 | 77 | connection.connect(); 78 | int responseCode = connection.getResponseCode(); 79 | 80 | if (responseCode == HttpURLConnection.HTTP_MOVED_PERM || 81 | responseCode == HttpURLConnection.HTTP_MOVED_TEMP) { 82 | wasRedirected = true; 83 | parsedUrl = new URL(parsedUrl, connection.getHeaderField("Location")); 84 | numRedirects++; 85 | } else { 86 | wasRedirected = false; 87 | } 88 | } while (!isCancelled() && wasRedirected && numRedirects < 10); 89 | 90 | String finalUrl = connection.getURL().toString(); 91 | connection.disconnect(); 92 | return finalUrl; 93 | 94 | } catch (Exception e) { 95 | GNLog.getInstance().logError(TAG, e.getMessage(), e); 96 | return null; 97 | } 98 | } 99 | 100 | @Override 101 | protected void onPostExecute(String finalUrl) { 102 | LoginManager loginManager = loginManagerReference.get(); 103 | if (loginManager == null) return; 104 | 105 | UrlInspector.getInstance().inspectUrl(finalUrl); 106 | String loginStatus; 107 | 108 | if (finalUrl == null) { 109 | loginManager.loggedIn = false; 110 | loginStatus = "default"; 111 | loginManager.setChanged(); 112 | loginManager.notifyObservers(); 113 | return; 114 | } 115 | 116 | // iterate through loginDetectionRegexes 117 | AppConfig appConfig = AppConfig.getInstance(loginManager.context); 118 | 119 | List regexes = appConfig.loginDetectRegexes; 120 | for (int i = 0; i < regexes.size(); i++) { 121 | Pattern regex = regexes.get(i); 122 | if (regex.matcher(finalUrl).matches()) { 123 | JSONObject entry = appConfig.loginDetectLocations.get(i); 124 | loginManager.loggedIn = entry.optBoolean("loggedIn", false); 125 | 126 | loginStatus = AppConfig.optString(entry, "menuName"); 127 | if (loginStatus == null) loginStatus = loginManager.loggedIn ? "loggedIn" : "default"; 128 | 129 | loginManager.setChanged(); 130 | loginManager.notifyObservers(); 131 | break; 132 | } 133 | } 134 | } 135 | 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /app/src/main/java/io/gonative/android/GoNativeWindowManager.java: -------------------------------------------------------------------------------- 1 | package io.gonative.android; 2 | 3 | import android.text.TextUtils; 4 | 5 | import java.util.LinkedHashMap; 6 | import java.util.Map; 7 | 8 | public class GoNativeWindowManager { 9 | private final LinkedHashMap windows; 10 | private ExcessWindowsClosedListener excessWindowsClosedListener; 11 | 12 | public GoNativeWindowManager() { 13 | windows = new LinkedHashMap<>(); 14 | } 15 | 16 | public void addNewWindow(String activityId, boolean isRoot) { 17 | this.windows.put(activityId, new ActivityWindow(activityId, isRoot)); 18 | } 19 | 20 | public void removeWindow(String activityId) { 21 | this.windows.remove(activityId); 22 | 23 | if (excessWindowsClosedListener != null && windows.size() <= 1) { 24 | excessWindowsClosedListener.onAllExcessWindowClosed(); 25 | } 26 | } 27 | 28 | public void setOnExcessWindowClosedListener(ExcessWindowsClosedListener listener) { 29 | this.excessWindowsClosedListener = listener; 30 | } 31 | 32 | public ActivityWindow getActivityWindowInfo(String activityId) { 33 | return windows.get(activityId); 34 | } 35 | 36 | public void setUrlLevel(String activityId, int urlLevel) { 37 | ActivityWindow window = windows.get(activityId); 38 | if (window != null) { 39 | window.setUrlLevels(urlLevel, window.parentUrlLevel); 40 | } 41 | } 42 | 43 | public int getUrlLevel(String activityId) { 44 | ActivityWindow window = windows.get(activityId); 45 | if (window != null) { 46 | return window.urlLevel; 47 | } 48 | return -1; 49 | } 50 | 51 | public void setParentUrlLevel(String activityId, int parentLevel) { 52 | ActivityWindow window = windows.get(activityId); 53 | if (window != null) { 54 | window.setUrlLevels(window.urlLevel, parentLevel); 55 | } 56 | } 57 | 58 | public int getParentUrlLevel(String activityId) { 59 | ActivityWindow window = windows.get(activityId); 60 | if (window != null) { 61 | return window.parentUrlLevel; 62 | } 63 | return -1; 64 | } 65 | 66 | public void setUrlLevels(String activityId, int urlLevel, int parentLevel) { 67 | ActivityWindow window = windows.get(activityId); 68 | if (window != null) { 69 | window.setUrlLevels(urlLevel, parentLevel); 70 | } 71 | } 72 | 73 | public boolean isRoot(String activityId) { 74 | ActivityWindow window = windows.get(activityId); 75 | if (window != null) { 76 | return window.isRoot; 77 | } 78 | return false; 79 | } 80 | 81 | public void setAsNewRoot(String activityId) { 82 | for (Map.Entry entry : windows.entrySet()) { 83 | ActivityWindow window = entry.getValue(); 84 | if (TextUtils.equals(activityId, entry.getKey())) { 85 | window.isRoot = true; 86 | } else { 87 | window.isRoot = false; 88 | } 89 | } 90 | } 91 | 92 | public void setIgnoreInterceptMaxWindows(String activityId, boolean ignore) { 93 | ActivityWindow window = windows.get(activityId); 94 | if (window != null) { 95 | window.ignoreInterceptMaxWindows = ignore; 96 | } 97 | } 98 | 99 | public boolean isIgnoreInterceptMaxWindows(String activityId) { 100 | ActivityWindow window = windows.get(activityId); 101 | if (window != null) { 102 | return window.ignoreInterceptMaxWindows; 103 | } 104 | return false; 105 | } 106 | 107 | public int getWindowCount() { 108 | return windows.size(); 109 | } 110 | 111 | // Returns ID of the next window after root as Excess window 112 | public String getExcessWindow() { 113 | for (Map.Entry entry : windows.entrySet()) { 114 | ActivityWindow window = entry.getValue(); 115 | if (window.isRoot) continue; 116 | return window.id; 117 | } 118 | return null; 119 | } 120 | 121 | public static class ActivityWindow { 122 | private final String id; 123 | private boolean isRoot; 124 | private int urlLevel; 125 | private int parentUrlLevel; 126 | private boolean ignoreInterceptMaxWindows; 127 | 128 | ActivityWindow(String id, boolean isRoot) { 129 | this.id = id; 130 | this.isRoot = isRoot; 131 | this.urlLevel = -1; 132 | this.parentUrlLevel = -1; 133 | } 134 | 135 | public void setUrlLevels(int urlLevel, int parentUrlLevel) { 136 | this.urlLevel = urlLevel; 137 | this.parentUrlLevel = parentUrlLevel; 138 | } 139 | 140 | @Override 141 | public String toString() { 142 | return "id=" + id + "\n" + 143 | "isRoot=" + isRoot + "\n" + 144 | "urlLevel=" + urlLevel + "\n" + 145 | "parentUrlLevel=" + parentUrlLevel; 146 | } 147 | } 148 | 149 | 150 | interface ExcessWindowsClosedListener { 151 | void onAllExcessWindowClosed(); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /app/src/normal/java/io/gonative/android/WebkitCookieManagerProxy.java: -------------------------------------------------------------------------------- 1 | package io.gonative.android; 2 | 3 | import java.io.IOException; 4 | import java.net.CookiePolicy; 5 | import java.net.CookieStore; 6 | import java.net.HttpCookie; 7 | import java.net.URI; 8 | import java.util.Arrays; 9 | import java.util.Calendar; 10 | import java.util.Date; 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | import io.gonative.gonative_core.AppConfig; 15 | import io.gonative.gonative_core.LeanUtils; 16 | 17 | // this syncs cookies between webkit (webview) and java.net classes 18 | public class WebkitCookieManagerProxy extends java.net.CookieManager { 19 | private static final String TAG = WebkitCookieManagerProxy.class.getName(); 20 | private android.webkit.CookieManager webkitCookieManager; 21 | 22 | public WebkitCookieManagerProxy() 23 | { 24 | this(null, null); 25 | } 26 | 27 | WebkitCookieManagerProxy(CookieStore store, CookiePolicy cookiePolicy) 28 | { 29 | super(null, cookiePolicy); 30 | 31 | this.webkitCookieManager = android.webkit.CookieManager.getInstance(); 32 | } 33 | 34 | // java.net.CookieManager overrides 35 | @Override 36 | public void put(URI uri, Map> responseHeaders) throws IOException 37 | { 38 | // make sure our args are valid 39 | if ((uri == null) || (responseHeaders == null)) return; 40 | 41 | // save our url once 42 | String url = uri.toString(); 43 | 44 | String expiryString = null; 45 | int sessionExpiry = AppConfig.getInstance(null).forceSessionCookieExpiry; 46 | 47 | // go over the headers 48 | for (String headerKey : responseHeaders.keySet()) 49 | { 50 | // ignore headers which aren't cookie related 51 | if ((headerKey == null) || !headerKey.equalsIgnoreCase("Set-Cookie")) continue; 52 | 53 | // process each of the headers 54 | for (String headerValue : responseHeaders.get(headerKey)) 55 | { 56 | boolean passOriginalHeader = true; 57 | if (sessionExpiry > 0) { 58 | List cookies = HttpCookie.parse(headerValue); 59 | for (HttpCookie cookie : cookies) { 60 | if (cookie.getMaxAge() < 0 || cookie.getDiscard()) { 61 | // this is a session cookie. Modify it and pass it to the webview. 62 | cookie.setMaxAge(sessionExpiry); 63 | cookie.setDiscard(false); 64 | if (expiryString == null) { 65 | Calendar calendar = Calendar.getInstance(); 66 | calendar.add(Calendar.SECOND, sessionExpiry); 67 | Date expiryDate = calendar.getTime(); 68 | expiryString = "; expires=" + LeanUtils.formatDateForCookie(expiryDate) + 69 | "; Max-Age=" + Integer.toString(sessionExpiry); 70 | } 71 | 72 | StringBuilder newHeader = new StringBuilder(); 73 | newHeader.append(cookie.toString()); 74 | newHeader.append(expiryString); 75 | if (cookie.getPath() != null) { 76 | newHeader.append("; path="); 77 | newHeader.append(cookie.getPath()); 78 | } 79 | if (cookie.getDomain() != null) { 80 | newHeader.append("; domain="); 81 | newHeader.append(cookie.getDomain()); 82 | } 83 | if (cookie.getSecure()) { 84 | newHeader.append("; secure"); 85 | } 86 | 87 | this.webkitCookieManager.setCookie(url, newHeader.toString()); 88 | passOriginalHeader = false; 89 | } 90 | } 91 | } 92 | 93 | if (passOriginalHeader) this.webkitCookieManager.setCookie(url, headerValue); 94 | } 95 | } 96 | } 97 | 98 | @Override 99 | public Map> get(URI uri, Map> requestHeaders) throws IOException 100 | { 101 | // make sure our args are valid 102 | if ((uri == null) || (requestHeaders == null)) throw new IllegalArgumentException("Argument is null"); 103 | 104 | // save our url once 105 | String url = uri.toString(); 106 | 107 | // prepare our response 108 | Map> res = new java.util.HashMap>(); 109 | 110 | // get the cookie 111 | String cookie = this.webkitCookieManager.getCookie(url); 112 | 113 | // return it 114 | if (cookie != null) res.put("Cookie", Arrays.asList(cookie)); 115 | return res; 116 | } 117 | 118 | @Override 119 | public CookieStore getCookieStore() 120 | { 121 | // we don't want anyone to work with this cookie store directly 122 | throw new UnsupportedOperationException(); 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /plugins.gradle: -------------------------------------------------------------------------------- 1 | import groovy.json.JsonSlurper 2 | import org.gradle.initialization.DefaultSettings 3 | import org.apache.tools.ant.taskdefs.condition.Os 4 | 5 | def generatedFileName = "PackageList.java" 6 | def generatedFilePackage = "io.gonative.android" 7 | def generatedFileContentsTemplate = """ 8 | package $generatedFilePackage; 9 | 10 | import android.app.Application; 11 | import android.content.Context; 12 | import android.content.res.Resources; 13 | 14 | import io.gonative.gonative_core.BridgeModule; 15 | import java.util.Arrays; 16 | import java.util.ArrayList; 17 | 18 | {{ packageImports }} 19 | 20 | public class PackageList { 21 | private Application application; 22 | 23 | public PackageList(Application application) { 24 | this.application = application; 25 | } 26 | 27 | private Resources getResources() { 28 | return this.getApplication().getResources(); 29 | } 30 | 31 | private Application getApplication() { 32 | return this.application; 33 | } 34 | 35 | private Context getApplicationContext() { 36 | return this.getApplication().getApplicationContext(); 37 | } 38 | 39 | public ArrayList getPackages() { 40 | return new ArrayList<>(Arrays.asList( 41 | {{ packageClassInstances }} 42 | )); 43 | } 44 | } 45 | """ 46 | 47 | class GoNativeModules { 48 | private Logger logger 49 | private ArrayList> modulesMetadata 50 | 51 | private packageName = "io.gonative.android" 52 | 53 | GoNativeModules(Logger logger) { 54 | this.logger = logger 55 | this.modulesMetadata = this.getModulesMetadata() 56 | } 57 | 58 | ArrayList> getModulesMetadata() { 59 | if (this.modulesMetadata != null) return this.modulesMetadata 60 | 61 | ArrayList> modulesMetadata = new ArrayList>() 62 | 63 | def finder = new FileNameFinder() 64 | def files = finder.getFileNames(System.getProperty("user.dir"), 'plugins/**/plugin-metadata.json') 65 | files.each { fileName -> 66 | def jsonFile = new File(fileName) 67 | def parsedJson = new JsonSlurper().parseText(jsonFile.text).plugin 68 | parsedJson["sourceDir"] = fileName.tokenize(File.separator)[-3..-2].join(File.separator) 69 | modulesMetadata.push(parsedJson) 70 | } 71 | 72 | return modulesMetadata 73 | } 74 | 75 | void addModuleProjects(DefaultSettings defaultSettings) { 76 | modulesMetadata.forEach { module -> 77 | String pluginName = module["pluginName"] 78 | String sourceDir = module["sourceDir"] 79 | this.logger.warn(sourceDir) 80 | defaultSettings.include(":${pluginName}") 81 | defaultSettings.project(":${pluginName}").projectDir = new File(defaultSettings.rootProject.projectDir, "./${sourceDir}") 82 | } 83 | } 84 | 85 | void addModuleDependencies(Project appProject) { 86 | modulesMetadata.forEach { module -> 87 | String pluginName = module["pluginName"] 88 | appProject.dependencies { 89 | implementation project(path: ":${pluginName}") 90 | } 91 | } 92 | } 93 | 94 | void generatePackagesFile(File outputDir, String generatedFileName, GString generatedFileContentsTemplate) { 95 | def packages = this.modulesMetadata 96 | String packageName = this.packageName 97 | 98 | String packageImports = "" 99 | String packageClassInstances = "" 100 | 101 | if (packages.size() > 0) { 102 | packageImports = "import ${packageName}.BuildConfig;\nimport ${packageName}.R;\n\n" 103 | packageImports = packageImports + packages.collect { 104 | "// ${it.name}\nimport ${it.packageName}.${it.classInstance};" 105 | }.join('\n') 106 | packageClassInstances = packages.collect { "new ${it.classInstance}()" }.join(",\n ") 107 | } 108 | 109 | String generatedFileContents = generatedFileContentsTemplate.toString() 110 | .replace("{{ packageImports }}", packageImports) 111 | .replace("{{ packageClassInstances }}", packageClassInstances) 112 | 113 | outputDir.mkdirs() 114 | final FileTreeBuilder treeBuilder = new FileTreeBuilder(outputDir) 115 | treeBuilder.file(generatedFileName).newWriter().withWriter { w -> 116 | w << generatedFileContents 117 | } 118 | } 119 | } 120 | 121 | def gonativeModules = new GoNativeModules(logger) 122 | 123 | ext.applyModulesSettingsGradle = { DefaultSettings defaultSettings -> 124 | gonativeModules.addModuleProjects(defaultSettings) 125 | } 126 | 127 | ext.applyNativeModulesAppBuildGradle = { Project project -> 128 | gonativeModules.addModuleDependencies(project) 129 | 130 | def generatedSrcDir = new File(buildDir, "generated/gncli/src/main/java") 131 | def generatedCodeDir = new File(generatedSrcDir, generatedFilePackage.replace('.', '/')) 132 | 133 | task generatePackageList { 134 | doLast { 135 | gonativeModules.generatePackagesFile(generatedCodeDir, generatedFileName, generatedFileContentsTemplate) 136 | } 137 | } 138 | 139 | preBuild.dependsOn generatePackageList 140 | 141 | android { 142 | sourceSets { 143 | main { 144 | java { 145 | srcDirs += generatedSrcDir 146 | } 147 | } 148 | } 149 | } 150 | } -------------------------------------------------------------------------------- /app/src/main/java/io/gonative/android/widget/CircleImageView.java: -------------------------------------------------------------------------------- 1 | package io.gonative.android.widget; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.graphics.RadialGradient; 8 | import android.graphics.Shader; 9 | import android.graphics.drawable.ShapeDrawable; 10 | import android.graphics.drawable.shapes.OvalShape; 11 | import android.view.View; 12 | import android.view.animation.Animation; 13 | 14 | import androidx.core.content.ContextCompat; 15 | import androidx.core.view.ViewCompat; 16 | 17 | /** 18 | * Private class created to work around issues with AnimationListeners being 19 | * called before the animation is actually complete and support shadows on older 20 | * platforms. 21 | */ 22 | class CircleImageView extends androidx.appcompat.widget.AppCompatImageView { 23 | 24 | private static final int KEY_SHADOW_COLOR = 0x1E000000; 25 | private static final int FILL_SHADOW_COLOR = 0x3D000000; 26 | // PX 27 | private static final float X_OFFSET = 0f; 28 | private static final float Y_OFFSET = 1.75f; 29 | private static final float SHADOW_RADIUS = 3.5f; 30 | private static final int SHADOW_ELEVATION = 4; 31 | 32 | private Animation.AnimationListener mListener; 33 | int mShadowRadius; 34 | 35 | CircleImageView(Context context, int color) { 36 | super(context); 37 | final float density = getContext().getResources().getDisplayMetrics().density; 38 | final int shadowYOffset = (int) (density * Y_OFFSET); 39 | final int shadowXOffset = (int) (density * X_OFFSET); 40 | 41 | mShadowRadius = (int) (density * SHADOW_RADIUS); 42 | 43 | ShapeDrawable circle; 44 | if (elevationSupported()) { 45 | circle = new ShapeDrawable(new OvalShape()); 46 | ViewCompat.setElevation(this, SHADOW_ELEVATION * density); 47 | } else { 48 | OvalShape oval = new CircleImageView.OvalShadow(mShadowRadius); 49 | circle = new ShapeDrawable(oval); 50 | setLayerType(View.LAYER_TYPE_SOFTWARE, circle.getPaint()); 51 | circle.getPaint().setShadowLayer(mShadowRadius, shadowXOffset, shadowYOffset, 52 | KEY_SHADOW_COLOR); 53 | final int padding = mShadowRadius; 54 | // set padding so the inner image sits correctly within the shadow. 55 | setPadding(padding, padding, padding, padding); 56 | } 57 | circle.getPaint().setColor(color); 58 | ViewCompat.setBackground(this, circle); 59 | } 60 | 61 | private boolean elevationSupported() { 62 | return android.os.Build.VERSION.SDK_INT >= 21; 63 | } 64 | 65 | @Override 66 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 67 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 68 | if (!elevationSupported()) { 69 | setMeasuredDimension(getMeasuredWidth() + mShadowRadius * 2, getMeasuredHeight() 70 | + mShadowRadius * 2); 71 | } 72 | } 73 | 74 | public void setAnimationListener(Animation.AnimationListener listener) { 75 | mListener = listener; 76 | } 77 | 78 | @Override 79 | public void onAnimationStart() { 80 | super.onAnimationStart(); 81 | if (mListener != null) { 82 | mListener.onAnimationStart(getAnimation()); 83 | } 84 | } 85 | 86 | @Override 87 | public void onAnimationEnd() { 88 | super.onAnimationEnd(); 89 | if (mListener != null) { 90 | mListener.onAnimationEnd(getAnimation()); 91 | } 92 | } 93 | 94 | /** 95 | * Update the background color of the circle image view. 96 | * 97 | * @param colorRes Id of a color resource. 98 | */ 99 | public void setBackgroundColorRes(int colorRes) { 100 | setBackgroundColor(ContextCompat.getColor(getContext(), colorRes)); 101 | } 102 | 103 | @Override 104 | public void setBackgroundColor(int color) { 105 | if (getBackground() instanceof ShapeDrawable) { 106 | ((ShapeDrawable) getBackground()).getPaint().setColor(color); 107 | } 108 | } 109 | 110 | private class OvalShadow extends OvalShape { 111 | private RadialGradient mRadialGradient; 112 | private Paint mShadowPaint; 113 | 114 | OvalShadow(int shadowRadius) { 115 | super(); 116 | mShadowPaint = new Paint(); 117 | mShadowRadius = shadowRadius; 118 | updateRadialGradient((int) rect().width()); 119 | } 120 | 121 | @Override 122 | protected void onResize(float width, float height) { 123 | super.onResize(width, height); 124 | updateRadialGradient((int) width); 125 | } 126 | 127 | @Override 128 | public void draw(Canvas canvas, Paint paint) { 129 | final int viewWidth = CircleImageView.this.getWidth(); 130 | final int viewHeight = CircleImageView.this.getHeight(); 131 | canvas.drawCircle(viewWidth / 2, viewHeight / 2, viewWidth / 2, mShadowPaint); 132 | canvas.drawCircle(viewWidth / 2, viewHeight / 2, viewWidth / 2 - mShadowRadius, paint); 133 | } 134 | 135 | private void updateRadialGradient(int diameter) { 136 | mRadialGradient = new RadialGradient(diameter / 2, diameter / 2, 137 | mShadowRadius, new int[] { FILL_SHADOW_COLOR, Color.TRANSPARENT }, 138 | null, Shader.TileMode.CLAMP); 139 | mShadowPaint.setShader(mRadialGradient); 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /app/src/main/java/io/gonative/android/widget/HandleView.kt: -------------------------------------------------------------------------------- 1 | package io.gonative.android.widget 2 | 3 | import android.animation.ArgbEvaluator 4 | import android.animation.ValueAnimator 5 | import android.content.Context 6 | import android.graphics.Color 7 | import android.graphics.PorterDuff 8 | import android.graphics.drawable.Drawable 9 | import android.util.AttributeSet 10 | import android.widget.ImageView 11 | import android.widget.RelativeLayout 12 | import android.widget.TextView 13 | import androidx.annotation.ColorInt 14 | import androidx.core.content.res.ResourcesCompat 15 | import io.gonative.android.R 16 | 17 | class HandleView : RelativeLayout { 18 | private val iconView: ImageView 19 | private val textView: TextView 20 | 21 | init { 22 | inflate(context, R.layout.view_handle, this) 23 | iconView = findViewById(R.id.icon) 24 | textView = findViewById(R.id.text) 25 | } 26 | 27 | @JvmOverloads 28 | constructor( 29 | context: Context, 30 | attrs: AttributeSet? = null, 31 | defStyle: Int = 0 32 | ) : super(context, attrs, defStyle) { 33 | context.theme.obtainStyledAttributes(attrs, R.styleable.HandleView, 0, 0).apply { 34 | val backgroundDrawable = getDrawable(R.styleable.HandleView_handleBackground) 35 | ?: ResourcesCompat.getDrawable( 36 | resources, 37 | R.drawable.shape_rounded, 38 | context.theme 39 | ) 40 | val iconDrawable = getDrawable(R.styleable.HandleView_iconDrawable) 41 | val text = getString(R.styleable.HandleView_text) 42 | val inactiveColor = getColor(R.styleable.HandleView_inactiveColor, inactiveColor) 43 | val activeColor = getColor(R.styleable.HandleView_activeColor, activeColor) 44 | initView(backgroundDrawable, iconDrawable, text, inactiveColor, activeColor) 45 | } 46 | } 47 | 48 | constructor( 49 | context: Context, 50 | backgroundDrawable: Drawable?, 51 | iconDrawable: Drawable?, 52 | text: String?, 53 | @ColorInt inactiveColor: Int, 54 | @ColorInt activeColor: Int, 55 | ) : super(context, null, 0) { 56 | initView(backgroundDrawable, iconDrawable, text, inactiveColor, activeColor) 57 | } 58 | 59 | var maxTextWidth: Int = Int.MIN_VALUE 60 | var inactiveColor: Int = Color.WHITE 61 | var activeColor: Int = Color.WHITE 62 | 63 | fun initView( 64 | backgroundDrawable: Drawable?, 65 | iconDrawable: Drawable?, 66 | text: String?, 67 | @ColorInt inactiveColor: Int, 68 | @ColorInt activeColor: Int 69 | ) { 70 | background = backgroundDrawable 71 | iconView.setImageDrawable(iconDrawable) 72 | setText(text) 73 | textView.layoutParams.let { 74 | it.width = 0 75 | textView.layoutParams = it 76 | } 77 | 78 | this.inactiveColor = inactiveColor 79 | this.activeColor = activeColor 80 | iconView.setColorFilter(inactiveColor) 81 | } 82 | 83 | fun setText(text: String?) { 84 | textView.layoutParams.width = LayoutParams.WRAP_CONTENT 85 | textView.text = text 86 | textView.measure( 87 | MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 88 | MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED) 89 | ) 90 | maxTextWidth = textView.measuredWidth 91 | textView.layoutParams.let { 92 | it.width = 0 93 | textView.layoutParams = it 94 | } 95 | } 96 | 97 | fun animateShowText() { 98 | if (textView.text.isEmpty()) { 99 | return 100 | } 101 | if (textView.layoutParams.width != 0) { 102 | textView.layoutParams.let { 103 | it.width = 0 104 | textView.layoutParams = it 105 | } 106 | } 107 | val animator = ValueAnimator.ofInt(0, maxTextWidth) 108 | animator.addUpdateListener { anim -> 109 | val value = anim.animatedValue as Int 110 | textView.layoutParams.let { 111 | it.width = value 112 | textView.layoutParams = it 113 | } 114 | } 115 | animator.duration = 300 116 | animator.start() 117 | } 118 | 119 | fun animateHideText() { 120 | if (textView.text.isEmpty()) { 121 | return 122 | } 123 | val animator = ValueAnimator.ofInt(maxTextWidth, 0) 124 | animator.duration = 300 125 | animator.addUpdateListener { anim -> 126 | val value = anim.animatedValue as Int 127 | val params = textView.layoutParams 128 | params.width = value 129 | textView.layoutParams = params 130 | } 131 | animator.start() 132 | } 133 | 134 | fun animateActive() { 135 | val animator = ValueAnimator.ofObject(ArgbEvaluator(), inactiveColor, activeColor) 136 | animator.addUpdateListener { anim -> 137 | val color = anim.animatedValue as Int 138 | textView.setTextColor(color) 139 | iconView.setColorFilter(color, PorterDuff.Mode.SRC_IN) 140 | } 141 | animator.duration = 100 142 | animator.start() 143 | } 144 | 145 | fun animateInactive() { 146 | val animator = ValueAnimator.ofObject(ArgbEvaluator(), activeColor, inactiveColor) 147 | animator.addUpdateListener { anim -> 148 | val color = anim.animatedValue as Int 149 | textView.setTextColor(color) 150 | iconView.setColorFilter(color, PorterDuff.Mode.SRC_IN) 151 | } 152 | animator.duration = 200 153 | animator.start() 154 | } 155 | } -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /app/src/main/java/io/gonative/android/Installation.java: -------------------------------------------------------------------------------- 1 | package io.gonative.android; 2 | 3 | import android.content.Context; 4 | import android.content.pm.ApplicationInfo; 5 | import android.content.pm.PackageInfo; 6 | import android.content.pm.PackageManager; 7 | import android.os.Build; 8 | import android.telephony.SubscriptionInfo; 9 | import android.telephony.SubscriptionManager; 10 | import android.util.Log; 11 | 12 | import androidx.core.app.ActivityCompat; 13 | 14 | import java.io.File; 15 | import java.io.FileOutputStream; 16 | import java.io.IOException; 17 | import java.io.RandomAccessFile; 18 | import java.util.ArrayList; 19 | import java.util.HashMap; 20 | import java.util.List; 21 | import java.util.Locale; 22 | import java.util.Map; 23 | import java.util.TimeZone; 24 | import java.util.UUID; 25 | 26 | import io.gonative.gonative_core.AppConfig; 27 | import io.gonative.gonative_core.GNLog; 28 | 29 | /** 30 | * Created by weiyin on 8/8/14. 31 | */ 32 | public class Installation { 33 | private static final String TAG = Installation.class.getName(); 34 | 35 | private static String sID = null; 36 | private static final String INSTALLATION = "INSTALLATION"; 37 | 38 | public synchronized static String id(Context context) { 39 | if (sID == null) { 40 | File installation = new File(context.getFilesDir(), INSTALLATION); 41 | try { 42 | if (!installation.exists()) 43 | writeInstallationFile(installation); 44 | sID = readInstallationFile(installation); 45 | } catch (Exception e) { 46 | throw new RuntimeException(e); 47 | } 48 | } 49 | return sID; 50 | } 51 | 52 | public static Map getInfo(Context context) { 53 | HashMap info = new HashMap<>(); 54 | 55 | info.put("platform", "android"); 56 | 57 | String publicKey = AppConfig.getInstance(context).publicKey; 58 | if (publicKey == null) publicKey = ""; 59 | info.put("publicKey", publicKey); 60 | 61 | String packageName = context.getPackageName(); 62 | info.put("appId", packageName); 63 | 64 | 65 | PackageManager manager = context.getPackageManager(); 66 | try { 67 | PackageInfo packageInfo = manager.getPackageInfo(packageName, 0); 68 | info.put("appVersion", packageInfo.versionName); 69 | info.put("appVersionCode", packageInfo.versionCode); 70 | } catch (PackageManager.NameNotFoundException e) { 71 | GNLog.getInstance().logError(TAG, e.getMessage(), e); 72 | } 73 | 74 | String distribution; 75 | boolean isDebuggable = ( 0 != ( context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE ) ); 76 | if (isDebuggable) { 77 | distribution = "debug"; 78 | } else { 79 | String installer = manager.getInstallerPackageName(packageName); 80 | if (installer == null) { 81 | distribution = "adhoc"; 82 | } else if (installer.equals("com.android.vending") || installer.equals("com.google.market")) { 83 | distribution = "playstore"; 84 | } else if (installer.equals("com.amazon.venezia")) { 85 | distribution = "amazon"; 86 | } else { 87 | distribution = installer; 88 | } 89 | } 90 | info.put("distribution", distribution); 91 | 92 | info.put("language", Locale.getDefault().getLanguage()); 93 | info.put("os", "Android"); 94 | info.put("osVersion", Build.VERSION.RELEASE); 95 | info.put("model", Build.MANUFACTURER + " " + Build.MODEL); 96 | info.put("hardware", Build.FINGERPRINT); 97 | info.put("timeZone", TimeZone.getDefault().getID()); 98 | info.put("deviceName", getDeviceName()); 99 | 100 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP_MR1) { 101 | SubscriptionManager subscriptionManager = SubscriptionManager.from(context); 102 | 103 | if (ActivityCompat.checkSelfPermission(context, android.Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) { 104 | List carriers = new ArrayList<>(); 105 | for (SubscriptionInfo subscriptionInfo : subscriptionManager.getActiveSubscriptionInfoList()) { 106 | carriers.add(subscriptionInfo.getCarrierName().toString()); 107 | } 108 | info.put("carrierNames", carriers); 109 | try { 110 | info.put("carrierName", carriers.get(0)); 111 | } catch ( IndexOutOfBoundsException e ) { 112 | Log.w(TAG, "getInfo: No carriers registered with subscription manager"); 113 | } 114 | } else { 115 | Log.w(TAG, "getInfo: Cannot get carrierNames, READ_PHONE_STATE not granted"); 116 | } 117 | } 118 | 119 | info.put("installationId", Installation.id(context)); 120 | 121 | return info; 122 | } 123 | 124 | private static String readInstallationFile(File installation) throws IOException { 125 | RandomAccessFile f = new RandomAccessFile(installation, "r"); 126 | byte[] bytes = new byte[(int) f.length()]; 127 | f.readFully(bytes); 128 | f.close(); 129 | return new String(bytes); 130 | } 131 | 132 | private static void writeInstallationFile(File installation) throws IOException { 133 | FileOutputStream out = new FileOutputStream(installation); 134 | String id = UUID.randomUUID().toString(); 135 | out.write(id.getBytes()); 136 | out.close(); 137 | } 138 | 139 | private static String getDeviceName() { 140 | String manufacturer = Build.MANUFACTURER; 141 | String model = Build.MODEL; 142 | String name; 143 | if (model.startsWith(manufacturer)) { 144 | name = model; 145 | } else { 146 | name = manufacturer + " " + model; 147 | } 148 | return name; 149 | } 150 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 75 | 76 | 83 | 84 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 108 | 111 | 112 | 113 | 114 | 117 | 120 | 121 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /app/src/main/java/io/gonative/android/RegistrationManager.java: -------------------------------------------------------------------------------- 1 | package io.gonative.android; 2 | 3 | import android.content.Context; 4 | import android.os.AsyncTask; 5 | import android.util.Log; 6 | 7 | import org.json.JSONArray; 8 | import org.json.JSONObject; 9 | 10 | import java.io.OutputStreamWriter; 11 | import java.net.HttpURLConnection; 12 | import java.net.URL; 13 | import java.util.HashMap; 14 | import java.util.Iterator; 15 | import java.util.LinkedList; 16 | import java.util.List; 17 | import java.util.Map; 18 | import java.util.regex.Pattern; 19 | 20 | import io.gonative.gonative_core.GNLog; 21 | import io.gonative.gonative_core.LeanUtils; 22 | 23 | /** 24 | * Created by weiyin on 10/4/15. 25 | */ 26 | public class RegistrationManager { 27 | private final static String TAG = RegistrationManager.class.getName(); 28 | 29 | private Context context; 30 | private JSONObject customData; 31 | private String lastUrl; 32 | 33 | private List registrationEndpoints; 34 | 35 | RegistrationManager(Context context) { 36 | this.context = context; 37 | this.registrationEndpoints = new LinkedList<>(); 38 | } 39 | 40 | public void processConfig(JSONArray endpoints) { 41 | registrationEndpoints.clear(); 42 | 43 | if (endpoints == null) return; 44 | 45 | for (int i = 0; i < endpoints.length(); i++) { 46 | JSONObject endpoint = endpoints.optJSONObject(i); 47 | if (endpoint == null) continue; 48 | 49 | String url = LeanUtils.optString(endpoint, "url"); 50 | if (url == null) { 51 | Log.w(TAG, "Invalid registration: endpoint url is null"); 52 | continue; 53 | } 54 | 55 | List urlRegexes = LeanUtils.createRegexArrayFromStrings(endpoint.opt("urlRegex")); 56 | 57 | RegistrationEndpoint registrationEndpoint = new RegistrationEndpoint(url, urlRegexes); 58 | registrationEndpoints.add(registrationEndpoint); 59 | } 60 | } 61 | 62 | public void checkUrl(String url) { 63 | this.lastUrl = url; 64 | for (RegistrationEndpoint endpoint : registrationEndpoints) { 65 | if (LeanUtils.stringMatchesAnyRegex(url, endpoint.urlRegexes)) { 66 | endpoint.sendRegistrationInfo(); 67 | } 68 | } 69 | } 70 | 71 | public void setCustomData(JSONObject customData) { 72 | this.customData = customData; 73 | registrationDataChanged(); 74 | } 75 | 76 | public void sendToAllEndpoints() { 77 | for (RegistrationEndpoint endpoint : registrationEndpoints) { 78 | endpoint.sendRegistrationInfo(); 79 | } 80 | } 81 | 82 | private void registrationDataChanged() { 83 | for (RegistrationEndpoint endpoint : registrationEndpoints) { 84 | if (this.lastUrl != null && 85 | LeanUtils.stringMatchesAnyRegex(this.lastUrl, endpoint.urlRegexes)) { 86 | endpoint.sendRegistrationInfo(); 87 | } 88 | } 89 | } 90 | 91 | public void subscriptionInfoChanged(){ 92 | registrationDataChanged(); 93 | } 94 | 95 | private class RegistrationEndpoint { 96 | private String postUrl; 97 | private List urlRegexes; 98 | 99 | RegistrationEndpoint(String postUrl, List urlRegexes) { 100 | this.postUrl = postUrl; 101 | this.urlRegexes = urlRegexes; 102 | } 103 | 104 | void sendRegistrationInfo() { 105 | new SendRegistrationTask(context, this, RegistrationManager.this).execute(); 106 | } 107 | } 108 | 109 | private static class SendRegistrationTask extends AsyncTask { 110 | private RegistrationEndpoint registrationEndpoint; 111 | private RegistrationManager registrationManager; 112 | private Context context; 113 | 114 | SendRegistrationTask(Context context, RegistrationEndpoint registrationEndpoint, RegistrationManager registrationManager) { 115 | this.registrationEndpoint = registrationEndpoint; 116 | this.registrationManager = registrationManager; 117 | this.context = context; 118 | } 119 | 120 | @Override 121 | protected Void doInBackground(Void... voids) { 122 | Map toSend = new HashMap<>(); 123 | 124 | toSend.putAll(Installation.getInfo(registrationManager.context)); 125 | 126 | // Append provider info to Map toSend 127 | if (((GoNativeApplication) context).getAnalyticsProviderInfo() != null) { 128 | toSend.putAll(((GoNativeApplication) context).getAnalyticsProviderInfo()); 129 | } 130 | 131 | if (registrationManager.customData != null) { 132 | Iterator keys = registrationManager.customData.keys(); 133 | while(keys.hasNext()) { 134 | String key = keys.next(); 135 | toSend.put("customData_" + key, registrationManager.customData.opt(key)); 136 | } 137 | } 138 | 139 | try { 140 | JSONObject json = new JSONObject(toSend); 141 | 142 | URL url = new URL(registrationEndpoint.postUrl); 143 | HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 144 | connection.setRequestMethod("POST"); 145 | connection.setRequestProperty("Content-Type", "application/json"); 146 | connection.setDoOutput(true); 147 | OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream(), "UTF-8"); 148 | writer.write(json.toString()); 149 | writer.close(); 150 | connection.connect(); 151 | int result = connection.getResponseCode(); 152 | 153 | if (result < 200 || result > 299) { 154 | Log.w(TAG, "Recevied status code " + result + " when posting to " + registrationEndpoint.postUrl); 155 | } 156 | } catch (Exception e) { 157 | GNLog.getInstance().logError(TAG, "Error posting to " + registrationEndpoint.postUrl, e); 158 | } 159 | 160 | return null; 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /app/src/normal/java/io/gonative/android/WebViewSetup.java: -------------------------------------------------------------------------------- 1 | package io.gonative.android; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Context; 5 | import android.os.Build; 6 | import android.os.Message; 7 | import android.webkit.CookieManager; 8 | import android.webkit.WebSettings; 9 | import android.webkit.WebView; 10 | 11 | import java.util.Map; 12 | 13 | import io.gonative.gonative_core.AppConfig; 14 | import io.gonative.gonative_core.GNLog; 15 | import io.gonative.gonative_core.GoNativeWebviewInterface; 16 | 17 | /** 18 | * Created by weiyin on 9/8/15. 19 | */ 20 | public class WebViewSetup { 21 | private static final String TAG = WebViewSetup.class.getName(); 22 | 23 | @SuppressLint("JavascriptInterface") 24 | public static void setupWebviewForActivity(GoNativeWebviewInterface webview, MainActivity activity) { 25 | if (!(webview instanceof LeanWebView)) { 26 | GNLog.getInstance().logError(TAG, "Expected webview to be of class LeanWebView and not " + webview.getClass().getName()); 27 | return; 28 | } 29 | 30 | LeanWebView wv = (LeanWebView)webview; 31 | 32 | setupWebview(wv, activity); 33 | 34 | UrlNavigation urlNavigation = new UrlNavigation(activity); 35 | urlNavigation.setCurrentWebviewUrl(webview.getUrl()); 36 | 37 | wv.setWebChromeClient(new GoNativeWebChromeClient(activity, urlNavigation)); 38 | wv.setWebViewClient(new GoNativeWebviewClient(activity, urlNavigation)); 39 | 40 | FileDownloader fileDownloader = activity.getFileDownloader(); 41 | if (fileDownloader != null) { 42 | wv.setDownloadListener(fileDownloader); 43 | fileDownloader.setUrlNavigation(urlNavigation); 44 | } 45 | 46 | ProfilePicker profilePicker = activity.getProfilePicker(); 47 | wv.removeJavascriptInterface("gonative_profile_picker"); 48 | if (profilePicker != null) { 49 | wv.addJavascriptInterface(profilePicker.getProfileJsBridge(), "gonative_profile_picker"); 50 | } 51 | 52 | wv.removeJavascriptInterface("gonative_status_checker"); 53 | wv.addJavascriptInterface(activity.getStatusCheckerBridge(), "gonative_status_checker"); 54 | 55 | wv.removeJavascriptInterface("gonative_file_writer_sharer"); 56 | wv.addJavascriptInterface(activity.getFileWriterSharer().getJavascriptBridge(), "gonative_file_writer_sharer"); 57 | 58 | wv.removeJavascriptInterface("JSBridge"); 59 | wv.addJavascriptInterface(activity.getJavascriptBridge(), "JSBridge"); 60 | 61 | ((GoNativeApplication) activity.getApplication()).mBridge.onWebviewSetUp(activity, wv); 62 | 63 | if (activity.getIntent().getBooleanExtra(MainActivity.EXTRA_WEBVIEW_WINDOW_OPEN, false)) { 64 | // send to other webview 65 | Message resultMsg = ((GoNativeApplication)activity.getApplication()).getWebviewMessage(); 66 | if (resultMsg != null) { 67 | WebView.WebViewTransport transport = (WebView.WebViewTransport)resultMsg.obj; 68 | if (transport != null) { 69 | transport.setWebView(wv); 70 | resultMsg.sendToTarget(); 71 | } 72 | } 73 | } 74 | } 75 | 76 | @SuppressWarnings("deprecation") 77 | @SuppressLint("SetJavaScriptEnabled") 78 | public static void setupWebview(GoNativeWebviewInterface webview, Context context) { 79 | if (!(webview instanceof LeanWebView)) { 80 | GNLog.getInstance().logError(TAG, "Expected webview to be of class LeanWebView and not " + webview.getClass().getName()); 81 | return; 82 | } 83 | 84 | AppConfig appConfig = AppConfig.getInstance(context); 85 | 86 | LeanWebView wv = (LeanWebView)webview; 87 | WebSettings webSettings = wv.getSettings(); 88 | 89 | if (AppConfig.getInstance(context).allowZoom) { 90 | webSettings.setBuiltInZoomControls(true); 91 | } 92 | else { 93 | webSettings.setBuiltInZoomControls(false); 94 | } 95 | 96 | webSettings.setDisplayZoomControls(false); 97 | webSettings.setLoadWithOverviewMode(true); 98 | webSettings.setUseWideViewPort(true); 99 | 100 | webSettings.setJavaScriptEnabled(true); 101 | webSettings.setJavaScriptCanOpenWindowsAutomatically(true); 102 | 103 | // font size bug fix, see https://stackoverflow.com/questions/41179357/android-webview-rem-units-scale-way-to-large-for-boxes 104 | webSettings.setMinimumFontSize(1); 105 | webSettings.setMinimumLogicalFontSize(1); 106 | 107 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 108 | webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE); 109 | CookieManager.getInstance().setAcceptThirdPartyCookies(wv, true); 110 | } 111 | 112 | webSettings.setDomStorageEnabled(true); 113 | webSettings.setCacheMode(appConfig.cacheMode.webSettingsCacheMode()); 114 | 115 | webSettings.setDatabaseEnabled(true); 116 | 117 | webSettings.setSaveFormData(false); 118 | webSettings.setSavePassword(false); 119 | webSettings.setUserAgentString(appConfig.userAgent); 120 | webSettings.setSupportMultipleWindows(appConfig.enableWindowOpen); 121 | webSettings.setGeolocationEnabled(appConfig.usesGeolocation); 122 | webSettings.setMediaPlaybackRequiresUserGesture(false); 123 | 124 | if (appConfig.webviewTextZoom > 0) { 125 | webSettings.setTextZoom(appConfig.webviewTextZoom); 126 | } 127 | } 128 | 129 | public static void setupWebviewGlobals(Context context) { 130 | // WebView debugging 131 | if(!AppConfig.getInstance(context).geckoViewEnabled) { 132 | Map installation = Installation.getInfo(context); 133 | String dist = (String)installation.get("distribution"); 134 | if (dist != null && (dist.equals("debug") || dist.equals("adhoc"))) { 135 | WebView.setWebContentsDebuggingEnabled(true); 136 | } 137 | } 138 | } 139 | 140 | public static void removeCallbacks(LeanWebView webview) { 141 | webview.setWebViewClient(null); 142 | webview.setWebChromeClient(null); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | import groovy.json.JsonSlurper 2 | 3 | apply plugin: 'com.android.application' 4 | apply plugin: 'kotlin-android' 5 | //[enabled by builder] apply plugin: 'com.google.gms.google-services' 6 | //[enabled by builder] apply plugin: 'com.google.firebase.crashlytics' 7 | 8 | ext { 9 | fbAppId = "" 10 | fbClientToken = "" 11 | onesignalAppId = "" 12 | adMobAppId = "" 13 | googleServiceInvalid = "false" 14 | auth0Domain = "" 15 | auth0Scheme = "" 16 | } 17 | 18 | task parseAppConfig { 19 | def jsonFile = file('src/main/assets/appConfig.json') 20 | def parsedJson = new JsonSlurper().parseText(jsonFile.text) 21 | if (parsedJson.services.facebook) { 22 | if (parsedJson.services.facebook.appId) { 23 | fbAppId = parsedJson.services.facebook.appId 24 | } 25 | if (parsedJson.services.facebook.clientToken) { 26 | fbClientToken = parsedJson.services.facebook.clientToken 27 | } 28 | } 29 | if (parsedJson.services.socialLogin && parsedJson.services.socialLogin.facebookLogin) { 30 | if (parsedJson.services.socialLogin.facebookLogin.appId) { 31 | fbAppId = parsedJson.services.socialLogin.facebookLogin.appId 32 | } 33 | if (parsedJson.services.socialLogin.facebookLogin.clientToken) { 34 | fbClientToken = parsedJson.services.socialLogin.facebookLogin.clientToken 35 | } 36 | } 37 | if (parsedJson.services.oneSignal && parsedJson.services.oneSignal.applicationId) { 38 | onesignalAppId = parsedJson.services.oneSignal.applicationId 39 | } 40 | if (parsedJson.services.admob && parsedJson.services.admob.admobAndroid && parsedJson.services.admob.admobAndroid.applicationId) { 41 | adMobAppId = parsedJson.services.admob.admobAndroid.applicationId 42 | } 43 | if (parsedJson.services.braze) { 44 | if (parsedJson.services.braze.androidApiKey) { 45 | gradle.ext.set("braze_api_key", parsedJson.services.braze.androidApiKey) 46 | } 47 | if (parsedJson.services.braze.androidEndpointKey) { 48 | gradle.ext.set("braze_endpoint_key", parsedJson.services.braze.androidEndpointKey) 49 | } 50 | } 51 | if (parsedJson.services.auth0) { 52 | if (parsedJson.services.auth0.domain) { 53 | auth0Domain = parsedJson.services.auth0.domain 54 | } 55 | if (parsedJson.services.auth0.scheme) { 56 | auth0Scheme = parsedJson.services.auth0.scheme 57 | } 58 | } 59 | } 60 | 61 | task checkGoogleService { 62 | plugins.withId("com.google.gms.google-services") { 63 | def googleServiceJsonFile = file('google-services.json') 64 | if (project.file(googleServiceJsonFile).exists()) { 65 | if (googleServiceJsonFile.text.isEmpty()) { 66 | googleServiceInvalid = "true" 67 | } 68 | } else { 69 | googleServiceInvalid = "true" 70 | } 71 | } 72 | } 73 | 74 | build.dependsOn parseAppConfig 75 | build.dependsOn checkGoogleService 76 | 77 | android { 78 | defaultConfig { 79 | compileSdk 33 80 | minSdkVersion 21 81 | targetSdkVersion 33 82 | applicationId "io.gonative.android" 83 | versionCode 1 84 | multiDexEnabled true 85 | vectorDrawables.useSupportLibrary = true 86 | 87 | manifestPlaceholders = [manifestApplicationId: "${applicationId}", 88 | onesignal_app_id: onesignalAppId, 89 | onesignal_google_project_number: "", 90 | admob_app_id: adMobAppId, 91 | facebook_app_id: fbAppId, 92 | facebook_client_token: fbClientToken, 93 | auth0Domain: auth0Domain, auth0Scheme: auth0Scheme ] 94 | } 95 | 96 | compileOptions { 97 | sourceCompatibility JavaVersion.VERSION_1_8 98 | targetCompatibility JavaVersion.VERSION_1_8 99 | } 100 | 101 | signingConfigs { 102 | release { 103 | storeFile file("../../release.keystore") 104 | storePassword "password" 105 | keyAlias "release" 106 | keyPassword "password" 107 | } 108 | upload { 109 | storeFile file("../../upload.keystore") 110 | storePassword "password" 111 | keyAlias "upload" 112 | keyPassword "password" 113 | } 114 | } 115 | 116 | buildTypes { 117 | debug { 118 | applicationIdSuffix ".debug" 119 | } 120 | release { 121 | minifyEnabled true 122 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt' 123 | zipAlignEnabled true 124 | debuggable project.getProperties().get("enableLogsInRelease").toBoolean() 125 | signingConfig signingConfigs.release 126 | } 127 | upload { 128 | minifyEnabled true 129 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt' 130 | zipAlignEnabled true 131 | matchingFallbacks = ['release'] 132 | debuggable project.getProperties().get("enableLogsInRelease").toBoolean() 133 | signingConfig signingConfigs.upload 134 | } 135 | buildTypes.each { 136 | it.buildConfigField 'boolean', 'GOOGLE_SERVICE_INVALID', googleServiceInvalid 137 | } 138 | } 139 | 140 | flavorDimensions "webview" 141 | 142 | productFlavors { 143 | normal { 144 | dimension "webview" 145 | } 146 | } 147 | namespace 'io.gonative.android' 148 | testNamespace '${applicationId}.test' 149 | } 150 | 151 | dependencies { 152 | /**** dependencies used by all apps ****/ 153 | implementation "androidx.core:core-ktx:1.10.1" 154 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 155 | implementation 'com.aurelhubert:ahbottomnavigation:2.3.4' 156 | implementation 'com.squareup:seismic:1.0.2' 157 | implementation 'androidx.webkit:webkit:1.7.0' 158 | implementation 'androidx.core:core-splashscreen:1.0.1' 159 | implementation "com.github.gonativeio:gonative-icons:$iconsVersion" 160 | implementation "com.github.gonativeio:gonative-android-core:$coreVersion" 161 | /**** end all apps ****/ 162 | 163 | /**** add-on module dependencies ****/ 164 | /**** end modules ****/ 165 | 166 | /**** Google Android and Play Services dependencies ****/ 167 | implementation 'androidx.multidex:multidex:2.0.1' 168 | implementation 'androidx.cardview:cardview:1.0.0' 169 | implementation 'androidx.browser:browser:1.5.0' 170 | implementation 'androidx.appcompat:appcompat:1.6.1' 171 | implementation 'com.google.android.material:material:1.9.0' 172 | implementation "androidx.drawerlayout:drawerlayout:1.2.0" 173 | implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' 174 | /**** end google ****/ 175 | 176 | /**** local dependencies ****/ 177 | implementation fileTree(dir: 'libs', include: '*.jar') 178 | implementation fileTree(dir: 'libs', include: '*.aar') 179 | /**** end local ****/ 180 | } 181 | 182 | apply from: file("../plugins.gradle"); applyNativeModulesAppBuildGradle(project) --------------------------------------------------------------------------------