Full Code of rheinfabrik/Heimdall.droid for AI

master 855d253c682c cached
61 files
91.4 KB
24.0k tokens
62 symbols
1 requests
Download .txt
Repository: rheinfabrik/Heimdall.droid
Branch: master
Commit: 855d253c682c
Files: 61
Total size: 91.4 KB

Directory structure:
gitextract_8ir_jlm8/

├── .gitignore
├── .travis.yml
├── LICENSE.txt
├── README.md
├── build.gradle
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── library/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       ├── main/
│       │   └── java/
│       │       └── de/
│       │           └── rheinfabrik/
│       │               └── heimdall2/
│       │                   ├── OAuth2AccessToken.kt
│       │                   ├── OAuth2AccessTokenManager.kt
│       │                   ├── OAuth2AccessTokenStorage.kt
│       │                   └── grants/
│       │                       ├── OAuth2AuthorizationCodeGrant.kt
│       │                       ├── OAuth2ClientCredentialsGrant.kt
│       │                       ├── OAuth2Grant.kt
│       │                       ├── OAuth2ImplicitGrant.kt
│       │                       ├── OAuth2RefreshAccessTokenGrant.kt
│       │                       └── OAuth2ResourceOwnerPasswordCredentialsGrant.kt
│       └── test/
│           ├── java/
│           │   └── de/
│           │       └── rheinfabrik/
│           │           └── heimdall2/
│           │               ├── OAuth2AccessTokenIsExpiredTest.kt
│           │               ├── OAuth2AccessTokenManagerGetValidAccessTokenTest.kt
│           │               ├── OAuth2AccessTokenManagerGrantNewAccessTokenTest.kt
│           │               ├── OAuth2AccessTokenManagerTest.kt
│           │               ├── OAuth2AccessTokenSerializationTest.kt
│           │               └── grants/
│           │                   ├── OAuth2AuthorizationCodeGrantTest.kt
│           │                   ├── OAuth2ClientCredentialsGrantTest.kt
│           │                   ├── OAuth2ImplicitGrantTest.kt
│           │                   ├── OAuth2RefreshAccessTokenGrantTest.kt
│           │                   └── OAuth2ResourceOwnerPasswordCredentialsGrantTest.kt
│           └── resources/
│               └── mockito-extensions/
│                   └── org.mockito.plugins.MockMaker
├── sample/
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       └── main/
│           ├── AndroidManifest.xml
│           ├── java/
│           │   └── de/
│           │       └── rheinfabrik/
│           │           └── heimdalldroid/
│           │               ├── TraktTvAPIConfiguration.java
│           │               ├── actvities/
│           │               │   ├── LoginActivity.java
│           │               │   └── MainActivity.java
│           │               ├── adapter/
│           │               │   ├── TraktTvListsRecyclerViewAdapter.java
│           │               │   └── viewholder/
│           │               │       └── TraktTvListViewHolder.java
│           │               ├── network/
│           │               │   ├── TraktTvApiFactory.java
│           │               │   ├── TraktTvApiService.java
│           │               │   ├── models/
│           │               │   │   ├── AccessTokenRequestBody.java
│           │               │   │   ├── RefreshTokenRequestBody.java
│           │               │   │   ├── RevokeAccessTokenBody.java
│           │               │   │   └── TraktTvList.java
│           │               │   └── oauth2/
│           │               │       ├── TraktTvAuthorizationCodeGrant.java
│           │               │       ├── TraktTvOauth2AccessTokenManager.java
│           │               │       └── TraktTvRefreshAccessTokenGrant.java
│           │               └── utils/
│           │                   ├── AlertDialogFactory.java
│           │                   ├── IntentFactory.java
│           │                   └── SharedPreferencesOAuth2AccessTokenStorage.java
│           └── res/
│               ├── layout/
│               │   ├── activity_login.xml
│               │   ├── activity_main.xml
│               │   └── item_view_trakt_tv_list.xml
│               ├── menu/
│               │   └── menu_main.xml
│               └── values/
│                   ├── colors.xml
│                   ├── strings.xml
│                   └── styles.xml
└── settings.gradle

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
# Built application files
*.apk
*.ap_
bin/
gen/
classes/
gen-external-apklibs/

# Eclipse project files
.classpath
.project
.metadata
.settings

# IntelliJ files
.idea
*.iml

# OSX files
.DS_Store

# Windows files
Thumbs.db

# vi swap files
*.swp

# backup files
*.bak


# Files for the Dalvik VM
*.dex

# Java class files
*.class

# Generated files
bin/
gen/

# Gradle files
.gradle/
build/
.gradle

#maven files
target/
/null

# Local configuration file (sdk path, etc)

local.properties

# Proguard folder generated by Eclipse
proguard/

#Log Files
*.log

sample/src/main/java/de/rheinfabrik/heimdalldroid/network/TraktTvAPIConfiguration.java


================================================
FILE: .travis.yml
================================================
language: android
sudo: required
jdk: oraclejdk8

env:
  global:
    - ANDROID_API_LEVEL=30
    - ANDROID_BUILD_TOOLS_VERSION=30.0.2
    - ANDROID_ABI=armeabi-v7a

android:
  components:
    - tools
    - platform-tools
    - extra-android-m2repository
  licenses:
    - 'android-sdk-preview-license-52d11cd2'
    - 'android-sdk-license-.+'
    - 'google-gdk-license-.+'

before_install:
  - touch $HOME/.android/repositories.cfg
  - yes | sdkmanager "platforms;android-30"
  - yes | sdkmanager "build-tools;30.0.2"

before_cache:
  - rm -f  $HOME/.gradle/caches/modules-2/modules-2.lock
  - rm -fr $HOME/.gradle/caches/*/plugin-resolution/

cache:
  directories:
    - $HOME/.gradle/caches/
    - $HOME/.gradle/wrapper/
    - $HOME/.android/build-cache

before_script:
  - chmod +x gradlew

script:
  - ./gradlew library:test


================================================
FILE: LICENSE.txt
================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "{}"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright 2015 B264 GmbH

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: README.md
================================================
# Deprecated Project

⚠️ This project is no longer maintained

This repository is deprecated and will no longer receive updates, bug fixes, or support.

# <img src="https://cloud.githubusercontent.com/assets/460060/8159821/b8bfeb32-136a-11e5-83ed-83b7fe01df3a.jpg" width="30" height="30"> Heimdall

Heimdall 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.

This library makes use of [RxJava](https://github.com/ReactiveX/RxJava). Therefore you should be familar with [Observables](https://github.com/ReactiveX/RxJava/wiki/Observable).

If you are an iOS Developer then please take a look at the [Swift version of Heimdall](https://github.com/trivago/Heimdallr.swift).

[![JitPack.io](https://jitpack.io/v/trivago/Heimdall.droid.svg)](https://jitpack.io/#trivago/Heimdall.droid)
[![Travis Ci](https://travis-ci.org/trivago/Heimdall.droid.svg?branch=master)](https://travis-ci.org/trivago/Heimdall.droid)
[![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-Heimdall.droid-brightgreen.svg?style=flat)](http://android-arsenal.com/details/1/2016)
[![Api](https://img.shields.io/badge/API-9%2B-brightgreen.svg?style=flat)](https://android-arsenal.com/api?level=9)

## Installation

Heimdall is ready to be used via [jitpack.io](https://jitpack.io/#rheinfabrik/Heimdall.droid).
Simply add the following code to your root `build.gradle`:

```groovy
allprojects {
    repositories {
        jcenter()
        maven { url "https://jitpack.io" }
    }
}
```

Now add the gradle dependency in your application's `build.gradle`:

```groovy
dependencies {
    compile 'com.github.rheinfabrik:Heimdall.droid:{latest_version}'
}
```

## Examples

Heimdall's main class is the `OAuth2AccessTokenManager`. It is responsible for retrieving a new access token and keeping it valid by refreshing it.

In 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!

```java 

SharedPreferencesOAuth2AccessTokenStorage<OAuth2AccessToken> storage = new SharedPreferencesOAuth2AccessTokenStorage<>(mySharedPreferences, OAuth2AccessToken.class);
OAuth2AccessTokenManager<> manager = new OAuth2AccessTokenManager<OAuth2AccessToken>(storage);

```

On 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. 

Here is an example of an `OAuth2ResourceOwnerPasswordCredentialsGrant`.

```java 
public class MyOAuth2Grant extends OAuth2ResourceOwnerPasswordCredentialsGrant<OAuth2AccessToken> {

    // Constructor

    @Override
    public Observable<OAuth2AccessToken> grantNewAccessToken() {
        // Create the network request based on the username, the password and the grant type.
        // You can use Retrofit to make things easier.
    }
}
```

Your 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. 

Here is an example of an `OAuth2RefreshAccessTokenGrant`.

```java
public class MyOAuth2Grant extends OAuth2RefreshAccessTokenGrant<OAuth2AccessToken> {

    // Constructor

    @Override
    public Observable<OAuth2AccessToken> grantNewAccessToken() {
        // Create the network request based on the grant type and the refresh token.
        // You can use Retrofit to make things easier.
    }
}
```

Mostly you will use the `OAuth2AuthorizationCodeGrant` to authorize the user via a third party service such as Trakt.tv.

The implemention of a grant authorizing with Trakt.tv might look as following:

```java
public final class TraktTVAuthorizationCodeGrant extends OAuth2AuthorizationCodeGrant<OAuth2AccessToken> {

    public String clientSecret;

    @Override
    public Uri buildAuthorizationUri() {
        return Uri.parse("https://trakt.tv/oauth/authorize")
                .buildUpon()
                .appendQueryParameter("client_id", clientId)
                .appendQueryParameter("redirect_uri", redirectUri)
                .appendQueryParameter("response_type", RESPONSE_TYPE).build();
    }

    @Override
    public Observable<OAuth2AccessToken> exchangeTokenForCode(String code) {
        // Create the network request based on the grant type, clientSecret and the retrieved code.
        // You can use Retrofit to make things easier.
    }
}
```

Using that grant with an Android WebView might look like this (please note that we use [Retrolambda](https://github.com/evant/gradle-retrolambda) here):

```java
// Create the grant
TraktTVAuthorizationCodeGrant grant = new TraktTVAuthorizationCodeGrant();
grant.clientSecret = "secret"
grant.clientId = "id"
grant.redirectUri = "uri"

// Set up web view loading
webView.setWebViewClient(new WebViewClient() {
 	
 	@Override
    public void onPageFinished(WebView view, String url) {
    	super.onPageFinished(view, url);

		// Tell the grant we loaded an url
        grant.onUrlLoadedCommand.onNext(Uri.parse(url));
    }
});

// Load the authorization url once build
grant.authorizationUri()
    .map(Uri::parse)
	.observeOn(AndroidSchedulers.mainThread())
	.subscribe(myWebView::load)

// Start the authorization process
grant.grantNewAccessToken()
	.subscribe(token -> Log.d("Heimdall", "New token: " + token))

```

## Sample Application

Please 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.

**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.


## About

Heimdall was built by [trivago](http://www.trivago.com) :factory:

## License

Heimdall is licensed under Apache Version 2.0.


================================================
FILE: build.gradle
================================================
// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:4.0.1'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}


================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
#Tue Oct 24 09:19:37 CEST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip


================================================
FILE: gradle.properties
================================================
# Project-wide Gradle settings.

# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.

# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html

# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8

# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
android.enableJetifier=true
android.useAndroidX=true


================================================
FILE: gradlew
================================================
#!/usr/bin/env bash

##############################################################################
##
##  Gradle start up script for UN*X
##
##############################################################################

# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""

APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`

# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"

warn ( ) {
    echo "$*"
}

die ( ) {
    echo
    echo "$*"
    echo
    exit 1
}

# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
  CYGWIN* )
    cygwin=true
    ;;
  Darwin* )
    darwin=true
    ;;
  MINGW* )
    msys=true
    ;;
esac

# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
    [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi

# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
    ls=`ls -ld "$PRG"`
    link=`expr "$ls" : '.*-> \(.*\)$'`
    if expr "$link" : '/.*' > /dev/null; then
        PRG="$link"
    else
        PRG=`dirname "$PRG"`"/$link"
    fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >&-
APP_HOME="`pwd -P`"
cd "$SAVED" >&-

CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar

# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
        # IBM's JDK on AIX uses strange locations for the executables
        JAVACMD="$JAVA_HOME/jre/sh/java"
    else
        JAVACMD="$JAVA_HOME/bin/java"
    fi
    if [ ! -x "$JAVACMD" ] ; then
        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
    fi
else
    JAVACMD="java"
    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi

# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
    MAX_FD_LIMIT=`ulimit -H -n`
    if [ $? -eq 0 ] ; then
        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
            MAX_FD="$MAX_FD_LIMIT"
        fi
        ulimit -n $MAX_FD
        if [ $? -ne 0 ] ; then
            warn "Could not set maximum file descriptor limit: $MAX_FD"
        fi
    else
        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
    fi
fi

# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi

# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`

    # We build the pattern for arguments to be converted via cygpath
    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
    SEP=""
    for dir in $ROOTDIRSRAW ; do
        ROOTDIRS="$ROOTDIRS$SEP$dir"
        SEP="|"
    done
    OURCYGPATTERN="(^($ROOTDIRS))"
    # Add a user-defined pattern to the cygpath arguments
    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
    fi
    # Now convert the arguments - kludge to limit ourselves to /bin/sh
    i=0
    for arg in "$@" ; do
        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option

        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
        else
            eval `echo args$i`="\"$arg\""
        fi
        i=$((i+1))
    done
    case $i in
        (0) set -- ;;
        (1) set -- "$args0" ;;
        (2) set -- "$args0" "$args1" ;;
        (3) set -- "$args0" "$args1" "$args2" ;;
        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
    esac
fi

# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
    JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"

exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"


================================================
FILE: gradlew.bat
================================================
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem  Gradle startup script for Windows
@rem
@rem ##########################################################################

@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal

@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=

set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%

@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome

set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init

echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe

if exist "%JAVA_EXE%" goto init

echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:init
@rem Get command-line arguments, handling Windowz variants

if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args

:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2

:win9xME_args_slurp
if "x%~1" == "x" goto execute

set CMD_LINE_ARGS=%*
goto execute

:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$

:execute
@rem Setup the command line

set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd

:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1

:mainEnd
if "%OS%"=="Windows_NT" endlocal

:omega


================================================
FILE: library/.gitignore
================================================
/build


================================================
FILE: library/build.gradle
================================================
apply plugin: 'kotlin'
apply plugin: 'groovy'
apply plugin: 'maven'

buildscript {
    ext.kotlin_version = '1.4.10'

    repositories {
        mavenCentral()
    }

    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

jar.archiveName = "Heimdall.Droid.jar"

group = 'com.github.rheinfabrik'

repositories {
    mavenCentral()
    jcenter()
}

dependencies {
    // Kotlin
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"

    // Rx
    implementation 'io.reactivex.rxjava2:rxjava:2.2.19'

    // GSON
    implementation 'com.google.code.gson:gson:2.8.6'

    // Testing
    testImplementation 'junit:junit:4.13'
    testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0"
    testImplementation "org.mockito:mockito-inline:3.5.11"
}

compileKotlin {
    kotlinOptions {
        jvmTarget = "1.8"
    }
}

compileTestKotlin {
    kotlinOptions {
        jvmTarget = "1.8"
    }
}


================================================
FILE: library/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/gilo/Library/Android/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html

# Add any project specific keep options here:

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}


================================================
FILE: library/src/main/java/de/rheinfabrik/heimdall2/OAuth2AccessToken.kt
================================================
package de.rheinfabrik.heimdall2

import com.google.gson.annotations.SerializedName
import java.io.Serializable
import java.util.Calendar

data class OAuth2AccessToken(
    /**
     * REQUIRED
     * The type of the token issued as described in https://tools.ietf.org/html/rfc6749#section-7.1.
     * Value is case insensitive.
     */
    @SerializedName("token_type")
    val tokenType: String = "",

    /**
     * REQUIRED
     * The access token issued by the authorization server.
     */
    @SerializedName("access_token")
    val accessToken: String = "",

    /**
     * OPTIONAL
     * The refresh token, which can be used to obtain new
     * access tokens using the same authorization grant as described
     * in https://tools.ietf.org/html/rfc6749#section-6.
     */
    @SerializedName("refresh_token")
    val refreshToken: String? = null,

    /**
     * RECOMMENDED
     * The lifetime in seconds of the access token.  For
     * example, the value "3600" denotes that the access token will
     * expire in one hour from the time the response was generated.
     * If omitted, the authorization server SHOULD provide the
     * expiration time via other means or document the default value.
     */
    @SerializedName("expires_in")
    val expiresIn: Int? = null,

    /**
     * The expiration date used by Heimdall.
     */
    @SerializedName("heimdall_expiration_date")
    val expirationDate: Calendar? = null
) : Serializable {

    // Public API

    /**
     * Returns whether the access token expired or not.
     *
     * @return True if expired. Otherwise false.
     */
    fun isExpired(): Boolean =
        expirationDate != null &&
            Calendar.getInstance().after(expirationDate)

}


