Project Setup
=============

.. javaimport::
    com.castlabs.*
    com.castlabs.android.*
    com.castlabs.android.player.*
    com.castlabs.android.player.hardware.*
    android.content.*

Requirements
------------
The |SDK| is delivered as a *Maven Repository* that can be added to
your application build. All required dependencies are either:

 * Contained in the bundle repository
 * Available on a public repository like *jcenter* or *Maven Central*
 * Part of the Android *Support Repository* that can be installed with the Android
   SDK-Manager.

To work with the |SDK| you need to use the Gradle build system for your
application builds.


Project Configuration
---------------------
The |SDK| can be added as a remote repository, specifying the content groups when needed::

    repositories{
        maven{
            url 'https://mvn.players.castlabs.com/'
            content{
                includeGroupAndSubgroups 'com.castlabs'
                includeGroup 'com.google.android.exoplayer'

                // Optional
                // includeGroupAndSubgroups 'tv.broadpeak'
                // includeGroupAndSubgroups 'com.nielsen'
            }
        }
    }

Alternatively, the |SDK| can be referenced from the stored bundle with ``repository`` folder.
This folder is a local *Maven Repository* that is used to find and integrate the |SDK| as a dependency
within your project.

You can enable the repository for your project in your `build.gradle`
script by adding it as a local Maven repository::

    repositories{
        maven{
            url '<path/to/castlabs/repository>'
        }
        jcenter()
    }

The `<path/to/castlabs/repository>` needs to be replaced with the actual path
to the ``repository`` folder that is bundled with the SDK. The repository does
not need to be part of your project structure explicitly and you do not need to
add it to your version control system. Instead, you can host the repository
outside of your project structure. You can for example use a Gradle property to
denote the path to the repository folder. 

The repository contains all the required `pom.xml` files to be uploaded to
a hosted *Maven* or *Ivy* repository. 

With the repository added to your build script, you can reference and load the
|SDK| and its components using::

    dependencies {
        ...
        compile 'com.castlabs.player:castlabs-sdk:|version|'
        ...
    }

This will add the SDK and its dependencies to the classpath of your application
and you can start referencing classes within the SDK.

After the SDK is added as a dependency, you need to initialize it before
you can use it. This is typically done in your Application class using the
:javaref:`PlayerSDK.init() <com.castlabs.android.PlayerSDK#init(Context)>`
method. For example:

.. code-block:: java

    public class DemoApp extends Application{
        @Override
        public void onCreate() {
            super.onCreate();
            ...
            PlayerSDK.init(getApplicationContext());
        }
    }
    
.. _license-loading:

License Loading
---------------
The |SDK| requires a license that needs to be distributed with your
application. By default, the SDK expects to find the license in your Application Manifest
as the `castlabs-license` meta-data Attribute. For example:

.. code-block:: xml

    <?xml version="1.0" encoding="utf-8"?>
    <manifest>
        ...
        <application>
            ...
            <meta-data android:name="castlabs-license"
                       android:value="<your-license-key>" />
            ...
        </application>
    </manifest>

The license key is bound to your Applications SHA-1 Fingerprint and Package name. You can
generate license keys on the  `Castlabs Download Portal <https://downloads.castlabs.com/browse/licenses/>`_.
In the `Licenses` section, you can create License Keys for your Application.

Depending on how you decide to manage your license file, you might want to load
it from a different location or use a different name. For
this, you can use an implementation of the
:javaref:`com.castlabs.LicenseLoader` interface. The SDK already comes
with an implementation to load the licenses from the application's Manifest
with the :javaref:`ManifestLicenseLoader <com.castlabs.ManifestLicenseLoader>`. This
is the default implementation. If you want to use a different loader, you can pass an
instance of the :javaref:`LicenseLoader
<com.castlabs.LicenseLoader>` to :javaref:`PlayerSDK.init()
<com.castlabs.android.PlayerSDK#init(Context, LicenseLoader)>`. For example:

.. code-block:: java

    PlayerSDK.init(getContext(),
                   new MyLicenseLoader());


