From 9290c0382a7794f95785dbe20b29dca09797261a Mon Sep 17 00:00:00 2001 From: Jenkins Date: Wed, 18 Dec 2024 14:22:05 -0800 Subject: [PATCH] release 1.1.0.10 --- README.md | 8 +- sample-app/app/build.gradle.kts | 73 +++-- sample-app/app/google-services.json | 16 +- sample-app/app/src/main/AndroidManifest.xml | 15 +- .../app/src/main/assets/config_dev2.json | 13 + .../app/src/main/assets/config_prod.json | 14 + .../app/src/main/assets/config_qa2.json | 13 + .../app/src/main/assets/empty_secrets.json | 20 ++ .../zscaler/sdk/demoapp/RetrofitApiClient.kt | 99 ------- .../ConfigActivity.kt} | 16 +- .../ConfigAdapter.kt} | 20 +- .../{ => configuration}/ZscalerSDKConfig.kt | 10 +- .../sdk/demoapp/constants/RequestMethod.kt | 5 + .../sdk/demoapp/{ => constants}/ZDKTunnel.kt | 2 +- .../demoapp/{ => constants}/ZdkConstants.kt | 2 +- .../{ => lifecycle}/AppLifecycleObserver.kt | 2 +- .../{ => lifecycle}/MainApplication.kt | 2 +- .../networking/ParentAppRetrofitClient.kt | 44 ++++ .../NotificationCancellationService.kt | 3 +- .../zscaler/sdk/demoapp/util/ProxyUtility.kt | 41 +++ .../sdk/demoapp/{ => util}/ZipUtility.kt | 2 +- .../sdk/demoapp/view/EventLogViewActivity.kt | 37 +++ .../sdk/demoapp/{ => view}/MainActivity.kt | 249 +++++++++++++----- .../sdk/demoapp/{ => view}/MainViewModel.kt | 56 ++-- .../sdk/demoapp/{ => view}/ZdkDialog.kt | 2 +- .../src/main/res/drawable/ic_event_log.xml | 9 + .../main/res/layout/activity_logger_view.xml | 30 +++ .../app/src/main/res/layout/activity_main.xml | 33 ++- .../src/main/res/layout/activity_setting.xml | 32 +-- .../app/src/main/res/layout/item_setting.xml | 5 +- sample-app/app/src/main/res/values/colors.xml | 1 + sample-app/app/src/main/res/values/dimens.xml | 7 + sample-app/app/src/main/res/values/secret.xml | 2 +- .../app/src/main/res/values/strings.xml | 2 + .../src/test/java/com/zscaler/ReadCrashLog.kt | 110 -------- sample-app/build.gradle.kts | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- sample-app/settings.gradle.kts | 6 +- 38 files changed, 598 insertions(+), 407 deletions(-) create mode 100644 sample-app/app/src/main/assets/config_dev2.json create mode 100644 sample-app/app/src/main/assets/config_prod.json create mode 100644 sample-app/app/src/main/assets/config_qa2.json create mode 100644 sample-app/app/src/main/assets/empty_secrets.json delete mode 100644 sample-app/app/src/main/java/com/zscaler/sdk/demoapp/RetrofitApiClient.kt rename sample-app/app/src/main/java/com/zscaler/sdk/demoapp/{SettingActivity.kt => configuration/ConfigActivity.kt} (88%) rename sample-app/app/src/main/java/com/zscaler/sdk/demoapp/{SettingsAdapter.kt => configuration/ConfigAdapter.kt} (85%) rename sample-app/app/src/main/java/com/zscaler/sdk/demoapp/{ => configuration}/ZscalerSDKConfig.kt (86%) create mode 100644 sample-app/app/src/main/java/com/zscaler/sdk/demoapp/constants/RequestMethod.kt rename sample-app/app/src/main/java/com/zscaler/sdk/demoapp/{ => constants}/ZDKTunnel.kt (63%) rename sample-app/app/src/main/java/com/zscaler/sdk/demoapp/{ => constants}/ZdkConstants.kt (98%) rename sample-app/app/src/main/java/com/zscaler/sdk/demoapp/{ => lifecycle}/AppLifecycleObserver.kt (94%) rename sample-app/app/src/main/java/com/zscaler/sdk/demoapp/{ => lifecycle}/MainApplication.kt (86%) create mode 100644 sample-app/app/src/main/java/com/zscaler/sdk/demoapp/networking/ParentAppRetrofitClient.kt rename sample-app/app/src/main/java/com/zscaler/sdk/demoapp/{ => service}/NotificationCancellationService.kt (95%) create mode 100644 sample-app/app/src/main/java/com/zscaler/sdk/demoapp/util/ProxyUtility.kt rename sample-app/app/src/main/java/com/zscaler/sdk/demoapp/{ => util}/ZipUtility.kt (97%) create mode 100644 sample-app/app/src/main/java/com/zscaler/sdk/demoapp/view/EventLogViewActivity.kt rename sample-app/app/src/main/java/com/zscaler/sdk/demoapp/{ => view}/MainActivity.kt (73%) rename sample-app/app/src/main/java/com/zscaler/sdk/demoapp/{ => view}/MainViewModel.kt (83%) rename sample-app/app/src/main/java/com/zscaler/sdk/demoapp/{ => view}/ZdkDialog.kt (94%) create mode 100644 sample-app/app/src/main/res/drawable/ic_event_log.xml create mode 100644 sample-app/app/src/main/res/layout/activity_logger_view.xml delete mode 100644 sample-app/app/src/test/java/com/zscaler/ReadCrashLog.kt diff --git a/README.md b/README.md index c5f5914..1ba1a81 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Zscaler Development Kit (Zscaler SDK), part of the Zscaler Zero Trust Exchange ## Integration -1. Make sure gpr.user and gpr.key are set to your github username and access token in local.properties. +1. Make sure gpr.user and gpr.key are set to your github username and access token in global gradle.properties file at ~/.gradle/gradle.properties 2. Add the Github repository to the dependencyResolutionManagement repositories in settings.gradle. ``` dependencyResolutionManagement { @@ -15,8 +15,8 @@ dependencyResolutionManagement { name = "ZscalerSDKAndroid" url = uri("https://maven.pkg.github.com/zscaler/zscaler-sdk-android") credentials { - username = settings.extra.get("gpr.user") as String? ?: System.getenv("GITHUB_USERNAME") - password = settings.extra.get("gpr.key") as String? ?: System.getenv("GITHUB_TOKEN") + username = settings.extra.get("gpr.user") as String ?: System.getenv("GITHUB_USERNAME") + password = settings.extra.get("gpr.key") as String ?: System.getenv("GITHUB_TOKEN") } } } @@ -29,4 +29,6 @@ dependencies { implementation("com.zscaler.sdk:zscalersdk-android:latest.release") } ``` +## Git Hub Access Token +Check the below link for generating the access token [[here](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-gradle-registry#authenticating-to-github-packages)] diff --git a/sample-app/app/build.gradle.kts b/sample-app/app/build.gradle.kts index 4414a70..5080299 100644 --- a/sample-app/app/build.gradle.kts +++ b/sample-app/app/build.gradle.kts @@ -8,9 +8,9 @@ plugins { id("com.android.application") id("org.jetbrains.kotlin.android") id("maven-publish") - id("com.github.triplet.play") version "3.9.1" - id("com.google.gms.google-services") - id("com.google.firebase.crashlytics") + id("com.google.gms.google-services") version "4.4.2" + id("com.google.firebase.crashlytics") version "3.0.2" + id("com.github.triplet.play") version "3.9.1" // for publishing test app bundle to play } var versionNameVal = "x.x-dev" @@ -31,12 +31,12 @@ if (project.hasProperty("buildVersionName")) { android { namespace = "com.zscaler.sdk.demoapp" - compileSdk = 34 + compileSdk = 35 defaultConfig { applicationId = "com.zscaler.sdk.android.testapp" - minSdk = 26 - targetSdk = 34 + minSdk = 28 + targetSdk = 35 versionCode = versionCodeVal versionName = versionNameVal @@ -44,6 +44,10 @@ android { vectorDrawables { useSupportLibrary = true } + ndk { + //noinspection ChromeOsAbiSupport + abiFilters += mutableSetOf("armeabi-v7a" , "arm64-v8a") // Exclude x86 and x86_64 + } } signingConfigs { @@ -89,11 +93,6 @@ android { viewBinding = true buildConfig = true } - composeOptions { - kotlinCompilerExtensionVersion = "1.5.1" - } - ndkVersion = "25.2.9519653" - buildToolsVersion = "34.0.0" publishing { singleVariant("release") { @@ -111,6 +110,9 @@ android { resolutionStrategy.set(ResolutionStrategy.AUTO) } } + + ndkVersion = "27.2.12479018" + buildToolsVersion = "35.0.0" } afterEvaluate { @@ -137,45 +139,36 @@ publishing { } dependencies { - implementation("androidx.appcompat:appcompat:1.3.1") - implementation("com.google.android.material:material:1.3.0") - implementation("androidx.activity:activity:1.2.4") - implementation("androidx.constraintlayout:constraintlayout:2.0.4") - implementation("androidx.core:core-ktx:1.6.0") - implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.3.1") - implementation("androidx.lifecycle:lifecycle-process:2.3.1") - implementation("com.google.code.gson:gson:2.8.2") - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0") - implementation ("androidx.compose.runtime:runtime-livedata:1.1.1") - - // ViewModel - implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1") - implementation("androidx.compose.runtime:runtime-livedata:1.0.0") - // ViewModel utilities for Compose - implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.4.0") - implementation("androidx.webkit:webkit:1.4.0") - - //retrofit + implementation("androidx.appcompat:appcompat:1.7.0") + implementation("com.google.android.material:material:1.12.0") + implementation("androidx.core:core-ktx:1.13.1") + implementation ("androidx.compose.runtime:runtime-livedata:1.7.1") + + // Androidx Lifecycle + implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.5") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.5") + implementation("androidx.lifecycle:lifecycle-process:2.8.5") + + // WebView + implementation("androidx.webkit:webkit:1.9.0") + + // retrofit implementation ("com.squareup.retrofit2:retrofit:2.11.0") implementation("com.squareup.retrofit2:converter-gson:2.11.0") - implementation("com.squareup.okhttp3:logging-interceptor:4.12.0") // zdk dependency implementation("com.zscaler.sdk:zscalersdk-android:latest.release") - implementation("androidx.security:security-crypto:1.1.0-alpha06") - - implementation(platform("com.google.firebase:firebase-bom:31.1.0")) - implementation("com.google.firebase:firebase-analytics") + // Firebase crashlytics + implementation(platform("com.google.firebase:firebase-bom:33.3.0")) implementation("com.google.firebase:firebase-crashlytics") implementation("com.google.firebase:firebase-crashlytics-ktx") +// implementation("com.google.firebase:firebase-analytics") // enable it for analytics reporting if required implementation("com.google.firebase:firebase-crashlytics-ndk") // test dependencies testImplementation("junit:junit:4.13.2") - testImplementation("org.mockito:mockito-core:5.1.0") - testImplementation("org.mockito.kotlin:mockito-kotlin:5.1.0") - androidTestImplementation("androidx.test.ext:junit:1.1.5") - androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") - + androidTestImplementation("androidx.test.ext:junit:1.2.1") + androidTestImplementation("androidx.test:runner:1.6.2") + androidTestImplementation("com.google.code.gson:gson:2.10.1") } diff --git a/sample-app/app/google-services.json b/sample-app/app/google-services.json index 9d0423d..1a14e0a 100644 --- a/sample-app/app/google-services.json +++ b/sample-app/app/google-services.json @@ -1,21 +1,21 @@ { "project_info": { - "project_number": "", - "project_id": "", - "storage_bucket": "" + "project_number": "126618292848", + "project_id": "zscaler-sdk-sample", + "storage_bucket": "zscaler-sdk-sample.appspot.com" }, "client": [ { "client_info": { - "mobilesdk_app_id": "", + "mobilesdk_app_id": "1:126618292848:android:9cdd2d6355da2f03339ab5", "android_client_info": { - "package_name": "" + "package_name": "com.zscaler.sdk.android.testapp" } }, "oauth_client": [], "api_key": [ { - "current_key": "" + "current_key": "AIzaSyC2XZ5E5oIwelfQOeNFSfh636tKvETqxXY" } ], "services": { @@ -25,5 +25,5 @@ } } ], - "configuration_version": "" -} \ No newline at end of file + "configuration_version": "1" +} diff --git a/sample-app/app/src/main/AndroidManifest.xml b/sample-app/app/src/main/AndroidManifest.xml index c0ab1bf..b0f7ef2 100644 --- a/sample-app/app/src/main/AndroidManifest.xml +++ b/sample-app/app/src/main/AndroidManifest.xml @@ -10,7 +10,7 @@ tools:ignore="ScopedStorage" /> + + - - @FormUrlEncoded - @POST - fun postData( - @Url url: String, - @FieldMap params: Map = emptyMap() - ): Call -} - -object ManualRetrofitApiClient { - private var TAG = "ManualRetrofitApiClient" - private var retrofit: Retrofit? = null - private var baseUrl: String = "" - - fun getRetrofitWithProxyInfo(baseUrl: String, proxyInfo: ZscalerSDKProxyInfo): Retrofit? { - if (retrofit == null || ManualRetrofitApiClient.baseUrl != baseUrl) { - val client = getOkHttpclient(proxyInfo) - ManualRetrofitApiClient.baseUrl = baseUrl - retrofit = Retrofit.Builder() - .baseUrl(baseUrl) - .client(client) - .addConverterFactory(GsonConverterFactory.create()) - .build() - } - return retrofit - } - - fun clearRetroFitInstance() { - retrofit = null - } - - private fun getOkHttpclient(zscalerSDKProxyInfo: ZscalerSDKProxyInfo): OkHttpClient { - val proxyHost = zscalerSDKProxyInfo.proxyHost - val proxyPort = zscalerSDKProxyInfo.proxyPort - val username = zscalerSDKProxyInfo.username - val password = zscalerSDKProxyInfo.password - val builder = OkHttpClient.Builder() - setProxyForHttpRequest(proxyHost, proxyPort) - if (username?.isNotEmpty() == true && password?.isNotEmpty() == true) { - val authenticator = Authenticator { _: Route?, response: Response -> - val credential = Credentials.basic(username, password) - response.request.newBuilder() - .header("Proxy-Authorization", credential) - .build() - } - builder.proxyAuthenticator(authenticator) - } - return builder.build() - } - - private fun setProxyForHttpRequest(proxyHost: String, proxyPort: Int) { - ProxySelector.setDefault(object : ProxySelector() { - override fun select(uri: URI?): MutableList { - val proxyList = mutableListOf() - proxyList.add(Proxy(Proxy.Type.HTTP, InetSocketAddress(proxyHost, proxyPort))) - return proxyList - } - - override fun connectFailed(uri: URI?, sa: SocketAddress?, ioe: IOException?) { - Log.d(TAG, "connectFailed : proxy connectFailed() with: uri = $uri, SocketAddress = $sa, IOException = ${ioe?.message}") - } - }) - } -} - -enum class HttpMethod { - GET, POST, WEB -} \ No newline at end of file diff --git a/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/SettingActivity.kt b/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/configuration/ConfigActivity.kt similarity index 88% rename from sample-app/app/src/main/java/com/zscaler/sdk/demoapp/SettingActivity.kt rename to sample-app/app/src/main/java/com/zscaler/sdk/demoapp/configuration/ConfigActivity.kt index e6c8770..ec1f53c 100644 --- a/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/SettingActivity.kt +++ b/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/configuration/ConfigActivity.kt @@ -1,4 +1,4 @@ -package com.zscaler.sdk.demoapp +package com.zscaler.sdk.demoapp.configuration import android.os.Bundle import android.util.Log @@ -6,14 +6,16 @@ import androidx.appcompat.app.AppCompatActivity import androidx.recyclerview.widget.LinearLayoutManager import com.zscaler.sdk.android.ZscalerSDK import com.zscaler.sdk.android.exception.ZscalerSDKException +import com.zscaler.sdk.demoapp.R import com.zscaler.sdk.demoapp.databinding.ActivitySettingBinding +import com.zscaler.sdk.demoapp.networking.ParentAppRetrofitClient -class SettingActivity : AppCompatActivity() { +class ConfigActivity : AppCompatActivity() { private val TAG = "SettingActivity" private lateinit var binding: ActivitySettingBinding private lateinit var settingsList: MutableList private var zscalerSDKConfigurationMap = mutableMapOf() - private lateinit var settingsAdapter: SettingsAdapter + private lateinit var settingsAdapter: ConfigAdapter override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -44,10 +46,10 @@ class SettingActivity : AppCompatActivity() { true ), SettingItem( - SettingType.BLOCK_JB_TRAFFIC, + SettingType.BLOCK_ROOT_TRAFFIC, getString(R.string.setting_title_block_root_traffic), getString(R.string.setting_desc_block_root_traffic), - zscalerSDKConfigurationMap.getOrDefault(SettingType.BLOCK_JB_TRAFFIC, false), true + zscalerSDKConfigurationMap.getOrDefault(SettingType.BLOCK_ROOT_TRAFFIC, false), true ), SettingItem( SettingType.BLOCK_ZPA_CONNECTION, @@ -71,11 +73,11 @@ class SettingActivity : AppCompatActivity() { false ) ) - settingsAdapter = SettingsAdapter(this, settingsList) + settingsAdapter = ConfigAdapter(this, settingsList) binding.settingsRecycleView.layoutManager = LinearLayoutManager(this) binding.settingsRecycleView.adapter = settingsAdapter binding.tvSettingDone.setOnClickListener { - ManualRetrofitApiClient.clearRetroFitInstance() + ParentAppRetrofitClient.clearRetroFitInstance() try { ZscalerSDK.setConfiguration(ZscalerSDKSetting.getZscalerSDKConfiguration()) } catch (exception: ZscalerSDKException) { diff --git a/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/SettingsAdapter.kt b/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/configuration/ConfigAdapter.kt similarity index 85% rename from sample-app/app/src/main/java/com/zscaler/sdk/demoapp/SettingsAdapter.kt rename to sample-app/app/src/main/java/com/zscaler/sdk/demoapp/configuration/ConfigAdapter.kt index 7751eb6..7465ae8 100644 --- a/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/SettingsAdapter.kt +++ b/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/configuration/ConfigAdapter.kt @@ -1,42 +1,39 @@ -package com.zscaler.sdk.demoapp +package com.zscaler.sdk.demoapp.configuration import android.content.Context import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup -import android.widget.BaseAdapter import androidx.recyclerview.widget.RecyclerView import androidx.viewbinding.ViewBinding -import com.zscaler.sdk.android.configuration.ZscalerSDKConfiguration import com.zscaler.sdk.android.configuration.ZscalerSDKConfiguration.* import com.zscaler.sdk.demoapp.databinding.ItemSettingBinding import com.zscaler.sdk.demoapp.databinding.ItemSettingRadioBinding -class SettingsAdapter( +class ConfigAdapter( private val context: Context, private val settingsList: MutableList, -) : RecyclerView.Adapter() { +) : RecyclerView.Adapter() { companion object { private const val VIEW_TYPE_TOGGLE = 1 private const val VIEW_TYPE_RADIO_BUTTONS = 2 } - class SettingsViewHolder(val binding: ViewBinding) : + class ConfigViewHolder(val binding: ViewBinding) : RecyclerView.ViewHolder(binding.root) - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingsViewHolder { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ConfigViewHolder { return when (viewType) { VIEW_TYPE_TOGGLE -> { val binding = ItemSettingBinding.inflate(LayoutInflater.from(context), parent, false) - SettingsViewHolder(binding) + ConfigViewHolder(binding) } VIEW_TYPE_RADIO_BUTTONS -> { val binding = ItemSettingRadioBinding.inflate(LayoutInflater.from(context), parent, false) - SettingsViewHolder(binding) + ConfigViewHolder(binding) } else -> throw IllegalArgumentException("Invalid view type") @@ -52,7 +49,7 @@ class SettingsAdapter( } } - override fun onBindViewHolder(holder: SettingsViewHolder, position: Int) { + override fun onBindViewHolder(holder: ConfigViewHolder, position: Int) { val settingItem = settingsList[position] when (getItemViewType(position)) { @@ -69,6 +66,7 @@ class SettingsAdapter( ZscalerSDKSetting.zscalerSDKConfigurationMap[settingItem.type] = binding.toggle.isChecked } + binding.toggle.text = settingItem.type.name } VIEW_TYPE_RADIO_BUTTONS -> { diff --git a/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/ZscalerSDKConfig.kt b/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/configuration/ZscalerSDKConfig.kt similarity index 86% rename from sample-app/app/src/main/java/com/zscaler/sdk/demoapp/ZscalerSDKConfig.kt rename to sample-app/app/src/main/java/com/zscaler/sdk/demoapp/configuration/ZscalerSDKConfig.kt index 2520b53..bf99b13 100644 --- a/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/ZscalerSDKConfig.kt +++ b/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/configuration/ZscalerSDKConfig.kt @@ -1,4 +1,4 @@ -package com.zscaler.sdk.demoapp +package com.zscaler.sdk.demoapp.configuration import com.zscaler.sdk.android.configuration.ZscalerSDKConfiguration @@ -6,7 +6,7 @@ enum class SettingType { URL_SESSIONS, WEB_VIEWS, PROXY_AUTHENTICATION, - BLOCK_JB_TRAFFIC, + BLOCK_ROOT_TRAFFIC, BLOCK_ZPA_CONNECTION, ENABLE_DEBUG_LOGS, LOG_LEVEL, @@ -38,6 +38,10 @@ object ZscalerSDKSetting { SettingType.PROXY_AUTHENTICATION, false ), + failIfDeviceCompromised = zscalerSDKConfigurationMap.getOrDefault( + SettingType.BLOCK_ROOT_TRAFFIC, + false + ), blockZPAConnectionsOnTunnelFailure = zscalerSDKConfigurationMap.getOrDefault( SettingType.BLOCK_ZPA_CONNECTION, false @@ -53,11 +57,13 @@ object ZscalerSDKSetting { fun defaultZscalerSDKConfiguration(): ZscalerSDKConfiguration { zscalerSDKConfigurationMap[SettingType.URL_SESSIONS] = true zscalerSDKConfigurationMap[SettingType.WEB_VIEWS] = true + zscalerSDKConfigurationMap[SettingType.BLOCK_ROOT_TRAFFIC] = true zscalerSDKConfigurationMap[SettingType.ENABLE_DEBUG_LOGS] = true logLevel = ZscalerSDKConfiguration.LogLevel.debug return ZscalerSDKConfiguration( automaticallyConfigureRequests = true, automaticallyConfigureWebviews = true, + failIfDeviceCompromised = true, enableDebugLogsInConsole = true, logLevel = ZscalerSDKConfiguration.LogLevel.debug ) diff --git a/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/constants/RequestMethod.kt b/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/constants/RequestMethod.kt new file mode 100644 index 0000000..7e7bccb --- /dev/null +++ b/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/constants/RequestMethod.kt @@ -0,0 +1,5 @@ +package com.zscaler.sdk.demoapp.constants + +enum class RequestMethod { + GET, POST, WEB +} \ No newline at end of file diff --git a/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/ZDKTunnel.kt b/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/constants/ZDKTunnel.kt similarity index 63% rename from sample-app/app/src/main/java/com/zscaler/sdk/demoapp/ZDKTunnel.kt rename to sample-app/app/src/main/java/com/zscaler/sdk/demoapp/constants/ZDKTunnel.kt index e5600f7..0b92416 100644 --- a/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/ZDKTunnel.kt +++ b/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/constants/ZDKTunnel.kt @@ -1,4 +1,4 @@ -package com.zscaler.sdk.demoapp +package com.zscaler.sdk.demoapp.constants enum class ZDKTunnel { PRE_LOGIN, diff --git a/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/ZdkConstants.kt b/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/constants/ZdkConstants.kt similarity index 98% rename from sample-app/app/src/main/java/com/zscaler/sdk/demoapp/ZdkConstants.kt rename to sample-app/app/src/main/java/com/zscaler/sdk/demoapp/constants/ZdkConstants.kt index 6dbeec0..a1e7046 100644 --- a/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/ZdkConstants.kt +++ b/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/constants/ZdkConstants.kt @@ -1,4 +1,4 @@ -package com.zscaler.sdk.demoapp +package com.zscaler.sdk.demoapp.constants const val NOTIFICATION_CHANNEL_ID = "ZSCALERSDK_NOTIFICATION_ID" const val NOTIFICATION_ID = 1 diff --git a/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/AppLifecycleObserver.kt b/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/lifecycle/AppLifecycleObserver.kt similarity index 94% rename from sample-app/app/src/main/java/com/zscaler/sdk/demoapp/AppLifecycleObserver.kt rename to sample-app/app/src/main/java/com/zscaler/sdk/demoapp/lifecycle/AppLifecycleObserver.kt index f31814e..dd19d6c 100644 --- a/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/AppLifecycleObserver.kt +++ b/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/lifecycle/AppLifecycleObserver.kt @@ -1,4 +1,4 @@ -package com.zscaler.sdk.demoapp +package com.zscaler.sdk.demoapp.lifecycle import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleObserver diff --git a/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/MainApplication.kt b/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/lifecycle/MainApplication.kt similarity index 86% rename from sample-app/app/src/main/java/com/zscaler/sdk/demoapp/MainApplication.kt rename to sample-app/app/src/main/java/com/zscaler/sdk/demoapp/lifecycle/MainApplication.kt index c72fa70..abd7b8f 100644 --- a/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/MainApplication.kt +++ b/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/lifecycle/MainApplication.kt @@ -1,4 +1,4 @@ -package com.zscaler.sdk.demoapp +package com.zscaler.sdk.demoapp.lifecycle import android.app.Application import androidx.lifecycle.ProcessLifecycleOwner diff --git a/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/networking/ParentAppRetrofitClient.kt b/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/networking/ParentAppRetrofitClient.kt new file mode 100644 index 0000000..41bb978 --- /dev/null +++ b/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/networking/ParentAppRetrofitClient.kt @@ -0,0 +1,44 @@ +package com.zscaler.sdk.demoapp.networking + +import okhttp3.ResponseBody +import retrofit2.Call +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import retrofit2.http.FieldMap +import retrofit2.http.FormUrlEncoded +import retrofit2.http.GET +import retrofit2.http.POST +import retrofit2.http.Url + + +interface ApiService { + + @GET + fun getData(@Url url: String): Call + + @FormUrlEncoded + @POST + fun postData( + @Url url: String, + @FieldMap params: Map = emptyMap() + ): Call +} +object ParentAppRetrofitClient { + private var retrofit: Retrofit? = null + private var baseUrl: String = "" + + fun getRetrofitClient(baseUrl: String): Retrofit? { + if (retrofit == null || ParentAppRetrofitClient.baseUrl != baseUrl) { + ParentAppRetrofitClient.baseUrl = baseUrl + retrofit = Retrofit.Builder() + .baseUrl(baseUrl) + .addConverterFactory(GsonConverterFactory.create()) + .build() + } + return retrofit + } + + fun clearRetroFitInstance() { + retrofit = null + } +} \ No newline at end of file diff --git a/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/NotificationCancellationService.kt b/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/service/NotificationCancellationService.kt similarity index 95% rename from sample-app/app/src/main/java/com/zscaler/sdk/demoapp/NotificationCancellationService.kt rename to sample-app/app/src/main/java/com/zscaler/sdk/demoapp/service/NotificationCancellationService.kt index ee095fa..84218a1 100644 --- a/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/NotificationCancellationService.kt +++ b/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/service/NotificationCancellationService.kt @@ -1,4 +1,4 @@ -package com.zscaler.sdk.demoapp +package com.zscaler.sdk.demoapp.service import android.app.NotificationManager import android.app.Service @@ -7,6 +7,7 @@ import android.content.Intent import android.content.pm.ServiceInfo import android.os.Build import android.os.IBinder +import com.zscaler.sdk.demoapp.constants.NOTIFICATION_ID /** diff --git a/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/util/ProxyUtility.kt b/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/util/ProxyUtility.kt new file mode 100644 index 0000000..872dd8b --- /dev/null +++ b/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/util/ProxyUtility.kt @@ -0,0 +1,41 @@ +package com.zscaler.sdk.demoapp.util + +import android.util.Log +import androidx.webkit.ProxyController +import androidx.webkit.WebViewFeature + +object ProxyUtility { + private const val TAG = "ProxyUtility" + + fun clearWebViewProxy() { + if (isWebKitClassAvailable()) { + if (WebViewFeature.isFeatureSupported(WebViewFeature.PROXY_OVERRIDE)) { + ProxyController.getInstance().clearProxyOverride( + { + Log.d(TAG, + "clearWebViewProxy: clearProxyOverride execute() called with: proxyManager = $it") + }, + { + Log.d(TAG, "clearWebViewProxy: clearProxyOverride run() called") + Log.d(TAG, "clearWebViewProxy: Failed to clear proxy in WebView") + } + ) + } else { + Log.e(TAG, "clearWebViewProxy: WebView proxy override not supported") + } + } else { + Log.e(TAG, "clearWebViewProxy: Androidx Webkit dependencies not found") + } + } + + private fun isWebKitClassAvailable(): Boolean { + try { + Class.forName("androidx.webkit.WebViewFeature") + Class.forName("androidx.webkit.ProxyController") + return true + } catch (e: Exception) { + Log.e(TAG, "isWebKitClassAvailable: Androidx Webkit dependencies not found") + } + return false + } +} \ No newline at end of file diff --git a/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/ZipUtility.kt b/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/util/ZipUtility.kt similarity index 97% rename from sample-app/app/src/main/java/com/zscaler/sdk/demoapp/ZipUtility.kt rename to sample-app/app/src/main/java/com/zscaler/sdk/demoapp/util/ZipUtility.kt index f2ef43a..2530be8 100644 --- a/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/ZipUtility.kt +++ b/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/util/ZipUtility.kt @@ -1,4 +1,4 @@ -package com.zscaler.sdk.demoapp +package com.zscaler.sdk.demoapp.util import android.content.Context import android.os.Build diff --git a/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/view/EventLogViewActivity.kt b/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/view/EventLogViewActivity.kt new file mode 100644 index 0000000..33a0ade --- /dev/null +++ b/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/view/EventLogViewActivity.kt @@ -0,0 +1,37 @@ +package com.zscaler.sdk.demoapp.view + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import com.zscaler.sdk.demoapp.databinding.ActivityLoggerViewBinding +import java.io.BufferedReader +import java.io.File + +class EventLogViewActivity : AppCompatActivity() { + + private lateinit var binding: ActivityLoggerViewBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityLoggerViewBinding.inflate(layoutInflater) + setContentView(binding.root) + + val csvData = readCSVFromInternalStorage() + binding.textViewCsvContent.text = csvData.joinToString("\n\n") + } + + private fun readCSVFromInternalStorage(): List { + val csvFile = File(filesDir.absolutePath + "/zdk/events.csv") + val content = mutableListOf() + + if (csvFile.exists()) { + val bufferedReader = BufferedReader(csvFile.reader()) + bufferedReader.forEachLine { line -> + content.add(line) + } + bufferedReader.close() + } else { + content.add("File not found!") + } + return content + } +} diff --git a/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/MainActivity.kt b/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/view/MainActivity.kt similarity index 73% rename from sample-app/app/src/main/java/com/zscaler/sdk/demoapp/MainActivity.kt rename to sample-app/app/src/main/java/com/zscaler/sdk/demoapp/view/MainActivity.kt index 9f824ad..07022d3 100644 --- a/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/MainActivity.kt +++ b/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/view/MainActivity.kt @@ -1,4 +1,4 @@ -package com.zscaler.sdk.demoapp +package com.zscaler.sdk.demoapp.view import android.Manifest import android.annotation.SuppressLint @@ -11,6 +11,7 @@ import android.content.Context import android.content.Intent import android.content.IntentFilter import android.content.pm.PackageManager +import android.content.pm.ResolveInfo import android.graphics.Bitmap import android.os.Build import android.os.Bundle @@ -39,10 +40,26 @@ import com.google.firebase.crashlytics.FirebaseCrashlytics import com.google.firebase.ktx.Firebase import com.google.firebase.ktx.initialize import com.zscaler.sdk.android.ZscalerSDK +import com.zscaler.sdk.android.configuration.ZscalerSDKConfiguration import com.zscaler.sdk.android.exception.ZscalerSDKException import com.zscaler.sdk.android.networking.ZscalerSDKRetrofit import com.zscaler.sdk.android.notification.ZscalerSDKNotificationEnum +import com.zscaler.sdk.demoapp.BuildConfig +import com.zscaler.sdk.demoapp.R +import com.zscaler.sdk.demoapp.util.ProxyUtility +import com.zscaler.sdk.demoapp.configuration.ConfigActivity +import com.zscaler.sdk.demoapp.configuration.ZscalerSDKSetting +import com.zscaler.sdk.demoapp.constants.NOTIFICATION_CHANNEL_ID +import com.zscaler.sdk.demoapp.constants.NOTIFICATION_ID +import com.zscaler.sdk.demoapp.constants.ZDKTunnel +import com.zscaler.sdk.demoapp.constants.zpaEmptyHtml +import com.zscaler.sdk.demoapp.constants.zpaErrorHtml +import com.zscaler.sdk.demoapp.constants.zpaNotConnectedHtml import com.zscaler.sdk.demoapp.databinding.ActivityMainBinding +import com.zscaler.sdk.demoapp.constants.RequestMethod +import com.zscaler.sdk.demoapp.networking.ParentAppRetrofitClient +import com.zscaler.sdk.demoapp.service.NotificationCancellationService +import com.zscaler.sdk.demoapp.util.ZipUtility import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -51,20 +68,20 @@ import java.io.FileInputStream import java.io.FileOutputStream import java.io.IOException import java.io.OutputStream -import java.text.SimpleDateFormat -import java.util.Date class MainActivity : AppCompatActivity() { private val TAG = "MainActivity" private lateinit var binding: ActivityMainBinding - private lateinit var logFileName: String + private lateinit var logsZipFileName: String private lateinit var mainViewModel: MainViewModel private var isRetrofitClientNeeded: Boolean = false - private var httpMethod: HttpMethod = HttpMethod.WEB + private lateinit var webView: WebView + private var requestMethod: RequestMethod = RequestMethod.WEB private lateinit var broadcastReceiver: BroadcastReceiver private lateinit var notificationManager: NotificationManager private val requestNotificationCode = 101 + private lateinit var sdkConfiguration: ZscalerSDKConfiguration private val WRITE_EXTERNAL_STORAGE_REQUEST_CODE = 1001 override fun onCreate(savedInstanceState: Bundle?) { @@ -82,6 +99,8 @@ class MainActivity : AppCompatActivity() { mainViewModel = viewModelProvider[MainViewModel::class.java] notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager setContentView(binding.root) + sdkConfiguration = ZscalerSDKSetting.getZscalerSDKConfiguration() + configureWebView() ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets -> val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) @@ -95,10 +114,42 @@ class MainActivity : AppCompatActivity() { spinnerListener() createNotificationChannel() initZdkConfiguration() + addEventLogsToUi() + } + + override fun onResume() { + if (sdkConfiguration.automaticallyConfigureWebviews != + ZscalerSDKSetting.getZscalerSDKConfiguration().automaticallyConfigureWebviews) { + sdkConfiguration = ZscalerSDKSetting.getZscalerSDKConfiguration() + configureWebView() + } else { + sdkConfiguration = ZscalerSDKSetting.getZscalerSDKConfiguration() + } + super.onResume() } private fun initZdkConfiguration() { - binding.llZscalerConfig.setOnClickListener { startActivity(Intent(this@MainActivity, SettingActivity::class.java)) } + binding.llZscalerConfig.setOnClickListener { startActivity(Intent(this@MainActivity, ConfigActivity::class.java)) } + } + + private fun addEventLogsToUi() { + binding.llEventLogs.setOnClickListener { startActivity(Intent(this@MainActivity, EventLogViewActivity::class.java)) } + } + + private fun configureWebView() { + if (binding.frameLayout.getChildAt(0) != null) { + binding.frameLayout.removeViewAt(0) + } + if (sdkConfiguration.automaticallyConfigureWebviews) { + binding.frameLayout.addView(WebView(this)) + } else { + val zscalerSDKWebView = ZscalerSDK.setUpWebView() + if (zscalerSDKWebView != null) { + binding.frameLayout.addView(zscalerSDKWebView) + } else { + binding.frameLayout.addView(WebView(this)) + } + } } /** @@ -109,6 +160,15 @@ class MainActivity : AppCompatActivity() { FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(!BuildConfig.DEBUG) } + private fun unregisterZDKReceiver() { + try { + unregisterReceiver(broadcastReceiver) + } catch (e: IllegalArgumentException) { + //do nothing if receiver is not already registered + } + } + + @SuppressLint("UnspecifiedRegisterReceiverFlag") private fun registerZDKReceiver() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { @@ -120,6 +180,14 @@ class MainActivity : AppCompatActivity() { val notificationMessage = intent?.getStringExtra(ZscalerSDK.NOTIFICATION_MESSAGE) Log.d(TAG, "onReceive() called with: notificationCode = $notificationCode, notificationMessage = $notificationMessage") notificationCode?.takeIf() {it > -1 + if (notificationCode == ZscalerSDKNotificationEnum.ZDK_TUNNEL_READY_TO_USE.ordinal || + notificationCode == ZscalerSDKNotificationEnum.ZDK_TUNNEL_DISCONNECTED.ordinal) { + ParentAppRetrofitClient.clearRetroFitInstance() + clearWebView() + } + if (notificationCode == ZscalerSDKNotificationEnum.ZDK_TUNNEL_DISCONNECTED.ordinal) { + unregisterZDKReceiver() + } createNotification(ZscalerSDKNotificationEnum.values()[notificationCode].message, ZscalerSDKNotificationEnum.values()[notificationCode].message) } } @@ -143,12 +211,12 @@ class MainActivity : AppCompatActivity() { ) { isRetrofitClientNeeded = when (position) { 1, 2 -> { - httpMethod = HttpMethod.entries.toTypedArray()[position.minus(1)] + requestMethod = RequestMethod.entries.toTypedArray()[position.minus(1)] true } else -> { - httpMethod = HttpMethod.WEB + requestMethod = RequestMethod.WEB false } } @@ -167,8 +235,12 @@ class MainActivity : AppCompatActivity() { } else if (mainViewModel.getSelectedTunnel() == ZDKTunnel.ZERO_TRUST) { mainViewModel.stopTunnel(resetStatusText = { getString(R.string.status_off) }) } + if (sdkConfiguration.automaticallyConfigureRequests) { + ParentAppRetrofitClient.clearRetroFitInstance() + } + clearWebView() try { - unregisterReceiver(broadcastReceiver) + unregisterZDKReceiver() } catch (e: RuntimeException) { Log.e(TAG, "onDestroy() :: exception raised while unregistering broadcastReceiver : ", e) } @@ -177,7 +249,8 @@ class MainActivity : AppCompatActivity() { @SuppressLint("SetJavaScriptEnabled") private fun enableBrowsing() { - binding.webview.settings.javaScriptEnabled = true + val webView = binding.frameLayout.getChildAt(0) as WebView + webView.settings.javaScriptEnabled = true addMessageOnWebView(zpaNotConnectedHtml) binding.goButton.setOnClickListener { var url = binding.browserUrlTextField.text.toString() @@ -187,28 +260,34 @@ class MainActivity : AppCompatActivity() { ZdkDialog.showMessageDialog(this, getString(R.string.enter_valid_url)) } else { url = addHttpsIfNeeded(url) - when (httpMethod) { - HttpMethod.GET -> - if (ZscalerSDKSetting.getZscalerSDKConfiguration().automaticallyConfigureRequests) { - mainViewModel.getData(url = url.ensureEndsWithSlash()) + when (requestMethod) { + RequestMethod.GET -> + if (sdkConfiguration.automaticallyConfigureRequests) { + mainViewModel.loadWithAutomaticConfig(url = url.ensureEndsWithSlash()) } else { - mainViewModel.loadManuallyWithProxyInfo(url.ensureEndsWithSlash(), true) + mainViewModel.loadWithSemiAutomaticConfig(url.ensureEndsWithSlash(), true) } - HttpMethod.POST -> - if (ZscalerSDKSetting.getZscalerSDKConfiguration().automaticallyConfigureRequests) { - mainViewModel.postData(url = url.ensureEndsWithSlash(), params = emptyMap()) + RequestMethod.POST -> + if (sdkConfiguration.automaticallyConfigureRequests) { + mainViewModel.loadPostDataWithAutomaticConfig(url = url.ensureEndsWithSlash(), params = emptyMap()) + } else { + mainViewModel.loadWithSemiAutomaticConfig(url.ensureEndsWithSlash(), false) + } + RequestMethod.WEB -> + if (sdkConfiguration.automaticallyConfigureWebviews) { + loadInWebViewWithAutomaticConfig(url = url) } else { - mainViewModel.loadManuallyWithProxyInfo(url.ensureEndsWithSlash(), false) + loadInWebViewWithSemiAutomaticConfig(url) } - HttpMethod.WEB -> loadUrlInWebView(url = url) } } } mainViewModel.responseData.observe(this) { responseData -> - binding.webview.loadUrl("about:blank") - binding.webview.clearCache(true) - if (httpMethod != HttpMethod.WEB) { - binding.webview.visibility = View.GONE + val webView = binding.frameLayout.getChildAt(0) as WebView + webView.loadUrl("about:blank") + webView.clearCache(true) + if (requestMethod != RequestMethod.WEB) { + webView.visibility = View.GONE binding.tvResponse.visibility = View.VISIBLE if (responseData != null) { if (responseData.toString() @@ -228,9 +307,10 @@ class MainActivity : AppCompatActivity() { } } - private fun loadUrlInWebView(url: String) { + private fun loadInWebViewWithAutomaticConfig(url: String) { binding.tvResponse.visibility = View.GONE - binding.webview.visibility = View.VISIBLE + val webView = binding.frameLayout.getChildAt(0) as WebView + webView.visibility = View.VISIBLE var formattedUrl = url.trim() if (!formattedUrl.startsWith("http://") && !formattedUrl.startsWith("https://") @@ -238,16 +318,16 @@ class MainActivity : AppCompatActivity() { ) { formattedUrl = "https://$formattedUrl" } - binding.webview.clearCache(true) - binding.webview.webViewClient = object : WebViewClient() { + webView.clearCache(true) + webView.webViewClient = object : WebViewClient() { override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) { super.onPageStarted(view, url, favicon) - binding.webview.contentDescription = "Page is loading..." + webView.contentDescription = "Page is loading..." } override fun onPageFinished(view: WebView?, url: String?) { super.onPageFinished(view, url) - binding.webview.contentDescription = "Page loaded successfully" + webView.contentDescription = "Page loaded successfully" } override fun onReceivedError( view: WebView?, @@ -256,16 +336,22 @@ class MainActivity : AppCompatActivity() { ) { super.onReceivedError(view, request, error) addMessageOnWebView(zpaErrorHtml) - binding.webview.contentDescription = "Failed to load the page" + webView.contentDescription = "Failed to load the page" } } - binding.webview.loadUrl(formattedUrl) + webView.loadUrl(formattedUrl) + } + + private fun loadInWebViewWithSemiAutomaticConfig(url: String) { + val zscalerSDKWebView = ZscalerSDK.setUpWebView() + zscalerSDKWebView?.loadUrl(url) } private fun addMessageOnWebView(htmlText: String) { if (mainViewModel.getSelectedTunnel() == ZDKTunnel.NO_SELECTION) { val encodedHtml = Base64.encodeToString(htmlText.toByteArray(), Base64.NO_PADDING) - binding.webview.loadData(encodedHtml, "text/html", "base64") + val webView = binding.frameLayout.getChildAt(0) as WebView + webView.loadData(encodedHtml, "text/html", "base64") } } @@ -275,8 +361,9 @@ class MainActivity : AppCompatActivity() { reloadWebView() if (isChecked) { // everytime clear any previously manually created retrofit when tunnel status changes. - ManualRetrofitApiClient.clearRetroFitInstance() + ParentAppRetrofitClient.clearRetroFitInstance() ZscalerSDKRetrofit.clearInstance() + clearWebView() registerZDKReceiver() binding.tvPreStatus.text = getString(R.string.status_format, "CONNECTING") @@ -300,6 +387,7 @@ class MainActivity : AppCompatActivity() { binding.tvPreStatus.visibility = View.VISIBLE binding.tvPreStatus.text = getString(R.string.error_status_format, it.toString()) + mainViewModel.zdkStatus.removeObservers(this) binding.zeroTrustToggle.isEnabled = true }) binding.zeroTrustToggle.isEnabled = false @@ -315,19 +403,14 @@ class MainActivity : AppCompatActivity() { addMessageOnWebView(zpaEmptyHtml) } } else { - unregisterReceiver(broadcastReceiver) - stopService(Intent(this, NotificationCancellationService::class.java)) - notificationManager.cancel(NOTIFICATION_ID) - Log.d( - TAG, - mainViewModel.stopTunnel(resetStatusText = { getString(R.string.status_off) }) - .toString() - ) - mainViewModel.stopTunnelStatusUpdates() - - binding.zeroTrustToggle.isEnabled = true - binding.zeroTrustToggle.contentDescription = "OFF" + ProxyUtility.clearWebViewProxy() + clearWebView() + unregisterZDKReceiver() + mainViewModel.stopTunnel(resetStatusText = { getString(R.string.status_off) }) binding.preLoginToggle.contentDescription = "OFF" + binding.zeroTrustToggle.contentDescription = "OFF" + binding.zeroTrustToggle.isEnabled = true + mainViewModel.stopTunnelStatusUpdates() binding.tvPreStatus.text = getString(R.string.status_format, "OFF") binding.tvPreStatus.visibility = View.INVISIBLE addMessageOnWebView(zpaNotConnectedHtml) @@ -338,20 +421,21 @@ class MainActivity : AppCompatActivity() { reloadWebView() if (isChecked) { // everytime clear any previously created retrofit when tunnel status changes. - ManualRetrofitApiClient.clearRetroFitInstance() + ParentAppRetrofitClient.clearRetroFitInstance() ZscalerSDKRetrofit.clearInstance() + clearWebView() registerZDKReceiver() binding.tvZeroStatus.text = getString(R.string.status_format, "CONNECTING") binding.tvPreStatus.visibility = View.INVISIBLE val appKey = binding.zdkIdTextField.text.toString() - val accessToke = binding.accessTokenTextField.text.toString() + val accessToken = binding.accessTokenTextField.text.toString() if (appKey.isBlank()) { binding.zdkIdTextField.error = getString(R.string.zdk_id_is_empty) binding.zeroTrustToggle.isChecked = false binding.zeroTrustToggle.contentDescription = "OFF" return@setOnCheckedChangeListener - } else if (accessToke.isBlank()) { + } else if (accessToken.isBlank()) { binding.accessTokenTextField.error = getString(R.string.access_token_is_empty) binding.zeroTrustToggle.isChecked = false binding.zeroTrustToggle.contentDescription = "OFF" @@ -363,13 +447,14 @@ class MainActivity : AppCompatActivity() { mainViewModel.startZeroTrustTunnel( appKey = appKey, udid = mainViewModel.getUdid(getString(R.string.random_udid)), - accessToken = accessToke, + accessToken = accessToken, onErrorOccurred = { binding.zeroTrustToggle.isChecked = false binding.zeroTrustToggle.contentDescription = "OFF" binding.tvZeroStatus.visibility = View.VISIBLE binding.tvZeroStatus.text = getString(R.string.error_status_format, it.toString()) + mainViewModel.zdkStatus.removeObservers(this) }) binding.preLoginToggle.isEnabled = false binding.zeroTrustToggle.contentDescription = "ON" @@ -384,31 +469,39 @@ class MainActivity : AppCompatActivity() { addMessageOnWebView(zpaEmptyHtml) } } else { - unregisterReceiver(broadcastReceiver) - stopService(Intent(this, NotificationCancellationService::class.java)) - notificationManager.cancel(NOTIFICATION_ID) + ProxyUtility.clearWebViewProxy() + clearWebView() + unregisterZDKReceiver() mainViewModel.stopTunnel(resetStatusText = { getString(R.string.status_off) }) - addMessageOnWebView(zpaNotConnectedHtml) binding.zeroTrustToggle.contentDescription = "OFF" binding.preLoginToggle.contentDescription = "OFF" binding.preLoginToggle.isEnabled = true mainViewModel.stopTunnelStatusUpdates() binding.tvZeroStatus.text = getString(R.string.status_format, "OFF") binding.tvZeroStatus.visibility = View.INVISIBLE + addMessageOnWebView(zpaNotConnectedHtml) } } } private fun reloadWebView() { - var url1: String = binding.webview.url.toString() - if (!(url1.isNullOrEmpty() || url1.equals("null"))) { + val webView : WebView = binding.frameLayout.getChildAt(0) as WebView + val url: String = webView.url.toString() + if (!(url.isEmpty() || url == "null")) { Toast.makeText(this, getString(R.string.reloading_web_page), Toast.LENGTH_SHORT).show() - binding.webview.loadUrl("about:blank") - binding.webview.clearCache(true) //required - binding.webview.loadUrl(url1) + webView.loadUrl("about:blank") + webView.clearCache(true) //required + webView.loadUrl(url) } } + private fun clearWebView() { + val webView : WebView = binding.frameLayout.getChildAt(0) as WebView + Log.d(TAG, "Clearing Webview") + webView.clearCache(true) + webView.clearHistory() + } + private fun addLoggerFunction() { binding.exportLogsButton.setOnClickListener { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) { @@ -444,9 +537,9 @@ class MainActivity : AppCompatActivity() { } private fun createZipFileAndShare() { - val currentDateTime = SimpleDateFormat("yyyy-MM-dd-hh-mm-ss.SS").format(Date()) - logFileName = "ZscalerSDK-$currentDateTime.zip" - ZipUtility.createEmptyZipFile(this, logFileName) + val currentTimeStamp = System.currentTimeMillis() + logsZipFileName = "ZscalerSDK_${currentTimeStamp/1000}.${currentTimeStamp%1000}.zip" + ZipUtility.createEmptyZipFile(this, logsZipFileName) ?.let { launchShareIntentLogZip(it) } @@ -456,7 +549,7 @@ class MainActivity : AppCompatActivity() { Toast.makeText(this, getString(R.string.saved_to_download), Toast.LENGTH_LONG).show() lifecycleScope.launch(Dispatchers.IO) { mainViewModel.exportLog(it) - val logZipFile = File(this@MainActivity.filesDir, logFileName) + val logZipFile = File(this@MainActivity.filesDir, logsZipFileName) val downloadFolder = this@MainActivity.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS) if (downloadFolder != null) { val stat = StatFs(downloadFolder.path) @@ -472,8 +565,30 @@ class MainActivity : AppCompatActivity() { val shareIntent = Intent(Intent.ACTION_SEND) shareIntent.type = "application/zip" shareIntent.putExtra(Intent.EXTRA_STREAM, fileUri) - shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - startActivity(Intent.createChooser(shareIntent, getString(R.string.share_zdk_log_zip_file))) + shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + val chooser = Intent.createChooser( + shareIntent, + getString(R.string.share_zdk_log_zip_file) + ) + val resInfoList: List = + this@MainActivity.packageManager + .queryIntentActivities(chooser, PackageManager.MATCH_DEFAULT_ONLY) + + if (resInfoList.isNotEmpty()) { + for (resolveInfo in resInfoList) { + val packageName = resolveInfo.activityInfo.packageName + this@MainActivity.grantUriPermission( + packageName, + fileUri, + Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION + ) + } + startActivity(chooser) + } else { + withContext(Dispatchers.Main) { + Toast.makeText(this@MainActivity, getString(R.string.no_share_activity_present), Toast.LENGTH_LONG).show() + } + } } } else { withContext(Dispatchers.Main) { @@ -494,7 +609,7 @@ class MainActivity : AppCompatActivity() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { saveLogToDownloadAboveAndEqualQ(context, logZipFile) } else { - saveLogToDownloadBelowQ(context, logFileName) + saveLogToDownloadBelowQ(context, logsZipFileName) } } @@ -578,8 +693,6 @@ class MainActivity : AppCompatActivity() { val channel = NotificationChannel(NOTIFICATION_CHANNEL_ID, name, importance).apply { description = descriptionText } - val notificationManager: NotificationManager = - getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationManager.createNotificationChannel(channel) } diff --git a/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/MainViewModel.kt b/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/view/MainViewModel.kt similarity index 83% rename from sample-app/app/src/main/java/com/zscaler/sdk/demoapp/MainViewModel.kt rename to sample-app/app/src/main/java/com/zscaler/sdk/demoapp/view/MainViewModel.kt index 3bff3de..1a99107 100644 --- a/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/MainViewModel.kt +++ b/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/view/MainViewModel.kt @@ -1,4 +1,4 @@ -package com.zscaler.sdk.demoapp +package com.zscaler.sdk.demoapp.view import android.app.Application import android.util.Log @@ -9,7 +9,9 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope import com.zscaler.sdk.android.ZscalerSDK import com.zscaler.sdk.android.exception.ZscalerSDKException -import com.zscaler.sdk.android.networking.ZscalerSDKRetrofit +import com.zscaler.sdk.demoapp.constants.ZDKTunnel +import com.zscaler.sdk.demoapp.networking.ApiService +import com.zscaler.sdk.demoapp.networking.ParentAppRetrofitClient import com.zscaler.sdk.demoapp.repository.SharedPrefsUserRepository import com.zscaler.sdk.demoapp.repository.UserRepository import kotlinx.coroutines.Dispatchers @@ -122,11 +124,17 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { fun stopTunnel(resetStatusText:()-> String): Unit { Log.d(TAG, "stopTunnel() called") - try { - val retVal = ZscalerSDK.stopTunnel() - if (retVal == 0) zdkStatus.value = resetStatusText() - } catch (e: Exception) { - Log.e(TAG, "stopTunnel() failed with exception ${e.message}") + viewModelScope.launch(Dispatchers.IO) { + try { + val retVal = ZscalerSDK.stopTunnel() + withContext(Dispatchers.Main) { + if (retVal == 0) { + zdkStatus.value = resetStatusText() + } + } + } catch (e: Exception) { + Log.e(TAG, "stopTunnel() failed with exception ${e.message}") + } } } @@ -164,13 +172,15 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { } } - fun getData(url: String) { - Log.d(TAG, "getData() called with: url = $url") - val apiService = ZscalerSDKRetrofit.getInstance(url).create(ApiService::class.java) + fun loadWithAutomaticConfig(url: String) { + Log.d(TAG, "loadWithAutomaticConfig() called with: url = $url") + val apiService = ParentAppRetrofitClient.getRetrofitClient(url)?.create(ApiService::class.java) viewModelScope.launch(Dispatchers.IO) { try { - val response = apiService.getData(url).execute() - parseRetrofitResponse(response) + val response = apiService?.getData(url)?.execute() + if (response != null) { + parseRetrofitResponse(response) + } } catch (e: IOException) { _responseData.postValue("Network error") e.printStackTrace() @@ -178,11 +188,9 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { } } - fun loadManuallyWithProxyInfo(url: String, method: Boolean) { - val proxyInfo = ZscalerSDK.proxyInfo() - val apiService = proxyInfo?.let { - ManualRetrofitApiClient.getRetrofitWithProxyInfo(baseUrl = url, it)?.create(ApiService::class.java) - } + fun loadWithSemiAutomaticConfig(url: String, method: Boolean) { + Log.d(TAG, "loadWithSemiAutomaticConfig() called with: url = $url") + val apiService = ZscalerSDK.setUpClient(null, url)?.create(ApiService::class.java) viewModelScope.launch(Dispatchers.IO) { try { val response = if(method) { @@ -190,7 +198,9 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { } else { apiService?.postData(url)?.execute() } - response?.let { parseRetrofitResponse(it) } + if (response != null) { + parseRetrofitResponse(response) + } } catch (e: IOException) { _responseData.postValue("Network error") e.printStackTrace() @@ -198,13 +208,15 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { } } - fun postData(url: String, params: Map) { + fun loadPostDataWithAutomaticConfig(url: String, params: Map) { Log.d(TAG, "postData() called with: url = $url, params = $params") - val apiService = ZscalerSDKRetrofit.getInstance(url).create(ApiService::class.java) + val apiService = ParentAppRetrofitClient.getRetrofitClient(url)?.create(ApiService::class.java) viewModelScope.launch(Dispatchers.IO) { try { - val response = apiService.postData(url, params).execute() - parseRetrofitResponse(response) + val response = apiService?.postData(url, params)?.execute() + if (response != null) { + parseRetrofitResponse(response) + } } catch (e: IOException) { _responseData.postValue("Network error") e.printStackTrace() diff --git a/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/ZdkDialog.kt b/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/view/ZdkDialog.kt similarity index 94% rename from sample-app/app/src/main/java/com/zscaler/sdk/demoapp/ZdkDialog.kt rename to sample-app/app/src/main/java/com/zscaler/sdk/demoapp/view/ZdkDialog.kt index 7a8597b..9c3e89a 100644 --- a/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/ZdkDialog.kt +++ b/sample-app/app/src/main/java/com/zscaler/sdk/demoapp/view/ZdkDialog.kt @@ -1,4 +1,4 @@ -package com.zscaler.sdk.demoapp +package com.zscaler.sdk.demoapp.view import android.content.Context import androidx.appcompat.app.AlertDialog diff --git a/sample-app/app/src/main/res/drawable/ic_event_log.xml b/sample-app/app/src/main/res/drawable/ic_event_log.xml new file mode 100644 index 0000000..21ace84 --- /dev/null +++ b/sample-app/app/src/main/res/drawable/ic_event_log.xml @@ -0,0 +1,9 @@ + + + diff --git a/sample-app/app/src/main/res/layout/activity_logger_view.xml b/sample-app/app/src/main/res/layout/activity_logger_view.xml new file mode 100644 index 0000000..d74f261 --- /dev/null +++ b/sample-app/app/src/main/res/layout/activity_logger_view.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + diff --git a/sample-app/app/src/main/res/layout/activity_main.xml b/sample-app/app/src/main/res/layout/activity_main.xml index bd4fa54..6f13680 100644 --- a/sample-app/app/src/main/res/layout/activity_main.xml +++ b/sample-app/app/src/main/res/layout/activity_main.xml @@ -6,7 +6,7 @@ android:layout_height="match_parent" android:paddingTop="10dp" android:orientation="vertical" - tools:context=".MainActivity"> + tools:context=".view.MainActivity"> @@ -210,6 +211,32 @@ android:text="@string/config_zscaler" /> + + + + + + + - diff --git a/sample-app/app/src/main/res/layout/activity_setting.xml b/sample-app/app/src/main/res/layout/activity_setting.xml index 50cd882..95560f3 100644 --- a/sample-app/app/src/main/res/layout/activity_setting.xml +++ b/sample-app/app/src/main/res/layout/activity_setting.xml @@ -8,22 +8,10 @@ android:id="@+id/tv_Setting_title" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignParentStart="true" - android:layout_alignParentTop="true" - android:textStyle="bold" - android:textColor="@color/black" android:text="@string/configuration_options" - android:textSize="@dimen/setting_text_size" /> - - + android:textColor="@color/black" + android:textSize="@dimen/setting_text_size" + android:textStyle="bold" /> + + diff --git a/sample-app/app/src/main/res/layout/item_setting.xml b/sample-app/app/src/main/res/layout/item_setting.xml index c202ba4..01b8781 100644 --- a/sample-app/app/src/main/res/layout/item_setting.xml +++ b/sample-app/app/src/main/res/layout/item_setting.xml @@ -8,7 +8,6 @@ android:id="@+id/title" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="Title" android:textSize="16sp" android:textStyle="bold" /> @@ -18,7 +17,6 @@ android:layout_height="wrap_content" android:layout_below="@id/title" android:layout_marginTop="6dp" - android:text="Description" android:textSize="14sp" /> + android:layout_centerVertical="false" + android:textSize="0sp"/> diff --git a/sample-app/app/src/main/res/values/colors.xml b/sample-app/app/src/main/res/values/colors.xml index f8c6127..5059f6d 100644 --- a/sample-app/app/src/main/res/values/colors.xml +++ b/sample-app/app/src/main/res/values/colors.xml @@ -7,4 +7,5 @@ #FF018786 #FF000000 #FFFFFFFF + #FFCCCCCC \ No newline at end of file diff --git a/sample-app/app/src/main/res/values/dimens.xml b/sample-app/app/src/main/res/values/dimens.xml index 59a027b..2b0d492 100644 --- a/sample-app/app/src/main/res/values/dimens.xml +++ b/sample-app/app/src/main/res/values/dimens.xml @@ -2,9 +2,16 @@ 16sp 16dp + 8dp + 32dp 8dp 10dp 10dp 16dp 60dp + 16dp + 32dp + 32dp + 12dp + 2 \ No newline at end of file diff --git a/sample-app/app/src/main/res/values/secret.xml b/sample-app/app/src/main/res/values/secret.xml index 8e4f220..ea9fa5e 100644 --- a/sample-app/app/src/main/res/values/secret.xml +++ b/sample-app/app/src/main/res/values/secret.xml @@ -2,4 +2,4 @@ - \ No newline at end of file + diff --git a/sample-app/app/src/main/res/values/strings.xml b/sample-app/app/src/main/res/values/strings.xml index d67d3fc..2bf445e 100644 --- a/sample-app/app/src/main/res/values/strings.xml +++ b/sample-app/app/src/main/res/values/strings.xml @@ -9,6 +9,7 @@ Zscaler Config PreLogin Tunnel ZeroTrust Tunnel + Event Logs Status: %1$s Status: Error %1$s Enter valid App Key @@ -22,6 +23,7 @@ Error loading data ZscalerSDK Log saved to download folder There is not enough disk space to export log files. + There is no activity which can handle exporting of logs. Download folder not found Configuration Options Save diff --git a/sample-app/app/src/test/java/com/zscaler/ReadCrashLog.kt b/sample-app/app/src/test/java/com/zscaler/ReadCrashLog.kt deleted file mode 100644 index 4488a28..0000000 --- a/sample-app/app/src/test/java/com/zscaler/ReadCrashLog.kt +++ /dev/null @@ -1,110 +0,0 @@ -package com.zscaler - -import java.io.BufferedReader -import java.io.BufferedWriter -import java.io.File -import java.io.FileWriter -import java.io.InputStreamReader - -fun main() { - val soFilePath = - "../zdklibrary/app/build/intermediates/merged_native_libs/debug/out/lib/arm64-v8a/libzdkcommonlibrary.so" - - val crashAddressesInput = arrayOf( - "00000000009a5564", - "000000000097634c", - "0000000000976320" - ) - - // Check if the input is valid - if (soFilePath.isNullOrBlank()) { - println("Invalid file path.") - return - } - - // Create a File object - val soFile = File(soFilePath) - - // Check if the file exists and is a file - if (!soFile.exists() || !soFile.isFile) { - println("The specified path does not point to a valid .so file.") - return - } - - // Convert input strings to an array of shortened addresses - val crashAddresses = crashAddressesInput.map { it.takeLast(6) } - - // Prepare the objdump command - val command = listOf("objdump", "-d", soFile.absolutePath) - - try { - // Create the process builder and start the process - val processBuilder = ProcessBuilder(command) - processBuilder.redirectErrorStream(true) // Merge error and output streams - val process = processBuilder.start() - - // Read the output from the command - val reader = BufferedReader(InputStreamReader(process.inputStream)) - val output = StringBuilder() - var line: String? - - // Read each line of output and append to the StringBuilder - while (reader.readLine().also { line = it } != null) { - output.appendLine(line) - } - - // Wait for the process to complete - val exitCode = process.waitFor() - - // Save the output to a file if the command executed successfully - if (exitCode == 0) { - // Define the output file path - val outputFileName = "${soFile.nameWithoutExtension}_objdump_output.txt" - val outputFile = File(soFile.parent, outputFileName) - - // Write output to the file - BufferedWriter(FileWriter(outputFile)).use { writer -> - writer.write(output.toString()) - } - - println("Objdump output saved to: ${outputFile.absolutePath}") - - // Print information for each crash address - crashAddresses.forEach { crashAddress -> - var matchFound = false - for (line in output.lines()) { - if (line.contains(crashAddress)) { - // Find the previous line with the function name - val previousLineIndex = output.lines().indexOf(line) - 1 - if (previousLineIndex >= 0) { - val functionLine = output.lines()[previousLineIndex] - println("Match found for crash address $crashAddress:") - println(functionLine) - } - println("Crash address found: $line") - matchFound = true - } - } - if (!matchFound) { - println("No match found for crash address: $crashAddress") - } - } - } else { - println("Error executing objdump command. Exit code: $exitCode") - } - } catch (e: Exception) { - println("An error occurred while executing the objdump command: ${e.message}") - } -} - - - - - - - - - - - - diff --git a/sample-app/build.gradle.kts b/sample-app/build.gradle.kts index 47290d7..9e3e976 100644 --- a/sample-app/build.gradle.kts +++ b/sample-app/build.gradle.kts @@ -1,5 +1,5 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "7.4.1" apply false + id("com.android.application") version "8.1.0" apply false id("org.jetbrains.kotlin.android") version "1.6.0" apply false } \ No newline at end of file diff --git a/sample-app/gradle/wrapper/gradle-wrapper.properties b/sample-app/gradle/wrapper/gradle-wrapper.properties index 35b8c03..bc853c3 100644 --- a/sample-app/gradle/wrapper/gradle-wrapper.properties +++ b/sample-app/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Mon Jan 22 22:34:19 IST 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/sample-app/settings.gradle.kts b/sample-app/settings.gradle.kts index c137f37..003abde 100644 --- a/sample-app/settings.gradle.kts +++ b/sample-app/settings.gradle.kts @@ -13,7 +13,11 @@ dependencyResolutionManagement { maven { name = "ZdkAndroid" url = uri("https://maven.pkg.github.com/zscaler/zscaler-sdk-android") - } + credentials { + username = settings.extra.get("gpr.user") as String? ?: System.getenv("GITHUB_USERNAME") + password = settings.extra.get("gpr.key") as String? ?: System.getenv("GITHUB_TOKEN") + } + } } }