================================================
FILE: library/src/main/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenManager.kt
================================================
package de.rheinfabrik.heimdall2

import de.rheinfabrik.heimdall2.grants.OAuth2Grant
import de.rheinfabrik.heimdall2.grants.OAuth2RefreshAccessTokenGrant
import io.reactivex.Single
import java.util.Calendar

open class OAuth2AccessTokenManager(
    private val mStorage: OAuth2AccessTokenStorage
) {

    // Public API

    /**
     * Returns the underlying storage.
     *
     * @return - An OAuth2AccessTokenStorage.
     */
    fun getStorage(): OAuth2AccessTokenStorage = mStorage

    /**
     * Grants a new access token using the given OAuth2 grant.
     *
     * @param grant A class implementing the OAuth2Grant interface.
     * @return - An Single emitting the granted access token.
     */
    fun grantNewAccessToken(
        grant: OAuth2Grant,
        calendar: Calendar = Calendar.getInstance()
    ): Single<OAuth2AccessToken> =
        grant.grantNewAccessToken()
            .map {
                if (it.expiresIn != null) {
                    val newExpirationDate = (calendar.clone() as Calendar).apply {
                        add(Calendar.SECOND, it.expiresIn)
                    }
                    it.copy(expirationDate = newExpirationDate)
                } else {
                    it
                }
            }
            .doOnSuccess { token ->
                mStorage.storeAccessToken(
                    token = token
                )
            }
            .cache()

    /**
     * Returns an Observable emitting an unexpired access token.
     * NOTE: In order to work, Heimdall needs an access token which has a refresh_token and an
     * expires_in field.
     *
     * @param refreshAccessTokenGrant The refresh grant that will be used if the access token is expired.
     * @return - An Single emitting an unexpired access token.
     */
    fun getValidAccessToken(refreshAccessTokenGrant: OAuth2RefreshAccessTokenGrant): Single<OAuth2AccessToken> =
        mStorage.getStoredAccessToken()
            .flatMap { accessToken ->
                if (accessToken.isExpired()) {
                    refreshAccessTokenGrant.refreshToken = accessToken.refreshToken
                    grantNewAccessToken(refreshAccessTokenGrant)
                } else {
                    Single.just(accessToken)
                }
            }
}


================================================
FILE: library/src/main/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenStorage.kt
================================================
package de.rheinfabrik.heimdall2

import io.reactivex.Single

/**
 * Interface used to define how to store and retrieve a stored access token.
 *
 * @param <TAccessToken> The access token type.
 */
interface OAuth2AccessTokenStorage {
    // Public API

    /**
     * Queries the stored access token.
     *
     * @return - An Observable emitting the stored access token.
     */
    fun getStoredAccessToken(): Single<OAuth2AccessToken>

    /**
     * Stores the given access token.
     *
     * @param token The access token which will be stored.
     */
    fun storeAccessToken(token: OAuth2AccessToken)

    /**
     * Checks whether there is or is not an access token
     *
     * @return - An Observable emitting true or false based on whether there is or is not an
     * access token.
     */
    fun hasAccessToken(): Single<Boolean>

    /**
     * Removes the stored access token.
     */
    fun removeAccessToken()
}


================================================
FILE: library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2AuthorizationCodeGrant.kt
================================================
package de.rheinfabrik.heimdall2.grants

import de.rheinfabrik.heimdall2.OAuth2AccessToken
import io.reactivex.Observable
import io.reactivex.Single
import io.reactivex.subjects.BehaviorSubject
import io.reactivex.subjects.PublishSubject
import java.net.URL
import java.net.URLDecoder

/**
 * Class representing the Authorization Code Grant as described in https://tools.ietf.org/html/rfc6749#section-4.1.
 *
 * @param <TAccessToken> The access token type.
 */
abstract class OAuth2AuthorizationCodeGrant(
    /**
     * REQUIRED
     * The client identifier as described in https://tools.ietf.org/html/rfc6749#section-2.2.
     */
    var clientId: String = "",

    /**
     * OPTIONAL
     * As described in https://tools.ietf.org/html/rfc6749#section-3.1.2.
     */
    var redirectUri: String? = null,

    /**
     * OPTIONAL
     * The scope of the access request as described in https://tools.ietf.org/html/rfc6749#section-3.3.
     */
    var scope: String? = null,

    /**
     * RECOMMENDED
     * An opaque value used by the client to maintain
     * state between the request and callback. The authorization
     * server includes this value when redirecting the user-agent back
     * to the client. The parameter SHOULD be used for preventing
     * cross-site request forgery as described in https://tools.ietf.org/html/rfc6749#section-10.12.
     */
    var state: String? = null
) : OAuth2Grant {

    // Constants
    companion object {
        @JvmField
        val RESPONSE_TYPE = "code"
        @JvmField
        val GRANT_TYPE = "authorization_code"
        private const val UTF_8 = "UTF-8"
    }

    // Public Members
    /**
     * Command you should send a value to whenever an url in e.g. your web view has been loaded.
     */
    val onUrlLoadedCommand = PublishSubject.create<URL>()

    // Private Members
    private val mAuthorizationUrlSubject = BehaviorSubject.create<URL>()

    // Abstract API

    /**
     * Called when the grant needs the authorization url.
     */
    abstract fun buildAuthorizationUrl(): URL

    /**
     * Called when the grant was able to grab the code and it wants to exchange for an access token.
     */
    abstract fun exchangeTokenUsingCode(code: String): Observable<OAuth2AccessToken>

    // Public API

    /**
     * Observable emitting the authorization Uri.
     */
    fun authorizationUri() = mAuthorizationUrlSubject

    override fun grantNewAccessToken(): Single<OAuth2AccessToken> {
        mAuthorizationUrlSubject.onNext(buildAuthorizationUrl())

        return onUrlLoadedCommand.map {
            getQueryParameters(it)[RESPONSE_TYPE]?.get(0)
        }.filter {
            it.isNotBlank()
        }.take(1)
            .retry()
            .concatMap {
                exchangeTokenUsingCode(it)
            }
            .singleOrError()
    }

    // Private API

    private fun getQueryParameters(url: URL): LinkedHashMap<String, MutableList<String>> {
        val queryParams = linkedMapOf<String, MutableList<String>>()
        url.query.split("&").forEach {
            val idx = it.indexOf("=")
            try {
                val key = if (idx > 0) {
                    URLDecoder.decode(
                        it.substring(0, idx),
                        UTF_8
                    )
                } else {
                    it
                }
                if (!queryParams.containsKey(key)) {
                    queryParams[key] = mutableListOf()
                }
                val value = if (idx > 0 && it.length > idx + 1) {
                    URLDecoder.decode(
                        it.substring(idx + 1),
                        UTF_8
                    )
                } else ""

                queryParams[key]?.add(value)
            } catch (e: Exception) {
                // Do nothing
            }
        }
        return queryParams
    }
}


================================================
FILE: library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2ClientCredentialsGrant.kt
================================================
package de.rheinfabrik.heimdall2.grants

abstract class OAuth2ClientCredentialsGrant(
    /**
     * OPTIONAL
     * The scope of the access request as described in https://tools.ietf.org/html/rfc6749#section-3.3.
     */
    val scope: String? = null
) :
    OAuth2Grant {

    // Constants

    companion object {
        /**
         * REQUIRED
         * The OAuth2 "grant_type".
         */
        @JvmField
        val GRANT_TYPE = "client_credentials"
    }
}


================================================
FILE: library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2Grant.kt
================================================
package de.rheinfabrik.heimdall2.grants

import de.rheinfabrik.heimdall2.OAuth2AccessToken
import io.reactivex.Single

/**
 * Interface describing an OAuth2 Grant as described in https://tools.ietf.org/html/rfc6749#page-23.
 *
 * @param <TAccessToken> The access token type.
 */
interface OAuth2Grant {

    // Abstract Api

    /**
     * Performs the actual request to grant a new access token.
     *
     * @return - An Observable emitting the granted access token.
     */
    fun grantNewAccessToken(): Single<OAuth2AccessToken>
}


================================================
FILE: library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2ImplicitGrant.kt
================================================
package de.rheinfabrik.heimdall2.grants

/**
 * Class representing the Implicit Grant as described in https://tools.ietf.org/html/rfc6749#section-4.2.
 *
 * @param <TAccessToken> The access token type.
 */