Note that in case the license key is invalid the SDK will not be initialized and the error will be sent via the 
:javaref:`PlayerListener.onError(CastlabsPlayerException) <com.castlabs.android.player.PlayerListener#onError(CastlabsPlayerException)>`.
The SDK however can still be re-initialized with a valid key if applicable.

User Id
+++++++

Your |SDK| license might require user tracking for accounting purposes. If this is the case, you
*must* provide a unique user id for each view session.


The value of this parameter must be unique for each end-user. Our backend only needs
to identify end users on a cross-session basis, but we don't use this information to do any correlation.

This means that the provided values should be `pseudonymized <https://en.wikipedia.org/wiki/Pseudonymization>`_.
One example of this could be hashing a user's email account.

This value can be informed through the parameter ``INTENT_USER_ID`` if using an ``Intent`` or with the ``userId`` field in ``PlayerConfig`` upon playback initialization.

.. note::
    In case your |SDK| license requires user identification, you *must* inform this field.

    Without specifying user ID parameter if mandatory, the SDK license check will result in a rejected license and failed playback attempt.
    If the license check fails due to missing USER_ID, a player exception will be raised::

        E/CastlabsPlayerException: [ERROR] [TYPE_USER_ID_NOT_PROVIDED] UserID is required for playback

If you're using DRMToday, the |SDK| will, by default, use the user id informed in the ``DrmConfiguration``, so there's no
obligation to fill in the ``userId`` in such scenario. You can still inform the value in the `Bundle` or
the ``PlayerConfig`` object and it will take precedence.

As mentioned, there are two ways of providing the end user ID to the SDK: ``INTENT_USER_ID`` or ``PlayerConfig.userId`` (which can be specified through the ``Builder``).
The example for this would be:

.. code-block:: java

    // Specify the UserID as an intent bundle parameter
    bundle.putString(SdkConsts.INTENT_USER_ID, "unique-user-id");

or:

.. code-block:: java

    // Alternatively, set the UserID in PlayerConfig as part of the config
    PlayerConfig playerConfig = new PlayerConfig.Builder("<url_to_stream>")
        .userID("unique-user-id")
        .get();

Third party re-signing conflicts
--------------------------------

Each license for the |SDK| is tied to a unique developer certificate. This link is checked through
the certificate's SHA1 fingerprint. For this reason, some third party processes which introduce re-signing
operations may interfere with the |SDK| licence check if such re-signing is done with a different key.

Protection tools
++++++++++++++++

Some Android app protection and obfuscation software needs to re-sign the app bundle or apk after
the protection and obfuscation actions have taken place. In order for the |SDK| license check to
succeed, it is necessary that the fingerprint of the key that is used to sign the application is the one
indicated upon the |SDK| key generation.

If you are experiencing issues with license validation after running protection software,
please make sure the third party protection tool is using the same key to re-sign the binary.

DexProtector
^^^^^^^^^^^^

.. External reference to Dexprotector
.. |dexprotector_link| raw:: html

   <a class="reference external" href="https://licelus.com/products/dexprotector" target="_blank">Licel's DexProtector</a>

If using |dexprotector_link|, you should pay close attention to the ``signMode`` element in
the configuration. Such should be set to ``release``, alongside the path and
information about the keystore to use for signing.

This is the same keystore from which the SHA1 fingerprint should have been extracted in order
to generate the license for the |SDK|.

Amazon App Store
++++++++++++++++

Please note that the Amazon App store re-signs submitted APKs and hence changes the signature of the Application 
and the signing key. Since the Castlabs License is bound to the signing key, you need to create a dedicated 
license using the SHA1 that is exposed on the Amazon Developer console.

.. figure:: _static/img/amazon_sha1.png

   Amazon App Store SHA1

.. _variant_builds:

Variant Builds
--------------

You can leverage the support of the Android build plugin for `"build variants"
<http://developer.android.com/tools/building/configuring-gradle.html#workBuildVariants>`_
to create different versions of your application from a single project. This is particularly useful
when you want to provide optimized experiences for different Android API levels or device capabilities.

For instance, you might want to create a build that targets newer Android versions with features
that rely on more recent APIs, and another build for older devices that uses fallback mechanisms or
has a slightly reduced feature set.

