Android SDK
===================
## Table of Contents    
- [Import](#import)
- [Installation](#installation)   
  - [Project](#project)   
  - [Settings](#settings)
  - [App](#app)
  - [Add fragment](#fragment)
- [Troubleshooting](#troubleshooting)   
- [Authors](#authors)

<a name="import"></a>
## Import

You must use git to download the sdk from [repository](https://pubgit.newsmemory.com/tecnavia/newsmemory-android-sdk)

1. Inside root folder run the following command:

```sh
git clone --depth 1 --branch 3.18.16 https://pubgit.newsmemory.com/tecnavia/newsmemory-android-sdk.git
```
2. if you already has the module you could update to another release by the following commands
```sh
cd newsmemory-android-sdk
git checkout tags/3.18.16
```
<a name="installation"></a>
## Installation

<a name="project"></a>
### Project Gradle
1. check main gradle repositories and dependencies
```java
buildscript {
    repositories {
        google()
        mavenCentral()
        gradlePluginPortal()
    }

    dependencies {
        classpath "com.android.tools.build:gradle:7.2.1"
        classpath "com.google.gms:google-services:4.3.4"
        //add crashlytics only if the aar file is included
        classpath "com.google.firebase:firebase-crashlytics-gradle:2.3.0"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.0"

    }
}

allprojects {
    repositories {
        google()
        mavenCentral()
        mavenLocal()
        gradlePluginPortal()
        maven {
            url "https://jitpack.io"
        }

        //If photodraweeview:1.1.3 for some reason is not available from Maven force this version
        configurations.all {
            resolutionStrategy {
                force "me.relex:photodraweeview:2.1.0"
            }
        }
    }
}
```

2. if you are using sdk for amazon and it is built for it add the following maven repository
```java
maven { 
    url "https://s3.amazonaws.com/android-listener/mvn-repo" 
}
```

3. you could customize all dependencies version by updating the following variables. !Pay attention: as the sdk may not start or work properly add these variables to ext object in main gradle file, see below example.

| variable                           | default | Description                                                    |
|:---------------------------------- |:-------:|:-------------------------------------------------------------- |
| compileSdk                         | 33      |                                                                |
| targetSdk                          | 33      |                                                                |
| minSdk                             | 24      |                                                                |
| frescoVersion                      | 2.5.0   | used by react native to display some images                    |
| soLoaderVersion                    | 0.10.4  | used to load needed system native libraries                    |
| okHttpVersion                      | 4.9.2   |                                                                |
| glideVersion                       | 4.12.0  |                                                                |
| kotlinGradleVersion                | 1.9.0   |                                                                |
| kotlinVersion                      | 1.9.0   |                                                                |
| webkitVersion                      | 1.4.0   |                                                                |
| androidxVersion                    | 1.8.0   |                                                                |
| androidxWorkRuntimeVersion         | 1.5.0   |                                                                |
| androidxAnnotationVersion          | 1.4.0   |                                                                |
| androidxViewpager2Version          | 1.0.0   |                                                                |
| androidxFragmentVersion            | 1.4.1   |                                                                |
| androidxBrowserVersion             | 1.4.0   |                                                                |
| androidxTransitionVersion          | 1.1.0   |                                                                |
| androidxCoordinatorlayoutVersion   | 1.1.0   |                                                                |
| androidxSwiperefreshlayoutVersion  | 1.0.0   |                                                                |
| androidxAppcompatVersion           | 1.0.2   |                                                                |
| androidxLegacySupportVersion       | 1.0.0   |                                                                |
| playServiceiidVersion              | 17.0.0  |                                                                |
| playServiceBaseVersion             | 18.0.1  |                                                                |
| **The following variables are used if you include on of the optional library listed in description**          |
| playServiceAnalyticsVersion        | 18.0.1  | react-native-google-analytics-bridge                           |
| playServiceAdsVersion              | 20.6.0  | react-native-prebid, react-native-dfp, react-native-admob      |
| playServiceMapsVersion             | 18.0.2  | react-native-maps                                              |
| playBillingVersion                 | 5.0.0   | react-native-iap for google                                    |
| amazonSdkVersion                   | 3.0.3   | react-native-iap for amazon                                    |

```java
ext {
    compileSdk = 34
    androidxVersion = "1.8.0"
    ...
}
```
<a name="settings"></a>
### Settings Gradle   
```java
include ':newsmemory-android-sdk'
```
<a name="app"></a>
### App Gradle
add the following lines if missing
 ```java
plugin: "com.android.application"
apply plugin: "com.google.gms.google-services"
//add crashlytics only if the aar file is included
apply plugin: 'com.google.firebase.crashlytics'

android{
    ...

    defaultConfig {
        ...
        //This line is required by react-native-iap, not included by default
        missingDimensionStrategy "store", "play"
    }

    ...

    packagingOptions {
        pickFirst "lib/x86/libc++_shared.so"
        pickFirst "lib/x86_64/libc++_shared.so"
        pickFirst "lib/arm64-v8a/libc++_shared.so"
        pickFirst "lib/armeabi-v7a/libc++_shared.so"
    }
}

dependencies{
    implementation project(":newsmemory-android-sdk")
    ...
}
```
<a name="fragment"></a>
## Add fragment to activity
```java
public class YourActivity extends AppCompatActivity {
    private TaFragment taFragment;
    ...
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if(taFragment == null) {
            taFragment = new TaFragment()
                .setDelegate(new TaFragmentDelegate() {
                    @Override
                    public void recreate() {
                        YourActivity.this.recreate();
                    }

                    @Override
                    public boolean isActionModeVisible() {
                        return false;
                    }

                    @Override
                    public boolean handleClose(){
                        //TODO implement it and return true if you consume the event
                        return false;
                    }

                    @Override
                    public boolean handleOpenUrl(String url){
                        //TODO implement it and return true if you consume the event
                        return false;
                    }

                    @Override
                    public boolean handleTrackAction(Bundle data){
                        //TODO implement it and return true if you consume the event
                        return false;
                    }
                    
                    @Override
                    public boolean handleTokenExpired(){
	                    //TODO implement it and return true if you consume the event
                        return false;
                    }
                })
                .setBuildProps(getBuildProps());
        } else {
            taFragment = (TaFragment) getSupportFragmentManager().findFragmentByTag("TA_FRAGMENT");
        }
         
        getSupportFragmentManager()
                .beginTransaction()
                .replace(R.id.rnFragment, taFragment, "TA_FRAGMENT")
                .commit();
    }

    public Bundle getBuildProps() {
        Bundle bundle = new Bundle();
        bundle.putString(TaConstants.TA_PSETUP, "replace_with_psetup");
        bundle.putString(TaConstants.TA_MACHINE, "replace_with_server");
        bundle.putString(TaConstants.TA_LOCALE, "en");
        bundle.putString(TaConstants.TA_TOKEN, "replace with a valid token");
        //the value inside R.string will be avilable after first build
        bundle.putString(TaConstants.TA_APP_VERSION_NAME, getString(com.tecnavia.sdk.R.string.APP_VERSION_NAME));
        bundle.putString(TaConstants.TA_APP_VERSION_CODE, getString(com.tecnavia.sdk.R.string.APP_VERSION_CODE));
        bundle.putString(TaConstants.TA_ANDROID_APP_ID,getString(com.tecnavia.sdk.R.string.ANDROID_APP_ID));
        bundle.putString(TaConstants.TA_APP_NAME, getString(com.tecnavia.sdk.R.string.APP_NAME));
        //must be true otherwise the module doesn't work in SDK mode
        bundle.putBoolean(TaConstants.TA_IS_ADDON, true);
        bundle.putString(TaConstants.TA_API_KEY, "Required to be authorized");
        
        // Specify the device type for the TA_LOCKED_ORIENTATION. If not provided, all devices will use TA_LOCKED_ORIENTATION.
        bundle.putString(TaConstants.TA_LOCKED_ORIENTATION_DEVICE, "tablet|phone");

        // Define the orientation for the app. If not specified, the app will use its default behavior.
        bundle.putString(TaConstants.TA_LOCKED_ORIENTATION, "portrait|landscape");

        //set the SDK referrer for GA4 analytics
        bundle.putString(TaConstants.TA_REFERRER, "referrer");

        //querystring-like list of parameters to pass to GA4 analytics
        bundle.putString(TaConstants.TA_EXTRA_GA4_PARAMS, "param1=value1&param2=value2&param3=value3");

        return bundle;
    }
}
```

the following constant is required by SDK to be authorized
```java
	bundle.putString(TaConstants.TA_API_KEY, "");
```

the following constants are required by SDK otherwise there will be some inconsinstecies and bugs
```java
    bundle.putString(TaConstants.TA_APP_VERSION_NAME, getString(com.tecnavia.sdk.R.string.APP_VERSION_NAME));
    bundle.putString(TaConstants.TA_APP_VERSION_CODE, getString(com.tecnavia.sdk.R.string.APP_VERSION_CODE));
    bundle.putString(TaConstants.TA_ANDROID_APP_ID,getString(com.tecnavia.sdk.R.string.ANDROID_APP_ID));
    bundle.putString(TaConstants.TA_APP_NAME, getString(com.tecnavia.sdk.R.string.APP_NAME));
    bundle.putBoolean(TaConstants.TA_IS_ADDON, true);
```

the following constant can be used for debug builds ONLY, to monitor the load time performances
```java
    bundle.putBoolean(TaConstants.TA_ENABLE_DEBUGGER, true);
```

1. The activity that load TaFragment must implement the following interface *DefaultHardwareBackBtnHandler*
```java
public class YourActivity extends AppCompatActivity implements DefaultHardwareBackBtnHandler {
    ...
    
    @Override
    public void invokeDefaultOnBackPressed() {
        //TODO handle finish
    }
}
```

2. you could extends activity class with TaActivity that already has an implementation of *DefaultHardwareBackBtnHandler* but also add some other methods to force locale.
```java
public class YourActivity extends TaActivity {
    ...
}
``` 

## Troubleshooting Guide

1. **Google Play Services Build Issues**

If you encounter build issues related to Google Play Services versions, consider adding the following setting to your `build.gradle` file:

```java
android {
    ...
    googleServices {
        disableVersionCheck = true
    }
    ...
}
```

2. **Java Base Access Error**

If you receive an error during the build process that reads `Unable to make field private final java.lang.String java.io.File.path accessible: module java.base does not "opens java.io" to unnamed module`, add the `--add-opens=java.base/java.io=ALL-UNNAMED` option to the `org.gradle.jvmargs` entry in your `gradle.properties` file:

```plaintext
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 --add-opens=java.base/java.io=ALL-UNNAMED
```

3. **Orientation Change Crash**

If your application crashes when the orientation changes, add the following attributes to your activity in the Android manifest:

```plaintext
configChanges="...|screenLayout|screenSize|smallestScreenSize"
```

For more information, refer to this [GitHub issue comment](https://github.com/software-mansion/react-native-screens/issues/17#issuecomment-923950313).

4. **Background or Awakening Crash**

If your application crashes when it moves to the background or wakes up, try adding the following to your activity:

```java
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(null);
}
```
For more information, refer to this [GitHub issue comment](https://github.com/software-mansion/react-native-screens/issues/17#issuecomment-424704067).

5. **Duplicate class**

If build fails due to `java.lang.RuntimeException: Duplicate class kotlin.collections.jdk8.CollectionsJDK8Kt ...`:

Add the following line to you `app/build.gradle`

```java
dependencies {
    ...

    // Fix Duplicate class
    implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.0"))
}
```
For more information, refer to this [GitHub issue comment](https://gist.github.com/danielcshn/7aa57155d766d46c043fde015f054d40).

6. **Android 14 Path traversal issue**

For apps targeting Android 14 (API level 34) or higher, Android prevents the `Zip Path Traversal Vulnerability` in the following way: `ZipFile(String)` and `ZipInputStream.getNextEntry()` throws a `ZipException` if zip file entry names contain ".." or start with "/".

Apps can opt-out from this validation by calling `dalvik.system.ZipPathValidator.clearCallback()`. For more information, refer to the [official Android documentation](https://developer.android.com/about/versions/14/behavior-changes-14#zip-path-traversal).

As the SDK might download zips including ".." or "/" in the entry name we need to add these lines at app startup.

```java
if (Build.VERSION.SDK_INT >= 34) {
   ZipPathValidator.clearCallback();
}
```

We suggest to add to you activity in the `onCreate` method.

```java
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (Build.VERSION.SDK_INT >= 34) {
           ZipPathValidator.clearCallback();
        }

        if(taFragment == null) {
           ...
        }

        ...
    }

```

## Authors
Nicolò Aquilini, iOS Software developer, Tecnavia

Andrea Mauri, Android Software developer, Tecnavia