abstract class OAuth2ImplicitGrant(
    /**
     * REQUIRED
     * The client identifier as described in https://tools.ietf.org/html/rfc6749#section-2.2.
     */
    var clientId: String = "",

    /**
     * OPTIONAL
     * As described in https://tools.ietf.org/html/rfc6749#section-3.1.2.
     */
    var redirectUri: String? = null,

    /**
     * OPTIONAL
     * The scope of the access request as described in https://tools.ietf.org/html/rfc6749#section-3.3.
     */
    var scope: String? = null,

    /**
     * RECOMMENDED
     * An opaque value used by the client to maintain
     * state between the request and callback. The authorization
     * server includes this value when redirecting the user-agent back
     * to the client. The parameter SHOULD be used for preventing
     * cross-site request forgery as described in https://tools.ietf.org/html/rfc6749#section-10.12.
     */
    var state: String? = null

) : OAuth2Grant {

    // Constants

    /**
     * REQUIRED
     * The "response_type" which MUST be "token".
     */
    companion object {
        @JvmField
        val RESPONSE_TYPE = "token"
    }
}


================================================
FILE: library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2RefreshAccessTokenGrant.kt
================================================
package de.rheinfabrik.heimdall2.grants

abstract class OAuth2RefreshAccessTokenGrant(
    /**
     * REQUIRED
     * The "refresh_token" issued to the client.
     */
    var refreshToken: String? = null,

    /**
     * OPTIONAL
     * The "scope" of the access request as described by here (https://tools.ietf.org/html/rfc6749#section-3.3).
     */
    var scope: String? = null
) :
    OAuth2Grant {
    // Constants

    /**
     * REQUIRED
     * The OAuth2 "grant_type".
     */
    companion object {
        @JvmField
        val GRANT_TYPE = "refresh_token"
    }
}


================================================
FILE: library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2ResourceOwnerPasswordCredentialsGrant.kt
================================================
package de.rheinfabrik.heimdall2.grants

abstract class OAuth2ResourceOwnerPasswordCredentialsGrant(
    /**
     * REQUIRED
     * The resource owner "username".
     */
    var username: String? = null,

    /**
     * REQUIRED
     * The resource owner "password".
     */
    var password: String? = null,

    /**
     * OPTIONAL
     * The "scope" of the access request as described by here (https://tools.ietf.org/html/rfc6749#section-3.3).
     */
    var scope: String? = null
) : OAuth2Grant {

    companion object {
        @JvmField
        val GRANT_TYPE = "password"
    }
}


================================================
FILE: library/src/test/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenIsExpiredTest.kt
================================================
package de.rheinfabrik.heimdall2

import org.junit.Assert.assertEquals
import org.junit.Test
import java.util.*

class OAuth2AccessTokenIsExpiredTest {

    @Test
    fun `when the expirationDate is null, isExpired method should return false`() {
        // given an accessToken
        val accessToken = OAuth2AccessToken(expirationDate = null)

        // when the isExpired method is called
        val value = accessToken.isExpired()

        // then false is returned
        assertEquals(value, false)

    }

    @Test
    fun `when the expirationDate is in the past, isExpired method should return false`() {
        // given a date in the past
        val pastCalendar = Calendar.getInstance()
        pastCalendar.add(Calendar.YEAR, 1)

        // and a token with a past date
        val accessToken = OAuth2AccessToken(expirationDate = pastCalendar)

        // when the isExpired method is called
        val value = accessToken.isExpired()

        // then true is returned
        assertEquals(value, false)
    }

    @Test
    fun `when the expirationDate is in the future, isExpired method should return false`() {
        // given a date in the past
        val futureDate = Calendar.getInstance()
        futureDate.add(Calendar.YEAR, 1)

        // and a token with a past date
        val accessToken = OAuth2AccessToken(expirationDate = futureDate)

        // when the isExpired method is called
        val value = accessToken.isExpired()

        // then true is returned
        assertEquals(value, false)
    }
}


================================================
FILE: library/src/test/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenManagerGetValidAccessTokenTest.kt
================================================
package de.rheinfabrik.heimdall2

import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.whenever
import de.rheinfabrik.heimdall2.grants.OAuth2RefreshAccessTokenGrant
import io.reactivex.Single
import org.junit.Test
import java.util.*

class OAuth2AccessTokenManagerGetValidAccessTokenTest {

    @Test
    fun `when subscribed to getValidAccessToken(), the non-expired token should be emitted`() {

        // given a non expired token
        val accessToken = mock<OAuth2AccessToken>().apply {
            whenever(isExpired()).thenReturn(false)
        }

        // and a token manager with a valid storage and token
        val storage = mock<OAuth2AccessTokenStorage>().apply {
            whenever(getStoredAccessToken()).thenReturn(Single.just(accessToken))
        }
        val grant = mock<OAuth2RefreshAccessTokenGrant>().apply {
            whenever(grantNewAccessToken()).thenReturn(Single.just(accessToken))
        }
        val tokenManager = OAuth2AccessTokenManager(
            mStorage = storage
        )

        // when a valid access token is requested
        val validTokenTest = tokenManager.getValidAccessToken(grant).test()

        // then the non expired token gets received
        validTokenTest.assertValue(accessToken)
    }

    @Test
    fun `when the token expires, the refresh grant should be called to refresh it`(){
        // given an expired accesstoken
        val accessToken = mock<OAuth2AccessToken>().apply {
            whenever(isExpired()).thenReturn(true)
        }

        // and a token manager
        val tokenManager = OAuth2AccessTokenManager(
            mStorage = mock<OAuth2AccessTokenStorage>().apply {
                whenever(getStoredAccessToken()).thenReturn(Single.just(accessToken))
            }
        )

        // and a OAuth2RefreshAccessTokenGrant
        val grant = mock<OAuth2RefreshAccessTokenGrant>()

        // when a valid token is needed
        tokenManager.getValidAccessToken(grant).test()

        // then a refresh grant asks for a new token
        verify(grant).grantNewAccessToken()

    }

    @Test
    fun `when the token expires, the grant should be updated with a new token`() {
        // given an expired OAuthAccessToken
        val pastDate = Calendar.getInstance()
        pastDate.add(Calendar.YEAR, -1)

        val accessToken = OAuth2AccessToken(
            refreshToken = "rt",
            expirationDate = pastDate
        )

        // and a token manager
        val tokenManager = OAuth2AccessTokenManager(
            mStorage = mock<OAuth2AccessTokenStorage>().apply {
                whenever(getStoredAccessToken()).thenReturn(Single.just(accessToken))
            }
        )

        // and a grant
        val grant = mock<OAuth2RefreshAccessTokenGrant>().apply {
            whenever(grantNewAccessToken()).thenReturn(Single.just(accessToken))
        }

        // when a valid access token is needed
        val grantToken = tokenManager.getValidAccessToken(grant).test()

        // then the grants new refreshToken gets updated
        grantToken.assertValue{
            it.refreshToken == accessToken.refreshToken
        }

    }
}


================================================
FILE: library/src/test/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenManagerGrantNewAccessTokenTest.kt
================================================
package de.rheinfabrik.heimdall2

import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.whenever
import de.rheinfabrik.heimdall2.grants.OAuth2Grant
import io.reactivex.Single
import org.junit.Test
import java.util.*

class OAuth2AccessTokenManagerGrantNewAccessTokenTest {

    @Test
    fun `when a new access token has an expiration date, the token manager should generate and set the correct expiration date`() {

        // given an OAuth2AccessToken
        val accessToken = OAuth2AccessToken(
            expirationDate = null
        )
        val changedAccessToken = accessToken.copy(
            expiresIn = 3
        )


        // and a grant that emits that token
        val grant = mock<OAuth2Grant>().apply {
            whenever(grantNewAccessToken()).thenReturn(Single.just(changedAccessToken))
        }

        // and a tokenManager
        val tokenManager = OAuth2AccessTokenManager(
            mStorage = mock()
        )

        // when a new access token is needed
        val calendar = Calendar.getInstance()
        val newToken = tokenManager.grantNewAccessToken(
            grant = grant,
            calendar = calendar
        ).test()

        // then the access token should have the correct expiration date
        newToken.assertValue {
            it.expirationDate?.timeInMillis == calendar.timeInMillis + 3000
        }
    }

    @Test
    fun `when a new access token doesn't have an expiration date, the token manager should not generate and a new  expiration date`() {

        // given an OAuth2AccessToken
        val accessToken = OAuth2AccessToken(
            expirationDate = null
        )
        val changedAccessToken = accessToken.copy(
            expiresIn = null
        )

        // and a grant that emits that token
        val grant = mock<OAuth2Grant>().apply {
            whenever(grantNewAccessToken()).thenReturn(Single.just(changedAccessToken))
        }

        // and a tokenManager
        val tokenManager = OAuth2AccessTokenManager(
            mStorage = mock()
        )

        // when a new access token is needed
        val calendar = Calendar.getInstance()
        val newToken = tokenManager.grantNewAccessToken(
            grant = grant,
            calendar = calendar
        ).test()

        // then the access token should have the correct expiration date
        newToken.assertValue {
            it.expirationDate == null
        }
    }

    @Test
    fun `when a new access token is needed, the storage should be called to store the token`() {
        // given a grant that emits a token
        val accessToken = OAuth2AccessToken()
        val grant = mock<OAuth2Grant>().apply {
            whenever(grantNewAccessToken()).thenReturn(Single.just(accessToken))
        }

        // and a token manager
        val storage = mock<OAuth2AccessTokenStorage>()
        val tokenManager = OAuth2AccessTokenManager(
            mStorage = storage
        )

        // when a new access token is needed
        tokenManager.grantNewAccessToken(grant).test()

        // then the storage is called
        verify(storage).storeAccessToken(accessToken)
    }

}


================================================
FILE: library/src/test/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenManagerTest.kt
================================================
package de.rheinfabrik.heimdall2

import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.whenever
import de.rheinfabrik.heimdall2.grants.OAuth2RefreshAccessTokenGrant
import io.reactivex.Single
import org.junit.Test


class OAuth2AccessTokenManagerTest {

    @Test
    fun `when the access token is accessed, the token manager should emit a non expired token`(){
        // given a stored access token
        val validToken = mock<OAuth2AccessToken>()
        val accessTokenStorage = mock<OAuth2AccessTokenStorage>()
        whenever(accessTokenStorage.getStoredAccessToken()).thenReturn(Single.just(validToken))

        // and a refresh access token grant
        val refreshAccessTokenGrant = mock<OAuth2RefreshAccessTokenGrant>()
        whenever(refreshAccessTokenGrant.grantNewAccessToken()).thenReturn(Single.just(validToken))

        // and a token manager
        val tokenManager = OAuth2AccessTokenManager(accessTokenStorage)

        // when the token is accessed
        val getValidAccessTokenTest = tokenManager.getValidAccessToken(refreshAccessTokenGrant).test()

        // then the token is received
        getValidAccessTokenTest.assertValue(validToken)
    }
}

================================================
FILE: library/src/test/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenSerializationTest.kt
================================================
package de.rheinfabrik.heimdall2

import com.google.gson.Gson
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import java.util.*


class OAuth2AccessTokenSerializationTest {

    @Before
    fun setup() {
        // Set default locale and time zone
        Locale.setDefault(Locale.GERMANY)
        TimeZone.setDefault(TimeZone.getTimeZone("CEST"))
    }

    @Test
    fun `when an OAuth2AccessToken is transformed to a JSON string, it should be created`() {

        // given an OAuth2AccessToken
        val accessToken = OAuth2AccessToken(
            refreshToken = "rt",
            expiresIn = 3600,
            accessToken = "at",
            tokenType = "bearer"
        )
        val expirationDate = Calendar.getInstance()
        expirationDate.timeInMillis = 0

        // and an updated expiration date
        val changedAccessToken = accessToken.copy(
            expirationDate = expirationDate
        )

        // when it gets serialized with Gson
        val json = Gson().toJson(changedAccessToken)

        // then the json should be written correctly
        assertEquals(
            "{\"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}}",
            json
        )
    }

    @Test
    fun `when a token is created from a JSON string, it should be created`() {

        // given a json string representing a OAuth2AccessToken
        val jsonString =
            "{\"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\"}"

        // and an expiration date
        val calendar = Calendar.getInstance()
        calendar.timeInMillis = 0

        // when it gets deserialized with Gson
        val accessToken = Gson().fromJson(jsonString, OAuth2AccessToken::class.java)

        // then the token should be the same
        assertEquals("rt", accessToken.refreshToken)
        assertEquals(3600, accessToken.expiresIn)
        assertEquals("at", accessToken.accessToken)
        assertEquals("bearer", accessToken.tokenType)
        assertEquals(calendar.timeInMillis, accessToken.expirationDate?.timeInMillis)
    }
}

