[
  {
    "path": ".gitignore",
    "content": "# Built application files\n*.apk\n*.ap_\nbin/\ngen/\nclasses/\ngen-external-apklibs/\n\n# Eclipse project files\n.classpath\n.project\n.metadata\n.settings\n\n# IntelliJ files\n.idea\n*.iml\n\n# OSX files\n.DS_Store\n\n# Windows files\nThumbs.db\n\n# vi swap files\n*.swp\n\n# backup files\n*.bak\n\n\n# Files for the Dalvik VM\n*.dex\n\n# Java class files\n*.class\n\n# Generated files\nbin/\ngen/\n\n# Gradle files\n.gradle/\nbuild/\n.gradle\n\n#maven files\ntarget/\n/null\n\n# Local configuration file (sdk path, etc)\n\nlocal.properties\n\n# Proguard folder generated by Eclipse\nproguard/\n\n#Log Files\n*.log\n\nsample/src/main/java/de/rheinfabrik/heimdalldroid/network/TraktTvAPIConfiguration.java\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: android\nsudo: required\njdk: oraclejdk8\n\nenv:\n  global:\n    - ANDROID_API_LEVEL=30\n    - ANDROID_BUILD_TOOLS_VERSION=30.0.2\n    - ANDROID_ABI=armeabi-v7a\n\nandroid:\n  components:\n    - tools\n    - platform-tools\n    - extra-android-m2repository\n  licenses:\n    - 'android-sdk-preview-license-52d11cd2'\n    - 'android-sdk-license-.+'\n    - 'google-gdk-license-.+'\n\nbefore_install:\n  - touch $HOME/.android/repositories.cfg\n  - yes | sdkmanager \"platforms;android-30\"\n  - yes | sdkmanager \"build-tools;30.0.2\"\n\nbefore_cache:\n  - rm -f  $HOME/.gradle/caches/modules-2/modules-2.lock\n  - rm -fr $HOME/.gradle/caches/*/plugin-resolution/\n\ncache:\n  directories:\n    - $HOME/.gradle/caches/\n    - $HOME/.gradle/wrapper/\n    - $HOME/.android/build-cache\n\nbefore_script:\n  - chmod +x gradlew\n\nscript:\n  - ./gradlew library:test\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"{}\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright 2015 B264 GmbH\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "# Deprecated Project\n\n⚠️ This project is no longer maintained\n\nThis repository is deprecated and will no longer receive updates, bug fixes, or support.\n\n# <img src=\"https://cloud.githubusercontent.com/assets/460060/8159821/b8bfeb32-136a-11e5-83ed-83b7fe01df3a.jpg\" width=\"30\" height=\"30\"> Heimdall\n\nHeimdall is an [OAuth 2.0](https://tools.ietf.org/html/rfc6749) client specifically designed for easy usage and high flexibility. It supports all grants as described in [Section 4](https://tools.ietf.org/html/rfc6749#section-4) as well as refreshing an access token as described in [Section 6](https://tools.ietf.org/html/rfc6749#section-6) of the [The OAuth 2.0 Authorization Framework](https://tools.ietf.org/html/rfc6749) specification.\n\nThis library makes use of [RxJava](https://github.com/ReactiveX/RxJava). Therefore you should be familar with [Observables](https://github.com/ReactiveX/RxJava/wiki/Observable).\n\nIf you are an iOS Developer then please take a look at the [Swift version of Heimdall](https://github.com/trivago/Heimdallr.swift).\n\n[![JitPack.io](https://jitpack.io/v/trivago/Heimdall.droid.svg)](https://jitpack.io/#trivago/Heimdall.droid)\n[![Travis Ci](https://travis-ci.org/trivago/Heimdall.droid.svg?branch=master)](https://travis-ci.org/trivago/Heimdall.droid)\n[![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-Heimdall.droid-brightgreen.svg?style=flat)](http://android-arsenal.com/details/1/2016)\n[![Api](https://img.shields.io/badge/API-9%2B-brightgreen.svg?style=flat)](https://android-arsenal.com/api?level=9)\n\n## Installation\n\nHeimdall is ready to be used via [jitpack.io](https://jitpack.io/#rheinfabrik/Heimdall.droid).\nSimply add the following code to your root `build.gradle`:\n\n```groovy\nallprojects {\n    repositories {\n        jcenter()\n        maven { url \"https://jitpack.io\" }\n    }\n}\n```\n\nNow add the gradle dependency in your application's `build.gradle`:\n\n```groovy\ndependencies {\n    compile 'com.github.rheinfabrik:Heimdall.droid:{latest_version}'\n}\n```\n\n## Examples\n\nHeimdall's main class is the `OAuth2AccessTokenManager`. It is responsible for retrieving a new access token and keeping it valid by refreshing it.\n\nIn order to initialize an `OAuth2AccessTokenManager` instance, you need to pass an object implementing the `OAuth2AccessTokenStorage` interface. You can use the predefined `SharedPreferencesOAuth2AccessTokenStorage` if it suits your needs. Make sure that your `OAuth2AccessTokenStorage` is as secure as possible!\n\n```java \n\nSharedPreferencesOAuth2AccessTokenStorage<OAuth2AccessToken> storage = new SharedPreferencesOAuth2AccessTokenStorage<>(mySharedPreferences, OAuth2AccessToken.class);\nOAuth2AccessTokenManager<> manager = new OAuth2AccessTokenManager<OAuth2AccessToken>(storage);\n\n```\n\nOn your manager instance you can now call `grantNewAccessToken(grant)` to receive a new access token. The grant instance you pass must implement the `OAuth2Grant` interface and your actual server call. \n\nHere is an example of an `OAuth2ResourceOwnerPasswordCredentialsGrant`.\n\n```java \npublic class MyOAuth2Grant extends OAuth2ResourceOwnerPasswordCredentialsGrant<OAuth2AccessToken> {\n\n    // Constructor\n\n    @Override\n    public Observable<OAuth2AccessToken> grantNewAccessToken() {\n        // Create the network request based on the username, the password and the grant type.\n        // You can use Retrofit to make things easier.\n    }\n}\n```\n\nYour manager instance also has a method called `getValidAccessToken(refreshGrant)`. This is probably the main reason we build this library. It firstly checks if the stored access token is expired and then either emits the unexpired one or refreshs it if it is expired using the passed refresh grant. \n\nHere is an example of an `OAuth2RefreshAccessTokenGrant`.\n\n```java\npublic class MyOAuth2Grant extends OAuth2RefreshAccessTokenGrant<OAuth2AccessToken> {\n\n    // Constructor\n\n    @Override\n    public Observable<OAuth2AccessToken> grantNewAccessToken() {\n        // Create the network request based on the grant type and the refresh token.\n        // You can use Retrofit to make things easier.\n    }\n}\n```\n\nMostly you will use the `OAuth2AuthorizationCodeGrant` to authorize the user via a third party service such as Trakt.tv.\n\nThe implemention of a grant authorizing with Trakt.tv might look as following:\n\n```java\npublic final class TraktTVAuthorizationCodeGrant extends OAuth2AuthorizationCodeGrant<OAuth2AccessToken> {\n\n    public String clientSecret;\n\n    @Override\n    public Uri buildAuthorizationUri() {\n        return Uri.parse(\"https://trakt.tv/oauth/authorize\")\n                .buildUpon()\n                .appendQueryParameter(\"client_id\", clientId)\n                .appendQueryParameter(\"redirect_uri\", redirectUri)\n                .appendQueryParameter(\"response_type\", RESPONSE_TYPE).build();\n    }\n\n    @Override\n    public Observable<OAuth2AccessToken> exchangeTokenForCode(String code) {\n        // Create the network request based on the grant type, clientSecret and the retrieved code.\n        // You can use Retrofit to make things easier.\n    }\n}\n```\n\nUsing that grant with an Android WebView might look like this (please note that we use [Retrolambda](https://github.com/evant/gradle-retrolambda) here):\n\n```java\n// Create the grant\nTraktTVAuthorizationCodeGrant grant = new TraktTVAuthorizationCodeGrant();\ngrant.clientSecret = \"secret\"\ngrant.clientId = \"id\"\ngrant.redirectUri = \"uri\"\n\n// Set up web view loading\nwebView.setWebViewClient(new WebViewClient() {\n \t\n \t@Override\n    public void onPageFinished(WebView view, String url) {\n    \tsuper.onPageFinished(view, url);\n\n\t\t// Tell the grant we loaded an url\n        grant.onUrlLoadedCommand.onNext(Uri.parse(url));\n    }\n});\n\n// Load the authorization url once build\ngrant.authorizationUri()\n    .map(Uri::parse)\n\t.observeOn(AndroidSchedulers.mainThread())\n\t.subscribe(myWebView::load)\n\n// Start the authorization process\ngrant.grantNewAccessToken()\n\t.subscribe(token -> Log.d(\"Heimdall\", \"New token: \" + token))\n\n```\n\n## Sample Application\n\nPlease also check out our sample application which performs an authorization against [trakt.tv](https://trakt.tv/) and displays a simple list of the user's watchlists.\n\n**Note:** In order to build the sample by yourself you have to [create a new application on trakt.tv](https://trakt.tv/oauth/applications/new) and add the credentials wherever `TraktTvAPIConfiguration.java` is used.\n\n\n## About\n\nHeimdall was built by [trivago](http://www.trivago.com) :factory:\n\n## License\n\nHeimdall is licensed under Apache Version 2.0.\n"
  },
  {
    "path": "build.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n    repositories {\n        google()\n        jcenter()\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:4.0.1'\n\n        // NOTE: Do not place your application dependencies here; they belong\n        // in the individual module build.gradle files\n    }\n}\n\nallprojects {\n    repositories {\n        google()\n        jcenter()\n    }\n}\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "#Tue Oct 24 09:19:37 CEST 2017\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-6.1.1-all.zip\n"
  },
  {
    "path": "gradle.properties",
    "content": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\n# Default value: -Xmx10248m -XX:MaxPermSize=256m\n# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8\n\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\nandroid.enableJetifier=true\nandroid.useAndroidX=true\n"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn ( ) {\n    echo \"$*\"\n}\n\ndie ( ) {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\nesac\n\n# For Cygwin, ensure paths are in UNIX format before anything is touched.\nif $cygwin ; then\n    [ -n \"$JAVA_HOME\" ] && JAVA_HOME=`cygpath --unix \"$JAVA_HOME\"`\nfi\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >&-\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >&-\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules\nfunction splitJvmOpts() {\n    JVM_OPTS=(\"$@\")\n}\neval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\nJVM_OPTS[${#JVM_OPTS[*]}]=\"-Dorg.gradle.appname=$APP_BASE_NAME\"\n\nexec \"$JAVACMD\" \"${JVM_OPTS[@]}\" -classpath \"$CLASSPATH\" org.gradle.wrapper.GradleWrapperMain \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:init\r\n@rem Get command-line arguments, handling Windowz variants\r\n\r\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\r\nif \"%@eval[2+2]\" == \"4\" goto 4NT_args\r\n\r\n:win9xME_args\r\n@rem Slurp the command line arguments.\r\nset CMD_LINE_ARGS=\r\nset _SKIP=2\r\n\r\n:win9xME_args_slurp\r\nif \"x%~1\" == \"x\" goto execute\r\n\r\nset CMD_LINE_ARGS=%*\r\ngoto execute\r\n\r\n:4NT_args\r\n@rem Get arguments from the 4NT Shell from JP Software\r\nset CMD_LINE_ARGS=%$\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "library/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "library/build.gradle",
    "content": "apply plugin: 'kotlin'\napply plugin: 'groovy'\napply plugin: 'maven'\n\nbuildscript {\n    ext.kotlin_version = '1.4.10'\n\n    repositories {\n        mavenCentral()\n    }\n\n    dependencies {\n        classpath \"org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version\"\n    }\n}\n\njar.archiveName = \"Heimdall.Droid.jar\"\n\ngroup = 'com.github.rheinfabrik'\n\nrepositories {\n    mavenCentral()\n    jcenter()\n}\n\ndependencies {\n    // Kotlin\n    implementation \"org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version\"\n\n    // Rx\n    implementation 'io.reactivex.rxjava2:rxjava:2.2.19'\n\n    // GSON\n    implementation 'com.google.code.gson:gson:2.8.6'\n\n    // Testing\n    testImplementation 'junit:junit:4.13'\n    testImplementation \"com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0\"\n    testImplementation \"org.mockito:mockito-inline:3.5.11\"\n}\n\ncompileKotlin {\n    kotlinOptions {\n        jvmTarget = \"1.8\"\n    }\n}\n\ncompileTestKotlin {\n    kotlinOptions {\n        jvmTarget = \"1.8\"\n    }\n}\n"
  },
  {
    "path": "library/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /Users/gilo/Library/Android/sdk/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n"
  },
  {
    "path": "library/src/main/java/de/rheinfabrik/heimdall2/OAuth2AccessToken.kt",
    "content": "package de.rheinfabrik.heimdall2\n\nimport com.google.gson.annotations.SerializedName\nimport java.io.Serializable\nimport java.util.Calendar\n\ndata class OAuth2AccessToken(\n    /**\n     * REQUIRED\n     * The type of the token issued as described in https://tools.ietf.org/html/rfc6749#section-7.1.\n     * Value is case insensitive.\n     */\n    @SerializedName(\"token_type\")\n    val tokenType: String = \"\",\n\n    /**\n     * REQUIRED\n     * The access token issued by the authorization server.\n     */\n    @SerializedName(\"access_token\")\n    val accessToken: String = \"\",\n\n    /**\n     * OPTIONAL\n     * The refresh token, which can be used to obtain new\n     * access tokens using the same authorization grant as described\n     * in https://tools.ietf.org/html/rfc6749#section-6.\n     */\n    @SerializedName(\"refresh_token\")\n    val refreshToken: String? = null,\n\n    /**\n     * RECOMMENDED\n     * The lifetime in seconds of the access token.  For\n     * example, the value \"3600\" denotes that the access token will\n     * expire in one hour from the time the response was generated.\n     * If omitted, the authorization server SHOULD provide the\n     * expiration time via other means or document the default value.\n     */\n    @SerializedName(\"expires_in\")\n    val expiresIn: Int? = null,\n\n    /**\n     * The expiration date used by Heimdall.\n     */\n    @SerializedName(\"heimdall_expiration_date\")\n    val expirationDate: Calendar? = null\n) : Serializable {\n\n    // Public API\n\n    /**\n     * Returns whether the access token expired or not.\n     *\n     * @return True if expired. Otherwise false.\n     */\n    fun isExpired(): Boolean =\n        expirationDate != null &&\n            Calendar.getInstance().after(expirationDate)\n\n}\n"
  },
  {
    "path": "library/src/main/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenManager.kt",
    "content": "package de.rheinfabrik.heimdall2\n\nimport de.rheinfabrik.heimdall2.grants.OAuth2Grant\nimport de.rheinfabrik.heimdall2.grants.OAuth2RefreshAccessTokenGrant\nimport io.reactivex.Single\nimport java.util.Calendar\n\nopen class OAuth2AccessTokenManager(\n    private val mStorage: OAuth2AccessTokenStorage\n) {\n\n    // Public API\n\n    /**\n     * Returns the underlying storage.\n     *\n     * @return - An OAuth2AccessTokenStorage.\n     */\n    fun getStorage(): OAuth2AccessTokenStorage = mStorage\n\n    /**\n     * Grants a new access token using the given OAuth2 grant.\n     *\n     * @param grant A class implementing the OAuth2Grant interface.\n     * @return - An Single emitting the granted access token.\n     */\n    fun grantNewAccessToken(\n        grant: OAuth2Grant,\n        calendar: Calendar = Calendar.getInstance()\n    ): Single<OAuth2AccessToken> =\n        grant.grantNewAccessToken()\n            .map {\n                if (it.expiresIn != null) {\n                    val newExpirationDate = (calendar.clone() as Calendar).apply {\n                        add(Calendar.SECOND, it.expiresIn)\n                    }\n                    it.copy(expirationDate = newExpirationDate)\n                } else {\n                    it\n                }\n            }\n            .doOnSuccess { token ->\n                mStorage.storeAccessToken(\n                    token = token\n                )\n            }\n            .cache()\n\n    /**\n     * Returns an Observable emitting an unexpired access token.\n     * NOTE: In order to work, Heimdall needs an access token which has a refresh_token and an\n     * expires_in field.\n     *\n     * @param refreshAccessTokenGrant The refresh grant that will be used if the access token is expired.\n     * @return - An Single emitting an unexpired access token.\n     */\n    fun getValidAccessToken(refreshAccessTokenGrant: OAuth2RefreshAccessTokenGrant): Single<OAuth2AccessToken> =\n        mStorage.getStoredAccessToken()\n            .flatMap { accessToken ->\n                if (accessToken.isExpired()) {\n                    refreshAccessTokenGrant.refreshToken = accessToken.refreshToken\n                    grantNewAccessToken(refreshAccessTokenGrant)\n                } else {\n                    Single.just(accessToken)\n                }\n            }\n}\n"
  },
  {
    "path": "library/src/main/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenStorage.kt",
    "content": "package de.rheinfabrik.heimdall2\n\nimport io.reactivex.Single\n\n/**\n * Interface used to define how to store and retrieve a stored access token.\n *\n * @param <TAccessToken> The access token type.\n */\ninterface OAuth2AccessTokenStorage {\n    // Public API\n\n    /**\n     * Queries the stored access token.\n     *\n     * @return - An Observable emitting the stored access token.\n     */\n    fun getStoredAccessToken(): Single<OAuth2AccessToken>\n\n    /**\n     * Stores the given access token.\n     *\n     * @param token The access token which will be stored.\n     */\n    fun storeAccessToken(token: OAuth2AccessToken)\n\n    /**\n     * Checks whether there is or is not an access token\n     *\n     * @return - An Observable emitting true or false based on whether there is or is not an\n     * access token.\n     */\n    fun hasAccessToken(): Single<Boolean>\n\n    /**\n     * Removes the stored access token.\n     */\n    fun removeAccessToken()\n}\n"
  },
  {
    "path": "library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2AuthorizationCodeGrant.kt",
    "content": "package de.rheinfabrik.heimdall2.grants\n\nimport de.rheinfabrik.heimdall2.OAuth2AccessToken\nimport io.reactivex.Observable\nimport io.reactivex.Single\nimport io.reactivex.subjects.BehaviorSubject\nimport io.reactivex.subjects.PublishSubject\nimport java.net.URL\nimport java.net.URLDecoder\n\n/**\n * Class representing the Authorization Code Grant as described in https://tools.ietf.org/html/rfc6749#section-4.1.\n *\n * @param <TAccessToken> The access token type.\n */\nabstract class OAuth2AuthorizationCodeGrant(\n    /**\n     * REQUIRED\n     * The client identifier as described in https://tools.ietf.org/html/rfc6749#section-2.2.\n     */\n    var clientId: String = \"\",\n\n    /**\n     * OPTIONAL\n     * As described in https://tools.ietf.org/html/rfc6749#section-3.1.2.\n     */\n    var redirectUri: String? = null,\n\n    /**\n     * OPTIONAL\n     * The scope of the access request as described in https://tools.ietf.org/html/rfc6749#section-3.3.\n     */\n    var scope: String? = null,\n\n    /**\n     * RECOMMENDED\n     * An opaque value used by the client to maintain\n     * state between the request and callback. The authorization\n     * server includes this value when redirecting the user-agent back\n     * to the client. The parameter SHOULD be used for preventing\n     * cross-site request forgery as described in https://tools.ietf.org/html/rfc6749#section-10.12.\n     */\n    var state: String? = null\n) : OAuth2Grant {\n\n    // Constants\n    companion object {\n        @JvmField\n        val RESPONSE_TYPE = \"code\"\n        @JvmField\n        val GRANT_TYPE = \"authorization_code\"\n        private const val UTF_8 = \"UTF-8\"\n    }\n\n    // Public Members\n    /**\n     * Command you should send a value to whenever an url in e.g. your web view has been loaded.\n     */\n    val onUrlLoadedCommand = PublishSubject.create<URL>()\n\n    // Private Members\n    private val mAuthorizationUrlSubject = BehaviorSubject.create<URL>()\n\n    // Abstract API\n\n    /**\n     * Called when the grant needs the authorization url.\n     */\n    abstract fun buildAuthorizationUrl(): URL\n\n    /**\n     * Called when the grant was able to grab the code and it wants to exchange for an access token.\n     */\n    abstract fun exchangeTokenUsingCode(code: String): Observable<OAuth2AccessToken>\n\n    // Public API\n\n    /**\n     * Observable emitting the authorization Uri.\n     */\n    fun authorizationUri() = mAuthorizationUrlSubject\n\n    override fun grantNewAccessToken(): Single<OAuth2AccessToken> {\n        mAuthorizationUrlSubject.onNext(buildAuthorizationUrl())\n\n        return onUrlLoadedCommand.map {\n            getQueryParameters(it)[RESPONSE_TYPE]?.get(0)\n        }.filter {\n            it.isNotBlank()\n        }.take(1)\n            .retry()\n            .concatMap {\n                exchangeTokenUsingCode(it)\n            }\n            .singleOrError()\n    }\n\n    // Private API\n\n    private fun getQueryParameters(url: URL): LinkedHashMap<String, MutableList<String>> {\n        val queryParams = linkedMapOf<String, MutableList<String>>()\n        url.query.split(\"&\").forEach {\n            val idx = it.indexOf(\"=\")\n            try {\n                val key = if (idx > 0) {\n                    URLDecoder.decode(\n                        it.substring(0, idx),\n                        UTF_8\n                    )\n                } else {\n                    it\n                }\n                if (!queryParams.containsKey(key)) {\n                    queryParams[key] = mutableListOf()\n                }\n                val value = if (idx > 0 && it.length > idx + 1) {\n                    URLDecoder.decode(\n                        it.substring(idx + 1),\n                        UTF_8\n                    )\n                } else \"\"\n\n                queryParams[key]?.add(value)\n            } catch (e: Exception) {\n                // Do nothing\n            }\n        }\n        return queryParams\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2ClientCredentialsGrant.kt",
    "content": "package de.rheinfabrik.heimdall2.grants\n\nabstract class OAuth2ClientCredentialsGrant(\n    /**\n     * OPTIONAL\n     * The scope of the access request as described in https://tools.ietf.org/html/rfc6749#section-3.3.\n     */\n    val scope: String? = null\n) :\n    OAuth2Grant {\n\n    // Constants\n\n    companion object {\n        /**\n         * REQUIRED\n         * The OAuth2 \"grant_type\".\n         */\n        @JvmField\n        val GRANT_TYPE = \"client_credentials\"\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2Grant.kt",
    "content": "package de.rheinfabrik.heimdall2.grants\n\nimport de.rheinfabrik.heimdall2.OAuth2AccessToken\nimport io.reactivex.Single\n\n/**\n * Interface describing an OAuth2 Grant as described in https://tools.ietf.org/html/rfc6749#page-23.\n *\n * @param <TAccessToken> The access token type.\n */\ninterface OAuth2Grant {\n\n    // Abstract Api\n\n    /**\n     * Performs the actual request to grant a new access token.\n     *\n     * @return - An Observable emitting the granted access token.\n     */\n    fun grantNewAccessToken(): Single<OAuth2AccessToken>\n}\n"
  },
  {
    "path": "library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2ImplicitGrant.kt",
    "content": "package de.rheinfabrik.heimdall2.grants\n\n/**\n * Class representing the Implicit Grant as described in https://tools.ietf.org/html/rfc6749#section-4.2.\n *\n * @param <TAccessToken> The access token type.\n */\n\nabstract class OAuth2ImplicitGrant(\n    /**\n     * REQUIRED\n     * The client identifier as described in https://tools.ietf.org/html/rfc6749#section-2.2.\n     */\n    var clientId: String = \"\",\n\n    /**\n     * OPTIONAL\n     * As described in https://tools.ietf.org/html/rfc6749#section-3.1.2.\n     */\n    var redirectUri: String? = null,\n\n    /**\n     * OPTIONAL\n     * The scope of the access request as described in https://tools.ietf.org/html/rfc6749#section-3.3.\n     */\n    var scope: String? = null,\n\n    /**\n     * RECOMMENDED\n     * An opaque value used by the client to maintain\n     * state between the request and callback. The authorization\n     * server includes this value when redirecting the user-agent back\n     * to the client. The parameter SHOULD be used for preventing\n     * cross-site request forgery as described in https://tools.ietf.org/html/rfc6749#section-10.12.\n     */\n    var state: String? = null\n\n) : OAuth2Grant {\n\n    // Constants\n\n    /**\n     * REQUIRED\n     * The \"response_type\" which MUST be \"token\".\n     */\n    companion object {\n        @JvmField\n        val RESPONSE_TYPE = \"token\"\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2RefreshAccessTokenGrant.kt",
    "content": "package de.rheinfabrik.heimdall2.grants\n\nabstract class OAuth2RefreshAccessTokenGrant(\n    /**\n     * REQUIRED\n     * The \"refresh_token\" issued to the client.\n     */\n    var refreshToken: String? = null,\n\n    /**\n     * OPTIONAL\n     * The \"scope\" of the access request as described by here (https://tools.ietf.org/html/rfc6749#section-3.3).\n     */\n    var scope: String? = null\n) :\n    OAuth2Grant {\n    // Constants\n\n    /**\n     * REQUIRED\n     * The OAuth2 \"grant_type\".\n     */\n    companion object {\n        @JvmField\n        val GRANT_TYPE = \"refresh_token\"\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2ResourceOwnerPasswordCredentialsGrant.kt",
    "content": "package de.rheinfabrik.heimdall2.grants\n\nabstract class OAuth2ResourceOwnerPasswordCredentialsGrant(\n    /**\n     * REQUIRED\n     * The resource owner \"username\".\n     */\n    var username: String? = null,\n\n    /**\n     * REQUIRED\n     * The resource owner \"password\".\n     */\n    var password: String? = null,\n\n    /**\n     * OPTIONAL\n     * The \"scope\" of the access request as described by here (https://tools.ietf.org/html/rfc6749#section-3.3).\n     */\n    var scope: String? = null\n) : OAuth2Grant {\n\n    companion object {\n        @JvmField\n        val GRANT_TYPE = \"password\"\n    }\n}\n"
  },
  {
    "path": "library/src/test/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenIsExpiredTest.kt",
    "content": "package de.rheinfabrik.heimdall2\n\nimport org.junit.Assert.assertEquals\nimport org.junit.Test\nimport java.util.*\n\nclass OAuth2AccessTokenIsExpiredTest {\n\n    @Test\n    fun `when the expirationDate is null, isExpired method should return false`() {\n        // given an accessToken\n        val accessToken = OAuth2AccessToken(expirationDate = null)\n\n        // when the isExpired method is called\n        val value = accessToken.isExpired()\n\n        // then false is returned\n        assertEquals(value, false)\n\n    }\n\n    @Test\n    fun `when the expirationDate is in the past, isExpired method should return false`() {\n        // given a date in the past\n        val pastCalendar = Calendar.getInstance()\n        pastCalendar.add(Calendar.YEAR, 1)\n\n        // and a token with a past date\n        val accessToken = OAuth2AccessToken(expirationDate = pastCalendar)\n\n        // when the isExpired method is called\n        val value = accessToken.isExpired()\n\n        // then true is returned\n        assertEquals(value, false)\n    }\n\n    @Test\n    fun `when the expirationDate is in the future, isExpired method should return false`() {\n        // given a date in the past\n        val futureDate = Calendar.getInstance()\n        futureDate.add(Calendar.YEAR, 1)\n\n        // and a token with a past date\n        val accessToken = OAuth2AccessToken(expirationDate = futureDate)\n\n        // when the isExpired method is called\n        val value = accessToken.isExpired()\n\n        // then true is returned\n        assertEquals(value, false)\n    }\n}\n"
  },
  {
    "path": "library/src/test/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenManagerGetValidAccessTokenTest.kt",
    "content": "package de.rheinfabrik.heimdall2\n\nimport com.nhaarman.mockitokotlin2.mock\nimport com.nhaarman.mockitokotlin2.verify\nimport com.nhaarman.mockitokotlin2.whenever\nimport de.rheinfabrik.heimdall2.grants.OAuth2RefreshAccessTokenGrant\nimport io.reactivex.Single\nimport org.junit.Test\nimport java.util.*\n\nclass OAuth2AccessTokenManagerGetValidAccessTokenTest {\n\n    @Test\n    fun `when subscribed to getValidAccessToken(), the non-expired token should be emitted`() {\n\n        // given a non expired token\n        val accessToken = mock<OAuth2AccessToken>().apply {\n            whenever(isExpired()).thenReturn(false)\n        }\n\n        // and a token manager with a valid storage and token\n        val storage = mock<OAuth2AccessTokenStorage>().apply {\n            whenever(getStoredAccessToken()).thenReturn(Single.just(accessToken))\n        }\n        val grant = mock<OAuth2RefreshAccessTokenGrant>().apply {\n            whenever(grantNewAccessToken()).thenReturn(Single.just(accessToken))\n        }\n        val tokenManager = OAuth2AccessTokenManager(\n            mStorage = storage\n        )\n\n        // when a valid access token is requested\n        val validTokenTest = tokenManager.getValidAccessToken(grant).test()\n\n        // then the non expired token gets received\n        validTokenTest.assertValue(accessToken)\n    }\n\n    @Test\n    fun `when the token expires, the refresh grant should be called to refresh it`(){\n        // given an expired accesstoken\n        val accessToken = mock<OAuth2AccessToken>().apply {\n            whenever(isExpired()).thenReturn(true)\n        }\n\n        // and a token manager\n        val tokenManager = OAuth2AccessTokenManager(\n            mStorage = mock<OAuth2AccessTokenStorage>().apply {\n                whenever(getStoredAccessToken()).thenReturn(Single.just(accessToken))\n            }\n        )\n\n        // and a OAuth2RefreshAccessTokenGrant\n        val grant = mock<OAuth2RefreshAccessTokenGrant>()\n\n        // when a valid token is needed\n        tokenManager.getValidAccessToken(grant).test()\n\n        // then a refresh grant asks for a new token\n        verify(grant).grantNewAccessToken()\n\n    }\n\n    @Test\n    fun `when the token expires, the grant should be updated with a new token`() {\n        // given an expired OAuthAccessToken\n        val pastDate = Calendar.getInstance()\n        pastDate.add(Calendar.YEAR, -1)\n\n        val accessToken = OAuth2AccessToken(\n            refreshToken = \"rt\",\n            expirationDate = pastDate\n        )\n\n        // and a token manager\n        val tokenManager = OAuth2AccessTokenManager(\n            mStorage = mock<OAuth2AccessTokenStorage>().apply {\n                whenever(getStoredAccessToken()).thenReturn(Single.just(accessToken))\n            }\n        )\n\n        // and a grant\n        val grant = mock<OAuth2RefreshAccessTokenGrant>().apply {\n            whenever(grantNewAccessToken()).thenReturn(Single.just(accessToken))\n        }\n\n        // when a valid access token is needed\n        val grantToken = tokenManager.getValidAccessToken(grant).test()\n\n        // then the grants new refreshToken gets updated\n        grantToken.assertValue{\n            it.refreshToken == accessToken.refreshToken\n        }\n\n    }\n}\n"
  },
  {
    "path": "library/src/test/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenManagerGrantNewAccessTokenTest.kt",
    "content": "package de.rheinfabrik.heimdall2\n\nimport com.nhaarman.mockitokotlin2.mock\nimport com.nhaarman.mockitokotlin2.verify\nimport com.nhaarman.mockitokotlin2.whenever\nimport de.rheinfabrik.heimdall2.grants.OAuth2Grant\nimport io.reactivex.Single\nimport org.junit.Test\nimport java.util.*\n\nclass OAuth2AccessTokenManagerGrantNewAccessTokenTest {\n\n    @Test\n    fun `when a new access token has an expiration date, the token manager should generate and set the correct expiration date`() {\n\n        // given an OAuth2AccessToken\n        val accessToken = OAuth2AccessToken(\n            expirationDate = null\n        )\n        val changedAccessToken = accessToken.copy(\n            expiresIn = 3\n        )\n\n\n        // and a grant that emits that token\n        val grant = mock<OAuth2Grant>().apply {\n            whenever(grantNewAccessToken()).thenReturn(Single.just(changedAccessToken))\n        }\n\n        // and a tokenManager\n        val tokenManager = OAuth2AccessTokenManager(\n            mStorage = mock()\n        )\n\n        // when a new access token is needed\n        val calendar = Calendar.getInstance()\n        val newToken = tokenManager.grantNewAccessToken(\n            grant = grant,\n            calendar = calendar\n        ).test()\n\n        // then the access token should have the correct expiration date\n        newToken.assertValue {\n            it.expirationDate?.timeInMillis == calendar.timeInMillis + 3000\n        }\n    }\n\n    @Test\n    fun `when a new access token doesn't have an expiration date, the token manager should not generate and a new  expiration date`() {\n\n        // given an OAuth2AccessToken\n        val accessToken = OAuth2AccessToken(\n            expirationDate = null\n        )\n        val changedAccessToken = accessToken.copy(\n            expiresIn = null\n        )\n\n        // and a grant that emits that token\n        val grant = mock<OAuth2Grant>().apply {\n            whenever(grantNewAccessToken()).thenReturn(Single.just(changedAccessToken))\n        }\n\n        // and a tokenManager\n        val tokenManager = OAuth2AccessTokenManager(\n            mStorage = mock()\n        )\n\n        // when a new access token is needed\n        val calendar = Calendar.getInstance()\n        val newToken = tokenManager.grantNewAccessToken(\n            grant = grant,\n            calendar = calendar\n        ).test()\n\n        // then the access token should have the correct expiration date\n        newToken.assertValue {\n            it.expirationDate == null\n        }\n    }\n\n    @Test\n    fun `when a new access token is needed, the storage should be called to store the token`() {\n        // given a grant that emits a token\n        val accessToken = OAuth2AccessToken()\n        val grant = mock<OAuth2Grant>().apply {\n            whenever(grantNewAccessToken()).thenReturn(Single.just(accessToken))\n        }\n\n        // and a token manager\n        val storage = mock<OAuth2AccessTokenStorage>()\n        val tokenManager = OAuth2AccessTokenManager(\n            mStorage = storage\n        )\n\n        // when a new access token is needed\n        tokenManager.grantNewAccessToken(grant).test()\n\n        // then the storage is called\n        verify(storage).storeAccessToken(accessToken)\n    }\n\n}\n"
  },
  {
    "path": "library/src/test/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenManagerTest.kt",
    "content": "package de.rheinfabrik.heimdall2\n\nimport com.nhaarman.mockitokotlin2.mock\nimport com.nhaarman.mockitokotlin2.whenever\nimport de.rheinfabrik.heimdall2.grants.OAuth2RefreshAccessTokenGrant\nimport io.reactivex.Single\nimport org.junit.Test\n\n\nclass OAuth2AccessTokenManagerTest {\n\n    @Test\n    fun `when the access token is accessed, the token manager should emit a non expired token`(){\n        // given a stored access token\n        val validToken = mock<OAuth2AccessToken>()\n        val accessTokenStorage = mock<OAuth2AccessTokenStorage>()\n        whenever(accessTokenStorage.getStoredAccessToken()).thenReturn(Single.just(validToken))\n\n        // and a refresh access token grant\n        val refreshAccessTokenGrant = mock<OAuth2RefreshAccessTokenGrant>()\n        whenever(refreshAccessTokenGrant.grantNewAccessToken()).thenReturn(Single.just(validToken))\n\n        // and a token manager\n        val tokenManager = OAuth2AccessTokenManager(accessTokenStorage)\n\n        // when the token is accessed\n        val getValidAccessTokenTest = tokenManager.getValidAccessToken(refreshAccessTokenGrant).test()\n\n        // then the token is received\n        getValidAccessTokenTest.assertValue(validToken)\n    }\n}"
  },
  {
    "path": "library/src/test/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenSerializationTest.kt",
    "content": "package de.rheinfabrik.heimdall2\n\nimport com.google.gson.Gson\nimport org.junit.Assert.assertEquals\nimport org.junit.Before\nimport org.junit.Test\nimport java.util.*\n\n\nclass OAuth2AccessTokenSerializationTest {\n\n    @Before\n    fun setup() {\n        // Set default locale and time zone\n        Locale.setDefault(Locale.GERMANY)\n        TimeZone.setDefault(TimeZone.getTimeZone(\"CEST\"))\n    }\n\n    @Test\n    fun `when an OAuth2AccessToken is transformed to a JSON string, it should be created`() {\n\n        // given an OAuth2AccessToken\n        val accessToken = OAuth2AccessToken(\n            refreshToken = \"rt\",\n            expiresIn = 3600,\n            accessToken = \"at\",\n            tokenType = \"bearer\"\n        )\n        val expirationDate = Calendar.getInstance()\n        expirationDate.timeInMillis = 0\n\n        // and an updated expiration date\n        val changedAccessToken = accessToken.copy(\n            expirationDate = expirationDate\n        )\n\n        // when it gets serialized with Gson\n        val json = Gson().toJson(changedAccessToken)\n\n        // then the json should be written correctly\n        assertEquals(\n            \"{\\\"token_type\\\":\\\"bearer\\\",\\\"access_token\\\":\\\"at\\\",\\\"refresh_token\\\":\\\"rt\\\",\\\"expires_in\\\":3600,\\\"heimdall_expiration_date\\\":{\\\"year\\\":1970,\\\"month\\\":0,\\\"dayOfMonth\\\":1,\\\"hourOfDay\\\":0,\\\"minute\\\":0,\\\"second\\\":0}}\",\n            json\n        )\n    }\n\n    @Test\n    fun `when a token is created from a JSON string, it should be created`() {\n\n        // given a json string representing a OAuth2AccessToken\n        val jsonString =\n            \"{\\\"access_token\\\":\\\"at\\\",\\\"heimdall_expiration_date\\\":{\\\"year\\\":1970,\\\"month\\\":0,\\\"dayOfMonth\\\":1,\\\"hourOfDay\\\":0,\\\"minute\\\":0,\\\"second\\\":0},\\\"expires_in\\\":3600,\\\"refresh_token\\\":\\\"rt\\\",\\\"token_type\\\":\\\"bearer\\\"}\"\n\n        // and an expiration date\n        val calendar = Calendar.getInstance()\n        calendar.timeInMillis = 0\n\n        // when it gets deserialized with Gson\n        val accessToken = Gson().fromJson(jsonString, OAuth2AccessToken::class.java)\n\n        // then the token should be the same\n        assertEquals(\"rt\", accessToken.refreshToken)\n        assertEquals(3600, accessToken.expiresIn)\n        assertEquals(\"at\", accessToken.accessToken)\n        assertEquals(\"bearer\", accessToken.tokenType)\n        assertEquals(calendar.timeInMillis, accessToken.expirationDate?.timeInMillis)\n    }\n}"
  },
  {
    "path": "library/src/test/java/de/rheinfabrik/heimdall2/grants/OAuth2AuthorizationCodeGrantTest.kt",
    "content": "package de.rheinfabrik.heimdall2.grants\n\nimport org.junit.Assert.assertEquals\nimport org.junit.Test\n\nclass OAuth2AuthorizationCodeGrantTest {\n\n    // Specifications for https://tools.ietf.org/html/rfc6749#section-4.1\n\n    @Test\n    fun `when an authorization code grant is created, the grant type is code`() {\n        // the grant type is the correct\n        assertEquals(OAuth2AuthorizationCodeGrant.GRANT_TYPE, \"authorization_code\")\n    }\n\n    @Test\n    fun `when an authorization code grant is created, the response type is code`() {\n        //  the grant type is the correct\n        assertEquals(OAuth2AuthorizationCodeGrant.RESPONSE_TYPE, \"code\")\n    }\n}\n"
  },
  {
    "path": "library/src/test/java/de/rheinfabrik/heimdall2/grants/OAuth2ClientCredentialsGrantTest.kt",
    "content": "package de.rheinfabrik.heimdall2.grants\n\nimport org.junit.Assert.assertEquals\nimport org.junit.Test\n\nclass OAuth2ClientCredentialsGrantTest {\n\n    // Specification for https://tools.ietf.org/html/rfc6749#section-4.4\n\n    @Test\n    fun `when a client credentials grant is created, the grant type is code`() {\n        // when an client credentials grant is created\n        val grant = OAuth2ClientCredentialsGrant\n\n        // then the grant type is the correct\n        assertEquals(grant.GRANT_TYPE, \"client_credentials\")\n    }\n}\n"
  },
  {
    "path": "library/src/test/java/de/rheinfabrik/heimdall2/grants/OAuth2ImplicitGrantTest.kt",
    "content": "package de.rheinfabrik.heimdall2.grants\n\nimport org.junit.Assert.assertEquals\nimport org.junit.Test\n\nclass OAuth2ImplicitGrantTest {\n\n    // Specifications for https://tools.ietf.org/html/rfc6749#section-4.2\n\n    @Test\n    fun `when an implicit grant is created, the response type is token`() {\n        // when an authorization code grant is created\n        val grant = OAuth2ImplicitGrant\n\n        // then the grant type is the correct\n        assertEquals(grant.RESPONSE_TYPE, \"token\")\n    }\n}\n"
  },
  {
    "path": "library/src/test/java/de/rheinfabrik/heimdall2/grants/OAuth2RefreshAccessTokenGrantTest.kt",
    "content": "package de.rheinfabrik.heimdall2.grants\n\nimport org.junit.Assert.assertEquals\nimport org.junit.Test\n\nclass OAuth2RefreshAccessTokenGrantTest {\n\n    // Specifications for https://tools.ietf.org/html/rfc6749#section-6\n\n    @Test\n    fun `when an refresh access token grant is created, the grant type is refresh_token`() {\n        // the grant type is the correct\n        assertEquals(OAuth2RefreshAccessTokenGrant.GRANT_TYPE, \"refresh_token\")\n    }\n}\n"
  },
  {
    "path": "library/src/test/java/de/rheinfabrik/heimdall2/grants/OAuth2ResourceOwnerPasswordCredentialsGrantTest.kt",
    "content": "package de.rheinfabrik.heimdall2.grants\n\nimport org.junit.Assert.assertEquals\nimport org.junit.Test\n\nclass OAuth2ResourceOwnerPasswordCredentialsGrantTest {\n\n    // Specifications for https://tools.ietf.org/html/rfc6749#section-4.3\n\n    @Test\n    fun `when a access token grant is created, the grant type is refresh_token`() {\n        // the grant type is the correct\n        assertEquals(OAuth2ResourceOwnerPasswordCredentialsGrant.GRANT_TYPE, \"password\")\n    }\n}\n"
  },
  {
    "path": "library/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker",
    "content": "mock-maker-inline"
  },
  {
    "path": "sample/build.gradle",
    "content": "apply plugin: 'com.android.application'\n\nbuildscript {\n    repositories {\n        mavenCentral()\n    }\n}\n\nandroid {\n    compileSdkVersion 30\n    buildToolsVersion \"30.0.2\"\n\n    defaultConfig {\n        applicationId \"de.rheinfabrik.heimdall\"\n        minSdkVersion 21\n        targetSdkVersion 30\n        versionCode 1\n        versionName \"1.0\"\n    }\n\n    // https://github.com/evant/gradle-retrolambda#android-studio-setup\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_1_8\n        targetCompatibility JavaVersion.VERSION_1_8\n    }\n}\n\ndependencies {\n\n    // Android\n    implementation 'androidx.appcompat:appcompat:1.2.0'\n    implementation 'androidx.recyclerview:recyclerview:1.1.0'\n    implementation 'androidx.cardview:cardview:1.0.0'\n\n    // Heimdall\n    implementation project(':library')\n\n    // Rx\n    implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'\n    implementation 'io.reactivex.rxjava2:rxjava:2.2.19'\n    implementation 'com.trello.rxlifecycle3:rxlifecycle-components:3.0.0'\n\n    // Serialization\n    implementation 'com.google.code.gson:gson:2.8.6'\n    implementation 'org.parceler:parceler-api:0.2.16'\n\n    // Network\n    implementation 'com.squareup.retrofit:retrofit:1.9.0'\n\n    // Butterknife\n    annotationProcessor 'com.jakewharton:butterknife-compiler:8.2.1'\n    implementation 'com.jakewharton:butterknife:8.2.1'\n}\n"
  },
  {
    "path": "sample/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /Users/gilo/Library/Android/sdk/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n"
  },
  {
    "path": "sample/src/main/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    package=\"de.rheinfabrik.heimdalldroid\">\n\n    <!-- Permissions -->\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n\n    <!-- Application -->\n    <application\n        android:allowBackup=\"true\"\n        android:appComponentFactory=\"whateverString\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:theme=\"@style/AppThemeNoActionBar\"\n        tools:replace=\"android:appComponentFactory\">\n\n        <!-- Main Activity -->\n        <activity\n            android:name=\"de.rheinfabrik.heimdalldroid.actvities.MainActivity\"\n            android:screenOrientation=\"portrait\">\n\n            <!--Intent filter -->\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n\n        </activity>\n\n        <!-- Login Activity -->\n        <activity\n            android:name=\"de.rheinfabrik.heimdalldroid.actvities.LoginActivity\"\n            android:screenOrientation=\"portrait\" />\n\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "sample/src/main/java/de/rheinfabrik/heimdalldroid/TraktTvAPIConfiguration.java",
    "content": "package de.rheinfabrik.heimdalldroid;\n\npublic class TraktTvAPIConfiguration {\n    public static final String CLIENT_ID = \"enter_your_client_id_here\";\n    public static final String CLIENT_SECRET = \"enter_your_client_secret_here\";\n    public static final String REDIRECT_URI = \"enter_your_redirect_uri_here\";\n}\n"
  },
  {
    "path": "sample/src/main/java/de/rheinfabrik/heimdalldroid/actvities/LoginActivity.java",
    "content": "package de.rheinfabrik.heimdalldroid.actvities;\n\nimport android.graphics.Bitmap;\nimport android.os.Bundle;\nimport android.view.View;\nimport android.webkit.WebView;\nimport android.webkit.WebViewClient;\n\nimport butterknife.BindView;\n\nimport com.trello.rxlifecycle3.components.support.RxAppCompatActivity;\nimport io.reactivex.android.schedulers.AndroidSchedulers;\nimport java.net.MalformedURLException;\nimport java.net.URL;\n\nimport butterknife.ButterKnife;\nimport de.rheinfabrik.heimdalldroid.R;\nimport de.rheinfabrik.heimdalldroid.network.oauth2.TraktTvAuthorizationCodeGrant;\nimport de.rheinfabrik.heimdalldroid.network.oauth2.TraktTvOauth2AccessTokenManager;\nimport de.rheinfabrik.heimdalldroid.utils.AlertDialogFactory;\nimport java.util.Calendar;\n\n/**\n * Activity used to let the user login with his GitHub credentials.\n * You may want to move most of this code to your presenter class or view model.\n * For the sake of simplicity the code is inside the activity.\n */\npublic class LoginActivity extends RxAppCompatActivity {\n\n    // Members\n\n    @BindView(R.id.webView)\n    protected WebView mWebView;\n\n    // Activity lifecycle\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        // Set content view\n        setContentView(R.layout.activity_login);\n\n        // Inject views\n        ButterKnife.bind(this);\n\n        // Start authorization\n        authorize();\n    }\n\n    // Private Api\n\n    private void authorize() {\n\n        // Grab a new token manger\n        TraktTvOauth2AccessTokenManager tokenManager = TraktTvOauth2AccessTokenManager.from(this);\n\n        // Grab a new grant\n        TraktTvAuthorizationCodeGrant grant = tokenManager.newAuthorizationCodeGrant();\n\n        // Listen for the authorization url and load it once needed\n        grant.authorizationUri()\n                .map(URL::toString)\n                .compose(bindToLifecycle())\n                .observeOn(AndroidSchedulers.mainThread())\n                .subscribe(mWebView::loadUrl);\n\n        // Sent loaded website to grant so it can check if we have an access token\n        mWebView.setWebViewClient(new WebViewClient() {\n\n            @Override\n            public void onPageStarted(WebView view, String urlString, Bitmap favicon) {\n                super.onPageStarted(view, urlString, favicon);\n\n                 try {\n                     URL url = new URL(urlString);\n                     grant.getOnUrlLoadedCommand().onNext(url);\n\n                     // Hide redirect page from user\n                     if (urlString.startsWith(grant.getRedirectUri())) {\n                         mWebView.setVisibility(View.GONE);\n                     }\n                 } catch (MalformedURLException ignored) {\n                    // Empty\n\n                }\n            }\n        });\n\n        // Start authorization and listen for success\n        tokenManager.grantNewAccessToken(grant, Calendar.getInstance())\n                .toObservable()\n                .compose(bindToLifecycle())\n                .observeOn(AndroidSchedulers.mainThread())\n                .subscribe(x -> handleSuccess(), x -> handleError());\n    }\n\n    // Set the result to ok and finish this activity\n    private void handleSuccess() {\n        setResult(RESULT_OK);\n        finish();\n    }\n\n    // Build an error dialog and show it\n    private void handleError() {\n        AlertDialogFactory.errorAlertDialog(this).show();\n    }\n}\n"
  },
  {
    "path": "sample/src/main/java/de/rheinfabrik/heimdalldroid/actvities/MainActivity.java",
    "content": "package de.rheinfabrik.heimdalldroid.actvities;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.view.Menu;\nimport android.view.MenuInflater;\nimport android.view.MenuItem;\nimport android.webkit.CookieManager;\n\nimport android.widget.Toolbar;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\nimport androidx.swiperefreshlayout.widget.SwipeRefreshLayout;\nimport com.trello.rxlifecycle3.components.support.RxAppCompatActivity;\nimport io.reactivex.Observable;\nimport io.reactivex.android.schedulers.AndroidSchedulers;\nimport java.util.List;\n\nimport butterknife.ButterKnife;\nimport butterknife.BindView;\nimport de.rheinfabrik.heimdalldroid.R;\nimport de.rheinfabrik.heimdalldroid.adapter.TraktTvListsRecyclerViewAdapter;\nimport de.rheinfabrik.heimdalldroid.network.TraktTvApiFactory;\nimport de.rheinfabrik.heimdalldroid.network.models.TraktTvList;\nimport de.rheinfabrik.heimdalldroid.network.oauth2.TraktTvOauth2AccessTokenManager;\nimport de.rheinfabrik.heimdalldroid.utils.AlertDialogFactory;\nimport de.rheinfabrik.heimdalldroid.utils.IntentFactory;\nimport retrofit.RetrofitError;\n\n/**\n * Activity showing either the list of the user's repositories or the login screen.\n * You may want to move most of this code to your presenter class or view model.\n * For the sake of simplicity the code is inside the activity.\n */\npublic class MainActivity extends RxAppCompatActivity {\n\n    // Constants\n\n    private static final int AUTHORIZATION_REQUEST_CODE = 1;\n\n    // Members\n\n    @BindView(R.id.recyclerView)\n    protected RecyclerView mRecyclerView;\n\n    @BindView(R.id.toolbar)\n    protected Toolbar mToolbar;\n\n    @BindView(R.id.swipeRefreshLayout)\n    protected SwipeRefreshLayout mSwipeRefreshLayout;\n\n    private TraktTvOauth2AccessTokenManager mTokenManager;\n\n    // Activity lifecycle\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        // Set content view\n        setContentView(R.layout.activity_main);\n\n        // Inject views\n        ButterKnife.bind(this);\n\n        // Setup toolbar\n        setActionBar(mToolbar);\n\n        // Setup swipe refresh layout\n        mSwipeRefreshLayout.setOnRefreshListener(MainActivity.this::refresh);\n\n        // Setup recycler view\n        LinearLayoutManager layoutManager = new LinearLayoutManager(this);\n        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);\n        mRecyclerView.setLayoutManager(layoutManager);\n\n        // Grab a new manager\n        mTokenManager = TraktTvOauth2AccessTokenManager.from(this);\n\n        // Check if we are logged in\n        refresh();\n    }\n\n    @Override\n    protected void onActivityResult(int requestCode, int resultCode, Intent data) {\n        super.onActivityResult(requestCode, resultCode, data);\n\n        // Check if the request code is correct\n        if (requestCode == AUTHORIZATION_REQUEST_CODE) {\n\n            // Check if login was successful\n            if (resultCode == Activity.RESULT_OK) {\n                loadLists();\n            }\n        }\n    }\n\n    @Override\n    public boolean onCreateOptionsMenu(Menu menu) {\n        MenuInflater inflater = getMenuInflater();\n        inflater.inflate(R.menu.menu_main, menu);\n\n        return true;\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(MenuItem item) {\n        if (item.getItemId() == R.id.logout) {\n            logout();\n        }\n\n        return super.onOptionsItemSelected(item);\n    }\n\n    // Private Api\n\n    private void loadLists() {\n\n        // Load the lists\n        Observable<List<TraktTvList>> listsObservable = mTokenManager\n\n                 /* Grab a valid access token (automatically refreshes the token if it is expired) */\n                .getValidAccessToken()\n\n                 /* Load lists */\n                .flatMapObservable(authorizationHeader -> TraktTvApiFactory.newApiService().getLists(authorizationHeader));\n\n        // Bind to lifecycle\n        listsObservable\n                .compose(bindToLifecycle())\n                .observeOn(AndroidSchedulers.mainThread())\n                .subscribe(lists -> {\n                    if (lists == null || lists.isEmpty()) {\n                        handleEmptyList();\n                    } else {\n                        handleSuccess(lists);\n                    }\n                }, this::handleError);\n    }\n\n    // Start the LoginActivity.\n    private void showLogin() {\n        mSwipeRefreshLayout.setRefreshing(false);\n\n        Intent loginIntent = IntentFactory.loginIntent(this);\n        startActivityForResult(loginIntent, AUTHORIZATION_REQUEST_CODE);\n    }\n\n    // Shows a dialog saying that there were no lists.\n    private void handleEmptyList() {\n        mSwipeRefreshLayout.setRefreshing(false);\n\n        AlertDialogFactory.noListsFoundDialog(this).show();\n    }\n\n    // Show an error dialog\n    private void handleError(Throwable error) {\n        mSwipeRefreshLayout.setRefreshing(false);\n\n        // Clear token and login if 401\n        if (error instanceof RetrofitError) {\n            RetrofitError retrofitError = (RetrofitError) error;\n            if (retrofitError.getResponse().getStatus() == 401) {\n                mTokenManager.getStorage().removeAccessToken();\n\n                refresh();\n            }\n        } else {\n            AlertDialogFactory.errorAlertDialog(this).show();\n        }\n    }\n\n    // Update our recycler view\n    private void handleSuccess(List<TraktTvList> traktTvLists) {\n        mSwipeRefreshLayout.setRefreshing(false);\n\n        mRecyclerView.setAdapter(new TraktTvListsRecyclerViewAdapter(traktTvLists));\n    }\n\n    // Check if logged in and show either login or load lists\n    private void refresh() {\n        mSwipeRefreshLayout.setRefreshing(true);\n\n        mTokenManager.getStorage().hasAccessToken()\n                .toObservable()\n                .compose(bindToLifecycle())\n                .observeOn(AndroidSchedulers.mainThread())\n                .subscribe(loggedIn -> {\n                    if (loggedIn) {\n                        loadLists();\n                    } else {\n                        showLogin();\n                    }\n                });\n    }\n\n    // Logs out the user\n    private void logout() {\n\n        // Ask token manager to revoke the token\n        mTokenManager.logout()\n                .toObservable()\n                .compose(bindToLifecycle())\n                .subscribe(x -> showLogin());\n\n        // Clear webview cache\n        CookieManager cookieManager = CookieManager.getInstance();\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            cookieManager.removeAllCookies(null);\n        } else {\n            cookieManager.removeAllCookie();\n        }\n    }\n}\n"
  },
  {
    "path": "sample/src/main/java/de/rheinfabrik/heimdalldroid/adapter/TraktTvListsRecyclerViewAdapter.java",
    "content": "package de.rheinfabrik.heimdalldroid.adapter;\n\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport androidx.recyclerview.widget.RecyclerView;\nimport java.util.List;\n\nimport de.rheinfabrik.heimdalldroid.R;\nimport de.rheinfabrik.heimdalldroid.adapter.viewholder.TraktTvListViewHolder;\nimport de.rheinfabrik.heimdalldroid.network.models.TraktTvList;\n\n/**\n * Simple adapter for showing a list of liked lists.\n */\npublic class TraktTvListsRecyclerViewAdapter extends RecyclerView.Adapter<TraktTvListViewHolder> {\n\n    // Members\n\n    private final List<TraktTvList> mTraktTvLists;\n\n    // Constructor\n\n    public TraktTvListsRecyclerViewAdapter(List<TraktTvList> traktTvLists) {\n        super();\n\n        mTraktTvLists = traktTvLists;\n    }\n\n    // RecyclerView.Adapter\n\n    @Override\n    public TraktTvListViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {\n        View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_view_trakt_tv_list, parent, false);\n        return new TraktTvListViewHolder(itemView);\n    }\n\n    @Override\n    public void onBindViewHolder(TraktTvListViewHolder holder, int position) {\n        holder.bind(mTraktTvLists.get(position));\n    }\n\n    @Override\n    public int getItemCount() {\n        if (mTraktTvLists != null) {\n            return mTraktTvLists.size();\n        }\n\n        return 0;\n    }\n}\n"
  },
  {
    "path": "sample/src/main/java/de/rheinfabrik/heimdalldroid/adapter/viewholder/TraktTvListViewHolder.java",
    "content": "package de.rheinfabrik.heimdalldroid.adapter.viewholder;\n\nimport android.view.View;\nimport android.widget.TextView;\n\nimport androidx.recyclerview.widget.RecyclerView;\nimport butterknife.ButterKnife;\nimport butterknife.BindView;\nimport de.rheinfabrik.heimdalldroid.R;\nimport de.rheinfabrik.heimdalldroid.network.models.TraktTvList;\n\n/**\n * View holder used to display a liked list.\n */\npublic class TraktTvListViewHolder extends RecyclerView.ViewHolder {\n\n    // Members\n\n    @BindView(R.id.titleTextView)\n    protected TextView mTitleTextView;\n\n    @BindView(R.id.descriptionTextView)\n    protected TextView mDescriptionTextView;\n\n    @BindView(R.id.likeCountTextView)\n    protected TextView mLikeCountTextView;\n\n    // Constructor\n\n    public TraktTvListViewHolder(View itemView) {\n        super(itemView);\n\n        // Inject views\n        ButterKnife.bind(this, itemView);\n    }\n\n    // Public Api\n\n    public void bind(TraktTvList traktTvList) {\n\n        // Set title\n        mTitleTextView.setText(traktTvList.name);\n\n        // Set description\n        mDescriptionTextView.setText(traktTvList.description);\n\n        // Set like count\n        mLikeCountTextView.setText(traktTvList.numberOfLikes + itemView.getContext().getString(R.string.likes_postfix));\n    }\n}\n"
  },
  {
    "path": "sample/src/main/java/de/rheinfabrik/heimdalldroid/network/TraktTvApiFactory.java",
    "content": "package de.rheinfabrik.heimdalldroid.network;\n\nimport com.google.gson.Gson;\n\nimport de.rheinfabrik.heimdalldroid.TraktTvAPIConfiguration;\nimport retrofit.RestAdapter;\nimport retrofit.converter.GsonConverter;\n\n/**\n * Factory class to generate new TraktTvApiService instances.\n */\npublic class TraktTvApiFactory {\n\n    // Constants\n\n    private static final String API_ENDPOINT = \"https://api-v2launch.trakt.tv\";\n\n    // Public Api\n\n    /**\n     * Creates a new preconfigured TraktTvApiService instance.\n     */\n    public static TraktTvApiService newApiService() {\n\n        // Set up rest adapter\n        RestAdapter.Builder restAdapterBuilder = new RestAdapter.Builder()\n                .setConverter(new GsonConverter(new Gson()))\n                .setRequestInterceptor(request -> {\n                    request.addHeader(\"Content-type\", \"application/json\");\n                    request.addHeader(\"trakt-api-version\", \"2\");\n                    request.addHeader(\"trakt-api-key\", TraktTvAPIConfiguration.CLIENT_ID);\n                })\n                .setLogLevel(RestAdapter.LogLevel.FULL)\n                .setEndpoint(API_ENDPOINT);\n\n        // Build raw api service\n        return restAdapterBuilder.build().create(TraktTvApiService.class);\n    }\n\n}\n"
  },
  {
    "path": "sample/src/main/java/de/rheinfabrik/heimdalldroid/network/TraktTvApiService.java",
    "content": "package de.rheinfabrik.heimdalldroid.network;\n\nimport io.reactivex.Observable;\nimport java.util.List;\n\nimport de.rheinfabrik.heimdall2.OAuth2AccessToken;\nimport de.rheinfabrik.heimdalldroid.network.models.AccessTokenRequestBody;\nimport de.rheinfabrik.heimdalldroid.network.models.RefreshTokenRequestBody;\nimport de.rheinfabrik.heimdalldroid.network.models.RevokeAccessTokenBody;\nimport de.rheinfabrik.heimdalldroid.network.models.TraktTvList;\nimport retrofit.http.Body;\nimport retrofit.http.GET;\nimport retrofit.http.Header;\nimport retrofit.http.POST;\n\n/**\n * Interface for communicating to the TraktTv API (http://docs.trakt.apiary.io/#).\n */\npublic interface TraktTvApiService {\n\n    // POST\n\n    @POST(\"/oauth/token\")\n    Observable<OAuth2AccessToken> grantNewAccessToken(@Body AccessTokenRequestBody body);\n\n    @POST(\"/oauth/token\")\n    Observable<OAuth2AccessToken> refreshAccessToken(@Body RefreshTokenRequestBody body);\n\n    @POST(\"/oauth/revoke\")\n    Observable<Void> revokeAccessToken(@Body RevokeAccessTokenBody body);\n\n    // GET\n\n    @GET(\"/users/me/lists\")\n    Observable<List<TraktTvList>> getLists(@Header(\"Authorization\") String authorizationHeader);\n}\n"
  },
  {
    "path": "sample/src/main/java/de/rheinfabrik/heimdalldroid/network/models/AccessTokenRequestBody.java",
    "content": "package de.rheinfabrik.heimdalldroid.network.models;\n\nimport com.google.gson.annotations.SerializedName;\n\nimport java.io.Serializable;\n\n/**\n * Body object used to exchange a code with an access token.\n */\npublic class AccessTokenRequestBody implements Serializable {\n\n    // Properties\n\n    @SerializedName(\"code\")\n    public String code;\n\n    @SerializedName(\"client_id\")\n    public String clientId;\n\n    @SerializedName(\"client_secret\")\n    public String clientSecret;\n\n    @SerializedName(\"redirect_uri\")\n    public String redirectUri;\n\n    @SerializedName(\"grant_type\")\n    public String grantType;\n\n    // Constructor\n\n    public AccessTokenRequestBody(String code, String clientId, String redirectUri, String clientSecret, String grantType) {\n        super();\n\n        this.code = code;\n        this.clientId = clientId;\n        this.redirectUri = redirectUri;\n        this.clientSecret = clientSecret;\n        this.grantType = grantType;\n    }\n}\n"
  },
  {
    "path": "sample/src/main/java/de/rheinfabrik/heimdalldroid/network/models/RefreshTokenRequestBody.java",
    "content": "package de.rheinfabrik.heimdalldroid.network.models;\n\nimport com.google.gson.annotations.SerializedName;\n\nimport java.io.Serializable;\n\n/**\n * Body used to refresh an access token.\n */\npublic class RefreshTokenRequestBody implements Serializable {\n\n    // Properties\n\n    @SerializedName(\"refresh_token\")\n    public String refreshToken;\n\n    @SerializedName(\"client_id\")\n    public String clientId;\n\n    @SerializedName(\"client_secret\")\n    public String clientSecret;\n\n    @SerializedName(\"redirect_uri\")\n    public String redirectUri;\n\n    @SerializedName(\"grant_type\")\n    public String grantType;\n\n    // Constructor\n\n    public RefreshTokenRequestBody(String refreshToken, String clientId, String clientSecret, String redirectUri, String grantType) {\n        super();\n\n        this.refreshToken = refreshToken;\n        this.clientId = clientId;\n        this.clientSecret = clientSecret;\n        this.redirectUri = redirectUri;\n        this.grantType = grantType;\n    }\n}\n"
  },
  {
    "path": "sample/src/main/java/de/rheinfabrik/heimdalldroid/network/models/RevokeAccessTokenBody.java",
    "content": "package de.rheinfabrik.heimdalldroid.network.models;\n\nimport com.google.gson.annotations.SerializedName;\n\n/**\n * Body used to revoke an access token.\n */\npublic class RevokeAccessTokenBody {\n\n    // Properties\n\n    @SerializedName(\"access_token\")\n    public String accessToken;\n\n    // Constructor\n\n    public RevokeAccessTokenBody(String accessToken) {\n        super();\n\n        this.accessToken = accessToken;\n    }\n}\n"
  },
  {
    "path": "sample/src/main/java/de/rheinfabrik/heimdalldroid/network/models/TraktTvList.java",
    "content": "package de.rheinfabrik.heimdalldroid.network.models;\n\nimport com.google.gson.annotations.SerializedName;\n\nimport java.io.Serializable;\n\n/**\n * Model describing a TraktTvList.\n */\npublic class TraktTvList implements Serializable {\n\n    // Properties\n\n    @SerializedName(\"name\")\n    public String name;\n\n    @SerializedName(\"description\")\n    public String description;\n\n    @SerializedName(\"likes\")\n    public int numberOfLikes;\n}\n"
  },
  {
    "path": "sample/src/main/java/de/rheinfabrik/heimdalldroid/network/oauth2/TraktTvAuthorizationCodeGrant.java",
    "content": "package de.rheinfabrik.heimdalldroid.network.oauth2;\n\nimport android.net.Uri;\n\nimport de.rheinfabrik.heimdall2.grants.OAuth2AuthorizationCodeGrant;\nimport io.reactivex.Observable;\nimport java.net.URL;\n\nimport de.rheinfabrik.heimdall2.OAuth2AccessToken;\nimport de.rheinfabrik.heimdalldroid.network.TraktTvApiFactory;\nimport de.rheinfabrik.heimdalldroid.network.models.AccessTokenRequestBody;\n\n/**\n * TraktTv authorization code grant as described in http://docs.trakt.apiary.io/#reference/authentication-oauth.\n */\npublic class TraktTvAuthorizationCodeGrant extends OAuth2AuthorizationCodeGrant {\n\n    // Properties\n\n    public String clientSecret;\n\n    // OAuth2AuthorizationCodeGrant\n\n    @Override\n    public URL buildAuthorizationUrl() {\n        try {\n            return new URL(\n                    Uri.parse(\"https://trakt.tv/oauth/authorize\")\n                            .buildUpon()\n                            .appendQueryParameter(\"client_id\", getClientId())\n                            .appendQueryParameter(\"redirect_uri\", getRedirectUri())\n                            .appendQueryParameter(\"response_type\", OAuth2AuthorizationCodeGrant.RESPONSE_TYPE)\n                            .build()\n                            .toString()\n            );\n        } catch (Exception ignored) {\n            return null;\n        }\n    }\n\n    @Override\n    public Observable<OAuth2AccessToken> exchangeTokenUsingCode(String code) {\n        AccessTokenRequestBody body = new AccessTokenRequestBody(\n                code, getClientId(), getRedirectUri(), clientSecret, GRANT_TYPE\n        );\n        return TraktTvApiFactory.newApiService().grantNewAccessToken(body);\n    }\n}\n"
  },
  {
    "path": "sample/src/main/java/de/rheinfabrik/heimdalldroid/network/oauth2/TraktTvOauth2AccessTokenManager.java",
    "content": "package de.rheinfabrik.heimdalldroid.network.oauth2;\n\nimport android.content.Context;\nimport android.content.SharedPreferences;\n\nimport de.rheinfabrik.heimdall2.OAuth2AccessToken;\nimport de.rheinfabrik.heimdall2.OAuth2AccessTokenManager;\nimport de.rheinfabrik.heimdall2.OAuth2AccessTokenStorage;\nimport de.rheinfabrik.heimdalldroid.TraktTvAPIConfiguration;\nimport de.rheinfabrik.heimdalldroid.network.TraktTvApiFactory;\nimport de.rheinfabrik.heimdalldroid.network.models.RevokeAccessTokenBody;\nimport de.rheinfabrik.heimdalldroid.utils.SharedPreferencesOAuth2AccessTokenStorage;\nimport io.reactivex.Single;\n\n/**\n * Token manger used to handle all your access token needs with the TraktTv API (http://docs.trakt.apiary.io/#).\n */\npublic final class TraktTvOauth2AccessTokenManager extends OAuth2AccessTokenManager {\n\n    // Factory methods\n\n    /**\n     * Creates a new preconfigured TraktTvOauth2AccessTokenManager based of a context.\n     */\n    public static TraktTvOauth2AccessTokenManager from(Context context) {\n\n        // Define the shared preferences where we will save the access token\n        SharedPreferences sharedPreferences = context.getSharedPreferences(\"TraktTvAccessTokenStorage\", Context.MODE_PRIVATE);\n\n        // Define the storage using the the previously defined preferences\n        SharedPreferencesOAuth2AccessTokenStorage<OAuth2AccessToken> tokenStorage = new SharedPreferencesOAuth2AccessTokenStorage<>(sharedPreferences, OAuth2AccessToken.class);\n\n        // Create the new TraktTvOauth2AccessTokenManager\n        return new TraktTvOauth2AccessTokenManager(tokenStorage);\n    }\n\n    // Constructor\n\n    public TraktTvOauth2AccessTokenManager(OAuth2AccessTokenStorage storage) {\n        super(storage);\n    }\n\n    // Public Api\n\n    /**\n     * Creates a new preconfigured TraktTvAuthorizationCodeGrant.\n     */\n    public TraktTvAuthorizationCodeGrant newAuthorizationCodeGrant() {\n        TraktTvAuthorizationCodeGrant grant = new TraktTvAuthorizationCodeGrant();\n        grant.setClientId(TraktTvAPIConfiguration.CLIENT_ID);\n        grant.clientSecret = TraktTvAPIConfiguration.CLIENT_SECRET;\n        grant.setRedirectUri(TraktTvAPIConfiguration.REDIRECT_URI);\n\n        return grant;\n    }\n\n    /**\n     * Returns a valid authorization header string using a preconfigured TraktTvRefreshAccessTokenGrant.\n     */\n    public Single<String> getValidAccessToken() {\n        TraktTvRefreshAccessTokenGrant grant = new TraktTvRefreshAccessTokenGrant();\n        grant.clientId = TraktTvAPIConfiguration.CLIENT_ID;\n        grant.clientSecret = TraktTvAPIConfiguration.CLIENT_SECRET;\n        grant.redirectUri = TraktTvAPIConfiguration.REDIRECT_URI;\n\n        return super.getValidAccessToken(grant).map(token -> token.getTokenType() + \" \" + token.getAccessToken());\n    }\n\n    /**\n     * Logs out the user if he is logged in.\n     */\n    public Single<Void> logout() {\n        return getStorage().getStoredAccessToken()\n                .toObservable()\n                .filter(token -> token != null)\n                .concatMap(accessToken -> {\n                    RevokeAccessTokenBody body = new RevokeAccessTokenBody(accessToken.getAccessToken());\n                    return TraktTvApiFactory.newApiService().revokeAccessToken(body);\n                })\n                .doOnNext(x -> getStorage().removeAccessToken()).singleOrError();\n    }\n}\n"
  },
  {
    "path": "sample/src/main/java/de/rheinfabrik/heimdalldroid/network/oauth2/TraktTvRefreshAccessTokenGrant.java",
    "content": "package de.rheinfabrik.heimdalldroid.network.oauth2;\n\nimport de.rheinfabrik.heimdall2.OAuth2AccessToken;\nimport de.rheinfabrik.heimdall2.grants.OAuth2RefreshAccessTokenGrant;\nimport de.rheinfabrik.heimdalldroid.network.TraktTvApiFactory;\nimport de.rheinfabrik.heimdalldroid.network.models.RefreshTokenRequestBody;\nimport io.reactivex.Single;\n\n/**\n * TraktTv refresh token grant as described in http://docs.trakt.apiary.io/#reference/authentication-oauth/token/exchange-refresh_token-for-access_token.\n */\npublic class TraktTvRefreshAccessTokenGrant extends OAuth2RefreshAccessTokenGrant {\n\n    // Properties\n\n    public String clientSecret;\n    public String clientId;\n    public String redirectUri;\n\n    // OAuth2RefreshAccessTokenGrant\n\n    @Override\n    public Single<OAuth2AccessToken> grantNewAccessToken() {\n        RefreshTokenRequestBody body = new RefreshTokenRequestBody(getRefreshToken(), clientId, clientSecret, redirectUri, GRANT_TYPE);\n        return TraktTvApiFactory.newApiService().refreshAccessToken(body).singleOrError();\n    }\n}\n"
  },
  {
    "path": "sample/src/main/java/de/rheinfabrik/heimdalldroid/utils/AlertDialogFactory.java",
    "content": "package de.rheinfabrik.heimdalldroid.utils;\n\nimport android.app.AlertDialog;\nimport android.content.Context;\n\nimport de.rheinfabrik.heimdalldroid.R;\n\n/**\n * Factory used to create AlertDialogs.\n */\npublic class AlertDialogFactory {\n\n    // Public Api\n\n    /**\n     * Creates an error dialog.\n     */\n    public static AlertDialog errorAlertDialog(Context context) {\n        return new AlertDialog.Builder(context)\n                .setTitle(R.string.error_title)\n                .setMessage(R.string.error_message)\n                .setPositiveButton(R.string.ok, (dialog, which) -> dialog.dismiss()).create();\n    }\n\n    /**\n     * Creates an dialog informing the user that there are no liked lists found.\n     */\n    public static AlertDialog noListsFoundDialog(Context context) {\n        return new AlertDialog.Builder(context)\n                .setTitle(R.string.error_title)\n                .setMessage(R.string.no_liked_lists_message)\n                .setPositiveButton(R.string.ok, (dialog, which) -> dialog.dismiss()).create();\n    }\n\n}\n"
  },
  {
    "path": "sample/src/main/java/de/rheinfabrik/heimdalldroid/utils/IntentFactory.java",
    "content": "package de.rheinfabrik.heimdalldroid.utils;\n\nimport android.content.Context;\nimport android.content.Intent;\n\nimport de.rheinfabrik.heimdalldroid.actvities.LoginActivity;\n\n/**\n * Factory used to create Intents.\n */\npublic class IntentFactory {\n\n    // Public Api\n\n    /**\n     * Creates an intent for showing the LoginActivity\n     */\n    public static Intent loginIntent(Context context) {\n        return new Intent(context, LoginActivity.class);\n    }\n}\n"
  },
  {
    "path": "sample/src/main/java/de/rheinfabrik/heimdalldroid/utils/SharedPreferencesOAuth2AccessTokenStorage.java",
    "content": "package de.rheinfabrik.heimdalldroid.utils;\n\nimport android.content.SharedPreferences;\n\nimport com.google.gson.Gson;\n\nimport de.rheinfabrik.heimdall2.OAuth2AccessToken;\nimport de.rheinfabrik.heimdall2.OAuth2AccessTokenStorage;\nimport io.reactivex.Single;\n\n/**\n * A simple storage that saves the access token as plain text in the passed shared preferences.\n * It is recommend to set the access mode to MODE_PRIVATE.\n *\n * @param <TAccessToken> The access token type.\n */\npublic class SharedPreferencesOAuth2AccessTokenStorage<TAccessToken extends OAuth2AccessToken> implements OAuth2AccessTokenStorage {\n\n    // Constants\n\n    private static final String ACCESS_TOKEN_PREFERENCES_KEY = \"OAuth2AccessToken\";\n\n    // Members\n\n    private final SharedPreferences mSharedPreferences;\n    private final Class mTokenClass;\n\n    // Constructor\n\n    /**\n     * Designated constructor.\n     *\n     * @param sharedPreferences The shared preferences used for saving the access token.\n     * @param tokenClass        The actual class of the access token.\n     */\n    public SharedPreferencesOAuth2AccessTokenStorage(SharedPreferences sharedPreferences, Class tokenClass) {\n        super();\n\n        if (tokenClass == null) {\n            throw new RuntimeException(\"TokenClass MUST NOT be null.\");\n        }\n\n        if (sharedPreferences == null) {\n            throw new RuntimeException(\"SharedPreferences MUST NOT be null.\");\n        }\n\n        mTokenClass = tokenClass;\n        mSharedPreferences = sharedPreferences;\n    }\n\n    // OAuth2AccessTokenStorage\n\n    @Override\n    public Single<OAuth2AccessToken> getStoredAccessToken() {\n        return Single\n                .just(mSharedPreferences.getString(ACCESS_TOKEN_PREFERENCES_KEY, null))\n                .map(json -> (TAccessToken) new Gson().fromJson(json, mTokenClass));\n    }\n\n    @Override\n    public void storeAccessToken(OAuth2AccessToken token) {\n        mSharedPreferences\n                .edit()\n                .putString(ACCESS_TOKEN_PREFERENCES_KEY, new Gson().toJson(token))\n                .apply();\n    }\n\n    @Override\n    public Single<Boolean> hasAccessToken() {\n        return Single.just(mSharedPreferences.contains(ACCESS_TOKEN_PREFERENCES_KEY));\n    }\n\n    @Override\n    public void removeAccessToken() {\n        mSharedPreferences\n                .edit()\n                .remove(ACCESS_TOKEN_PREFERENCES_KEY)\n                .apply();\n    }\n}\n"
  },
  {
    "path": "sample/src/main/res/layout/activity_login.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<!-- WebView -->\n<WebView android:id=\"@+id/webView\"\n         xmlns:android=\"http://schemas.android.com/apk/res/android\"\n         android:layout_width=\"match_parent\"\n         android:layout_height=\"match_parent\"/>\n"
  },
  {
    "path": "sample/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <!-- Toolbar -->\n    <android.widget.Toolbar\n        android:id=\"@+id/toolbar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"?attr/actionBarSize\"\n        android:background=\"@color/red_orange\"\n        android:elevation=\"1dp\"\n        android:theme=\"@style/AppThemeNoActionBar\" />\n\n    <!-- Swipe refresh layout -->\n    <androidx.swiperefreshlayout.widget.SwipeRefreshLayout\n        android:id=\"@+id/swipeRefreshLayout\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n\n        <!-- RecyclerView -->\n        <androidx.recyclerview.widget.RecyclerView\n            android:id=\"@+id/recyclerView\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:background=\"@color/hint_of_red\" />\n\n    </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>\n\n</LinearLayout>\n"
  },
  {
    "path": "sample/src/main/res/layout/item_view_trakt_tv_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<!-- Card view -->\n<android.support.v7.widget.CardView\n    android:id=\"@+id/search_result_card\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:card_view=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_margin=\"5dp\"\n    card_view:cardCornerRadius=\"4dp\">\n\n    <!-- Container -->\n    <RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"match_parent\"\n                    android:layout_marginBottom=\"8dp\"\n                    android:layout_marginLeft=\"16dp\"\n                    android:layout_marginRight=\"16dp\"\n                    android:layout_marginTop=\"8dp\">\n\n        <!-- Title -->\n        <TextView\n            android:id=\"@+id/titleTextView\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentLeft=\"true\"\n            android:textColor=\"@color/bunker\"\n            android:textSize=\"16sp\"\n            android:textStyle=\"bold\"\n            tools:text=\"Title\"/>\n\n\n        <!-- Description -->\n        <TextView\n            android:id=\"@+id/descriptionTextView\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_below=\"@+id/titleTextView\"\n            android:layout_toLeftOf=\"@+id/likeCountTextView\"\n            android:textColor=\"@color/bunker\"\n            android:textSize=\"14sp\"\n            tools:text=\"Description\"/>\n\n        <!-- Number of likes -->\n        <TextView\n            android:id=\"@+id/likeCountTextView\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentRight=\"true\"\n            android:layout_centerVertical=\"true\"\n            android:textColor=\"@color/red_orange\"\n            android:textSize=\"14sp\"\n            tools:text=\"100 Likes\"/>\n\n    </RelativeLayout>\n\n</android.support.v7.widget.CardView>\n"
  },
  {
    "path": "sample/src/main/res/menu/menu_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n      xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    >\n\n    <!-- Logout -->\n    <item\n        android:id=\"@+id/logout\"\n        android:title=\"@string/menu_action_logout\"\n        app:showAsAction=\"always\"/>\n</menu>\n"
  },
  {
    "path": "sample/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<resources>\n\n    <color name=\"white\">#FFFFFFFF</color>\n    <color name=\"hint_of_red\">#F9F9F9</color>\n    <color name=\"persian_red\">#FFD32F2F</color>\n    <color name=\"red_orange\">#FFF44336</color>\n    <color name=\"bunker\">#FF282B2E</color>\n\n</resources>\n"
  },
  {
    "path": "sample/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<resources>\n\n    <!-- The Application name -->\n    <string name=\"app_name\">Heimdall.droid</string>\n\n    <!-- Dialogs -->\n    <string name=\"error_title\">Oooops</string>\n    <string name=\"error_message\">There was an error. Please try again later.</string>\n    <string name=\"no_liked_lists_message\">There are no lists on your account. Please create some\n        over at http://trakt.tv!\n    </string>\n    <string name=\"ok\">OK</string>\n\n    <!-- Item views -->\n    <string name=\"likes_postfix\">\" Likes\"</string>\n    <string name=\"menu_action_logout\">Logout</string>\n\n</resources>\n"
  },
  {
    "path": "sample/src/main/res/values/styles.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppThemeNoActionBar\" parent=\"Theme.AppCompat.Light.NoActionBar\">\n\n        <!-- Colors -->\n        <item name=\"colorPrimary\">@color/red_orange</item>\n        <item name=\"colorPrimaryDark\">@color/persian_red</item>\n        <item name=\"android:textColorPrimary\">@color/white</item>\n\n    </style>\n\n</resources>\n"
  },
  {
    "path": "settings.gradle",
    "content": "include ':sample', ':library'\n"
  }
]