diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..b4aea39 Binary files /dev/null and b/.DS_Store differ diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..97a8530 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +Web Demo \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..61a9130 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..fc42dde --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/app/.DS_Store b/app/.DS_Store new file mode 100644 index 0000000..d30fde7 Binary files /dev/null and b/app/.DS_Store differ diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..bfc67a0 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,41 @@ +plugins { + id 'com.android.application' +} + +android { + compileSdk 31 + + defaultConfig { + applicationId "com.aron.webdemo" + minSdk 28 + targetSdk 31 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 + } +} + +dependencies { + implementation 'androidx.appcompat:appcompat:1.4.1' + implementation 'com.google.android.material:material:1.5.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + implementation 'androidx.browser:browser:1.4.0' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + + // WS1 Intelligence SDK (compile directive is deprecated; use implementation instead) + implementation 'com.crittercism:crittercism-android-agent:+' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..90ce2a6 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,29 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile + +# WS1 Intelligence +-dontwarn com.crittercism.** +-keep public class com.crittercism.** +-keepclassmembers public class com.crittercism.** +{ + *; +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/aron/webdemo/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/aron/webdemo/ExampleInstrumentedTest.java new file mode 100644 index 0000000..e63bf08 --- /dev/null +++ b/app/src/androidTest/java/com/aron/webdemo/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.aron.webdemo; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + assertEquals("com.aron.webdemo", appContext.getPackageName()); + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..5417770 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/aron/webdemo/AppConfig.java b/app/src/main/java/com/aron/webdemo/AppConfig.java new file mode 100644 index 0000000..fd1999e --- /dev/null +++ b/app/src/main/java/com/aron/webdemo/AppConfig.java @@ -0,0 +1,59 @@ +package com.aron.webdemo; + +import android.content.Context; +import android.content.RestrictionsManager; +import android.os.Bundle; + + +public class AppConfig { + private static final String INTELLIGENCE_APP_ID = "INTELLIGENCE_APP_ID"; + private static final String INTELLIGENCE_APP_LOGGING_LEVEL = "INTELLIGENCE_APP_LOGGING_LEVEL"; + private static final String ENROLLMENT_USERNAME = "ENROLLMENT_USERNAME"; + private static final String URL = "URL"; + + private String intelligenceAppId; + private String intelligenceAppLoggingLevel; + private String enrollmentUsername; + private String url; + + + public AppConfig(Context context) { + RestrictionsManager restrictionsManager = (RestrictionsManager) context.getSystemService(Context.RESTRICTIONS_SERVICE); + Bundle appRestrictions = restrictionsManager.getApplicationRestrictions(); + getApplicationRestrictions(appRestrictions); + } + + private void getApplicationRestrictions(Bundle appRestrictions) { + if (appRestrictions.containsKey(INTELLIGENCE_APP_ID)) { + intelligenceAppId = appRestrictions.getString(INTELLIGENCE_APP_ID); + } + + if (appRestrictions.containsKey(INTELLIGENCE_APP_LOGGING_LEVEL)) { + intelligenceAppLoggingLevel = appRestrictions.getString(INTELLIGENCE_APP_LOGGING_LEVEL); + } + + if (appRestrictions.containsKey(ENROLLMENT_USERNAME)) { + enrollmentUsername = appRestrictions.getString(ENROLLMENT_USERNAME); + } + + if (appRestrictions.containsKey(URL)) { + url = appRestrictions.getString(URL); + } + } + + public String getIntelligenceAppId() { + return intelligenceAppId; + } + + public String getIntelligenceAppLoggingLevel() { + return intelligenceAppLoggingLevel; + } + + public String getEnrollmentUsername() { + return enrollmentUsername; + } + + public String getUrl() { + return url; + } +} diff --git a/app/src/main/java/com/aron/webdemo/MainActivity.java b/app/src/main/java/com/aron/webdemo/MainActivity.java new file mode 100644 index 0000000..1302a7c --- /dev/null +++ b/app/src/main/java/com/aron/webdemo/MainActivity.java @@ -0,0 +1,275 @@ +package com.aron.webdemo; + +import static androidx.browser.customtabs.CustomTabsService.ACTION_CUSTOM_TABS_CONNECTION; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.browser.customtabs.CustomTabsClient; +import androidx.browser.customtabs.CustomTabsIntent; +import androidx.browser.customtabs.CustomTabsServiceConnection; +import androidx.browser.customtabs.CustomTabsSession; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.text.Editable; +import android.util.Log; +import android.view.View; +import android.webkit.WebView; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.Toast; + +import com.crittercism.app.Crittercism; +import com.google.android.material.textfield.TextInputEditText; + +import java.util.ArrayList; +import java.util.List; + +public class MainActivity extends AppCompatActivity implements View.OnClickListener { + + private final String TAG = "WebDemo-MainActivity"; + private boolean usingIntelligenceSdk; + private AppConfig appConfig; + private String intelligenceAppId; + private String intelligenceAppLoggingLevel; + private String enrollmentUsername; + private String startingUrl; + + @Override + protected void onCreate(Bundle savedInstanceState) { + appConfig = new AppConfig(getApplicationContext()); + + configureIntelligenceSdk(); + setUrlEditTextView(); + + if (usingIntelligenceSdk) { + Crittercism.leaveBreadcrumb("Main_Activity_Screen"); + Crittercism.beginUserFlow("Main_Activity_User_Flow"); + Crittercism.sendAppLoadData(); + Log.i(TAG, "App crashed on previous run: " + Crittercism.didCrashOnLastLoad()); + } + + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + Button startCustomTab = (Button) findViewById(R.id.start_custom_tab); + startCustomTab.setOnClickListener(this); + + Button startWebView = (Button) findViewById(R.id.start_web_view); + startWebView.setOnClickListener(this); + + // Check if other browser apps support Chrome Custom Tabs + getCustomTabsPackages(this); + } + + private void configureIntelligenceSdk() { + // Get app config values passed by WS1 UEM + intelligenceAppId = appConfig.getIntelligenceAppId(); + if (intelligenceAppId != null && !intelligenceAppId.isEmpty()) { + usingIntelligenceSdk = true; + intelligenceAppLoggingLevel = appConfig.getIntelligenceAppLoggingLevel(); + enrollmentUsername = appConfig.getEnrollmentUsername(); + + Log.i(TAG, "Intelligence App ID App Config: " + intelligenceAppId); + Log.i(TAG, "Intelligence App Logging Level App Config: " + intelligenceAppLoggingLevel); + Log.i(TAG, "WS1 UEM Enrolled User Username App Config: " + enrollmentUsername); + + // Set Intelligence app ID for SDK + Crittercism.initialize(getApplicationContext(), intelligenceAppId); + + // Set Intelligence SDK app logging level + switch (intelligenceAppLoggingLevel) { + case "DEBUG": + Crittercism.setLoggingLevel(Crittercism.LoggingLevel.Debug); + break; + case "ERROR": + Crittercism.setLoggingLevel(Crittercism.LoggingLevel.Error); + break; + case "WARN": + Crittercism.setLoggingLevel(Crittercism.LoggingLevel.Warning); + break; + case "INFO": + Crittercism.setLoggingLevel(Crittercism.LoggingLevel.Info); + break; + case "SILENT": + Crittercism.setLoggingLevel(Crittercism.LoggingLevel.Silent); + break; + default: + Crittercism.setLoggingLevel(Crittercism.LoggingLevel.Debug); + break; + } + + // Set enrollment user username for Intelligence if given as app config + if (enrollmentUsername != null && !enrollmentUsername.isEmpty()) { + Crittercism.setUsername(enrollmentUsername); + } + } + else { + Log.w(TAG, "Intelligence App ID App Config is NULL or empty. The Intelligence SDK will not be used."); + usingIntelligenceSdk = false; + } + } + + private void setUrlEditTextView() { + startingUrl = appConfig.getUrl(); + Log.i(TAG, "Starting URL App Config: " + startingUrl); + + if (startingUrl != null && !startingUrl.isEmpty()) { + EditText editText = (EditText) findViewById(R.id.url); + editText.setText(startingUrl, TextView.BufferType.EDITABLE); + } + } + + @Override + public void onClick(View view) { + EditText editText = (EditText) findViewById(R.id.url); + String url = editText.getText().toString(); + url = url.trim(); + + if (url == null || url.isEmpty()) { + Toast.makeText(this, "Please enter a URL and try again.", Toast.LENGTH_SHORT).show(); + Log.w(TAG, "URL is empty or invalid. Please enter a URL and try again."); + return; + } + + if (!url.startsWith("http")) { + url = "http://" + url; + } + + switch (view.getId()) { + case R.id.start_custom_tab: + onClickStartCustomTab(url); + break; + case R.id.start_web_view: + onClickStartWebView(url); + break; + default: + break; + } + } + + private void onClickStartCustomTab(String url) { + Log.i(TAG, "Opening Chrome Custom Tab for URL " + url); + + if (usingIntelligenceSdk) { + Crittercism.leaveBreadcrumb("Chrome_Custom_Tab"); + Crittercism.beginUserFlow("Chrome_Custom_Tab_User_Flow"); + } + + CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder(); + CustomTabsIntent customTabsIntent = builder.build(); + + customTabsIntent.launchUrl(this, Uri.parse(url)); + + if (usingIntelligenceSdk) { + Crittercism.endUserFlow("Chrome_Custom_Tab_User_Flow"); + } + } + + private void onClickStartWebView(String url) { + Log.i(TAG, "Opening Web View for URL " + url); + + Intent intent = new Intent(this, WebViewActivity.class); + intent.putExtra(WebViewActivity.EXTRA_URL, url); + + if (usingIntelligenceSdk) { + intent.putExtra(WebViewActivity.EXTRA_USE_INTELLIGENCE_SDK, true); + } + + startActivity(intent); + } + + /** + * Returns a list of packages that support Custom Tabs. + */ + public static ArrayList getCustomTabsPackages(Context context) { + PackageManager pm = context.getPackageManager(); + // Get default VIEW intent handler. + Intent activityIntent = new Intent() + .setAction(Intent.ACTION_VIEW) + .addCategory(Intent.CATEGORY_BROWSABLE) + .setData(Uri.fromParts("http", "", null)); + + // Get all apps that can handle VIEW intents. + List resolvedActivityList = pm.queryIntentActivities(activityIntent, 0); + ArrayList packagesSupportingCustomTabs = new ArrayList<>(); + for (ResolveInfo info : resolvedActivityList) { + Intent serviceIntent = new Intent(); + serviceIntent.setAction(ACTION_CUSTOM_TABS_CONNECTION); + serviceIntent.setPackage(info.activityInfo.packageName); + // Check if this package also resolves the Custom Tabs service. + if (pm.resolveService(serviceIntent, 0) != null) { + packagesSupportingCustomTabs.add(info); + Log.i("WebDemo-MainActivity", "Found package that supports Chrome Custom Tabs: " + info.activityInfo.packageName); + } + } + return packagesSupportingCustomTabs; + } + + @Override + public void onStart() { + super.onStart(); + + if (usingIntelligenceSdk) { + Crittercism.leaveBreadcrumb("App_Started"); + } + } + + @Override + public void onRestart() { + super.onRestart(); + + if (usingIntelligenceSdk) { + Crittercism.leaveBreadcrumb("App_Restarted"); + } + } + + @Override + public void onResume() { + super.onResume(); + + if (usingIntelligenceSdk) { + Crittercism.leaveBreadcrumb("App_Resumed"); + Log.i(TAG, "App crashed on previous run: " + Crittercism.didCrashOnLastLoad()); + } + } + + @Override + public void onPause() { + super.onPause(); + + if (usingIntelligenceSdk) { + Crittercism.leaveBreadcrumb("App_Paused"); + } + } + + @Override + public void onStop() { + super.onStop(); + + if (usingIntelligenceSdk) { + Crittercism.leaveBreadcrumb("App_Stopped"); + Crittercism.endUserFlow("Main_Activity_User_Flow"); + Crittercism.getNetworkInstrumentation(); + Crittercism.sendAppLoadData(); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + + if (usingIntelligenceSdk) { + Crittercism.leaveBreadcrumb("App_Destroyed"); + Crittercism.sendAppLoadData(); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aron/webdemo/WebViewActivity.java b/app/src/main/java/com/aron/webdemo/WebViewActivity.java new file mode 100644 index 0000000..23fb6b4 --- /dev/null +++ b/app/src/main/java/com/aron/webdemo/WebViewActivity.java @@ -0,0 +1,112 @@ +package com.aron.webdemo; + +import androidx.appcompat.app.AppCompatActivity; + +import android.os.Bundle; +import android.util.Log; +import android.view.MenuItem; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebViewClient; + +import com.crittercism.app.Crittercism; + +public class WebViewActivity extends AppCompatActivity { + + private final String TAG = "WebDemo-WebViewActivity"; + private boolean usingIntelligenceSdk; + public static final String EXTRA_URL = "extra.url"; + public static final String EXTRA_USE_INTELLIGENCE_SDK = "extra.useIntelligence"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_web_view); + + String url = getIntent().getStringExtra(EXTRA_URL); + WebView webView = findViewById(R.id.webview); + webView.setWebViewClient(new WebViewClient()); + WebSettings webSettings = webView.getSettings(); + webSettings.setJavaScriptEnabled(true); + setTitle(url); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + usingIntelligenceSdk = getIntent().getBooleanExtra(EXTRA_USE_INTELLIGENCE_SDK, false); + if (usingIntelligenceSdk) { + Crittercism.leaveBreadcrumb("Web_View_Created"); + Crittercism.beginUserFlow("Web_View_User_Flow"); + Crittercism.instrumentWebView(webView); + Crittercism.getNetworkInstrumentation(); + } + + webView.loadUrl(url); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + // Respond to the action bar's Up/Home button + case android.R.id.home: + finish(); + return true; + } + return super.onOptionsItemSelected(item); + } + + + @Override + public void onStart() { + super.onStart(); + + if (usingIntelligenceSdk) { + Crittercism.leaveBreadcrumb("Web_View_Started"); + } + } + + @Override + public void onRestart() { + super.onRestart(); + + if (usingIntelligenceSdk) { + Crittercism.leaveBreadcrumb("Web_View_Restarted"); + } + } + + @Override + public void onResume() { + if (usingIntelligenceSdk) { + Crittercism.leaveBreadcrumb("Web_View_Resumed"); + Log.i(TAG, "App crashed on previous run: " + Crittercism.didCrashOnLastLoad()); + } + super.onResume(); + } + + @Override + public void onPause() { + if (usingIntelligenceSdk) { + Crittercism.leaveBreadcrumb("Web_View_Paused"); + } + super.onPause(); + } + + @Override + public void onStop() { + super.onStop(); + + if (usingIntelligenceSdk) { + Crittercism.leaveBreadcrumb("Web_View_Stopped"); + Crittercism.endUserFlow("Web_View_User_Flow"); + Crittercism.sendAppLoadData(); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + + if (usingIntelligenceSdk) { + Crittercism.leaveBreadcrumb("Web_View_Destroyed"); + Crittercism.sendAppLoadData(); + } + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..eb3926d --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,55 @@ + + + + + + + +