================================================
FILE: library/src/test/java/de/rheinfabrik/heimdall2/grants/OAuth2AuthorizationCodeGrantTest.kt
================================================
package de.rheinfabrik.heimdall2.grants

import org.junit.Assert.assertEquals
import org.junit.Test

class OAuth2AuthorizationCodeGrantTest {

    // Specifications for https://tools.ietf.org/html/rfc6749#section-4.1

    @Test
    fun `when an authorization code grant is created, the grant type is code`() {
        // the grant type is the correct
        assertEquals(OAuth2AuthorizationCodeGrant.GRANT_TYPE, "authorization_code")
    }

    @Test
    fun `when an authorization code grant is created, the response type is code`() {
        //  the grant type is the correct
        assertEquals(OAuth2AuthorizationCodeGrant.RESPONSE_TYPE, "code")
    }
}


================================================
FILE: library/src/test/java/de/rheinfabrik/heimdall2/grants/OAuth2ClientCredentialsGrantTest.kt
================================================
package de.rheinfabrik.heimdall2.grants

import org.junit.Assert.assertEquals
import org.junit.Test

class OAuth2ClientCredentialsGrantTest {

    // Specification for https://tools.ietf.org/html/rfc6749#section-4.4

    @Test
    fun `when a client credentials grant is created, the grant type is code`() {
        // when an client credentials grant is created
        val grant = OAuth2ClientCredentialsGrant

        // then the grant type is the correct
        assertEquals(grant.GRANT_TYPE, "client_credentials")
    }
}


================================================
FILE: library/src/test/java/de/rheinfabrik/heimdall2/grants/OAuth2ImplicitGrantTest.kt
================================================
package de.rheinfabrik.heimdall2.grants

import org.junit.Assert.assertEquals
import org.junit.Test

class OAuth2ImplicitGrantTest {

    // Specifications for https://tools.ietf.org/html/rfc6749#section-4.2

    @Test
    fun `when an implicit grant is created, the response type is token`() {
        // when an authorization code grant is created
        val grant = OAuth2ImplicitGrant

        // then the grant type is the correct
        assertEquals(grant.RESPONSE_TYPE, "token")
    }
}


================================================
FILE: library/src/test/java/de/rheinfabrik/heimdall2/grants/OAuth2RefreshAccessTokenGrantTest.kt
================================================
package de.rheinfabrik.heimdall2.grants

import org.junit.Assert.assertEquals
import org.junit.Test

class OAuth2RefreshAccessTokenGrantTest {

    // Specifications for https://tools.ietf.org/html/rfc6749#section-6

    @Test
    fun `when an refresh access token grant is created, the grant type is refresh_token`() {
        // the grant type is the correct
        assertEquals(OAuth2RefreshAccessTokenGrant.GRANT_TYPE, "refresh_token")
    }
}


================================================
FILE: library/src/test/java/de/rheinfabrik/heimdall2/grants/OAuth2ResourceOwnerPasswordCredentialsGrantTest.kt
================================================
package de.rheinfabrik.heimdall2.grants

import org.junit.Assert.assertEquals
import org.junit.Test

class OAuth2ResourceOwnerPasswordCredentialsGrantTest {

    // Specifications for https://tools.ietf.org/html/rfc6749#section-4.3

    @Test
    fun `when a access token grant is created, the grant type is refresh_token`() {
        // the grant type is the correct
        assertEquals(OAuth2ResourceOwnerPasswordCredentialsGrant.GRANT_TYPE, "password")
    }
}


================================================
FILE: library/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
================================================
mock-maker-inline

================================================
FILE: sample/build.gradle
================================================
apply plugin: 'com.android.application'

buildscript {
    repositories {
        mavenCentral()
    }
}

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.2"

    defaultConfig {
        applicationId "de.rheinfabrik.heimdall"
        minSdkVersion 21
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"
    }

    // https://github.com/evant/gradle-retrolambda#android-studio-setup
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {

    // Android
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'androidx.recyclerview:recyclerview:1.1.0'
    implementation 'androidx.cardview:cardview:1.0.0'

    // Heimdall
    implementation project(':library')

    // Rx
    implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
    implementation 'io.reactivex.rxjava2:rxjava:2.2.19'
    implementation 'com.trello.rxlifecycle3:rxlifecycle-components:3.0.0'

    // Serialization
    implementation 'com.google.code.gson:gson:2.8.6'
    implementation 'org.parceler:parceler-api:0.2.16'

    // Network
    implementation 'com.squareup.retrofit:retrofit:1.9.0'

    // Butterknife
    annotationProcessor 'com.jakewharton:butterknife-compiler:8.2.1'
    implementation 'com.jakewharton:butterknife:8.2.1'
}


================================================
FILE: sample/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/gilo/Library/Android/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html

# Add any project specific keep options here:

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}


================================================
FILE: sample/src/main/AndroidManifest.xml
================================================
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="de.rheinfabrik.heimdalldroid">

    <!-- Permissions -->
    <uses-permission android:name="android.permission.INTERNET" />

    <!-- Application -->
    <application
        android:allowBackup="true"
        android:appComponentFactory="whateverString"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppThemeNoActionBar"
        tools:replace="android:appComponentFactory">

        <!-- Main Activity -->
        <activity
            android:name="de.rheinfabrik.heimdalldroid.actvities.MainActivity"
            android:screenOrientation="portrait">

            <!--Intent filter -->
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

        </activity>

        <!-- Login Activity -->
        <activity
            android:name="de.rheinfabrik.heimdalldroid.actvities.LoginActivity"
            android:screenOrientation="portrait" />

    </application>

</manifest>


================================================
FILE: sample/src/main/java/de/rheinfabrik/heimdalldroid/TraktTvAPIConfiguration.java
================================================
package de.rheinfabrik.heimdalldroid;

public class TraktTvAPIConfiguration {
    public static final String CLIENT_ID = "enter_your_client_id_here";
    public static final String CLIENT_SECRET = "enter_your_client_secret_here";
    public static final String REDIRECT_URI = "enter_your_redirect_uri_here";
}


================================================
FILE: sample/src/main/java/de/rheinfabrik/heimdalldroid/actvities/LoginActivity.java
================================================
package de.rheinfabrik.heimdalldroid.actvities;

import android.graphics.Bitmap;
import android.os.Bundle;
import android.view.View;
import android.webkit.WebView;
import android.webkit.WebViewClient;

import butterknife.BindView;

import com.trello.rxlifecycle3.components.support.RxAppCompatActivity;
import io.reactivex.android.schedulers.AndroidSchedulers;
import java.net.MalformedURLException;
import java.net.URL;

import butterknife.ButterKnife;
import de.rheinfabrik.heimdalldroid.R;
import de.rheinfabrik.heimdalldroid.network.oauth2.TraktTvAuthorizationCodeGrant;
import de.rheinfabrik.heimdalldroid.network.oauth2.TraktTvOauth2AccessTokenManager;
import de.rheinfabrik.heimdalldroid.utils.AlertDialogFactory;
import java.util.Calendar;

/**
 * Activity used to let the user login with his GitHub credentials.
 * You may want to move most of this code to your presenter class or view model.
 * For the sake of simplicity the code is inside the activity.
 */
public class LoginActivity extends RxAppCompatActivity {

    // Members

    @BindView(R.id.webView)
    protected WebView mWebView;

    // Activity lifecycle

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Set content view
        setContentView(R.layout.activity_login);

        // Inject views
        ButterKnife.bind(this);

        // Start authorization
        authorize();
    }

    // Private Api

    private void authorize() {

        // Grab a new token manger
        TraktTvOauth2AccessTokenManager tokenManager = TraktTvOauth2AccessTokenManager.from(this);

        // Grab a new grant
        TraktTvAuthorizationCodeGrant grant = tokenManager.newAuthorizationCodeGrant();

        // Listen for the authorization url and load it once needed
        grant.authorizationUri()
                .map(URL::toString)
                .compose(bindToLifecycle())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(mWebView::loadUrl);

        // Sent loaded website to grant so it can check if we have an access token
        mWebView.setWebViewClient(new WebViewClient() {

            @Override
            public void onPageStarted(WebView view, String urlString, Bitmap favicon) {
                super.onPageStarted(view, urlString, favicon);

                 try {
                     URL url = new URL(urlString);
                     grant.getOnUrlLoadedCommand().onNext(url);

                     // Hide redirect page from user
                     if (urlString.startsWith(grant.getRedirectUri())) {
                         mWebView.setVisibility(View.GONE);
                     }
                 } catch (MalformedURLException ignored) {
                    // Empty

                }
            }
        });

        // Start authorization and listen for success
        tokenManager.grantNewAccessToken(grant, Calendar.getInstance())
                .toObservable()
                .compose(bindToLifecycle())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(x -> handleSuccess(), x -> handleError());
    }

    // Set the result to ok and finish this activity
    private void handleSuccess() {
        setResult(RESULT_OK);
        finish();
    }

    // Build an error dialog and show it
    private void handleError() {
        AlertDialogFactory.errorAlertDialog(this).show();
    }
}


================================================
FILE: sample/src/main/java/de/rheinfabrik/heimdalldroid/actvities/MainActivity.java
================================================
package de.rheinfabrik.heimdalldroid.actvities;

import android.app.Activity;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.webkit.CookieManager;

import android.widget.Toolbar;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.trello.rxlifecycle3.components.support.RxAppCompatActivity;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import java.util.List;

import butterknife.ButterKnife;
import butterknife.BindView;
import de.rheinfabrik.heimdalldroid.R;
import de.rheinfabrik.heimdalldroid.adapter.TraktTvListsRecyclerViewAdapter;
import de.rheinfabrik.heimdalldroid.network.TraktTvApiFactory;
import de.rheinfabrik.heimdalldroid.network.models.TraktTvList;
import de.rheinfabrik.heimdalldroid.network.oauth2.TraktTvOauth2AccessTokenManager;
import de.rheinfabrik.heimdalldroid.utils.AlertDialogFactory;
import de.rheinfabrik.heimdalldroid.utils.IntentFactory;
import retrofit.RetrofitError;

/**
 * Activity showing either the list of the user's repositories or the login screen.
 * You may want to move most of this code to your presenter class or view model.
 * For the sake of simplicity the code is inside the activity.
 */
public class MainActivity extends RxAppCompatActivity {

    // Constants

    private static final int AUTHORIZATION_REQUEST_CODE = 1;

    // Members

    @BindView(R.id.recyclerView)
    protected RecyclerView mRecyclerView;

    @BindView(R.id.toolbar)
    protected Toolbar mToolbar;

    @BindView(R.id.swipeRefreshLayout)
    protected SwipeRefreshLayout mSwipeRefreshLayout;

    private TraktTvOauth2AccessTokenManager mTokenManager;

    // Activity lifecycle

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Set content view
        setContentView(R.layout.activity_main);

        // Inject views
        ButterKnife.bind(this);

        // Setup toolbar
        setActionBar(mToolbar);

        // Setup swipe refresh layout
        mSwipeRefreshLayout.setOnRefreshListener(MainActivity.this::refresh);

        // Setup recycler view
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        mRecyclerView.setLayoutManager(layoutManager);

        // Grab a new manager
        mTokenManager = TraktTvOauth2AccessTokenManager.from(this);

        // Check if we are logged in
        refresh();
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        // Check if the request code is correct
        if (requestCode == AUTHORIZATION_REQUEST_CODE) {

            // Check if login was successful
            if (resultCode == Activity.RESULT_OK) {
                loadLists();
            }
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.menu_main, menu);

        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == R.id.logout) {
            logout();
        }

        return super.onOptionsItemSelected(item);
    }

    // Private Api

    private void loadLists() {

        // Load the lists
        Observable<List<TraktTvList>> listsObservable = mTokenManager

                 /* Grab a valid access token (automatically refreshes the token if it is expired) */
                .getValidAccessToken()

                 /* Load lists */
                .flatMapObservable(authorizationHeader -> TraktTvApiFactory.newApiService().getLists(authorizationHeader));

        // Bind to lifecycle
        listsObservable
                .compose(bindToLifecycle())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(lists -> {
                    if (lists == null || lists.isEmpty()) {
                        handleEmptyList();
                    } else {
                        handleSuccess(lists);
                    }
                }, this::handleError);
    }

    // Start the LoginActivity.
    private void showLogin() {
        mSwipeRefreshLayout.setRefreshing(false);

        Intent loginIntent = IntentFactory.loginIntent(this);
        startActivityForResult(loginIntent, AUTHORIZATION_REQUEST_CODE);
    }

    // Shows a dialog saying that there were no lists.
    private void handleEmptyList() {
        mSwipeRefreshLayout.setRefreshing(false);

        AlertDialogFactory.noListsFoundDialog(this).show();
    }

    // Show an error dialog
    private void handleError(Throwable error) {
        mSwipeRefreshLayout.setRefreshing(false);

        // Clear token and login if 401
        if (error instanceof RetrofitError) {
            RetrofitError retrofitError = (RetrofitError) error;
            if (retrofitError.getResponse().getStatus() == 401) {
                mTokenManager.getStorage().removeAccessToken();

                refresh();
            }
        } else {
            AlertDialogFactory.errorAlertDialog(this).show();
        }
    }

    // Update our recycler view
    private void handleSuccess(List<TraktTvList> traktTvLists) {
        mSwipeRefreshLayout.setRefreshing(false);

        mRecyclerView.setAdapter(new TraktTvListsRecyclerViewAdapter(traktTvLists));
    }

    // Check if logged in and show either login or load lists
    private void refresh() {
        mSwipeRefreshLayout.setRefreshing(true);

        mTokenManager.getStorage().hasAccessToken()
                .toObservable()
                .compose(bindToLifecycle())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(loggedIn -> {
                    if (loggedIn) {
                        loadLists();
                    } else {
                        showLogin();
                    }
                });
    }

    // Logs out the user
    private void logout() {

        // Ask token manager to revoke the token
        mTokenManager.logout()
                .toObservable()
                .compose(bindToLifecycle())
                .subscribe(x -> showLogin());

        // Clear webview cache
        CookieManager cookieManager = CookieManager.getInstance();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            cookieManager.removeAllCookies(null);
        } else {
            cookieManager.removeAllCookie();
        }
    }
}