A detailed discussion of build variants is out of scope for this documentation, but a simple example
looks like this:

.. code-block:: groovy

    ...
    android {
        ...
        defaultConfig {
            minSdkVersion 21
            // Common configurations for all variants
            applicationId "com.example.myapp"
            // Set a base versionCode. Flavors will need specific ones.
            versionCode 1
            // Other common configurations
        }
        signingConfigs { ... }
        buildTypes { ... }
        productFlavors {
            // Flavor for newer devices, targeting API 23 (Marshmallow) and above
            modern {
                minSdkVersion 23
                // For multiple APKs in Play Store, the one with higher API support
                // often needs a higher version code.
                versionCode 200 // Example: 200 (prefix) + defaultConfig.versionCode
                versionNameSuffix "-modern"
            }
            // Flavor for older devices, supporting from API 21 (Lollipop) up to API 22
            legacy {
                minSdkVersion 21
                // This variant targets older APIs, so it should have a lower version code
                // to ensure users on newer OS versions get the 'modern' APK.
                versionCode 100 // Example: 100 (prefix) + defaultConfig.versionCode
                versionNameSuffix "-legacy"
                // Important: If you want this APK to only be for devices up to API 22
                // you would define maxSdkVersion here.
                // maxSdkVersion 22
            }
        }

        // Optional: If you're using App Bundles (recommended), Google Play handles
        // delivering the correct APK based on device API level.
        // If you are manually uploading multiple APKs, ensure your versionCode logic
        // is correct. See: https://developer.android.com/google/play/publishing/multiple-apks.html
    }
    ...
    dependencies {
        ...

        // Common dependencies for all variants
        implementation 'com.castlabs.player:castlabs-sdk:|version|' // Or your current SDK version

        // You could have dependencies specific to a flavor based on API level needs:
        // modernImplementation 'androidx.appcompat:appcompat:1.6.1' // Example with a newer library version
        // legacyImplementation 'androidx.appcompat:appcompat:1.3.1' // Example with an older compatible version
        ...
    }
    ...

In this example, we create two product flavors: "modern" and "legacy".

- **modern Flavor:**

  * Targets `minSdkVersion 23` (Android Marshmallow).
  * It's assigned a higher `versionCode` (e.g., `200`). This is important if you're publishing multiple APKs to the Google Play Store, as it ensures devices capable of running this version receive it.

- **legacy Flavor:**

  * Targets an older `minSdkVersion 21` (Android Lollipop).
  * It has a lower `versionCode` (e.g., `100`).
  * The commented-out `maxSdkVersion 22` shows how you could restrict this APK to only be installable on devices up to API level 22. If you set `maxSdkVersion` for the `legacy` flavor and a `minSdkVersion` for the `modern` flavor that is higher than the `legacy` flavor's `maxSdkVersion`, you ensure that there is no overlap in API level targeting.


Split APK Builds
----------------

The SDK includes native components for ARM
and x86 architectures bundled in their AAR files. Similar to the variant builds
(see :ref:`variant_builds`), you can use the *split APK* feature of the Android
Gradle plugin to generate APK versions specific to ARM and x86 architectures
and further reduce the APK size of the final application for a single variant.
You can find more information about ABI splits and variant builds in `the
Android Tools wiki
<http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits>`_.


Proguard Configuration
----------------------

The |SDK| and all the plugins embed a Proguard configuration in their ``aar``
files and there is no need to specify anything specific to the Castlabs SDK in
your Proguard configuration file.

Protocol buffer dependencies
----------------------------

The |SDK| uses Google's protobuf library some of its components. More precisely,
the ``protobuf-javalite`` artifact is being used. If you're using in your project
another version of such library, you'll need to exclude it from the conflicting
packages so the build goes through without finding duplicate classes.

Note that this also applies to transitive dependencies. One common example is Google's
``play-services-cast-framework`` used for the Google Cast sender:

.. code-block:: groovy

    // Don't include the transitive protobuf dependency
    implementation ('com.google.android.gms:play-services-cast-framework:X.Y.Z') {
        exclude group: 'com.google.protobuf'
    }