================================================
FILE: sample/src/main/java/de/rheinfabrik/heimdalldroid/adapter/TraktTvListsRecyclerViewAdapter.java
================================================
package de.rheinfabrik.heimdalldroid.adapter;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.recyclerview.widget.RecyclerView;
import java.util.List;

import de.rheinfabrik.heimdalldroid.R;
import de.rheinfabrik.heimdalldroid.adapter.viewholder.TraktTvListViewHolder;
import de.rheinfabrik.heimdalldroid.network.models.TraktTvList;

/**
 * Simple adapter for showing a list of liked lists.
 */
public class TraktTvListsRecyclerViewAdapter extends RecyclerView.Adapter<TraktTvListViewHolder> {

    // Members

    private final List<TraktTvList> mTraktTvLists;

    // Constructor

    public TraktTvListsRecyclerViewAdapter(List<TraktTvList> traktTvLists) {
        super();

        mTraktTvLists = traktTvLists;
    }

    // RecyclerView.Adapter

    @Override
    public TraktTvListViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_view_trakt_tv_list, parent, false);
        return new TraktTvListViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(TraktTvListViewHolder holder, int position) {
        holder.bind(mTraktTvLists.get(position));
    }

    @Override
    public int getItemCount() {
        if (mTraktTvLists != null) {
            return mTraktTvLists.size();
        }

        return 0;
    }
}


================================================
FILE: sample/src/main/java/de/rheinfabrik/heimdalldroid/adapter/viewholder/TraktTvListViewHolder.java
================================================
package de.rheinfabrik.heimdalldroid.adapter.viewholder;

import android.view.View;
import android.widget.TextView;

import androidx.recyclerview.widget.RecyclerView;
import butterknife.ButterKnife;
import butterknife.BindView;
import de.rheinfabrik.heimdalldroid.R;
import de.rheinfabrik.heimdalldroid.network.models.TraktTvList;

/**
 * View holder used to display a liked list.
 */
public class TraktTvListViewHolder extends RecyclerView.ViewHolder {

    // Members

    @BindView(R.id.titleTextView)
    protected TextView mTitleTextView;

    @BindView(R.id.descriptionTextView)
    protected TextView mDescriptionTextView;

    @BindView(R.id.likeCountTextView)
    protected TextView mLikeCountTextView;

    // Constructor

    public TraktTvListViewHolder(View itemView) {
        super(itemView);

        // Inject views
        ButterKnife.bind(this, itemView);
    }

    // Public Api

    public void bind(TraktTvList traktTvList) {

        // Set title
        mTitleTextView.setText(traktTvList.name);

        // Set description
        mDescriptionTextView.setText(traktTvList.description);

        // Set like count
        mLikeCountTextView.setText(traktTvList.numberOfLikes + itemView.getContext().getString(R.string.likes_postfix));
    }
}


================================================
FILE: sample/src/main/java/de/rheinfabrik/heimdalldroid/network/TraktTvApiFactory.java
================================================
package de.rheinfabrik.heimdalldroid.network;

import com.google.gson.Gson;

import de.rheinfabrik.heimdalldroid.TraktTvAPIConfiguration;
import retrofit.RestAdapter;
import retrofit.converter.GsonConverter;

/**
 * Factory class to generate new TraktTvApiService instances.
 */
public class TraktTvApiFactory {

    // Constants

    private static final String API_ENDPOINT = "https://api-v2launch.trakt.tv";

    // Public Api

    /**
     * Creates a new preconfigured TraktTvApiService instance.
     */
    public static TraktTvApiService newApiService() {

        // Set up rest adapter
        RestAdapter.Builder restAdapterBuilder = new RestAdapter.Builder()
                .setConverter(new GsonConverter(new Gson()))
                .setRequestInterceptor(request -> {
                    request.addHeader("Content-type", "application/json");
                    request.addHeader("trakt-api-version", "2");
                    request.addHeader("trakt-api-key", TraktTvAPIConfiguration.CLIENT_ID);
                })
                .setLogLevel(RestAdapter.LogLevel.FULL)
                .setEndpoint(API_ENDPOINT);

        // Build raw api service
        return restAdapterBuilder.build().create(TraktTvApiService.class);
    }

}


================================================
FILE: sample/src/main/java/de/rheinfabrik/heimdalldroid/network/TraktTvApiService.java
================================================
package de.rheinfabrik.heimdalldroid.network;

import io.reactivex.Observable;
import java.util.List;

import de.rheinfabrik.heimdall2.OAuth2AccessToken;
import de.rheinfabrik.heimdalldroid.network.models.AccessTokenRequestBody;
import de.rheinfabrik.heimdalldroid.network.models.RefreshTokenRequestBody;
import de.rheinfabrik.heimdalldroid.network.models.RevokeAccessTokenBody;
import de.rheinfabrik.heimdalldroid.network.models.TraktTvList;
import retrofit.http.Body;
import retrofit.http.GET;
import retrofit.http.Header;
import retrofit.http.POST;

/**
 * Interface for communicating to the TraktTv API (http://docs.trakt.apiary.io/#).
 */
public interface TraktTvApiService {

    // POST

    @POST("/oauth/token")
    Observable<OAuth2AccessToken> grantNewAccessToken(@Body AccessTokenRequestBody body);

    @POST("/oauth/token")
    Observable<OAuth2AccessToken> refreshAccessToken(@Body RefreshTokenRequestBody body);

    @POST("/oauth/revoke")
    Observable<Void> revokeAccessToken(@Body RevokeAccessTokenBody body);

    // GET

    @GET("/users/me/lists")
    Observable<List<TraktTvList>> getLists(@Header("Authorization") String authorizationHeader);
}


================================================
FILE: sample/src/main/java/de/rheinfabrik/heimdalldroid/network/models/AccessTokenRequestBody.java
================================================
package de.rheinfabrik.heimdalldroid.network.models;

import com.google.gson.annotations.SerializedName;

import java.io.Serializable;

/**
 * Body object used to exchange a code with an access token.
 */
public class AccessTokenRequestBody implements Serializable {

    // Properties

    @SerializedName("code")
    public String code;

    @SerializedName("client_id")
    public String clientId;

    @SerializedName("client_secret")
    public String clientSecret;

    @SerializedName("redirect_uri")
    public String redirectUri;

    @SerializedName("grant_type")
    public String grantType;

    // Constructor

    public AccessTokenRequestBody(String code, String clientId, String redirectUri, String clientSecret, String grantType) {
        super();

        this.code = code;
        this.clientId = clientId;
        this.redirectUri = redirectUri;
        this.clientSecret = clientSecret;
        this.grantType = grantType;
    }
}


================================================
FILE: sample/src/main/java/de/rheinfabrik/heimdalldroid/network/models/RefreshTokenRequestBody.java
================================================
package de.rheinfabrik.heimdalldroid.network.models;

import com.google.gson.annotations.SerializedName;

import java.io.Serializable;

/**
 * Body used to refresh an access token.
 */
public class RefreshTokenRequestBody implements Serializable {

    // Properties

    @SerializedName("refresh_token")
    public String refreshToken;

    @SerializedName("client_id")
    public String clientId;

    @SerializedName("client_secret")
    public String clientSecret;

    @SerializedName("redirect_uri")
    public String redirectUri;

    @SerializedName("grant_type")
    public String grantType;

    // Constructor

    public RefreshTokenRequestBody(String refreshToken, String clientId, String clientSecret, String redirectUri, String grantType) {
        super();

        this.refreshToken = refreshToken;
        this.clientId = clientId;
        this.clientSecret = clientSecret;
        this.redirectUri = redirectUri;
        this.grantType = grantType;
    }
}


================================================
FILE: sample/src/main/java/de/rheinfabrik/heimdalldroid/network/models/RevokeAccessTokenBody.java
================================================
package de.rheinfabrik.heimdalldroid.network.models;

import com.google.gson.annotations.SerializedName;

/**
 * Body used to revoke an access token.
 */
public class RevokeAccessTokenBody {

    // Properties

    @SerializedName("access_token")
    public String accessToken;

    // Constructor

    public RevokeAccessTokenBody(String accessToken) {
        super();

        this.accessToken = accessToken;
    }
}


================================================
FILE: sample/src/main/java/de/rheinfabrik/heimdalldroid/network/models/TraktTvList.java
================================================
package de.rheinfabrik.heimdalldroid.network.models;

import com.google.gson.annotations.SerializedName;

import java.io.Serializable;

/**
 * Model describing a TraktTvList.
 */
public class TraktTvList implements Serializable {

    // Properties

    @SerializedName("name")
    public String name;

    @SerializedName("description")
    public String description;

    @SerializedName("likes")
    public int numberOfLikes;
}


================================================
FILE: sample/src/main/java/de/rheinfabrik/heimdalldroid/network/oauth2/TraktTvAuthorizationCodeGrant.java
================================================
package de.rheinfabrik.heimdalldroid.network.oauth2;

import android.net.Uri;

import de.rheinfabrik.heimdall2.grants.OAuth2AuthorizationCodeGrant;
import io.reactivex.Observable;
import java.net.URL;

import de.rheinfabrik.heimdall2.OAuth2AccessToken;
import de.rheinfabrik.heimdalldroid.network.TraktTvApiFactory;
import de.rheinfabrik.heimdalldroid.network.models.AccessTokenRequestBody;

/**
 * TraktTv authorization code grant as described in http://docs.trakt.apiary.io/#reference/authentication-oauth.
 */
public class TraktTvAuthorizationCodeGrant extends OAuth2AuthorizationCodeGrant {

    // Properties

    public String clientSecret;

    // OAuth2AuthorizationCodeGrant

    @Override
    public URL buildAuthorizationUrl() {
        try {
            return new URL(
                    Uri.parse("https://trakt.tv/oauth/authorize")
                            .buildUpon()
                            .appendQueryParameter("client_id", getClientId())
                            .appendQueryParameter("redirect_uri", getRedirectUri())
                            .appendQueryParameter("response_type", OAuth2AuthorizationCodeGrant.RESPONSE_TYPE)
                            .build()
                            .toString()
            );
        } catch (Exception ignored) {
            return null;
        }
    }

    @Override
    public Observable<OAuth2AccessToken> exchangeTokenUsingCode(String code) {
        AccessTokenRequestBody body = new AccessTokenRequestBody(
                code, getClientId(), getRedirectUri(), clientSecret, GRANT_TYPE
        );
        return TraktTvApiFactory.newApiService().grantNewAccessToken(body);
    }
}


================================================
FILE: sample/src/main/java/de/rheinfabrik/heimdalldroid/network/oauth2/TraktTvOauth2AccessTokenManager.java
================================================
package de.rheinfabrik.heimdalldroid.network.oauth2;

import android.content.Context;
import android.content.SharedPreferences;

import de.rheinfabrik.heimdall2.OAuth2AccessToken;
import de.rheinfabrik.heimdall2.OAuth2AccessTokenManager;
import de.rheinfabrik.heimdall2.OAuth2AccessTokenStorage;
import de.rheinfabrik.heimdalldroid.TraktTvAPIConfiguration;
import de.rheinfabrik.heimdalldroid.network.TraktTvApiFactory;
import de.rheinfabrik.heimdalldroid.network.models.RevokeAccessTokenBody;
import de.rheinfabrik.heimdalldroid.utils.SharedPreferencesOAuth2AccessTokenStorage;
import io.reactivex.Single;

/**
 * Token manger used to handle all your access token needs with the TraktTv API (http://docs.trakt.apiary.io/#).
 */
public final class TraktTvOauth2AccessTokenManager extends OAuth2AccessTokenManager {

    // Factory methods

    /**
     * Creates a new preconfigured TraktTvOauth2AccessTokenManager based of a context.
     */
    public static TraktTvOauth2AccessTokenManager from(Context context) {

        // Define the shared preferences where we will save the access token
        SharedPreferences sharedPreferences = context.getSharedPreferences("TraktTvAccessTokenStorage", Context.MODE_PRIVATE);

        // Define the storage using the the previously defined preferences
        SharedPreferencesOAuth2AccessTokenStorage<OAuth2AccessToken> tokenStorage = new SharedPreferencesOAuth2AccessTokenStorage<>(sharedPreferences, OAuth2AccessToken.class);

        // Create the new TraktTvOauth2AccessTokenManager
        return new TraktTvOauth2AccessTokenManager(tokenStorage);
    }

    // Constructor

    public TraktTvOauth2AccessTokenManager(OAuth2AccessTokenStorage storage) {
        super(storage);
    }

    // Public Api

    /**
     * Creates a new preconfigured TraktTvAuthorizationCodeGrant.
     */
    public TraktTvAuthorizationCodeGrant newAuthorizationCodeGrant() {
        TraktTvAuthorizationCodeGrant grant = new TraktTvAuthorizationCodeGrant();
        grant.setClientId(TraktTvAPIConfiguration.CLIENT_ID);
        grant.clientSecret = TraktTvAPIConfiguration.CLIENT_SECRET;
        grant.setRedirectUri(TraktTvAPIConfiguration.REDIRECT_URI);

        return grant;
    }

    /**
     * Returns a valid authorization header string using a preconfigured TraktTvRefreshAccessTokenGrant.
     */
    public Single<String> getValidAccessToken() {
        TraktTvRefreshAccessTokenGrant grant = new TraktTvRefreshAccessTokenGrant();
        grant.clientId = TraktTvAPIConfiguration.CLIENT_ID;
        grant.clientSecret = TraktTvAPIConfiguration.CLIENT_SECRET;
        grant.redirectUri = TraktTvAPIConfiguration.REDIRECT_URI;

        return super.getValidAccessToken(grant).map(token -> token.getTokenType() + " " + token.getAccessToken());
    }

    /**
     * Logs out the user if he is logged in.
     */
    public Single<Void> logout() {
        return getStorage().getStoredAccessToken()
                .toObservable()
                .filter(token -> token != null)
                .concatMap(accessToken -> {
                    RevokeAccessTokenBody body = new RevokeAccessTokenBody(accessToken.getAccessToken());
                    return TraktTvApiFactory.newApiService().revokeAccessToken(body);
                })
                .doOnNext(x -> getStorage().removeAccessToken()).singleOrError();
    }
}


================================================
FILE: sample/src/main/java/de/rheinfabrik/heimdalldroid/network/oauth2/TraktTvRefreshAccessTokenGrant.java
================================================
package de.rheinfabrik.heimdalldroid.network.oauth2;

import de.rheinfabrik.heimdall2.OAuth2AccessToken;
import de.rheinfabrik.heimdall2.grants.OAuth2RefreshAccessTokenGrant;
import de.rheinfabrik.heimdalldroid.network.TraktTvApiFactory;
import de.rheinfabrik.heimdalldroid.network.models.RefreshTokenRequestBody;
import io.reactivex.Single;

/**
 * TraktTv refresh token grant as described in http://docs.trakt.apiary.io/#reference/authentication-oauth/token/exchange-refresh_token-for-access_token.
 */
public class TraktTvRefreshAccessTokenGrant extends OAuth2RefreshAccessTokenGrant {

    // Properties

    public String clientSecret;
    public String clientId;
    public String redirectUri;

    // OAuth2RefreshAccessTokenGrant

    @Override
    public Single<OAuth2AccessToken> grantNewAccessToken() {
        RefreshTokenRequestBody body = new RefreshTokenRequestBody(getRefreshToken(), clientId, clientSecret, redirectUri, GRANT_TYPE);
        return TraktTvApiFactory.newApiService().refreshAccessToken(body).singleOrError();
    }
}


================================================
FILE: sample/src/main/java/de/rheinfabrik/heimdalldroid/utils/AlertDialogFactory.java
================================================
package de.rheinfabrik.heimdalldroid.utils;

import android.app.AlertDialog;
import android.content.Context;

import de.rheinfabrik.heimdalldroid.R;

/**
 * Factory used to create AlertDialogs.
 */
public class AlertDialogFactory {

    // Public Api

    /**
     * Creates an error dialog.
     */
    public static AlertDialog errorAlertDialog(Context context) {
        return new AlertDialog.Builder(context)
                .setTitle(R.string.error_title)
                .setMessage(R.string.error_message)
                .setPositiveButton(R.string.ok, (dialog, which) -> dialog.dismiss()).create();
    }

    /**
     * Creates an dialog informing the user that there are no liked lists found.
     */
    public static AlertDialog noListsFoundDialog(Context context) {
        return new AlertDialog.Builder(context)
                .setTitle(R.string.error_title)
                .setMessage(R.string.no_liked_lists_message)
                .setPositiveButton(R.string.ok, (dialog, which) -> dialog.dismiss()).create();
    }

}


================================================
FILE: sample/src/main/java/de/rheinfabrik/heimdalldroid/utils/IntentFactory.java
================================================
package de.rheinfabrik.heimdalldroid.utils;

import android.content.Context;
import android.content.Intent;

import de.rheinfabrik.heimdalldroid.actvities.LoginActivity;

/**
 * Factory used to create Intents.
 */
public class IntentFactory {

    // Public Api

    /**
     * Creates an intent for showing the LoginActivity
     */
    public static Intent loginIntent(Context context) {
        return new Intent(context, LoginActivity.class);
    }
}


================================================
FILE: sample/src/main/java/de/rheinfabrik/heimdalldroid/utils/SharedPreferencesOAuth2AccessTokenStorage.java
================================================
package de.rheinfabrik.heimdalldroid.utils;

import android.content.SharedPreferences;

import com.google.gson.Gson;

import de.rheinfabrik.heimdall2.OAuth2AccessToken;
import de.rheinfabrik.heimdall2.OAuth2AccessTokenStorage;
import io.reactivex.Single;

/**
 * A simple storage that saves the access token as plain text in the passed shared preferences.
 * It is recommend to set the access mode to MODE_PRIVATE.
 *
 * @param <TAccessToken> The access token type.
 */
public class SharedPreferencesOAuth2AccessTokenStorage<TAccessToken extends OAuth2AccessToken> implements OAuth2AccessTokenStorage {

    // Constants

    private static final String ACCESS_TOKEN_PREFERENCES_KEY = "OAuth2AccessToken";

    // Members

    private final SharedPreferences mSharedPreferences;
    private final Class mTokenClass;

    // Constructor

    /**
     * Designated constructor.
     *
     * @param sharedPreferences The shared preferences used for saving the access token.
     * @param tokenClass        The actual class of the access token.
     */
    public SharedPreferencesOAuth2AccessTokenStorage(SharedPreferences sharedPreferences, Class tokenClass) {
        super();

        if (tokenClass == null) {
            throw new RuntimeException("TokenClass MUST NOT be null.");
        }

        if (sharedPreferences == null) {
            throw new RuntimeException("SharedPreferences MUST NOT be null.");
        }

        mTokenClass = tokenClass;
        mSharedPreferences = sharedPreferences;
    }

    // OAuth2AccessTokenStorage

    @Override
    public Single<OAuth2AccessToken> getStoredAccessToken() {
        return Single
                .just(mSharedPreferences.getString(ACCESS_TOKEN_PREFERENCES_KEY, null))
                .map(json -> (TAccessToken) new Gson().fromJson(json, mTokenClass));
    }

    @Override
    public void storeAccessToken(OAuth2AccessToken token) {
        mSharedPreferences
                .edit()
                .putString(ACCESS_TOKEN_PREFERENCES_KEY, new Gson().toJson(token))
                .apply();
    }

    @Override
    public Single<Boolean> hasAccessToken() {
        return Single.just(mSharedPreferences.contains(ACCESS_TOKEN_PREFERENCES_KEY));
    }

    @Override
    public void removeAccessToken() {
        mSharedPreferences
                .edit()
                .remove(ACCESS_TOKEN_PREFERENCES_KEY)
                .apply();
    }
}


================================================
FILE: sample/src/main/res/layout/activity_login.xml
================================================
<?xml version="1.0" encoding="utf-8"?>

<!-- WebView -->
<WebView android:id="@+id/webView"
         xmlns:android="http://schemas.android.com/apk/res/android"
         android:layout_width="match_parent"
         android:layout_height="match_parent"/>


================================================
FILE: sample/src/main/res/layout/activity_main.xml
================================================
<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <!-- Toolbar -->
    <android.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="@color/red_orange"
        android:elevation="1dp"
        android:theme="@style/AppThemeNoActionBar" />

    <!-- Swipe refresh layout -->
    <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
        android:id="@+id/swipeRefreshLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <!-- RecyclerView -->
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@color/hint_of_red" />

    </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

</LinearLayout>


================================================
FILE: sample/src/main/res/layout/item_view_trakt_tv_list.xml
================================================
<?xml version="1.0" encoding="utf-8"?>

<!-- Card view -->
<android.support.v7.widget.CardView
    android:id="@+id/search_result_card"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="5dp"
    card_view:cardCornerRadius="4dp">

    <!-- Container -->
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:layout_marginBottom="8dp"
                    android:layout_marginLeft="16dp"
                    android:layout_marginRight="16dp"
                    android:layout_marginTop="8dp">

        <!-- Title -->
        <TextView
            android:id="@+id/titleTextView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:textColor="@color/bunker"
            android:textSize="16sp"
            android:textStyle="bold"
            tools:text="Title"/>


        <!-- Description -->
        <TextView
            android:id="@+id/descriptionTextView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@+id/titleTextView"
            android:layout_toLeftOf="@+id/likeCountTextView"
            android:textColor="@color/bunker"
            android:textSize="14sp"
            tools:text="Description"/>

        <!-- Number of likes -->
        <TextView
            android:id="@+id/likeCountTextView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true"
            android:textColor="@color/red_orange"
            android:textSize="14sp"
            tools:text="100 Likes"/>

    </RelativeLayout>

</android.support.v7.widget.CardView>


================================================
FILE: sample/src/main/res/menu/menu_main.xml
================================================
<?xml version="1.0" encoding="utf-8"?>


<menu xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto"
    >

    <!-- Logout -->
    <item
        android:id="@+id/logout"
        android:title="@string/menu_action_logout"
        app:showAsAction="always"/>
</menu>


================================================
FILE: sample/src/main/res/values/colors.xml
================================================
<?xml version="1.0" encoding="utf-8"?>

<resources>

    <color name="white">#FFFFFFFF</color>
    <color name="hint_of_red">#F9F9F9</color>
    <color name="persian_red">#FFD32F2F</color>
    <color name="red_orange">#FFF44336</color>
    <color name="bunker">#FF282B2E</color>

</resources>


================================================
FILE: sample/src/main/res/values/strings.xml
================================================
<?xml version="1.0" encoding="utf-8"?>

<resources>

    <!-- The Application name -->
    <string name="app_name">Heimdall.droid</string>

    <!-- Dialogs -->
    <string name="error_title">Oooops</string>
    <string name="error_message">There was an error. Please try again later.</string>
    <string name="no_liked_lists_message">There are no lists on your account. Please create some
        over at http://trakt.tv!
    </string>
    <string name="ok">OK</string>

    <!-- Item views -->
    <string name="likes_postfix">" Likes"</string>
    <string name="menu_action_logout">Logout</string>

</resources>


================================================
FILE: sample/src/main/res/values/styles.xml
================================================
<?xml version="1.0" encoding="utf-8"?>

<resources>

    <!-- Base application theme. -->
    <style name="AppThemeNoActionBar" parent="Theme.AppCompat.Light.NoActionBar">

        <!-- Colors -->
        <item name="colorPrimary">@color/red_orange</item>
        <item name="colorPrimaryDark">@color/persian_red</item>
        <item name="android:textColorPrimary">@color/white</item>

    </style>

</resources>


================================================
FILE: settings.gradle
================================================
include ':sample', ':library'
Download .txt
gitextract_8ir_jlm8/

├── .gitignore
├── .travis.yml
├── LICENSE.txt
├── README.md
├── build.gradle
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── library/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       ├── main/
│       │   └── java/
│       │       └── de/
│       │           └── rheinfabrik/
│       │               └── heimdall2/
│       │                   ├── OAuth2AccessToken.kt
│       │                   ├── OAuth2AccessTokenManager.kt
│       │                   ├── OAuth2AccessTokenStorage.kt
│       │                   └── grants/
│       │                       ├── OAuth2AuthorizationCodeGrant.kt
│       │                       ├── OAuth2ClientCredentialsGrant.kt
│       │                       ├── OAuth2Grant.kt
│       │                       ├── OAuth2ImplicitGrant.kt
│       │                       ├── OAuth2RefreshAccessTokenGrant.kt
│       │                       └── OAuth2ResourceOwnerPasswordCredentialsGrant.kt
│       └── test/
│           ├── java/
│           │   └── de/
│           │       └── rheinfabrik/
│           │           └── heimdall2/
│           │               ├── OAuth2AccessTokenIsExpiredTest.kt
│           │               ├── OAuth2AccessTokenManagerGetValidAccessTokenTest.kt
│           │               ├── OAuth2AccessTokenManagerGrantNewAccessTokenTest.kt
│           │               ├── OAuth2AccessTokenManagerTest.kt
│           │               ├── OAuth2AccessTokenSerializationTest.kt
│           │               └── grants/
│           │                   ├── OAuth2AuthorizationCodeGrantTest.kt
│           │                   ├── OAuth2ClientCredentialsGrantTest.kt
│           │                   ├── OAuth2ImplicitGrantTest.kt
│           │                   ├── OAuth2RefreshAccessTokenGrantTest.kt
│           │                   └── OAuth2ResourceOwnerPasswordCredentialsGrantTest.kt
│           └── resources/
│               └── mockito-extensions/
│                   └── org.mockito.plugins.MockMaker
├── sample/
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       └── main/
│           ├── AndroidManifest.xml
│           ├── java/
│           │   └── de/
│           │       └── rheinfabrik/
│           │           └── heimdalldroid/
│           │               ├── TraktTvAPIConfiguration.java
│           │               ├── actvities/
│           │               │   ├── LoginActivity.java
│           │               │   └── MainActivity.java
│           │               ├── adapter/
│           │               │   ├── TraktTvListsRecyclerViewAdapter.java
│           │               │   └── viewholder/
│           │               │       └── TraktTvListViewHolder.java
│           │               ├── network/
│           │               │   ├── TraktTvApiFactory.java
│           │               │   ├── TraktTvApiService.java
│           │               │   ├── models/
│           │               │   │   ├── AccessTokenRequestBody.java
│           │               │   │   ├── RefreshTokenRequestBody.java
│           │               │   │   ├── RevokeAccessTokenBody.java
│           │               │   │   └── TraktTvList.java
│           │               │   └── oauth2/
│           │               │       ├── TraktTvAuthorizationCodeGrant.java
│           │               │       ├── TraktTvOauth2AccessTokenManager.java
│           │               │       └── TraktTvRefreshAccessTokenGrant.java
│           │               └── utils/
│           │                   ├── AlertDialogFactory.java
│           │                   ├── IntentFactory.java
│           │                   └── SharedPreferencesOAuth2AccessTokenStorage.java
│           └── res/
│               ├── layout/
│               │   ├── activity_login.xml
│               │   ├── activity_main.xml
│               │   └── item_view_trakt_tv_list.xml
│               ├── menu/
│               │   └── menu_main.xml
│               └── values/
│                   ├── colors.xml
│                   ├── strings.xml
│                   └── styles.xml
└── settings.gradle
Download .txt
SYMBOL INDEX (62 symbols across 17 files)

FILE: sample/src/main/java/de/rheinfabrik/heimdalldroid/TraktTvAPIConfiguration.java
  class TraktTvAPIConfiguration (line 3) | public class TraktTvAPIConfiguration {

FILE: sample/src/main/java/de/rheinfabrik/heimdalldroid/actvities/LoginActivity.java
  class LoginActivity (line 28) | public class LoginActivity extends RxAppCompatActivity {
    method onCreate (line 37) | @Override
    method authorize (line 53) | private void authorize() {
    method handleSuccess (line 99) | private void handleSuccess() {
    method handleError (line 105) | private void handleError() {

FILE: sample/src/main/java/de/rheinfabrik/heimdalldroid/actvities/MainActivity.java
  class MainActivity (line 37) | public class MainActivity extends RxAppCompatActivity {
    method onCreate (line 58) | @Override
    method onActivityResult (line 86) | @Override
    method onCreateOptionsMenu (line 100) | @Override
    method onOptionsItemSelected (line 108) | @Override
    method loadLists (line 119) | private void loadLists() {
    method showLogin (line 144) | private void showLogin() {
    method handleEmptyList (line 152) | private void handleEmptyList() {
    method handleError (line 159) | private void handleError(Throwable error) {
    method handleSuccess (line 176) | private void handleSuccess(List<TraktTvList> traktTvLists) {
    method refresh (line 183) | private void refresh() {
    method logout (line 200) | private void logout() {

FILE: sample/src/main/java/de/rheinfabrik/heimdalldroid/adapter/TraktTvListsRecyclerViewAdapter.java
  class TraktTvListsRecyclerViewAdapter (line 17) | public class TraktTvListsRecyclerViewAdapter extends RecyclerView.Adapte...
    method TraktTvListsRecyclerViewAdapter (line 25) | public TraktTvListsRecyclerViewAdapter(List<TraktTvList> traktTvLists) {
    method onCreateViewHolder (line 33) | @Override
    method onBindViewHolder (line 39) | @Override
    method getItemCount (line 44) | @Override

FILE: sample/src/main/java/de/rheinfabrik/heimdalldroid/adapter/viewholder/TraktTvListViewHolder.java
  class TraktTvListViewHolder (line 15) | public class TraktTvListViewHolder extends RecyclerView.ViewHolder {
    method TraktTvListViewHolder (line 30) | public TraktTvListViewHolder(View itemView) {
    method bind (line 39) | public void bind(TraktTvList traktTvList) {

FILE: sample/src/main/java/de/rheinfabrik/heimdalldroid/network/TraktTvApiFactory.java
  class TraktTvApiFactory (line 12) | public class TraktTvApiFactory {
    method newApiService (line 23) | public static TraktTvApiService newApiService() {

FILE: sample/src/main/java/de/rheinfabrik/heimdalldroid/network/TraktTvApiService.java
  type TraktTvApiService (line 19) | public interface TraktTvApiService {
    method grantNewAccessToken (line 23) | @POST("/oauth/token")
    method refreshAccessToken (line 26) | @POST("/oauth/token")
    method revokeAccessToken (line 29) | @POST("/oauth/revoke")
    method getLists (line 34) | @GET("/users/me/lists")

FILE: sample/src/main/java/de/rheinfabrik/heimdalldroid/network/models/AccessTokenRequestBody.java
  class AccessTokenRequestBody (line 10) | public class AccessTokenRequestBody implements Serializable {
    method AccessTokenRequestBody (line 31) | public AccessTokenRequestBody(String code, String clientId, String red...

FILE: sample/src/main/java/de/rheinfabrik/heimdalldroid/network/models/RefreshTokenRequestBody.java
  class RefreshTokenRequestBody (line 10) | public class RefreshTokenRequestBody implements Serializable {
    method RefreshTokenRequestBody (line 31) | public RefreshTokenRequestBody(String refreshToken, String clientId, S...

FILE: sample/src/main/java/de/rheinfabrik/heimdalldroid/network/models/RevokeAccessTokenBody.java
  class RevokeAccessTokenBody (line 8) | public class RevokeAccessTokenBody {
    method RevokeAccessTokenBody (line 17) | public RevokeAccessTokenBody(String accessToken) {

FILE: sample/src/main/java/de/rheinfabrik/heimdalldroid/network/models/TraktTvList.java
  class TraktTvList (line 10) | public class TraktTvList implements Serializable {

FILE: sample/src/main/java/de/rheinfabrik/heimdalldroid/network/oauth2/TraktTvAuthorizationCodeGrant.java
  class TraktTvAuthorizationCodeGrant (line 16) | public class TraktTvAuthorizationCodeGrant extends OAuth2AuthorizationCo...
    method buildAuthorizationUrl (line 24) | @Override
    method exchangeTokenUsingCode (line 41) | @Override

FILE: sample/src/main/java/de/rheinfabrik/heimdalldroid/network/oauth2/TraktTvOauth2AccessTokenManager.java
  class TraktTvOauth2AccessTokenManager (line 18) | public final class TraktTvOauth2AccessTokenManager extends OAuth2AccessT...
    method from (line 25) | public static TraktTvOauth2AccessTokenManager from(Context context) {
    method TraktTvOauth2AccessTokenManager (line 39) | public TraktTvOauth2AccessTokenManager(OAuth2AccessTokenStorage storag...
    method newAuthorizationCodeGrant (line 48) | public TraktTvAuthorizationCodeGrant newAuthorizationCodeGrant() {
    method getValidAccessToken (line 60) | public Single<String> getValidAccessToken() {
    method logout (line 72) | public Single<Void> logout() {

FILE: sample/src/main/java/de/rheinfabrik/heimdalldroid/network/oauth2/TraktTvRefreshAccessTokenGrant.java
  class TraktTvRefreshAccessTokenGrant (line 12) | public class TraktTvRefreshAccessTokenGrant extends OAuth2RefreshAccessT...
    method grantNewAccessToken (line 22) | @Override

FILE: sample/src/main/java/de/rheinfabrik/heimdalldroid/utils/AlertDialogFactory.java
  class AlertDialogFactory (line 11) | public class AlertDialogFactory {
    method errorAlertDialog (line 18) | public static AlertDialog errorAlertDialog(Context context) {
    method noListsFoundDialog (line 28) | public static AlertDialog noListsFoundDialog(Context context) {

FILE: sample/src/main/java/de/rheinfabrik/heimdalldroid/utils/IntentFactory.java
  class IntentFactory (line 11) | public class IntentFactory {
    method loginIntent (line 18) | public static Intent loginIntent(Context context) {

FILE: sample/src/main/java/de/rheinfabrik/heimdalldroid/utils/SharedPreferencesOAuth2AccessTokenStorage.java
  class SharedPreferencesOAuth2AccessTokenStorage (line 17) | public class SharedPreferencesOAuth2AccessTokenStorage<TAccessToken exte...
    method SharedPreferencesOAuth2AccessTokenStorage (line 36) | public SharedPreferencesOAuth2AccessTokenStorage(SharedPreferences sha...
    method getStoredAccessToken (line 53) | @Override
    method storeAccessToken (line 60) | @Override
    method hasAccessToken (line 68) | @Override
    method removeAccessToken (line 73) | @Override
Condensed preview — 61 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (103K chars).
[
  {
    "path": ".gitignore",
    "chars": 646,
    "preview": "# Built application files\n*.apk\n*.ap_\nbin/\ngen/\nclasses/\ngen-external-apklibs/\n\n# Eclipse project files\n.classpath\n.proj"
  },
  {
    "path": ".travis.yml",
    "chars": 827,
    "preview": "language: android\nsudo: required\njdk: oraclejdk8\n\nenv:\n  global:\n    - ANDROID_API_LEVEL=30\n    - ANDROID_BUILD_TOOLS_VE"
  },
  {
    "path": "LICENSE.txt",
    "chars": 11339,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "README.md",
    "chars": 6561,
    "preview": "# Deprecated Project\n\n⚠️ This project is no longer maintained\n\nThis repository is deprecated and will no longer receive "
  },
  {
    "path": "build.gradle",
    "chars": 470,
    "preview": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n    r"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "chars": 233,
    "preview": "#Tue Oct 24 09:19:37 CEST 2017\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER"
  },
  {
    "path": "gradle.properties",
    "chars": 909,
    "preview": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will o"
  },
  {
    "path": "gradlew",
    "chars": 5080,
    "preview": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start "
  },
  {
    "path": "gradlew.bat",
    "chars": 2404,
    "preview": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@r"
  },
  {
    "path": "library/.gitignore",
    "chars": 7,
    "preview": "/build\n"
  },
  {
    "path": "library/build.gradle",
    "chars": 976,
    "preview": "apply plugin: 'kotlin'\napply plugin: 'groovy'\napply plugin: 'maven'\n\nbuildscript {\n    ext.kotlin_version = '1.4.10'\n\n  "
  },
  {
    "path": "library/proguard-rules.pro",
    "chars": 662,
    "preview": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /U"
  },
  {
    "path": "library/src/main/java/de/rheinfabrik/heimdall2/OAuth2AccessToken.kt",
    "chars": 1728,
    "preview": "package de.rheinfabrik.heimdall2\n\nimport com.google.gson.annotations.SerializedName\nimport java.io.Serializable\nimport j"
  },
  {
    "path": "library/src/main/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenManager.kt",
    "chars": 2289,
    "preview": "package de.rheinfabrik.heimdall2\n\nimport de.rheinfabrik.heimdall2.grants.OAuth2Grant\nimport de.rheinfabrik.heimdall2.gra"
  },
  {
    "path": "library/src/main/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenStorage.kt",
    "chars": 936,
    "preview": "package de.rheinfabrik.heimdall2\n\nimport io.reactivex.Single\n\n/**\n * Interface used to define how to store and retrieve "
  },
  {
    "path": "library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2AuthorizationCodeGrant.kt",
    "chars": 3882,
    "preview": "package de.rheinfabrik.heimdall2.grants\n\nimport de.rheinfabrik.heimdall2.OAuth2AccessToken\nimport io.reactivex.Observabl"
  },
  {
    "path": "library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2ClientCredentialsGrant.kt",
    "chars": 468,
    "preview": "package de.rheinfabrik.heimdall2.grants\n\nabstract class OAuth2ClientCredentialsGrant(\n    /**\n     * OPTIONAL\n     * The"
  },
  {
    "path": "library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2Grant.kt",
    "chars": 537,
    "preview": "package de.rheinfabrik.heimdall2.grants\n\nimport de.rheinfabrik.heimdall2.OAuth2AccessToken\nimport io.reactivex.Single\n\n/"
  },
  {
    "path": "library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2ImplicitGrant.kt",
    "chars": 1340,
    "preview": "package de.rheinfabrik.heimdall2.grants\n\n/**\n * Class representing the Implicit Grant as described in https://tools.ietf"
  },
  {
    "path": "library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2RefreshAccessTokenGrant.kt",
    "chars": 576,
    "preview": "package de.rheinfabrik.heimdall2.grants\n\nabstract class OAuth2RefreshAccessTokenGrant(\n    /**\n     * REQUIRED\n     * Th"
  },
  {
    "path": "library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2ResourceOwnerPasswordCredentialsGrant.kt",
    "chars": 590,
    "preview": "package de.rheinfabrik.heimdall2.grants\n\nabstract class OAuth2ResourceOwnerPasswordCredentialsGrant(\n    /**\n     * REQU"
  },
  {
    "path": "library/src/test/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenIsExpiredTest.kt",
    "chars": 1541,
    "preview": "package de.rheinfabrik.heimdall2\n\nimport org.junit.Assert.assertEquals\nimport org.junit.Test\nimport java.util.*\n\nclass O"
  },
  {
    "path": "library/src/test/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenManagerGetValidAccessTokenTest.kt",
    "chars": 3225,
    "preview": "package de.rheinfabrik.heimdall2\n\nimport com.nhaarman.mockitokotlin2.mock\nimport com.nhaarman.mockitokotlin2.verify\nimpo"
  },
  {
    "path": "library/src/test/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenManagerGrantNewAccessTokenTest.kt",
    "chars": 3219,
    "preview": "package de.rheinfabrik.heimdall2\n\nimport com.nhaarman.mockitokotlin2.mock\nimport com.nhaarman.mockitokotlin2.verify\nimpo"
  },
  {
    "path": "library/src/test/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenManagerTest.kt",
    "chars": 1205,
    "preview": "package de.rheinfabrik.heimdall2\n\nimport com.nhaarman.mockitokotlin2.mock\nimport com.nhaarman.mockitokotlin2.whenever\nim"
  },
  {
    "path": "library/src/test/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenSerializationTest.kt",
    "chars": 2396,
    "preview": "package de.rheinfabrik.heimdall2\n\nimport com.google.gson.Gson\nimport org.junit.Assert.assertEquals\nimport org.junit.Befo"
  },
  {
    "path": "library/src/test/java/de/rheinfabrik/heimdall2/grants/OAuth2AuthorizationCodeGrantTest.kt",
    "chars": 660,
    "preview": "package de.rheinfabrik.heimdall2.grants\n\nimport org.junit.Assert.assertEquals\nimport org.junit.Test\n\nclass OAuth2Authori"
  },
  {
    "path": "library/src/test/java/de/rheinfabrik/heimdall2/grants/OAuth2ClientCredentialsGrantTest.kt",
    "chars": 528,
    "preview": "package de.rheinfabrik.heimdall2.grants\n\nimport org.junit.Assert.assertEquals\nimport org.junit.Test\n\nclass OAuth2ClientC"
  },
  {
    "path": "library/src/test/java/de/rheinfabrik/heimdall2/grants/OAuth2ImplicitGrantTest.kt",
    "chars": 496,
    "preview": "package de.rheinfabrik.heimdall2.grants\n\nimport org.junit.Assert.assertEquals\nimport org.junit.Test\n\nclass OAuth2Implici"
  },
  {
    "path": "library/src/test/java/de/rheinfabrik/heimdall2/grants/OAuth2RefreshAccessTokenGrantTest.kt",
    "chars": 449,
    "preview": "package de.rheinfabrik.heimdall2.grants\n\nimport org.junit.Assert.assertEquals\nimport org.junit.Test\n\nclass OAuth2Refresh"
  },
  {
    "path": "library/src/test/java/de/rheinfabrik/heimdall2/grants/OAuth2ResourceOwnerPasswordCredentialsGrantTest.kt",
    "chars": 465,
    "preview": "package de.rheinfabrik.heimdall2.grants\n\nimport org.junit.Assert.assertEquals\nimport org.junit.Test\n\nclass OAuth2Resourc"
  },
  {
    "path": "library/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker",
    "chars": 17,
    "preview": "mock-maker-inline"
  },
  {
    "path": "sample/build.gradle",
    "chars": 1362,
    "preview": "apply plugin: 'com.android.application'\n\nbuildscript {\n    repositories {\n        mavenCentral()\n    }\n}\n\nandroid {\n    "
  },
  {
    "path": "sample/proguard-rules.pro",
    "chars": 662,
    "preview": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /U"
  },
  {
    "path": "sample/src/main/AndroidManifest.xml",
    "chars": 1230,
    "preview": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n"
  },
  {
    "path": "sample/src/main/java/de/rheinfabrik/heimdalldroid/TraktTvAPIConfiguration.java",
    "chars": 310,
    "preview": "package de.rheinfabrik.heimdalldroid;\n\npublic class TraktTvAPIConfiguration {\n    public static final String CLIENT_ID ="
  },
  {
    "path": "sample/src/main/java/de/rheinfabrik/heimdalldroid/actvities/LoginActivity.java",
    "chars": 3450,
    "preview": "package de.rheinfabrik.heimdalldroid.actvities;\n\nimport android.graphics.Bitmap;\nimport android.os.Bundle;\nimport androi"
  },
  {
    "path": "sample/src/main/java/de/rheinfabrik/heimdalldroid/actvities/MainActivity.java",
    "chars": 6819,
    "preview": "package de.rheinfabrik.heimdalldroid.actvities;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport andr"
  },
  {
    "path": "sample/src/main/java/de/rheinfabrik/heimdalldroid/adapter/TraktTvListsRecyclerViewAdapter.java",
    "chars": 1408,
    "preview": "package de.rheinfabrik.heimdalldroid.adapter;\n\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport andr"
  },
  {
    "path": "sample/src/main/java/de/rheinfabrik/heimdalldroid/adapter/viewholder/TraktTvListViewHolder.java",
    "chars": 1268,
    "preview": "package de.rheinfabrik.heimdalldroid.adapter.viewholder;\n\nimport android.view.View;\nimport android.widget.TextView;\n\nimp"
  },
  {
    "path": "sample/src/main/java/de/rheinfabrik/heimdalldroid/network/TraktTvApiFactory.java",
    "chars": 1252,
    "preview": "package de.rheinfabrik.heimdalldroid.network;\n\nimport com.google.gson.Gson;\n\nimport de.rheinfabrik.heimdalldroid.TraktTv"
  },
  {
    "path": "sample/src/main/java/de/rheinfabrik/heimdalldroid/network/TraktTvApiService.java",
    "chars": 1170,
    "preview": "package de.rheinfabrik.heimdalldroid.network;\n\nimport io.reactivex.Observable;\nimport java.util.List;\n\nimport de.rheinfa"
  },
  {
    "path": "sample/src/main/java/de/rheinfabrik/heimdalldroid/network/models/AccessTokenRequestBody.java",
    "chars": 953,
    "preview": "package de.rheinfabrik.heimdalldroid.network.models;\n\nimport com.google.gson.annotations.SerializedName;\n\nimport java.io"
  },
  {
    "path": "sample/src/main/java/de/rheinfabrik/heimdalldroid/network/models/RefreshTokenRequestBody.java",
    "chars": 976,
    "preview": "package de.rheinfabrik.heimdalldroid.network.models;\n\nimport com.google.gson.annotations.SerializedName;\n\nimport java.io"
  },
  {
    "path": "sample/src/main/java/de/rheinfabrik/heimdalldroid/network/models/RevokeAccessTokenBody.java",
    "chars": 420,
    "preview": "package de.rheinfabrik.heimdalldroid.network.models;\n\nimport com.google.gson.annotations.SerializedName;\n\n/**\n * Body us"
  },
  {
    "path": "sample/src/main/java/de/rheinfabrik/heimdalldroid/network/models/TraktTvList.java",
    "chars": 431,
    "preview": "package de.rheinfabrik.heimdalldroid.network.models;\n\nimport com.google.gson.annotations.SerializedName;\n\nimport java.io"
  },
  {
    "path": "sample/src/main/java/de/rheinfabrik/heimdalldroid/network/oauth2/TraktTvAuthorizationCodeGrant.java",
    "chars": 1668,
    "preview": "package de.rheinfabrik.heimdalldroid.network.oauth2;\n\nimport android.net.Uri;\n\nimport de.rheinfabrik.heimdall2.grants.OA"
  },
  {
    "path": "sample/src/main/java/de/rheinfabrik/heimdalldroid/network/oauth2/TraktTvOauth2AccessTokenManager.java",
    "chars": 3365,
    "preview": "package de.rheinfabrik.heimdalldroid.network.oauth2;\n\nimport android.content.Context;\nimport android.content.SharedPrefe"
  },
  {
    "path": "sample/src/main/java/de/rheinfabrik/heimdalldroid/network/oauth2/TraktTvRefreshAccessTokenGrant.java",
    "chars": 1049,
    "preview": "package de.rheinfabrik.heimdalldroid.network.oauth2;\n\nimport de.rheinfabrik.heimdall2.OAuth2AccessToken;\nimport de.rhein"
  },
  {
    "path": "sample/src/main/java/de/rheinfabrik/heimdalldroid/utils/AlertDialogFactory.java",
    "chars": 1042,
    "preview": "package de.rheinfabrik.heimdalldroid.utils;\n\nimport android.app.AlertDialog;\nimport android.content.Context;\n\nimport de."
  },
  {
    "path": "sample/src/main/java/de/rheinfabrik/heimdalldroid/utils/IntentFactory.java",
    "chars": 455,
    "preview": "package de.rheinfabrik.heimdalldroid.utils;\n\nimport android.content.Context;\nimport android.content.Intent;\n\nimport de.r"
  },
  {
    "path": "sample/src/main/java/de/rheinfabrik/heimdalldroid/utils/SharedPreferencesOAuth2AccessTokenStorage.java",
    "chars": 2412,
    "preview": "package de.rheinfabrik.heimdalldroid.utils;\n\nimport android.content.SharedPreferences;\n\nimport com.google.gson.Gson;\n\nim"
  },
  {
    "path": "sample/src/main/res/layout/activity_login.xml",
    "chars": 253,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<!-- WebView -->\n<WebView android:id=\"@+id/webView\"\n         xmlns:android=\"http"
  },
  {
    "path": "sample/src/main/res/layout/activity_main.xml",
    "chars": 1128,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    and"
  },
  {
    "path": "sample/src/main/res/layout/item_view_trakt_tv_list.xml",
    "chars": 2191,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<!-- Card view -->\n<android.support.v7.widget.CardView\n    android:id=\"@+id/sear"
  },
  {
    "path": "sample/src/main/res/menu/menu_main.xml",
    "chars": 329,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n      xmlns:ap"
  },
  {
    "path": "sample/src/main/res/values/colors.xml",
    "chars": 293,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<resources>\n\n    <color name=\"white\">#FFFFFFFF</color>\n    <color name=\"hint_of_"
  },
  {
    "path": "sample/src/main/res/values/strings.xml",
    "chars": 616,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<resources>\n\n    <!-- The Application name -->\n    <string name=\"app_name\">Heimd"
  },
  {
    "path": "sample/src/main/res/values/styles.xml",
    "chars": 414,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppThemeNoAct"
  },
  {
    "path": "settings.gradle",
    "chars": 30,
    "preview": "include ':sample', ':library'\n"
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the rheinfabrik/Heimdall.droid GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 61 files (91.4 KB), approximately 24.0k tokens, and a symbol index with 62 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!