[
  {
    "path": ".gitignore",
    "content": "#################\n## AndroidStudio\n#################\n\n.gradle\nproject.properties\n.idea\ngen\n*.class\nout\n*.iml\n\n#################\n## Eclipse\n#################\n\n*.pydevproject\n.project\n.metadata\nbin/\ntmp/\n*.tmp\n*.bak\n*.swp\n*~.nib\nlocal.properties\n.classpath\n.settings/\n.loadpath\n\n# External tool builders\n.externalToolBuilders/\n\n# Locally stored \"Eclipse launch configurations\"\n*.launch\n\n# CDT-specific\n.cproject\n\n# PDT-specific\n.buildpath\n\n\n#################\n## Visual Studio\n#################\n\n## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n\n# User-specific files\n*.suo\n*.user\n*.sln.docstates\n\n# Build results\n\n[Dd]ebug/\n[Rr]elease/\nx64/\nbuild/\n[Bb]in/\n[Oo]bj/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n*_i.c\n*_p.c\n*.ilk\n*.meta\n*.obj\n*.pch\n*.pdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*.log\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.log\n*.scc\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opensdf\n*.sdf\n*.cachefile\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# NCrunch\n*.ncrunch*\n.*crunch*.local.xml\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.Publish.xml\n*.pubxml\n\n# NuGet Packages Directory\n## TODO: If you have NuGet Package Restore enabled, uncomment the next line\n#packages/\n\n# Windows Azure Build Output\ncsx\n*.build.csdef\n\n# Windows Store app package directory\nAppPackages/\n\n# Others\nsql/\n*.Cache\nClientBin/\n[Ss]tyle[Cc]op.*\n~$*\n*~\n*.dbmdl\n*.[Pp]ublish.xml\n*.pfx\n*.publishsettings\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file to a newer\n# Visual Studio version. Backup files are not needed, because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\n\n# SQL Server files\nApp_Data/*.mdf\nApp_Data/*.ldf\n\n#############\n## Windows detritus\n#############\n\n# Windows image file caches\nThumbs.db\nehthumbs.db\n\n# Folder config file\nDesktop.ini\n\n# Recycle Bin used on file shares\n$RECYCLE.BIN/\n\n# Mac crap\n.DS_Store\n\n\n#############\n## Python\n#############\n\n*.py[co]\n\n# Packages\n*.egg\n*.egg-info\ndist/\nbuild/\neggs/\nparts/\nvar/\nsdist/\ndevelop-eggs/\n.installed.cfg\n\n# Installer logs\npip-log.txt\n\n# Unit test / coverage reports\n.coverage\n.tox\n\n#Translations\n*.mo\n\n#Mr Developer\n.mr.developer.cfg\n"
  },
  {
    "path": "README.md",
    "content": "# DeckView\nA ViewGroup that mimics Android (Lollipop) Recent apps screen layout.\n\n######Note: \nDeckView is **not** a true recycler. It *does* recycle views - but it also updates progress map for *all* of its children on each scroll step. This will result in lags with large datasets.\n\n\n"
  },
  {
    "path": "app/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "app/build.gradle",
    "content": "apply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion 22\n    buildToolsVersion \"22.0.1\"\n\n    defaultConfig {\n        applicationId \"com.appeaser.deckviewlibrary\"\n        minSdkVersion 21\n        targetSdkVersion 22\n        versionCode 1\n        versionName \"1.0\"\n    }\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n}\n\ndependencies {\n    compile fileTree(dir: 'libs', include: ['*.jar'])\n}\n"
  },
  {
    "path": "app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in C:\\Users\\Vikram\\Documents\\Android\\adt-bundle-windows-x86-20130219\\sdk/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n"
  },
  {
    "path": "app/src/androidTest/java/com/appeaser/deckviewlibrary/ApplicationTest.java",
    "content": "package com.appeaser.deckviewlibrary;\n\nimport android.app.Application;\nimport android.test.ApplicationTestCase;\n\n/**\n * <a href=\"http://d.android.com/tools/testing/testing_android.html\">Testing Fundamentals</a>\n */\npublic class ApplicationTest extends ApplicationTestCase<Application> {\n    public ApplicationTest() {\n        super(Application.class);\n    }\n}"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.appeaser.deckviewlibrary\">\n\n    <application\n        android:allowBackup=\"true\"\n        android:label=\"@string/app_name\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:theme=\"@style/AppTheme\">\n\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">DeckViewLibrary</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"android:Theme.Holo.Light.DarkActionBar\">\n        <!-- Customize your theme here. -->\n    </style>\n\n</resources>\n"
  },
  {
    "path": "build.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n    repositories {\n        jcenter()\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:1.1.0'\n\n        // NOTE: Do not place your application dependencies here; they belong\n        // in the individual module build.gradle files\n    }\n}\n\nallprojects {\n    repositories {\n        jcenter()\n    }\n}\n"
  },
  {
    "path": "deckview/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "deckview/build.gradle",
    "content": "apply plugin: 'com.android.library'\n\nandroid {\n    compileSdkVersion 22\n    buildToolsVersion \"22.0.1\"\n\n    defaultConfig {\n        minSdkVersion 21\n        targetSdkVersion 22\n        versionCode 1\n        versionName \"1.0\"\n    }\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n}\n\ndependencies {\n    compile fileTree(dir: 'libs', include: ['*.jar'])\n}\n"
  },
  {
    "path": "deckview/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in C:/Users/Vikram/Documents/Android/adt-bundle-windows-x86-20130219/sdk/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n"
  },
  {
    "path": "deckview/src/androidTest/java/com/appeaser/deckview/ApplicationTest.java",
    "content": "package com.appeaser.deckview;\n\nimport android.app.Application;\nimport android.test.ApplicationTestCase;\n\n/**\n * <a href=\"http://d.android.com/tools/testing/testing_android.html\">Testing Fundamentals</a>\n */\npublic class ApplicationTest extends ApplicationTestCase<Application> {\n    public ApplicationTest() {\n        super(Application.class);\n    }\n}"
  },
  {
    "path": "deckview/src/main/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.appeaser.deckview\">\n\n    <application\n        android:allowBackup=\"true\"\n        android:label=\"@string/app_name\">\n\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "deckview/src/main/java/com/appeaser/deckview/helpers/DeckChildViewTransform.java",
    "content": "package com.appeaser.deckview.helpers;\n\nimport android.animation.ValueAnimator;\nimport android.graphics.Rect;\nimport android.view.View;\nimport android.view.ViewPropertyAnimator;\nimport android.view.animation.Interpolator;\n\n/**\n * Created by Vikram on 02/04/2015.\n */\n/* The transform state for a task view */\npublic class DeckChildViewTransform {\n    public int startDelay = 0;\n    public int translationY = 0;\n    public float translationZ = 0;\n    public float scale = 1f;\n    public float alpha = 1f;\n    public boolean visible = false;\n    public Rect rect = new Rect();\n    public float p = 0f;\n\n    public DeckChildViewTransform() {\n        // Do nothing\n    }\n\n    public DeckChildViewTransform(DeckChildViewTransform o) {\n        startDelay = o.startDelay;\n        translationY = o.translationY;\n        translationZ = o.translationZ;\n        scale = o.scale;\n        alpha = o.alpha;\n        visible = o.visible;\n        rect.set(o.rect);\n        p = o.p;\n    }\n\n    /**\n     * Resets the current transform\n     */\n    public void reset() {\n        startDelay = 0;\n        translationY = 0;\n        translationZ = 0;\n        scale = 1f;\n        alpha = 1f;\n        visible = false;\n        rect.setEmpty();\n        p = 0f;\n    }\n\n    /**\n     * Convenience functions to compare against current property values\n     */\n    public boolean hasAlphaChangedFrom(float v) {\n        return (Float.compare(alpha, v) != 0);\n    }\n\n    public boolean hasScaleChangedFrom(float v) {\n        return (Float.compare(scale, v) != 0);\n    }\n\n    public boolean hasTranslationYChangedFrom(float v) {\n        return (Float.compare(translationY, v) != 0);\n    }\n\n    public boolean hasTranslationZChangedFrom(float v) {\n        return (Float.compare(translationZ, v) != 0);\n    }\n\n    /**\n     * Applies this transform to a view.\n     */\n    public void applyToTaskView(View v, int duration, Interpolator interp, boolean allowLayers,\n                                boolean allowShadows, ValueAnimator.AnimatorUpdateListener updateCallback) {\n        // Check to see if any properties have changed, and update the task view\n        if (duration > 0) {\n            ViewPropertyAnimator anim = v.animate();\n            boolean requiresLayers = false;\n\n            // Animate to the final state\n            if (hasTranslationYChangedFrom(v.getTranslationY())) {\n                anim.translationY(translationY);\n            }\n            if (allowShadows && hasTranslationZChangedFrom(v.getTranslationZ())) {\n                anim.translationZ(translationZ);\n            }\n            if (hasScaleChangedFrom(v.getScaleX())) {\n                anim.scaleX(scale)\n                        .scaleY(scale);\n                requiresLayers = true;\n            }\n            if (hasAlphaChangedFrom(v.getAlpha())) {\n                // Use layers if we animate alpha\n                anim.alpha(alpha);\n                requiresLayers = true;\n            }\n            if (requiresLayers && allowLayers) {\n                anim.withLayer();\n            }\n            if (updateCallback != null) {\n                anim.setUpdateListener(updateCallback);\n            } else {\n                anim.setUpdateListener(null);\n            }\n            anim.setStartDelay(startDelay)\n                    .setDuration(duration)\n                    .setInterpolator(interp)\n                    .start();\n        } else {\n            // Set the changed properties\n            if (hasTranslationYChangedFrom(v.getTranslationY())) {\n                v.setTranslationY(translationY);\n            }\n            if (allowShadows && hasTranslationZChangedFrom(v.getTranslationZ())) {\n                v.setTranslationZ(translationZ);\n            }\n            if (hasScaleChangedFrom(v.getScaleX())) {\n                v.setScaleX(scale);\n                v.setScaleY(scale);\n            }\n            if (hasAlphaChangedFrom(v.getAlpha())) {\n                v.setAlpha(alpha);\n            }\n        }\n    }\n\n    /**\n     * Reset the transform on a view.\n     */\n    public static void reset(View v) {\n        v.setTranslationX(0f);\n        v.setTranslationY(0f);\n        v.setTranslationZ(0f);\n        v.setScaleX(1f);\n        v.setScaleY(1f);\n        v.setAlpha(1f);\n    }\n\n    @Override\n    public String toString() {\n        return \"TaskViewTransform delay: \" + startDelay + \" y: \" + translationY + \" z: \" + translationZ +\n                \" scale: \" + scale + \" alpha: \" + alpha + \" visible: \" + visible + \" rect: \" + rect +\n                \" p: \" + p;\n    }\n}\n"
  },
  {
    "path": "deckview/src/main/java/com/appeaser/deckview/helpers/DeckViewConfig.java",
    "content": "package com.appeaser.deckview.helpers;\n\n/**\n * Created by Vikram on 02/04/2015.\n */\n\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.content.res.Configuration;\nimport android.content.res.Resources;\nimport android.graphics.Rect;\nimport android.util.DisplayMetrics;\nimport android.util.TypedValue;\nimport android.view.animation.AnimationUtils;\nimport android.view.animation.Interpolator;\n\nimport com.appeaser.deckview.R;\nimport com.appeaser.deckview.utilities.DVConstants;\n\n/**\n * Configuration helper\n */\npublic class DeckViewConfig {\n    static DeckViewConfig sInstance;\n    static int sPrevConfigurationHashCode;\n\n    /**\n     * Levels of svelte in increasing severity/austerity.\n     */\n    // No svelting.\n    public static final int SVELTE_NONE = 0;\n    // Limit thumbnail cache to number of visible thumbnails when Recents was loaded, disable\n    // caching thumbnails as you scroll.\n    public static final int SVELTE_LIMIT_CACHE = 1;\n    // Disable the thumbnail cache, load thumbnails asynchronously when the activity loads and\n    // evict all thumbnails when hidden.\n    public static final int SVELTE_DISABLE_CACHE = 2;\n    // Disable all thumbnail loading.\n    public static final int SVELTE_DISABLE_LOADING = 3;\n\n    /**\n     * Animations\n     */\n    public float animationPxMovementPerSecond;\n\n    /**\n     * Interpolators\n     */\n    public Interpolator fastOutSlowInInterpolator;\n    public Interpolator fastOutLinearInInterpolator;\n    public Interpolator linearOutSlowInInterpolator;\n    public Interpolator quintOutInterpolator;\n\n    /**\n     * Filtering\n     */\n    public int filteringCurrentViewsAnimDuration;\n    public int filteringNewViewsAnimDuration;\n\n    /**\n     * Insets\n     */\n    public Rect systemInsets = new Rect();\n    public Rect displayRect = new Rect();\n\n    /**\n     * Layout\n     */\n    boolean isLandscape;\n\n    /**\n     * Task stack\n     */\n    public int taskStackScrollDuration;\n    public int taskStackMaxDim;\n    public int taskStackTopPaddingPx;\n    public float taskStackWidthPaddingPct;\n    public float taskStackOverscrollPct;\n\n    /**\n     * Transitions\n     */\n    public int transitionEnterFromAppDelay;\n    public int transitionEnterFromHomeDelay;\n\n    /**\n     * Task view animation and styles\n     */\n    public int taskViewEnterFromAppDuration;\n    public int taskViewEnterFromHomeDuration;\n    public int taskViewEnterFromHomeStaggerDelay;\n    public int taskViewExitToAppDuration;\n    public int taskViewExitToHomeDuration;\n    public int taskViewRemoveAnimDuration;\n    public int taskViewRemoveAnimTranslationXPx;\n    public int taskViewTranslationZMinPx;\n    public int taskViewTranslationZMaxPx;\n    public int taskViewRoundedCornerRadiusPx;\n    public int taskViewHighlightPx;\n    public int taskViewAffiliateGroupEnterOffsetPx;\n    public float taskViewThumbnailAlpha;\n\n    /**\n     * Task bar colors\n     */\n    public int taskBarViewDefaultBackgroundColor;\n    public int taskBarViewLightTextColor;\n    public int taskBarViewDarkTextColor;\n    public int taskBarViewHighlightColor;\n    public float taskBarViewAffiliationColorMinAlpha;\n\n    /**\n     * Task bar size & animations\n     */\n    public int taskBarHeight;\n    public int taskBarDismissDozeDelaySeconds;\n\n    /**\n     * Nav bar scrim\n     */\n    public int navBarScrimEnterDuration;\n\n    /**\n     * Launch states\n     */\n    public boolean launchedWithAltTab;\n    public boolean launchedWithNoRecentTasks;\n    public boolean launchedFromAppWithThumbnail;\n    public boolean launchedFromHome;\n    public boolean launchedFromSearchHome;\n    public boolean launchedReuseTaskStackViews;\n    public boolean launchedHasConfigurationChanged;\n    public int launchedToTaskId;\n    public int launchedNumVisibleTasks;\n    public int launchedNumVisibleThumbnails;\n\n    /**\n     * Misc *\n     */\n    public boolean useHardwareLayers;\n    public int altTabKeyDelay;\n    public boolean fakeShadows;\n\n    /**\n     * Dev options and global settings\n     */\n    public boolean debugModeEnabled;\n    public int svelteLevel;\n\n    /**\n     * Private constructor\n     */\n    private DeckViewConfig(Context context) {\n        // Properties that don't have to be reloaded with each configuration change can be loaded\n        // here.\n\n        // Interpolators\n        fastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,\n                R.interpolator.fast_out_slow_in);\n        fastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,\n                R.interpolator.fast_out_linear_in);\n        linearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,\n                R.interpolator.linear_out_slow_in);\n        quintOutInterpolator = AnimationUtils.loadInterpolator(context,\n                R.interpolator.decelerate_quint);\n    }\n\n    /**\n     * Updates the configuration to the current context\n     */\n    public static DeckViewConfig reinitialize(Context context) {\n        if (sInstance == null) {\n            sInstance = new DeckViewConfig(context);\n        }\n        int configHashCode = context.getResources().getConfiguration().hashCode();\n        if (sPrevConfigurationHashCode != configHashCode) {\n            sInstance.update(context);\n            sPrevConfigurationHashCode = configHashCode;\n        }\n\n        sInstance.updateOnReinitialize(context);\n        return sInstance;\n    }\n\n    /**\n     * Returns the current recents configuration\n     */\n    public static DeckViewConfig getInstance() {\n        return sInstance;\n    }\n\n    /**\n     * Updates the state, given the specified context\n     */\n    void update(Context context) {\n        SharedPreferences settings = context.getSharedPreferences(context.getPackageName(), 0);\n        Resources res = context.getResources();\n        DisplayMetrics dm = res.getDisplayMetrics();\n\n        // Debug mode\n        debugModeEnabled = settings.getBoolean(DVConstants.Values.App.Key_DebugModeEnabled, false);\n\n        // Layout\n        isLandscape = res.getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;\n\n        // Insets\n        displayRect.set(0, 0, dm.widthPixels, dm.heightPixels);\n\n        // Animations\n        animationPxMovementPerSecond =\n                res.getDimensionPixelSize(R.dimen.animation_movement_in_dps_per_second);\n\n        // Filtering\n        filteringCurrentViewsAnimDuration =\n                res.getInteger(R.integer.filter_animate_current_views_duration);\n        filteringNewViewsAnimDuration =\n                res.getInteger(R.integer.filter_animate_new_views_duration);\n\n        // Task stack\n        taskStackScrollDuration =\n                res.getInteger(R.integer.animate_deck_scroll_duration);\n        TypedValue widthPaddingPctValue = new TypedValue();\n        res.getValue(R.dimen.deck_width_padding_percentage, widthPaddingPctValue, true);\n        taskStackWidthPaddingPct = widthPaddingPctValue.getFloat();\n        TypedValue stackOverscrollPctValue = new TypedValue();\n        res.getValue(R.dimen.deck_overscroll_percentage, stackOverscrollPctValue, true);\n        taskStackOverscrollPct = stackOverscrollPctValue.getFloat();\n        taskStackMaxDim = res.getInteger(R.integer.max_deck_view_dim);\n        taskStackTopPaddingPx = res.getDimensionPixelSize(R.dimen.deck_top_padding);\n\n        // Transition\n        transitionEnterFromAppDelay =\n                res.getInteger(R.integer.enter_from_app_transition_duration);\n        transitionEnterFromHomeDelay =\n                res.getInteger(R.integer.enter_from_home_transition_duration);\n\n        // Task view animation and styles\n        taskViewEnterFromAppDuration =\n                res.getInteger(R.integer.task_enter_from_app_duration);\n        taskViewEnterFromHomeDuration =\n                res.getInteger(R.integer.task_enter_from_home_duration);\n        taskViewEnterFromHomeStaggerDelay =\n                res.getInteger(R.integer.task_enter_from_home_stagger_delay);\n        taskViewExitToAppDuration =\n                res.getInteger(R.integer.task_exit_to_app_duration);\n        taskViewExitToHomeDuration =\n                res.getInteger(R.integer.task_exit_to_home_duration);\n        taskViewRemoveAnimDuration =\n                res.getInteger(R.integer.animate_task_view_remove_duration);\n        taskViewRemoveAnimTranslationXPx =\n                res.getDimensionPixelSize(R.dimen.task_view_remove_anim_translation_x);\n        taskViewRoundedCornerRadiusPx =\n                res.getDimensionPixelSize(R.dimen.task_view_rounded_corners_radius);\n        taskViewHighlightPx = res.getDimensionPixelSize(R.dimen.task_view_highlight);\n        taskViewTranslationZMinPx = res.getDimensionPixelSize(R.dimen.task_view_z_min);\n        taskViewTranslationZMaxPx = res.getDimensionPixelSize(R.dimen.task_view_z_max);\n        taskViewAffiliateGroupEnterOffsetPx =\n                res.getDimensionPixelSize(R.dimen.task_view_affiliate_group_enter_offset);\n        TypedValue thumbnailAlphaValue = new TypedValue();\n        res.getValue(R.dimen.task_view_thumbnail_alpha, thumbnailAlphaValue, true);\n        taskViewThumbnailAlpha = thumbnailAlphaValue.getFloat();\n\n        // Task bar colors\n        taskBarViewDefaultBackgroundColor =\n                res.getColor(R.color.task_bar_default_background_color);\n        taskBarViewLightTextColor =\n                res.getColor(R.color.task_bar_light_text_color);\n        taskBarViewDarkTextColor =\n                res.getColor(R.color.task_bar_dark_text_color);\n        taskBarViewHighlightColor =\n                res.getColor(R.color.task_bar_highlight_color);\n        TypedValue affMinAlphaPctValue = new TypedValue();\n        res.getValue(R.dimen.task_affiliation_color_min_alpha_percentage, affMinAlphaPctValue, true);\n        taskBarViewAffiliationColorMinAlpha = affMinAlphaPctValue.getFloat();\n\n        // Task bar size & animations\n        taskBarHeight = res.getDimensionPixelSize(R.dimen.deck_child_header_bar_height);\n        taskBarDismissDozeDelaySeconds =\n                res.getInteger(R.integer.task_bar_dismiss_delay_seconds);\n\n        // Nav bar scrim\n        navBarScrimEnterDuration =\n                res.getInteger(R.integer.nav_bar_scrim_enter_duration);\n\n        // Misc\n        useHardwareLayers = res.getBoolean(R.bool.config_use_hardware_layers);\n        altTabKeyDelay = res.getInteger(R.integer.deck_alt_tab_key_delay);\n        fakeShadows = res.getBoolean(R.bool.config_fake_shadows);\n        svelteLevel = res.getInteger(R.integer.deck_svelte_level);\n    }\n\n    /**\n     * Updates the system insets\n     */\n    public void updateSystemInsets(Rect insets) {\n        systemInsets.set(insets);\n    }\n\n    /**\n     * Updates the search bar app widget\n     */\n    public void updateSearchBarAppWidgetId(Context context, int appWidgetId) {\n\n    }\n\n    /**\n     * Updates the states that need to be re-read whenever we re-initialize.\n     */\n    void updateOnReinitialize(Context context/*, SystemServicesProxy ssp*/) {\n\n    }\n\n    /**\n     * Called when the configuration has changed, and we want to reset any configuration specific\n     * members.\n     */\n    public void updateOnConfigurationChange() {\n        // Reset this flag on configuration change to ensure that we recreate new task views\n        launchedReuseTaskStackViews = false;\n        // Set this flag to indicate that the configuration has changed since Recents last launched\n        launchedHasConfigurationChanged = true;\n    }\n\n    /**\n     * Returns the task stack bounds in the current orientation. These bounds do not account for\n     * the system insets.\n     */\n    public void getTaskStackBounds(int windowWidth, int windowHeight, int topInset, int rightInset,\n                                   Rect taskStackBounds) {\n        taskStackBounds.set(0, 0, windowWidth, windowHeight);\n    }\n}"
  },
  {
    "path": "deckview/src/main/java/com/appeaser/deckview/helpers/DeckViewSwipeHelper.java",
    "content": "package com.appeaser.deckview.helpers;\n\n/**\n * Created by Vikram on 02/04/2015.\n */\n\nimport android.animation.Animator;\nimport android.animation.AnimatorListenerAdapter;\nimport android.animation.ObjectAnimator;\nimport android.animation.ValueAnimator;\nimport android.annotation.TargetApi;\nimport android.os.Build;\nimport android.util.DisplayMetrics;\nimport android.view.MotionEvent;\nimport android.view.VelocityTracker;\nimport android.view.View;\nimport android.view.animation.LinearInterpolator;\n\n/**\n * This class facilitates swipe to dismiss. It defines an interface to be implemented by the\n * by the class hosting the views that need to swiped, and, using this interface, handles touch\n * events and translates / fades / animates the view as it is dismissed.\n */\npublic class DeckViewSwipeHelper {\n    static final String TAG = \"DeckViewSwipeHelper\";\n    private static final boolean SLOW_ANIMATIONS = false; // DEBUG;\n    private static final boolean CONSTRAIN_SWIPE = true;\n    private static final boolean FADE_OUT_DURING_SWIPE = true;\n    private static final boolean DISMISS_IF_SWIPED_FAR_ENOUGH = true;\n\n    public static final int X = 0;\n    public static final int Y = 1;\n\n    private static LinearInterpolator sLinearInterpolator = new LinearInterpolator();\n\n    private float SWIPE_ESCAPE_VELOCITY = 100f; // dp/sec\n    private int DEFAULT_ESCAPE_ANIMATION_DURATION = 75; // ms\n    private int MAX_ESCAPE_ANIMATION_DURATION = 150; // ms\n    private static final int SNAP_ANIM_LEN = SLOW_ANIMATIONS ? 1000 : 250; // ms\n\n    public static float ALPHA_FADE_START = 0.15f; // fraction of thumbnail width\n    // where fade starts\n    static final float ALPHA_FADE_END = 0.65f; // fraction of thumbnail width\n    // beyond which alpha->0\n    private float mMinAlpha = 0f;\n\n    private float mPagingTouchSlop;\n    Callback mCallback;\n    private int mSwipeDirection;\n    private VelocityTracker mVelocityTracker;\n\n    private float mInitialTouchPos;\n    private boolean mDragging;\n\n    private View mCurrView;\n    private boolean mCanCurrViewBeDimissed;\n    private float mDensityScale;\n\n    public boolean mAllowSwipeTowardsStart = true;\n    public boolean mAllowSwipeTowardsEnd = true;\n    private boolean mRtl;\n\n    public DeckViewSwipeHelper(int swipeDirection, Callback callback, float densityScale,\n                               float pagingTouchSlop) {\n        mCallback = callback;\n        mSwipeDirection = swipeDirection;\n        mVelocityTracker = VelocityTracker.obtain();\n        mDensityScale = densityScale;\n        mPagingTouchSlop = pagingTouchSlop;\n    }\n\n    public void setDensityScale(float densityScale) {\n        mDensityScale = densityScale;\n    }\n\n    public void setPagingTouchSlop(float pagingTouchSlop) {\n        mPagingTouchSlop = pagingTouchSlop;\n    }\n\n    public void cancelOngoingDrag() {\n        if (mDragging) {\n            if (mCurrView != null) {\n                mCallback.onDragCancelled(mCurrView);\n                setTranslation(mCurrView, 0);\n                mCallback.onSnapBackCompleted(mCurrView);\n                mCurrView = null;\n            }\n            mDragging = false;\n        }\n    }\n\n    public void resetTranslation(View v) {\n        setTranslation(v, 0);\n    }\n\n    private float getPos(MotionEvent ev) {\n        return mSwipeDirection == X ? ev.getX() : ev.getY();\n    }\n\n    private float getTranslation(View v) {\n        return mSwipeDirection == X ? v.getTranslationX() : v.getTranslationY();\n    }\n\n    private float getVelocity(VelocityTracker vt) {\n        return mSwipeDirection == X ? vt.getXVelocity() :\n                vt.getYVelocity();\n    }\n\n    private ObjectAnimator createTranslationAnimation(View v, float newPos) {\n        ObjectAnimator anim = ObjectAnimator.ofFloat(v,\n                mSwipeDirection == X ? View.TRANSLATION_X : View.TRANSLATION_Y, newPos);\n        return anim;\n    }\n\n    private float getPerpendicularVelocity(VelocityTracker vt) {\n        return mSwipeDirection == X ? vt.getYVelocity() :\n                vt.getXVelocity();\n    }\n\n    private void setTranslation(View v, float translate) {\n        if (mSwipeDirection == X) {\n            v.setTranslationX(translate);\n        } else {\n            v.setTranslationY(translate);\n        }\n    }\n\n    private float getSize(View v) {\n        final DisplayMetrics dm = v.getContext().getResources().getDisplayMetrics();\n        return mSwipeDirection == X ? dm.widthPixels : dm.heightPixels;\n    }\n\n    public void setMinAlpha(float minAlpha) {\n        mMinAlpha = minAlpha;\n    }\n\n    float getAlphaForOffset(View view) {\n        float viewSize = getSize(view);\n        final float fadeSize = ALPHA_FADE_END * viewSize;\n        float result = 1.0f;\n        float pos = getTranslation(view);\n        if (pos >= viewSize * ALPHA_FADE_START) {\n            result = 1.0f - (pos - viewSize * ALPHA_FADE_START) / fadeSize;\n        } else if (pos < viewSize * (1.0f - ALPHA_FADE_START)) {\n            result = 1.0f + (viewSize * ALPHA_FADE_START + pos) / fadeSize;\n        }\n        result = Math.min(result, 1.0f);\n        result = Math.max(result, 0f);\n        return Math.max(mMinAlpha, result);\n    }\n\n    /**\n     * Determines whether the given view has RTL layout.\n     */\n    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)\n    public static boolean isLayoutRtl(View view) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {\n            return View.LAYOUT_DIRECTION_RTL == view.getLayoutDirection();\n        } else {\n            return false;\n        }\n    }\n\n    public boolean onInterceptTouchEvent(MotionEvent ev) {\n        final int action = ev.getAction();\n\n        switch (action) {\n            case MotionEvent.ACTION_DOWN:\n                mDragging = false;\n                mCurrView = mCallback.getChildAtPosition(ev);\n                mVelocityTracker.clear();\n                if (mCurrView != null) {\n                    mRtl = isLayoutRtl(mCurrView);\n                    mCanCurrViewBeDimissed = mCallback.canChildBeDismissed(mCurrView);\n                    mVelocityTracker.addMovement(ev);\n                    mInitialTouchPos = getPos(ev);\n                } else {\n                    mCanCurrViewBeDimissed = false;\n                }\n                break;\n            case MotionEvent.ACTION_MOVE:\n                if (mCurrView != null) {\n                    mVelocityTracker.addMovement(ev);\n                    float pos = getPos(ev);\n                    float delta = pos - mInitialTouchPos;\n                    if (Math.abs(delta) > mPagingTouchSlop) {\n                        mCallback.onBeginDrag(mCurrView);\n                        mDragging = true;\n                        mInitialTouchPos = pos - getTranslation(mCurrView);\n                    }\n                }\n                break;\n            case MotionEvent.ACTION_UP:\n            case MotionEvent.ACTION_CANCEL:\n                mDragging = false;\n                mCurrView = null;\n                break;\n        }\n        return mDragging;\n    }\n\n    /**\n     * @param view     The view to be dismissed\n     * @param velocity The desired pixels/second speed at which the view should move\n     */\n    private void dismissChild(final View view, float velocity) {\n        final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(view);\n        float newPos;\n        if (velocity < 0\n                || (velocity == 0 && getTranslation(view) < 0)\n                // if we use the Menu to dismiss an item in landscape, animate up\n                || (velocity == 0 && getTranslation(view) == 0 && mSwipeDirection == Y)) {\n            newPos = -getSize(view);\n        } else {\n            newPos = getSize(view);\n        }\n        int duration = MAX_ESCAPE_ANIMATION_DURATION;\n        if (velocity != 0) {\n            duration = Math.min(duration,\n                    (int) (Math.abs(newPos - getTranslation(view)) *\n                            1000f / Math.abs(velocity)));\n        } else {\n            duration = DEFAULT_ESCAPE_ANIMATION_DURATION;\n        }\n\n        ValueAnimator anim = createTranslationAnimation(view, newPos);\n        anim.setInterpolator(sLinearInterpolator);\n        anim.setDuration(duration);\n        anim.addListener(new AnimatorListenerAdapter() {\n            @Override\n            public void onAnimationEnd(Animator animation) {\n                mCallback.onChildDismissed(view);\n                if (FADE_OUT_DURING_SWIPE && canAnimViewBeDismissed) {\n                    view.setAlpha(1.f);\n                }\n            }\n        });\n        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {\n            @Override\n            public void onAnimationUpdate(ValueAnimator animation) {\n                if (FADE_OUT_DURING_SWIPE && canAnimViewBeDismissed) {\n                    view.setAlpha(getAlphaForOffset(view));\n                }\n            }\n        });\n        anim.start();\n    }\n\n    private void snapChild(final View view, float velocity) {\n        final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(view);\n        ValueAnimator anim = createTranslationAnimation(view, 0);\n        int duration = SNAP_ANIM_LEN;\n        anim.setDuration(duration);\n        anim.setInterpolator(DeckViewConfig.getInstance().linearOutSlowInInterpolator);\n        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {\n            @Override\n            public void onAnimationUpdate(ValueAnimator animation) {\n                if (FADE_OUT_DURING_SWIPE && canAnimViewBeDismissed) {\n                    view.setAlpha(getAlphaForOffset(view));\n                }\n                mCallback.onSwipeChanged(mCurrView, view.getTranslationX());\n            }\n        });\n        anim.addListener(new AnimatorListenerAdapter() {\n            @Override\n            public void onAnimationEnd(Animator animation) {\n                if (FADE_OUT_DURING_SWIPE && canAnimViewBeDismissed) {\n                    view.setAlpha(1.0f);\n                }\n                mCallback.onSnapBackCompleted(view);\n            }\n        });\n        anim.start();\n    }\n\n    public boolean onTouchEvent(MotionEvent ev) {\n        if (!mDragging) {\n            if (!onInterceptTouchEvent(ev)) {\n                return mCanCurrViewBeDimissed;\n            }\n        }\n\n        mVelocityTracker.addMovement(ev);\n        final int action = ev.getAction();\n        switch (action) {\n            case MotionEvent.ACTION_OUTSIDE:\n            case MotionEvent.ACTION_MOVE:\n                if (mCurrView != null) {\n                    float delta = getPos(ev) - mInitialTouchPos;\n                    setSwipeAmount(delta);\n                    mCallback.onSwipeChanged(mCurrView, delta);\n                }\n                break;\n            case MotionEvent.ACTION_UP:\n            case MotionEvent.ACTION_CANCEL:\n                if (mCurrView != null) {\n                    endSwipe(mVelocityTracker);\n                }\n                break;\n        }\n        return true;\n    }\n\n    private void setSwipeAmount(float amount) {\n        // don't let items that can't be dismissed be dragged more than\n        // maxScrollDistance\n        if (CONSTRAIN_SWIPE\n                && (!isValidSwipeDirection(amount) || !mCallback.canChildBeDismissed(mCurrView))) {\n            float size = getSize(mCurrView);\n            float maxScrollDistance = 0.15f * size;\n            if (Math.abs(amount) >= size) {\n                amount = amount > 0 ? maxScrollDistance : -maxScrollDistance;\n            } else {\n                amount = maxScrollDistance * (float) Math.sin((amount / size) * (Math.PI / 2));\n            }\n        }\n        setTranslation(mCurrView, amount);\n        if (FADE_OUT_DURING_SWIPE && mCanCurrViewBeDimissed) {\n            float alpha = getAlphaForOffset(mCurrView);\n            mCurrView.setAlpha(alpha);\n        }\n    }\n\n    private boolean isValidSwipeDirection(float amount) {\n        if (mSwipeDirection == X) {\n            if (mRtl) {\n                return (amount <= 0) ? mAllowSwipeTowardsEnd : mAllowSwipeTowardsStart;\n            } else {\n                return (amount <= 0) ? mAllowSwipeTowardsStart : mAllowSwipeTowardsEnd;\n            }\n        }\n\n        // Vertical swipes are always valid.\n        return true;\n    }\n\n    private void endSwipe(VelocityTracker velocityTracker) {\n        velocityTracker.computeCurrentVelocity(1000 /* px/sec */);\n        float velocity = getVelocity(velocityTracker);\n        float perpendicularVelocity = getPerpendicularVelocity(velocityTracker);\n        float escapeVelocity = SWIPE_ESCAPE_VELOCITY * mDensityScale;\n        float translation = getTranslation(mCurrView);\n        // Decide whether to dismiss the current view\n        boolean childSwipedFarEnough = DISMISS_IF_SWIPED_FAR_ENOUGH &&\n                Math.abs(translation) > 0.6 * getSize(mCurrView);\n        boolean childSwipedFastEnough = (Math.abs(velocity) > escapeVelocity) &&\n                (Math.abs(velocity) > Math.abs(perpendicularVelocity)) &&\n                (velocity > 0) == (translation > 0);\n\n        boolean dismissChild = mCallback.canChildBeDismissed(mCurrView)\n                && isValidSwipeDirection(translation)\n                && (childSwipedFastEnough || childSwipedFarEnough);\n\n        if (dismissChild) {\n            // flingadingy\n            dismissChild(mCurrView, childSwipedFastEnough ? velocity : 0f);\n        } else {\n            // snappity\n            mCallback.onDragCancelled(mCurrView);\n            snapChild(mCurrView, velocity);\n        }\n    }\n\n    public interface Callback {\n        View getChildAtPosition(MotionEvent ev);\n\n        boolean canChildBeDismissed(View v);\n\n        void onBeginDrag(View v);\n\n        void onSwipeChanged(View v, float delta);\n\n        void onChildDismissed(View v);\n\n        void onSnapBackCompleted(View v);\n\n        void onDragCancelled(View v);\n    }\n}\n"
  },
  {
    "path": "deckview/src/main/java/com/appeaser/deckview/helpers/FakeShadowDrawable.java",
    "content": "package com.appeaser.deckview.helpers;\n\n/**\n * Created by Vikram on 01/04/2015.\n */\n\nimport android.content.res.Resources;\nimport android.graphics.Canvas;\nimport android.graphics.ColorFilter;\nimport android.graphics.LinearGradient;\nimport android.graphics.Paint;\nimport android.graphics.Path;\nimport android.graphics.PixelFormat;\nimport android.graphics.RadialGradient;\nimport android.graphics.Rect;\nimport android.graphics.RectF;\nimport android.graphics.Shader;\nimport android.graphics.drawable.Drawable;\nimport android.util.Log;\n\nimport com.appeaser.deckview.R;\n\n/**\n * A rounded rectangle drawable which also includes a shadow around. This is mostly copied from\n * frameworks/support/v7/cardview/eclair-mr1/android/support/v7/widget/\n * RoundRectDrawableWithShadow.java revision c42ba8c000d1e6ce85e152dfc17089a0a69e739f with a few\n * modifications to suit our needs in SystemUI.\n */\npublic class FakeShadowDrawable extends Drawable {\n    // used to calculate content padding\n    final static double COS_45 = Math.cos(Math.toRadians(45));\n\n    final static float SHADOW_MULTIPLIER = 1.5f;\n\n    final float mInsetShadow; // extra shadow to avoid gaps between card and shadow\n\n    Paint mCornerShadowPaint;\n\n    Paint mEdgeShadowPaint;\n\n    final RectF mCardBounds;\n\n    float mCornerRadius;\n\n    Path mCornerShadowPath;\n\n    // updated value with inset\n    float mMaxShadowSize;\n\n    // actual value set by developer\n    float mRawMaxShadowSize;\n\n    // multiplied value to account for shadow offset\n    float mShadowSize;\n\n    // actual value set by developer\n    float mRawShadowSize;\n\n    private boolean mDirty = true;\n\n    private final int mShadowStartColor;\n\n    private final int mShadowEndColor;\n\n    private boolean mAddPaddingForCorners = true;\n\n    /**\n     * If shadow size is set to a value above max shadow, we print a warning\n     */\n    private boolean mPrintedShadowClipWarning = false;\n\n    public FakeShadowDrawable(Resources resources, DeckViewConfig config) {\n        mShadowStartColor = resources.getColor(R.color.fake_shadow_start_color);\n        mShadowEndColor = resources.getColor(R.color.fake_shadow_end_color);\n        mInsetShadow = resources.getDimension(R.dimen.fake_shadow_inset);\n        setShadowSize(resources.getDimensionPixelSize(R.dimen.fake_shadow_size),\n                resources.getDimensionPixelSize(R.dimen.fake_shadow_size));\n        mCornerShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);\n        mCornerShadowPaint.setStyle(Paint.Style.FILL);\n        mCornerShadowPaint.setDither(true);\n        mCornerRadius = config.taskViewRoundedCornerRadiusPx;\n        mCardBounds = new RectF();\n        mEdgeShadowPaint = new Paint(mCornerShadowPaint);\n    }\n\n    @Override\n    public void setAlpha(int alpha) {\n        mCornerShadowPaint.setAlpha(alpha);\n        mEdgeShadowPaint.setAlpha(alpha);\n    }\n\n    @Override\n    protected void onBoundsChange(Rect bounds) {\n        super.onBoundsChange(bounds);\n        mDirty = true;\n    }\n\n    void setShadowSize(float shadowSize, float maxShadowSize) {\n        if (shadowSize < 0 || maxShadowSize < 0) {\n            throw new IllegalArgumentException(\"invalid shadow size\");\n        }\n        if (shadowSize > maxShadowSize) {\n            shadowSize = maxShadowSize;\n            if (!mPrintedShadowClipWarning) {\n                Log.w(\"CardView\", \"Shadow size is being clipped by the max shadow size. See \"\n                        + \"{CardView#setMaxCardElevation}.\");\n                mPrintedShadowClipWarning = true;\n            }\n        }\n        if (mRawShadowSize == shadowSize && mRawMaxShadowSize == maxShadowSize) {\n            return;\n        }\n        mRawShadowSize = shadowSize;\n        mRawMaxShadowSize = maxShadowSize;\n        mShadowSize = shadowSize * SHADOW_MULTIPLIER + mInsetShadow;\n        mMaxShadowSize = maxShadowSize + mInsetShadow;\n        mDirty = true;\n        invalidateSelf();\n    }\n\n    @Override\n    public boolean getPadding(Rect padding) {\n        int vOffset = (int) Math.ceil(calculateVerticalPadding(mRawMaxShadowSize, mCornerRadius,\n                mAddPaddingForCorners));\n        int hOffset = (int) Math.ceil(calculateHorizontalPadding(mRawMaxShadowSize, mCornerRadius,\n                mAddPaddingForCorners));\n        padding.set(hOffset, vOffset, hOffset, vOffset);\n        return true;\n    }\n\n    static float calculateVerticalPadding(float maxShadowSize, float cornerRadius,\n                                          boolean addPaddingForCorners) {\n        if (addPaddingForCorners) {\n            return (float) (maxShadowSize * SHADOW_MULTIPLIER + (1 - COS_45) * cornerRadius);\n        } else {\n            return maxShadowSize * SHADOW_MULTIPLIER;\n        }\n    }\n\n    static float calculateHorizontalPadding(float maxShadowSize, float cornerRadius,\n                                            boolean addPaddingForCorners) {\n        if (addPaddingForCorners) {\n            return (float) (maxShadowSize + (1 - COS_45) * cornerRadius);\n        } else {\n            return maxShadowSize;\n        }\n    }\n\n    @Override\n    public void setColorFilter(ColorFilter cf) {\n        mCornerShadowPaint.setColorFilter(cf);\n        mEdgeShadowPaint.setColorFilter(cf);\n    }\n\n    @Override\n    public int getOpacity() {\n        return PixelFormat.OPAQUE;\n    }\n\n    @Override\n    public void draw(Canvas canvas) {\n        if (mDirty) {\n            buildComponents(getBounds());\n            mDirty = false;\n        }\n        canvas.translate(0, mRawShadowSize / 4);\n        drawShadow(canvas);\n        canvas.translate(0, -mRawShadowSize / 4);\n    }\n\n    private void drawShadow(Canvas canvas) {\n        final float edgeShadowTop = -mCornerRadius - mShadowSize;\n        final float inset = mCornerRadius + mInsetShadow + mRawShadowSize / 2;\n        final boolean drawHorizontalEdges = mCardBounds.width() - 2 * inset > 0;\n        final boolean drawVerticalEdges = mCardBounds.height() - 2 * inset > 0;\n        // LT\n        int saved = canvas.save();\n        canvas.translate(mCardBounds.left + inset, mCardBounds.top + inset);\n        canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);\n        if (drawHorizontalEdges) {\n            canvas.drawRect(0, edgeShadowTop,\n                    mCardBounds.width() - 2 * inset, -mCornerRadius,\n                    mEdgeShadowPaint);\n        }\n        canvas.restoreToCount(saved);\n        // RB\n        saved = canvas.save();\n        canvas.translate(mCardBounds.right - inset, mCardBounds.bottom - inset);\n        canvas.rotate(180f);\n        canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);\n        if (drawHorizontalEdges) {\n            canvas.drawRect(0, edgeShadowTop,\n                    mCardBounds.width() - 2 * inset, -mCornerRadius + mShadowSize,\n                    mEdgeShadowPaint);\n        }\n        canvas.restoreToCount(saved);\n        // LB\n        saved = canvas.save();\n        canvas.translate(mCardBounds.left + inset, mCardBounds.bottom - inset);\n        canvas.rotate(270f);\n        canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);\n        if (drawVerticalEdges) {\n            canvas.drawRect(0, edgeShadowTop,\n                    mCardBounds.height() - 2 * inset, -mCornerRadius, mEdgeShadowPaint);\n        }\n        canvas.restoreToCount(saved);\n        // RT\n        saved = canvas.save();\n        canvas.translate(mCardBounds.right - inset, mCardBounds.top + inset);\n        canvas.rotate(90f);\n        canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);\n        if (drawVerticalEdges) {\n            canvas.drawRect(0, edgeShadowTop,\n                    mCardBounds.height() - 2 * inset, -mCornerRadius, mEdgeShadowPaint);\n        }\n        canvas.restoreToCount(saved);\n    }\n\n    private void buildShadowCorners() {\n        RectF innerBounds = new RectF(-mCornerRadius, -mCornerRadius, mCornerRadius, mCornerRadius);\n        RectF outerBounds = new RectF(innerBounds);\n        outerBounds.inset(-mShadowSize, -mShadowSize);\n\n        if (mCornerShadowPath == null) {\n            mCornerShadowPath = new Path();\n        } else {\n            mCornerShadowPath.reset();\n        }\n        mCornerShadowPath.setFillType(Path.FillType.EVEN_ODD);\n        mCornerShadowPath.moveTo(-mCornerRadius, 0);\n        mCornerShadowPath.rLineTo(-mShadowSize, 0);\n        // outer arc\n        mCornerShadowPath.arcTo(outerBounds, 180f, 90f, false);\n        // inner arc\n        mCornerShadowPath.arcTo(innerBounds, 270f, -90f, false);\n        mCornerShadowPath.close();\n\n        float startRatio = mCornerRadius / (mCornerRadius + mShadowSize);\n        mCornerShadowPaint.setShader(new RadialGradient(0, 0, mCornerRadius + mShadowSize,\n                new int[]{mShadowStartColor, mShadowStartColor, mShadowEndColor},\n                new float[]{0f, startRatio, 1f}\n                , Shader.TileMode.CLAMP));\n\n        // we offset the content shadowSize/2 pixels up to make it more realistic.\n        // this is why edge shadow shader has some extra space\n        // When drawing bottom edge shadow, we use that extra space.\n        mEdgeShadowPaint.setShader(new LinearGradient(0, -mCornerRadius + mShadowSize, 0,\n                -mCornerRadius - mShadowSize,\n                new int[]{mShadowStartColor, mShadowStartColor, mShadowEndColor},\n                new float[]{0f, .5f, 1f}, Shader.TileMode.CLAMP));\n    }\n\n    private void buildComponents(Rect bounds) {\n        // Card is offset SHADOW_MULTIPLIER * maxShadowSize to account for the shadow shift.\n        // We could have different top-bottom offsets to avoid extra gap above but in that case\n        // center aligning Views inside the CardView would be problematic.\n        final float verticalOffset = mMaxShadowSize * SHADOW_MULTIPLIER;\n        mCardBounds.set(bounds.left + mMaxShadowSize, bounds.top + verticalOffset,\n                bounds.right - mMaxShadowSize, bounds.bottom - verticalOffset);\n        buildShadowCorners();\n    }\n\n    float getMinWidth() {\n        final float content = 2 *\n                Math.max(mRawMaxShadowSize, mCornerRadius + mInsetShadow + mRawMaxShadowSize / 2);\n        return content + (mRawMaxShadowSize + mInsetShadow) * 2;\n    }\n\n    float getMinHeight() {\n        final float content = 2 * Math.max(mRawMaxShadowSize, mCornerRadius + mInsetShadow\n                + mRawMaxShadowSize * SHADOW_MULTIPLIER / 2);\n        return content + (mRawMaxShadowSize * SHADOW_MULTIPLIER + mInsetShadow) * 2;\n    }\n}\n"
  },
  {
    "path": "deckview/src/main/java/com/appeaser/deckview/utilities/DVConstants.java",
    "content": "package com.appeaser.deckview.utilities;\n\n/**\n * Created by Vikram on 02/04/2015.\n */\npublic class DVConstants {\n    public static class DebugFlags {\n        // Enable this with any other debug flag to see more info\n        public static final boolean Verbose = false;\n\n        public static class App {\n            // Enables debug drawing for the transition thumbnail\n            public static final boolean EnableTransitionThumbnailDebugMode = false;\n            // Enables the filtering of tasks according to their grouping\n            public static final boolean EnableTaskFiltering = false;\n            // Enables clipping of tasks against each other\n            public static final boolean EnableTaskStackClipping = true;\n            // Enables tapping on the TaskBar to launch the task\n            public static final boolean EnableTaskBarTouchEvents = true;\n            // Enables app-info pane on long-pressing the icon\n            public static final boolean EnableDevAppInfoOnLongPress = true;\n            // Enables debug mode\n            public static final boolean EnableDebugMode = false;\n            // Enables the search bar layout\n            public static final boolean EnableSearchLayout = true;\n            // Enables the thumbnail alpha on the front-most task\n            public static final boolean EnableThumbnailAlphaOnFrontmost = false;\n            // This disables the bitmap and icon caches\n            public static final boolean DisableBackgroundCache = false;\n            // Enables the simulated task affiliations\n            public static final boolean EnableSimulatedTaskGroups = false;\n            // Defines the number of mock task affiliations per group\n            public static final int TaskAffiliationsGroupCount = 12;\n            // Enables us to create mock recents tasks\n            public static final boolean EnableSystemServicesProxy = false;\n            // Defines the number of mock recents packages to create\n            public static final int SystemServicesProxyMockPackageCount = 3;\n            // Defines the number of mock recents tasks to create\n            public static final int SystemServicesProxyMockTaskCount = 100;\n        }\n    }\n\n    public static class Values {\n        public static class App {\n            public static int AppWidgetHostId = 1024;\n            public static String Key_SearchAppWidgetId = \"searchAppWidgetId\";\n            public static String Key_DebugModeEnabled = \"debugModeEnabled\";\n            public static String DebugModeVersion = \"A\";\n        }\n\n        public static class DView {\n            public static final int TaskStackMinOverscrollRange = 32;\n            public static final int TaskStackMaxOverscrollRange = 128;\n            public static final int FilterStartDelay = 25;\n        }\n    }\n}\n"
  },
  {
    "path": "deckview/src/main/java/com/appeaser/deckview/utilities/DVUtils.java",
    "content": "package com.appeaser.deckview.utilities;\n\nimport android.animation.Animator;\nimport android.graphics.Color;\nimport android.graphics.Matrix;\nimport android.graphics.Rect;\nimport android.graphics.RectF;\nimport android.view.View;\n\nimport com.appeaser.deckview.helpers.DeckViewConfig;\n\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.util.ArrayList;\n\n/**\n * Created by Vikram on 02/04/2015.\n */\npublic class DVUtils {\n    // Reflection methods for altering shadows\n    private static Method sPropertyMethod;\n\n    static {\n        try {\n            Class<?> c = Class.forName(\"android.view.GLES20Canvas\");\n            sPropertyMethod = c.getDeclaredMethod(\"setProperty\", String.class, String.class);\n            if (!sPropertyMethod.isAccessible()) sPropertyMethod.setAccessible(true);\n        } catch (ClassNotFoundException e) {\n            e.printStackTrace();\n        } catch (NoSuchMethodException e) {\n            e.printStackTrace();\n        }\n    }\n\n    /**\n     * Calculates a consistent animation duration (ms) for all animations depending on the movement\n     * of the object being animated.\n     */\n    public static int calculateTranslationAnimationDuration(int distancePx) {\n        return calculateTranslationAnimationDuration(distancePx, 100);\n    }\n\n    public static int calculateTranslationAnimationDuration(int distancePx, int minDuration) {\n        DeckViewConfig config = DeckViewConfig.getInstance();\n        return Math.max(minDuration, (int) (1000f /* ms/s */ *\n                (Math.abs(distancePx) / config.animationPxMovementPerSecond)));\n    }\n\n    /**\n     * Scales a rect about its centroid\n     */\n    public static void scaleRectAboutCenter(Rect r, float scale) {\n        if (scale != 1.0f) {\n            int cx = r.centerX();\n            int cy = r.centerY();\n            r.offset(-cx, -cy);\n            r.left = (int) (r.left * scale + 0.5f);\n            r.top = (int) (r.top * scale + 0.5f);\n            r.right = (int) (r.right * scale + 0.5f);\n            r.bottom = (int) (r.bottom * scale + 0.5f);\n            r.offset(cx, cy);\n        }\n    }\n\n    /**\n     * Maps a coorindate in a descendant view into the parent.\n     */\n    public static float mapCoordInDescendentToSelf(View descendant, View root,\n                                                   float[] coord, boolean includeRootScroll) {\n        ArrayList<View> ancestorChain = new ArrayList<View>();\n\n        float[] pt = {coord[0], coord[1]};\n\n        View v = descendant;\n        while (v != root && v != null) {\n            ancestorChain.add(v);\n            v = (View) v.getParent();\n        }\n        ancestorChain.add(root);\n\n        float scale = 1.0f;\n        int count = ancestorChain.size();\n        for (int i = 0; i < count; i++) {\n            View v0 = ancestorChain.get(i);\n            // For TextViews, scroll has a meaning which relates to the text position\n            // which is very strange... ignore the scroll.\n            if (v0 != descendant || includeRootScroll) {\n                pt[0] -= v0.getScrollX();\n                pt[1] -= v0.getScrollY();\n            }\n\n            v0.getMatrix().mapPoints(pt);\n            pt[0] += v0.getLeft();\n            pt[1] += v0.getTop();\n            scale *= v0.getScaleX();\n        }\n\n        coord[0] = pt[0];\n        coord[1] = pt[1];\n        return scale;\n    }\n\n    /**\n     * Maps a coordinate in the root to a descendent.\n     */\n    public static float mapCoordInSelfToDescendent(View descendant, View root,\n                                                   float[] coord, Matrix tmpInverseMatrix) {\n        ArrayList<View> ancestorChain = new ArrayList<View>();\n\n        float[] pt = {coord[0], coord[1]};\n\n        View v = descendant;\n        while (v != root) {\n            ancestorChain.add(v);\n            v = (View) v.getParent();\n        }\n        ancestorChain.add(root);\n\n        float scale = 1.0f;\n        int count = ancestorChain.size();\n        tmpInverseMatrix.set(IDENTITY_MATRIX);\n        for (int i = count - 1; i >= 0; i--) {\n            View ancestor = ancestorChain.get(i);\n            View next = i > 0 ? ancestorChain.get(i - 1) : null;\n\n            pt[0] += ancestor.getScrollX();\n            pt[1] += ancestor.getScrollY();\n\n            if (next != null) {\n                pt[0] -= next.getLeft();\n                pt[1] -= next.getTop();\n                next.getMatrix().invert(tmpInverseMatrix);\n                tmpInverseMatrix.mapPoints(pt);\n                scale *= next.getScaleX();\n            }\n        }\n\n        coord[0] = pt[0];\n        coord[1] = pt[1];\n        return scale;\n    }\n\n    /**\n     * Calculates the constrast between two colors, using the algorithm provided by the WCAG v2.\n     */\n    public static float computeContrastBetweenColors(int bg, int fg) {\n        float bgR = Color.red(bg) / 255f;\n        float bgG = Color.green(bg) / 255f;\n        float bgB = Color.blue(bg) / 255f;\n        bgR = (bgR < 0.03928f) ? bgR / 12.92f : (float) Math.pow((bgR + 0.055f) / 1.055f, 2.4f);\n        bgG = (bgG < 0.03928f) ? bgG / 12.92f : (float) Math.pow((bgG + 0.055f) / 1.055f, 2.4f);\n        bgB = (bgB < 0.03928f) ? bgB / 12.92f : (float) Math.pow((bgB + 0.055f) / 1.055f, 2.4f);\n        float bgL = 0.2126f * bgR + 0.7152f * bgG + 0.0722f * bgB;\n\n        float fgR = Color.red(fg) / 255f;\n        float fgG = Color.green(fg) / 255f;\n        float fgB = Color.blue(fg) / 255f;\n        fgR = (fgR < 0.03928f) ? fgR / 12.92f : (float) Math.pow((fgR + 0.055f) / 1.055f, 2.4f);\n        fgG = (fgG < 0.03928f) ? fgG / 12.92f : (float) Math.pow((fgG + 0.055f) / 1.055f, 2.4f);\n        fgB = (fgB < 0.03928f) ? fgB / 12.92f : (float) Math.pow((fgB + 0.055f) / 1.055f, 2.4f);\n        float fgL = 0.2126f * fgR + 0.7152f * fgG + 0.0722f * fgB;\n\n        return Math.abs((fgL + 0.05f) / (bgL + 0.05f));\n    }\n\n    /**\n     * Returns the base color overlaid with another overlay color with a specified alpha.\n     */\n    public static int getColorWithOverlay(int baseColor, int overlayColor, float overlayAlpha) {\n        return Color.rgb(\n                (int) (overlayAlpha * Color.red(baseColor) +\n                        (1f - overlayAlpha) * Color.red(overlayColor)),\n                (int) (overlayAlpha * Color.green(baseColor) +\n                        (1f - overlayAlpha) * Color.green(overlayColor)),\n                (int) (overlayAlpha * Color.blue(baseColor) +\n                        (1f - overlayAlpha) * Color.blue(overlayColor)));\n    }\n\n    /**\n     * Sets some private shadow properties.\n     */\n    public static void setShadowProperty(String property, String value)\n            throws IllegalAccessException, InvocationTargetException {\n        sPropertyMethod.invoke(null, property, value);\n    }\n\n    /**\n     * Cancels an animation ensuring that if it has listeners, onCancel and onEnd\n     * are not called.\n     */\n    public static void cancelAnimationWithoutCallbacks(Animator animator) {\n        if (animator != null) {\n            animator.removeAllListeners();\n            animator.cancel();\n        }\n    }\n\n    public static Matrix IDENTITY_MATRIX = new Matrix() {\n        void oops() {\n            throw new IllegalStateException(\"Matrix can not be modified\");\n        }\n\n        @Override\n        public void set(Matrix src) {\n            oops();\n        }\n\n        @Override\n        public void reset() {\n            oops();\n        }\n\n        @Override\n        public void setTranslate(float dx, float dy) {\n            oops();\n        }\n\n        @Override\n        public void setScale(float sx, float sy, float px, float py) {\n            oops();\n        }\n\n        @Override\n        public void setScale(float sx, float sy) {\n            oops();\n        }\n\n        @Override\n        public void setRotate(float degrees, float px, float py) {\n            oops();\n        }\n\n        @Override\n        public void setRotate(float degrees) {\n            oops();\n        }\n\n        @Override\n        public void setSinCos(float sinValue, float cosValue, float px, float py) {\n            oops();\n        }\n\n        @Override\n        public void setSinCos(float sinValue, float cosValue) {\n            oops();\n        }\n\n        @Override\n        public void setSkew(float kx, float ky, float px, float py) {\n            oops();\n        }\n\n        @Override\n        public void setSkew(float kx, float ky) {\n            oops();\n        }\n\n        @Override\n        public boolean setConcat(Matrix a, Matrix b) {\n            oops();\n            return false;\n        }\n\n        @Override\n        public boolean preTranslate(float dx, float dy) {\n            oops();\n            return false;\n        }\n\n        @Override\n        public boolean preScale(float sx, float sy, float px, float py) {\n            oops();\n            return false;\n        }\n\n        @Override\n        public boolean preScale(float sx, float sy) {\n            oops();\n            return false;\n        }\n\n        @Override\n        public boolean preRotate(float degrees, float px, float py) {\n            oops();\n            return false;\n        }\n\n        @Override\n        public boolean preRotate(float degrees) {\n            oops();\n            return false;\n        }\n\n        @Override\n        public boolean preSkew(float kx, float ky, float px, float py) {\n            oops();\n            return false;\n        }\n\n        @Override\n        public boolean preSkew(float kx, float ky) {\n            oops();\n            return false;\n        }\n\n        @Override\n        public boolean preConcat(Matrix other) {\n            oops();\n            return false;\n        }\n\n        @Override\n        public boolean postTranslate(float dx, float dy) {\n            oops();\n            return false;\n        }\n\n        @Override\n        public boolean postScale(float sx, float sy, float px, float py) {\n            oops();\n            return false;\n        }\n\n        @Override\n        public boolean postScale(float sx, float sy) {\n            oops();\n            return false;\n        }\n\n        @Override\n        public boolean postRotate(float degrees, float px, float py) {\n            oops();\n            return false;\n        }\n\n        @Override\n        public boolean postRotate(float degrees) {\n            oops();\n            return false;\n        }\n\n        @Override\n        public boolean postSkew(float kx, float ky, float px, float py) {\n            oops();\n            return false;\n        }\n\n        @Override\n        public boolean postSkew(float kx, float ky) {\n            oops();\n            return false;\n        }\n\n        @Override\n        public boolean postConcat(Matrix other) {\n            oops();\n            return false;\n        }\n\n        @Override\n        public boolean setRectToRect(RectF src, RectF dst, ScaleToFit stf) {\n            oops();\n            return false;\n        }\n\n        @Override\n        public boolean setPolyToPoly(float[] src, int srcIndex, float[] dst, int dstIndex,\n                                     int pointCount) {\n            oops();\n            return false;\n        }\n\n        @Override\n        public void setValues(float[] values) {\n            oops();\n        }\n    };\n}\n"
  },
  {
    "path": "deckview/src/main/java/com/appeaser/deckview/utilities/DozeTrigger.java",
    "content": "package com.appeaser.deckview.utilities;\n\n/**\n * Created by Vikram on 01/04/2015.\n */\n\nimport android.os.Handler;\n\n/**\n * A dozer is a class that fires a trigger after it falls asleep.  You can occasionally poke it to\n * wake it up, but it will fall asleep if left untouched.\n */\npublic class DozeTrigger {\n\n    Handler mHandler;\n\n    boolean mIsDozing;\n    boolean mHasTriggered;\n    int mDozeDurationSeconds;\n    Runnable mSleepRunnable;\n\n    // Sleep-runnable\n    Runnable mDozeRunnable = new Runnable() {\n        @Override\n        public void run() {\n            mSleepRunnable.run();\n            mIsDozing = false;\n            mHasTriggered = true;\n        }\n    };\n\n    public DozeTrigger(int dozeDurationSeconds, Runnable sleepRunnable) {\n        mHandler = new Handler();\n        mDozeDurationSeconds = dozeDurationSeconds;\n        mSleepRunnable = sleepRunnable;\n    }\n\n    /**\n     * Starts dozing. This also resets the trigger flag.\n     */\n    public void startDozing() {\n        forcePoke();\n        mHasTriggered = false;\n    }\n\n    /**\n     * Stops dozing.\n     */\n    public void stopDozing() {\n        mHandler.removeCallbacks(mDozeRunnable);\n        mIsDozing = false;\n    }\n\n    /**\n     * Poke this dozer to wake it up for a little bit, if it is dozing.\n     */\n    public void poke() {\n        if (mIsDozing) {\n            forcePoke();\n        }\n    }\n\n    /**\n     * Poke this dozer to wake it up for a little bit.\n     */\n    void forcePoke() {\n        mHandler.removeCallbacks(mDozeRunnable);\n        mHandler.postDelayed(mDozeRunnable, mDozeDurationSeconds * 1000);\n        mIsDozing = true;\n    }\n\n    /**\n     * Returns whether we are dozing or not.\n     */\n    public boolean isDozing() {\n        return mIsDozing;\n    }\n\n    /**\n     * Returns whether the trigger has fired at least once.\n     */\n    public boolean hasTriggered() {\n        return mHasTriggered;\n    }\n\n    /**\n     * Resets the doze trigger state.\n     */\n    public void resetTrigger() {\n        mHasTriggered = false;\n    }\n}\n"
  },
  {
    "path": "deckview/src/main/java/com/appeaser/deckview/utilities/ReferenceCountedTrigger.java",
    "content": "package com.appeaser.deckview.utilities;\n\n/**\n * Created by Vikram on 01/04/2015.\n */\n\nimport android.animation.Animator;\nimport android.animation.AnimatorListenerAdapter;\nimport android.content.Context;\n\nimport java.util.ArrayList;\n\n/**\n * A ref counted trigger that does some logic when the count is first incremented, or last\n * decremented.  Not thread safe as it's not currently needed.\n */\npublic class ReferenceCountedTrigger {\n\n    Context mContext;\n    int mCount;\n    ArrayList<Runnable> mFirstIncRunnables = new ArrayList<Runnable>();\n    ArrayList<Runnable> mLastDecRunnables = new ArrayList<Runnable>();\n    Runnable mErrorRunnable;\n\n    // Convenience runnables\n    Runnable mIncrementRunnable = new Runnable() {\n        @Override\n        public void run() {\n            increment();\n        }\n    };\n    Runnable mDecrementRunnable = new Runnable() {\n        @Override\n        public void run() {\n            decrement();\n        }\n    };\n\n    public ReferenceCountedTrigger(Context context, Runnable firstIncRunnable,\n                                   Runnable lastDecRunnable, Runnable errorRunanable) {\n        mContext = context;\n        if (firstIncRunnable != null) mFirstIncRunnables.add(firstIncRunnable);\n        if (lastDecRunnable != null) mLastDecRunnables.add(lastDecRunnable);\n        mErrorRunnable = errorRunanable;\n    }\n\n    /**\n     * Increments the ref count\n     */\n    public void increment() {\n        if (mCount == 0 && !mFirstIncRunnables.isEmpty()) {\n            int numRunnables = mFirstIncRunnables.size();\n            for (int i = 0; i < numRunnables; i++) {\n                mFirstIncRunnables.get(i).run();\n            }\n        }\n        mCount++;\n    }\n\n    /**\n     * Convenience method to increment this trigger as a runnable\n     */\n    public Runnable incrementAsRunnable() {\n        return mIncrementRunnable;\n    }\n\n    /**\n     * Adds a runnable to the last-decrement runnables list.\n     */\n    public void addLastDecrementRunnable(Runnable r) {\n        // To ensure that the last decrement always calls, we increment and decrement after setting\n        // the last decrement runnable\n        boolean ensureLastDecrement = (mCount == 0);\n        if (ensureLastDecrement) increment();\n        mLastDecRunnables.add(r);\n        if (ensureLastDecrement) decrement();\n    }\n\n    /**\n     * Decrements the ref count\n     */\n    public void decrement() {\n        mCount--;\n        if (mCount == 0 && !mLastDecRunnables.isEmpty()) {\n            int numRunnables = mLastDecRunnables.size();\n            for (int i = 0; i < numRunnables; i++) {\n                mLastDecRunnables.get(i).run();\n            }\n        } else if (mCount < 0) {\n            if (mErrorRunnable != null) {\n                mErrorRunnable.run();\n            } else {\n                new Throwable(\"Invalid ref count\").printStackTrace();\n                //Console.logError(mContext, \"Invalid ref count\");\n            }\n        }\n    }\n\n    /**\n     * Convenience method to decrement this trigger as a runnable.\n     */\n    public Runnable decrementAsRunnable() {\n        return mDecrementRunnable;\n    }\n\n    /**\n     * Convenience method to decrement this trigger as a animator listener.\n     */\n    public Animator.AnimatorListener decrementOnAnimationEnd() {\n        return new AnimatorListenerAdapter() {\n            @Override\n            public void onAnimationEnd(Animator animation) {\n                decrement();\n            }\n        };\n    }\n\n    /**\n     * Returns the current ref count\n     */\n    public int getCount() {\n        return mCount;\n    }\n}\n"
  },
  {
    "path": "deckview/src/main/java/com/appeaser/deckview/views/AnimateableDeckChildViewBounds.java",
    "content": "package com.appeaser.deckview.views;\n\nimport android.graphics.Outline;\nimport android.graphics.Rect;\nimport android.view.View;\nimport android.view.ViewOutlineProvider;\n\nimport com.appeaser.deckview.helpers.DeckViewConfig;\n\n/**\n * Created by Vikram on 02/04/2015.\n */\n/* An outline provider that has a clip and outline that can be animated. */\npublic class AnimateableDeckChildViewBounds extends ViewOutlineProvider {\n\n    DeckViewConfig mConfig;\n\n    DeckChildView mSourceView;\n    Rect mClipRect = new Rect();\n    Rect mClipBounds = new Rect();\n    int mCornerRadius;\n    float mAlpha = 1f;\n    final float mMinAlpha = 0.25f;\n\n    public AnimateableDeckChildViewBounds(DeckChildView source, int cornerRadius) {\n        mConfig = DeckViewConfig.getInstance();\n        mSourceView = source;\n        mCornerRadius = cornerRadius;\n        setClipBottom(getClipBottom());\n    }\n\n    @Override\n    public void getOutline(View view, Outline outline) {\n        outline.setAlpha(mMinAlpha + mAlpha / (1f - mMinAlpha));\n        outline.setRoundRect(mClipRect.left, mClipRect.top,\n                mSourceView.getWidth() - mClipRect.right,\n                mSourceView.getHeight() - mClipRect.bottom,\n                mCornerRadius);\n    }\n\n    /**\n     * Sets the view outline alpha.\n     */\n    void setAlpha(float alpha) {\n        if (Float.compare(alpha, mAlpha) != 0) {\n            mAlpha = alpha;\n            mSourceView.invalidateOutline();\n        }\n    }\n\n    /**\n     * Sets the bottom clip.\n     */\n    public void setClipBottom(int bottom) {\n        if (bottom != mClipRect.bottom) {\n            mClipRect.bottom = bottom;\n            mSourceView.invalidateOutline();\n            updateClipBounds();\n            if (!mConfig.useHardwareLayers) {\n                mSourceView.mThumbnailView.updateThumbnailVisibility(\n                        bottom - mSourceView.getPaddingBottom());\n            }\n        }\n    }\n\n    /**\n     * Returns the bottom clip.\n     */\n    public int getClipBottom() {\n        return mClipRect.bottom;\n    }\n\n    private void updateClipBounds() {\n        mClipBounds.set(mClipRect.left, mClipRect.top,\n                mSourceView.getWidth() - mClipRect.right,\n                mSourceView.getHeight() - mClipRect.bottom);\n        mSourceView.setClipBounds(mClipBounds);\n    }\n}\n"
  },
  {
    "path": "deckview/src/main/java/com/appeaser/deckview/views/DeckChildView.java",
    "content": "package com.appeaser.deckview.views;\n\nimport android.animation.Animator;\nimport android.animation.ObjectAnimator;\nimport android.animation.ValueAnimator;\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.Color;\nimport android.graphics.Paint;\nimport android.graphics.PorterDuff;\nimport android.graphics.PorterDuffColorFilter;\nimport android.graphics.Rect;\nimport android.graphics.drawable.Drawable;\nimport android.util.AttributeSet;\nimport android.util.Log;\nimport android.view.View;\nimport android.view.animation.AccelerateInterpolator;\nimport android.view.animation.Interpolator;\nimport android.view.animation.PathInterpolator;\nimport android.widget.FrameLayout;\n\nimport com.appeaser.deckview.R;\nimport com.appeaser.deckview.helpers.DeckChildViewTransform;\nimport com.appeaser.deckview.helpers.DeckViewConfig;\nimport com.appeaser.deckview.helpers.FakeShadowDrawable;\nimport com.appeaser.deckview.utilities.DVConstants;\nimport com.appeaser.deckview.utilities.DVUtils;\n\n/**\n * Created by Vikram on 02/04/2015.\n */\n/* A task view */\npublic class DeckChildView<T> extends FrameLayout implements\n        View.OnClickListener, View.OnLongClickListener {\n\n    /**\n     * The TaskView callbacks\n     */\n    interface DeckChildViewCallbacks<T> {\n        public void onDeckChildViewAppIconClicked(DeckChildView dcv);\n\n        public void onDeckChildViewAppInfoClicked(DeckChildView dcv);\n\n        public void onDeckChildViewClicked(DeckChildView<T> dcv, T key);\n\n        public void onDeckChildViewDismissed(DeckChildView<T> dcv);\n\n        public void onDeckChildViewClipStateChanged(DeckChildView dcv);\n\n        public void onDeckChildViewFocusChanged(DeckChildView<T> dcv, boolean focused);\n    }\n\n    DeckViewConfig mConfig;\n\n    float mTaskProgress;\n    ObjectAnimator mTaskProgressAnimator;\n    float mMaxDimScale;\n    int mDimAlpha;\n    AccelerateInterpolator mDimInterpolator = new AccelerateInterpolator(1f);\n    PorterDuffColorFilter mDimColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_ATOP);\n    Paint mDimLayerPaint = new Paint();\n\n    T mKey;\n    boolean mTaskDataLoaded;\n    boolean mIsFocused;\n    boolean mFocusAnimationsEnabled;\n    boolean mClipViewInStack;\n    AnimateableDeckChildViewBounds mViewBounds;\n\n    View mContent;\n    DeckChildViewThumbnail mThumbnailView;\n    DeckChildViewHeader mHeaderView;\n    DeckChildViewCallbacks<T> mCb;\n\n    public static final Interpolator ALPHA_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);\n\n    // Optimizations\n    ValueAnimator.AnimatorUpdateListener mUpdateDimListener =\n            new ValueAnimator.AnimatorUpdateListener() {\n                @Override\n                public void onAnimationUpdate(ValueAnimator animation) {\n                    setTaskProgress((Float) animation.getAnimatedValue());\n                }\n            };\n\n\n    public DeckChildView(Context context) {\n        this(context, null);\n    }\n\n    public DeckChildView(Context context, AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    public DeckChildView(Context context, AttributeSet attrs, int defStyleAttr) {\n        this(context, attrs, defStyleAttr, 0);\n    }\n\n    public DeckChildView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {\n        super(context, attrs, defStyleAttr, defStyleRes);\n        mConfig = DeckViewConfig.getInstance();\n        mMaxDimScale = mConfig.taskStackMaxDim / 255f;\n        mClipViewInStack = true;\n        mViewBounds = new AnimateableDeckChildViewBounds(this, mConfig.taskViewRoundedCornerRadiusPx);\n        setTaskProgress(getTaskProgress());\n        setDim(getDim());\n        if (mConfig.fakeShadows) {\n            setBackground(new FakeShadowDrawable(context.getResources(), mConfig));\n        }\n        setOutlineProvider(mViewBounds);\n    }\n\n    /**\n     * Set callback\n     */\n    void setCallbacks(DeckChildViewCallbacks cb) {\n        mCb = cb;\n    }\n\n    /**\n     * Resets this TaskView for reuse.\n     */\n    void reset() {\n        resetViewProperties();\n        resetNoUserInteractionState();\n        setClipViewInStack(false);\n        setCallbacks(null);\n    }\n\n    /**\n     * Gets the task\n     */\n    T getAttachedKey() {\n        return mKey;\n    }\n\n    /**\n     * Returns the view bounds.\n     */\n    AnimateableDeckChildViewBounds getViewBounds() {\n        return mViewBounds;\n    }\n\n    @Override\n    protected void onFinishInflate() {\n        // Bind the views\n        mContent = findViewById(R.id.task_view_content);\n        mHeaderView = (DeckChildViewHeader) findViewById(R.id.task_view_bar);\n        mThumbnailView = (DeckChildViewThumbnail) findViewById(R.id.task_view_thumbnail);\n        mThumbnailView.updateClipToTaskBar(mHeaderView);\n    }\n\n    @Override\n    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n        int width = MeasureSpec.getSize(widthMeasureSpec);\n        int height = MeasureSpec.getSize(heightMeasureSpec);\n\n        int widthWithoutPadding = width - getPaddingLeft() - getPaddingRight();\n        int heightWithoutPadding = height - getPaddingTop() - getPaddingBottom();\n\n        // Measure the content\n        mContent.measure(MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),\n                MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY));\n\n        // Measure the bar view, and action button\n        mHeaderView.measure(MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),\n                MeasureSpec.makeMeasureSpec(mConfig.taskBarHeight, MeasureSpec.EXACTLY));\n\n        // Measure the thumbnail to be square\n        mThumbnailView.measure(\n                MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),\n                MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY));\n        setMeasuredDimension(width, height);\n        invalidateOutline();\n    }\n\n    /**\n     * Synchronizes this view's properties with the task's transform\n     */\n    void updateViewPropertiesToTaskTransform(DeckChildViewTransform toTransform, int duration) {\n        updateViewPropertiesToTaskTransform(toTransform, duration, null);\n    }\n\n    void updateViewPropertiesToTaskTransform(DeckChildViewTransform toTransform, int duration,\n                                             ValueAnimator.AnimatorUpdateListener updateCallback) {\n        // Apply the transform\n        toTransform.applyToTaskView(this, duration, mConfig.fastOutSlowInInterpolator, false,\n                !mConfig.fakeShadows, updateCallback);\n\n        // Update the task progress\n        DVUtils.cancelAnimationWithoutCallbacks(mTaskProgressAnimator);\n        if (duration <= 0) {\n            setTaskProgress(toTransform.p);\n        } else {\n            mTaskProgressAnimator = ObjectAnimator.ofFloat(this, \"taskProgress\", toTransform.p);\n            mTaskProgressAnimator.setDuration(duration);\n            mTaskProgressAnimator.addUpdateListener(mUpdateDimListener);\n            mTaskProgressAnimator.start();\n        }\n    }\n\n    /**\n     * Resets this view's properties\n     */\n    void resetViewProperties() {\n        setDim(0);\n        setLayerType(View.LAYER_TYPE_NONE, null);\n        DeckChildViewTransform.reset(this);\n    }\n\n    /**\n     * When we are un/filtering, this method will set up the transform that we are animating to,\n     * in order to hide the task.\n     */\n    void prepareTaskTransformForFilterTaskHidden(DeckChildViewTransform toTransform) {\n        // Fade the view out and slide it away\n        toTransform.alpha = 0f;\n        toTransform.translationY += 200;\n        toTransform.translationZ = 0;\n    }\n\n    /**\n     * When we are un/filtering, this method will setup the transform that we are animating from,\n     * in order to show the task.\n     */\n    void prepareTaskTransformForFilterTaskVisible(DeckChildViewTransform fromTransform) {\n        // Fade the view in\n        fromTransform.alpha = 0f;\n    }\n\n    /**\n     * Prepares this task view for the enter-recents animations.  This is called earlier in the\n     * first layout because the actual animation into recents may take a long time.\n     */\n    void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask,\n                                      boolean occludesLaunchTarget, int offscreenY) {\n        int initialDim = getDim();\n        if (mConfig.launchedHasConfigurationChanged) {\n            // Just load the views as-is\n        } else if (mConfig.launchedFromAppWithThumbnail) {\n            if (isTaskViewLaunchTargetTask) {\n                // Set the dim to 0 so we can animate it in\n                initialDim = 0;\n            } else if (occludesLaunchTarget) {\n                // Move the task view off screen (below) so we can animate it in\n                setTranslationY(offscreenY);\n            }\n\n        } else if (mConfig.launchedFromHome) {\n            // Move the task view off screen (below) so we can animate it in\n            setTranslationY(offscreenY);\n            setTranslationZ(0);\n            setScaleX(1f);\n            setScaleY(1f);\n        }\n        // Apply the current dim\n        setDim(initialDim);\n        // Prepare the thumbnail view alpha\n        mThumbnailView.prepareEnterRecentsAnimation(isTaskViewLaunchTargetTask);\n    }\n\n    /**\n     * Animates this task view as it enters recents\n     */\n    void startEnterRecentsAnimation(final ViewAnimation.TaskViewEnterContext ctx) {\n        Log.i(getClass().getSimpleName(), \"startEnterRecentsAnimation\");\n        final DeckChildViewTransform transform = ctx.currentTaskTransform;\n        int startDelay = 0;\n\n        if (mConfig.launchedFromHome) {\n            Log.i(getClass().getSimpleName(), \"mConfig.launchedFromHome false\");\n            // Animate the tasks up\n            int frontIndex = (ctx.currentStackViewCount - ctx.currentStackViewIndex - 1);\n            int delay = mConfig.transitionEnterFromHomeDelay +\n                    frontIndex * mConfig.taskViewEnterFromHomeStaggerDelay;\n\n            setScaleX(transform.scale);\n            setScaleY(transform.scale);\n            if (!mConfig.fakeShadows) {\n                animate().translationZ(transform.translationZ);\n            }\n            animate()\n                    .translationY(transform.translationY)\n                    .setStartDelay(delay)\n                    .setUpdateListener(ctx.updateListener)\n                    .setInterpolator(mConfig.quintOutInterpolator)\n                    .setDuration(mConfig.taskViewEnterFromHomeDuration +\n                            frontIndex * mConfig.taskViewEnterFromHomeStaggerDelay)\n                    .withEndAction(new Runnable() {\n                        @Override\n                        public void run() {\n                            // Decrement the post animation trigger\n                            ctx.postAnimationTrigger.decrement();\n                        }\n                    })\n                    .start();\n            ctx.postAnimationTrigger.increment();\n            startDelay = delay;\n        }\n\n        // Enable the focus animations from this point onwards so that they aren't affected by the\n        // window transitions\n        postDelayed(new Runnable() {\n            @Override\n            public void run() {\n                enableFocusAnimations();\n            }\n        }, startDelay);\n    }\n\n    /**\n     * Animates this task view as it leaves recents by pressing home.\n     */\n    void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) {\n        animate()\n                .translationY(ctx.offscreenTranslationY)\n                .setStartDelay(0)\n                .setUpdateListener(null)\n                .setInterpolator(mConfig.fastOutLinearInInterpolator)\n                .setDuration(mConfig.taskViewExitToHomeDuration)\n                .withEndAction(ctx.postAnimationTrigger.decrementAsRunnable())\n                .start();\n        ctx.postAnimationTrigger.increment();\n    }\n\n    /**\n     * Animates this task view as it exits recents\n     */\n    void startLaunchTaskAnimation(final Runnable postAnimRunnable, boolean isLaunchingTask,\n                                  boolean occludesLaunchTarget, boolean lockToTask) {\n        if (isLaunchingTask) {\n            // Animate the thumbnail alpha back into full opacity for the window animation out\n            mThumbnailView.startLaunchTaskAnimation(postAnimRunnable);\n\n            // Animate the dim\n            if (mDimAlpha > 0) {\n                ObjectAnimator anim = ObjectAnimator.ofInt(this, \"dim\", 0);\n                anim.setDuration(mConfig.taskViewExitToAppDuration);\n                anim.setInterpolator(mConfig.fastOutLinearInInterpolator);\n                anim.start();\n            }\n        } else {\n            // Hide the dismiss button\n            mHeaderView.startLaunchTaskDismissAnimation();\n            // If this is another view in the task grouping and is in front of the launch task,\n            // animate it away first\n            if (occludesLaunchTarget) {\n                animate().alpha(0f)\n                        .translationY(getTranslationY() + mConfig.taskViewAffiliateGroupEnterOffsetPx)\n                        .setStartDelay(0)\n                        .setUpdateListener(null)\n                        .setInterpolator(mConfig.fastOutLinearInInterpolator)\n                        .setDuration(mConfig.taskViewExitToAppDuration)\n                        .start();\n            }\n        }\n    }\n\n    /**\n     * Animates the deletion of this task view\n     */\n    void startDeleteTaskAnimation(final Runnable r) {\n        // Disabling clipping with the stack while the view is animating away\n        setClipViewInStack(false);\n\n        animate().translationX(mConfig.taskViewRemoveAnimTranslationXPx)\n                .alpha(0f)\n                .setStartDelay(0)\n                .setUpdateListener(null)\n                .setInterpolator(mConfig.fastOutSlowInInterpolator)\n                .setDuration(mConfig.taskViewRemoveAnimDuration)\n                .withEndAction(new Runnable() {\n                    @Override\n                    public void run() {\n                        // We just throw this into a runnable because starting a view property\n                        // animation using layers can cause inconsisten results if we try and\n                        // update the layers while the animation is running.  In some cases,\n                        // the runnabled passed in may start an animation which also uses layers\n                        // so we defer all this by posting this.\n                        r.run();\n\n                        // Re-enable clipping with the stack (we will reuse this view)\n                        setClipViewInStack(true);\n                    }\n                })\n                .start();\n    }\n\n    /**\n     * Animates this task view if the user does not interact with the stack after a certain time.\n     */\n    void startNoUserInteractionAnimation() {\n        mHeaderView.startNoUserInteractionAnimation();\n    }\n\n    /**\n     * Mark this task view that the user does has not interacted with the stack after a certain time.\n     */\n    void setNoUserInteractionState() {\n        mHeaderView.setNoUserInteractionState();\n    }\n\n    /**\n     * Resets the state tracking that the user has not interacted with the stack after a certain time.\n     */\n    void resetNoUserInteractionState() {\n        mHeaderView.resetNoUserInteractionState();\n    }\n\n    /**\n     * Dismisses this task.\n     */\n    void dismissTask() {\n        // Animate out the view and call the callback\n        final DeckChildView<T> tv = this;\n        startDeleteTaskAnimation(new Runnable() {\n            @Override\n            public void run() {\n                if (mCb != null) {\n                    mCb.onDeckChildViewDismissed(tv);\n                }\n            }\n        });\n    }\n\n    /**\n     * Returns whether this view should be clipped, or any views below should clip against this\n     * view.\n     */\n    boolean shouldClipViewInStack() {\n        return mClipViewInStack && (getVisibility() == View.VISIBLE);\n    }\n\n    /**\n     * Sets whether this view should be clipped, or clipped against.\n     */\n    public void setClipViewInStack(boolean clip) {\n        if (clip != mClipViewInStack) {\n            mClipViewInStack = clip;\n            if (mCb != null) {\n                mCb.onDeckChildViewClipStateChanged(this);\n            }\n        }\n    }\n\n    /**\n     * Sets the current task progress.\n     */\n    public void setTaskProgress(float p) {\n        mTaskProgress = p;\n        mViewBounds.setAlpha(p);\n        updateDimFromTaskProgress();\n    }\n\n    /**\n     * Returns the current task progress.\n     */\n    public float getTaskProgress() {\n        return mTaskProgress;\n    }\n\n    /**\n     * Returns the current dim.\n     */\n    public void setDim(int dim) {\n        mDimAlpha = dim;\n        if (mConfig.useHardwareLayers) {\n            // Defer setting hardware layers if we have not yet measured, or there is no dim to draw\n            if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0) {\n                mDimColorFilter =\n                        new PorterDuffColorFilter(Color.argb(mDimAlpha, 0, 0, 0),\n                                PorterDuff.Mode.SRC_ATOP);\n                mDimLayerPaint.setColorFilter(mDimColorFilter);\n                mContent.setLayerType(LAYER_TYPE_HARDWARE, mDimLayerPaint);\n            }\n        } else {\n            float dimAlpha = mDimAlpha / 255.0f;\n            if (mThumbnailView != null) {\n                mThumbnailView.setDimAlpha(dimAlpha);\n            }\n            if (mHeaderView != null) {\n                mHeaderView.setDimAlpha(dim);\n            }\n        }\n    }\n\n    /**\n     * Returns the current dim.\n     */\n    public int getDim() {\n        return mDimAlpha;\n    }\n\n    /**\n     * Animates the dim to the task progress.\n     */\n    void animateDimToProgress(int delay, int duration, Animator.AnimatorListener postAnimRunnable) {\n        // Animate the dim into view as well\n        int toDim = getDimFromTaskProgress();\n        if (toDim != getDim()) {\n            ObjectAnimator anim = ObjectAnimator.ofInt(DeckChildView.this, \"dim\", toDim);\n            anim.setStartDelay(delay);\n            anim.setDuration(duration);\n            if (postAnimRunnable != null) {\n                anim.addListener(postAnimRunnable);\n            }\n            anim.start();\n        }\n    }\n\n    /**\n     * Compute the dim as a function of the scale of this view.\n     */\n    int getDimFromTaskProgress() {\n        float dim = mMaxDimScale * mDimInterpolator.getInterpolation(1f - mTaskProgress);\n        return (int) (dim * 255);\n    }\n\n    /**\n     * Update the dim as a function of the scale of this view.\n     */\n    void updateDimFromTaskProgress() {\n        setDim(getDimFromTaskProgress());\n    }\n\n    /**** View focus state ****/\n\n    /**\n     * Sets the focused task explicitly. We need a separate flag because requestFocus() won't happen\n     * if the view is not currently visible, or we are in touch state (where we still want to keep\n     * track of focus).\n     */\n    public void setFocusedTask(boolean animateFocusedState) {\n        mIsFocused = true;\n        if (mFocusAnimationsEnabled) {\n            // Focus the header bar\n            mHeaderView.onTaskViewFocusChanged(true, animateFocusedState);\n        }\n        // Update the thumbnail alpha with the focus\n        mThumbnailView.onFocusChanged(true);\n        // Call the callback\n        if (mCb != null) {\n            mCb.onDeckChildViewFocusChanged(this, true);\n        }\n        // Workaround, we don't always want it focusable in touch mode, but we want the first task\n        // to be focused after the enter-recents animation, which can be triggered from either touch\n        // or keyboard\n        setFocusableInTouchMode(true);\n        requestFocus();\n        setFocusableInTouchMode(false);\n        invalidate();\n    }\n\n    /**\n     * Unsets the focused task explicitly.\n     */\n    void unsetFocusedTask() {\n        mIsFocused = false;\n        if (mFocusAnimationsEnabled) {\n            // Un-focus the header bar\n            mHeaderView.onTaskViewFocusChanged(false, true);\n        }\n\n        // Update the thumbnail alpha with the focus\n        mThumbnailView.onFocusChanged(false);\n        // Call the callback\n        if (mCb != null) {\n            mCb.onDeckChildViewFocusChanged(this, false);\n        }\n        invalidate();\n    }\n\n    /**\n     * Updates the explicitly focused state when the view focus changes.\n     */\n    @Override\n    protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {\n        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);\n        if (!gainFocus) {\n            unsetFocusedTask();\n        }\n    }\n\n    /**\n     * Returns whether we have explicitly been focused.\n     */\n    public boolean isFocusedTask() {\n        return mIsFocused || isFocused();\n    }\n\n    /**\n     * Enables all focus animations.\n     */\n    void enableFocusAnimations() {\n        boolean wasFocusAnimationsEnabled = mFocusAnimationsEnabled;\n        mFocusAnimationsEnabled = true;\n        if (mIsFocused && !wasFocusAnimationsEnabled) {\n            // Re-notify the header if we were focused and animations were not previously enabled\n            mHeaderView.onTaskViewFocusChanged(true, true);\n        }\n    }\n\n    /**** TaskCallbacks Implementation ****/\n\n    /**\n     * Binds this task view to the task\n     */\n    public void onTaskBound(T key) {\n        mKey = key;\n    }\n\n    private boolean isBound() {\n        return mKey != null;\n    }\n\n    /**\n     * Binds this task view to the task\n     */\n    public void onTaskUnbound() {\n        mKey = null;\n    }\n\n    public Bitmap getThumbnail() {\n        if (mThumbnailView != null) {\n            return mThumbnailView.getThumbnail();\n        }\n\n        return null;\n    }\n\n    public void onDataLoaded(T key, Bitmap thumbnail, Drawable headerIcon,\n                             String headerTitle, int headerBgColor) {\n        if (!isBound() || !mKey.equals(key))\n            return;\n\n        if (mThumbnailView != null && mHeaderView != null) {\n            // Bind each of the views to the new task data\n            mThumbnailView.rebindToTask(thumbnail);\n            mHeaderView.rebindToTask(headerIcon, headerTitle, headerBgColor);\n            // Rebind any listeners\n            mHeaderView.mApplicationIcon.setOnClickListener(this);\n            mHeaderView.mDismissButton.setOnClickListener(this);\n\n            // TODO: Check if this functionality is needed\n            mHeaderView.mApplicationIcon.setOnLongClickListener(this);\n        }\n        mTaskDataLoaded = true;\n    }\n\n    public void onDataUnloaded() {\n        if (mThumbnailView != null && mHeaderView != null) {\n            // Unbind each of the views from the task data and remove the task callback\n            mThumbnailView.unbindFromTask();\n            mHeaderView.unbindFromTask();\n            // Unbind any listeners\n            mHeaderView.mApplicationIcon.setOnClickListener(null);\n            mHeaderView.mDismissButton.setOnClickListener(null);\n            if (DVConstants.DebugFlags.App.EnableDevAppInfoOnLongPress) {\n                mHeaderView.mApplicationIcon.setOnLongClickListener(null);\n            }\n        }\n        mTaskDataLoaded = false;\n    }\n\n    /**\n     * Enables/disables handling touch on this task view.\n     */\n    public void setTouchEnabled(boolean enabled) {\n        setOnClickListener(enabled ? this : null);\n    }\n\n    /**\n     * * View.OnClickListener Implementation ***\n     */\n\n    @Override\n    public void onClick(final View v) {\n        final DeckChildView<T> tv = this;\n        final boolean delayViewClick = (v != this);\n        if (delayViewClick) {\n            // We purposely post the handler delayed to allow for the touch feedback to draw\n            postDelayed(new Runnable() {\n                @Override\n                public void run() {\n                    if (DVConstants.DebugFlags.App.EnableTaskFiltering\n                            && v == mHeaderView.mApplicationIcon) {\n                        if (mCb != null) {\n                            mCb.onDeckChildViewAppIconClicked(tv);\n                        }\n                    } else if (v == mHeaderView.mDismissButton) {\n                        dismissTask();\n                    }\n                }\n            }, 125);\n        } else {\n            if (mCb != null) {\n                mCb.onDeckChildViewClicked(tv, tv.getAttachedKey());\n            }\n        }\n    }\n\n    /**\n     * * View.OnLongClickListener Implementation ***\n     */\n\n    @Override\n    public boolean onLongClick(View v) {\n        if (v == mHeaderView.mApplicationIcon) {\n            if (mCb != null) {\n                mCb.onDeckChildViewAppInfoClicked(this);\n                return true;\n            }\n        }\n        return false;\n    }\n}\n"
  },
  {
    "path": "deckview/src/main/java/com/appeaser/deckview/views/DeckChildViewHeader.java",
    "content": "package com.appeaser.deckview.views;\n\nimport android.animation.Animator;\nimport android.animation.AnimatorListenerAdapter;\nimport android.animation.AnimatorSet;\nimport android.animation.ArgbEvaluator;\nimport android.animation.ObjectAnimator;\nimport android.animation.ValueAnimator;\nimport android.content.Context;\nimport android.content.res.ColorStateList;\nimport android.content.res.Resources;\nimport android.graphics.Canvas;\nimport android.graphics.Color;\nimport android.graphics.Outline;\nimport android.graphics.Paint;\nimport android.graphics.PorterDuff;\nimport android.graphics.PorterDuffColorFilter;\nimport android.graphics.PorterDuffXfermode;\nimport android.graphics.drawable.ColorDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.graphics.drawable.GradientDrawable;\nimport android.graphics.drawable.RippleDrawable;\nimport android.util.AttributeSet;\nimport android.view.MotionEvent;\nimport android.view.View;\nimport android.view.ViewOutlineProvider;\nimport android.widget.FrameLayout;\nimport android.widget.ImageView;\nimport android.widget.TextView;\n\nimport com.appeaser.deckview.R;\nimport com.appeaser.deckview.helpers.DeckViewConfig;\nimport com.appeaser.deckview.utilities.DVConstants;\nimport com.appeaser.deckview.utilities.DVUtils;\n\n/**\n * Created by Vikram on 02/04/2015.\n */\n/* The task bar view */\npublic class DeckChildViewHeader extends FrameLayout {\n\n    DeckViewConfig mConfig;\n\n    // Header views\n    ImageView mDismissButton;\n    ImageView mApplicationIcon;\n    TextView mActivityDescription;\n\n    // Header drawables\n    boolean mCurrentPrimaryColorIsDark;\n    int mCurrentPrimaryColor;\n    int mBackgroundColor;\n    Drawable mLightDismissDrawable;\n    Drawable mDarkDismissDrawable;\n    RippleDrawable mBackground;\n    GradientDrawable mBackgroundColorDrawable;\n    AnimatorSet mFocusAnimator;\n    String mDismissContentDescription;\n\n    // Static highlight that we draw at the top of each view\n    static Paint sHighlightPaint;\n\n    // Header dim, which is only used when task view hardware layers are not used\n    Paint mDimLayerPaint = new Paint();\n    PorterDuffColorFilter mDimColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_ATOP);\n\n    public DeckChildViewHeader(Context context) {\n        this(context, null);\n    }\n\n    public DeckChildViewHeader(Context context, AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    public DeckChildViewHeader(Context context, AttributeSet attrs, int defStyleAttr) {\n        this(context, attrs, defStyleAttr, 0);\n    }\n\n    public DeckChildViewHeader(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {\n        super(context, attrs, defStyleAttr, defStyleRes);\n        mConfig = DeckViewConfig.getInstance();\n        setWillNotDraw(false);\n        setClipToOutline(true);\n        setOutlineProvider(new ViewOutlineProvider() {\n            @Override\n            public void getOutline(View view, Outline outline) {\n                outline.setRect(0, 0, getMeasuredWidth(), getMeasuredHeight());\n            }\n        });\n\n        // Load the dismiss resources\n        Resources res = context.getResources();\n        mLightDismissDrawable = res.getDrawable(R.drawable.deck_child_view_dismiss_light);\n        mDarkDismissDrawable = res.getDrawable(R.drawable.deck_child_view_dismiss_dark);\n        mDismissContentDescription =\n                res.getString(R.string.accessibility_item_will_be_dismissed);\n\n        // Configure the highlight paint\n        if (sHighlightPaint == null) {\n            sHighlightPaint = new Paint();\n            sHighlightPaint.setStyle(Paint.Style.STROKE);\n            sHighlightPaint.setStrokeWidth(mConfig.taskViewHighlightPx);\n            sHighlightPaint.setColor(mConfig.taskBarViewHighlightColor);\n            sHighlightPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));\n            sHighlightPaint.setAntiAlias(true);\n        }\n    }\n\n    @Override\n    public boolean onTouchEvent(MotionEvent event) {\n        // We ignore taps on the task bar except on the filter and dismiss buttons\n        if (!DVConstants.DebugFlags.App.EnableTaskBarTouchEvents) return true;\n\n        return super.onTouchEvent(event);\n    }\n\n    @Override\n    protected void onFinishInflate() {\n        // Initialize the icon and description views\n        mApplicationIcon = (ImageView) findViewById(R.id.application_icon);\n        mActivityDescription = (TextView) findViewById(R.id.activity_description);\n        mDismissButton = (ImageView) findViewById(R.id.dismiss_task);\n\n        // Hide the backgrounds if they are ripple drawables\n        if (!DVConstants.DebugFlags.App.EnableTaskFiltering) {\n            if (mApplicationIcon.getBackground() instanceof RippleDrawable) {\n                mApplicationIcon.setBackground(null);\n            }\n        }\n\n        mBackgroundColorDrawable = (GradientDrawable) getContext().getDrawable(R.drawable\n                .deck_child_view_header_bg_color);\n        // Copy the ripple drawable since we are going to be manipulating it\n        mBackground = (RippleDrawable)\n                getContext().getDrawable(R.drawable.deck_child_view_header_bg);\n        mBackground = (RippleDrawable) mBackground.mutate().getConstantState().newDrawable();\n        mBackground.setColor(ColorStateList.valueOf(0));\n        mBackground.setDrawableByLayerId(mBackground.getId(0), mBackgroundColorDrawable);\n        setBackground(mBackground);\n    }\n\n    @Override\n    protected void onDraw(Canvas canvas) {\n        // Draw the highlight at the top edge (but put the bottom edge just out of view)\n        float offset = (float) Math.ceil(mConfig.taskViewHighlightPx / 2f);\n        float radius = mConfig.taskViewRoundedCornerRadiusPx;\n        int count = canvas.save(Canvas.CLIP_SAVE_FLAG);\n        canvas.clipRect(0, 0, getMeasuredWidth(), getMeasuredHeight());\n        canvas.drawRoundRect(-offset, 0f, (float) getMeasuredWidth() + offset,\n                getMeasuredHeight() + radius, radius, radius, sHighlightPaint);\n        canvas.restoreToCount(count);\n    }\n\n    @Override\n    public boolean hasOverlappingRendering() {\n        return false;\n    }\n\n    /**\n     * Sets the dim alpha, only used when we are not using hardware layers.\n     * (see RecentsConfiguration.useHardwareLayers)\n     */\n    void setDimAlpha(int alpha) {\n        mDimColorFilter = new PorterDuffColorFilter(Color.argb(alpha, 0, 0, 0),\n                PorterDuff.Mode.SRC_ATOP);\n        mDimLayerPaint.setColorFilter(mDimColorFilter);\n        setLayerType(LAYER_TYPE_HARDWARE, mDimLayerPaint);\n    }\n\n    /**\n     * Returns the secondary color for a primary color.\n     */\n    int getSecondaryColor(int primaryColor, boolean useLightOverlayColor) {\n        int overlayColor = useLightOverlayColor ? Color.WHITE : Color.BLACK;\n        return DVUtils.getColorWithOverlay(primaryColor, overlayColor, 0.8f);\n    }\n\n    /**\n     * Binds the bar view to the task\n     */\n    //public void rebindToTask(Task t) {\n    public void rebindToTask(Drawable headerIcon, String headerTitle, int headerBgColor) {\n        // If an activity icon is defined, then we use that as the primary icon to show in the bar,\n        // otherwise, we fall back to the application icon\n        mApplicationIcon.setImageDrawable(headerIcon);\n        mApplicationIcon.setContentDescription(headerTitle);\n\n        mActivityDescription.setText(headerTitle);\n\n        // Try and apply the system ui tint\n        int existingBgColor = (getBackground() instanceof ColorDrawable) ?\n                ((ColorDrawable) getBackground()).getColor() : 0;\n        if (existingBgColor != headerBgColor) {\n            mBackgroundColorDrawable.setColor(headerBgColor);\n            mBackgroundColor = headerBgColor;\n        }\n        mCurrentPrimaryColor = headerBgColor;\n        //mCurrentPrimaryColorIsDark = t.useLightOnPrimaryColor;\n        mActivityDescription.setTextColor(mConfig.taskBarViewLightTextColor);\n        mDismissButton.setImageDrawable(mLightDismissDrawable);\n        mDismissButton.setContentDescription(String.format(mDismissContentDescription,\n                headerTitle));\n    }\n\n    /**\n     * Unbinds the bar view from the task\n     */\n    void unbindFromTask() {\n        mApplicationIcon.setImageDrawable(null);\n    }\n\n    /**\n     * Animates this task bar dismiss button when launching a task.\n     */\n    void startLaunchTaskDismissAnimation() {\n        if (mDismissButton.getVisibility() == View.VISIBLE) {\n            mDismissButton.animate().cancel();\n            mDismissButton.animate()\n                    .alpha(0f)\n                    .setStartDelay(0)\n                    .setInterpolator(mConfig.fastOutSlowInInterpolator)\n                    .setDuration(mConfig.taskViewExitToAppDuration)\n                    .withLayer()\n                    .start();\n        }\n    }\n\n    /**\n     * Animates this task bar if the user does not interact with the stack after a certain time.\n     */\n    void startNoUserInteractionAnimation() {\n        if (mDismissButton.getVisibility() != View.VISIBLE) {\n            mDismissButton.setVisibility(View.VISIBLE);\n            mDismissButton.setAlpha(0f);\n            mDismissButton.animate()\n                    .alpha(1f)\n                    .setStartDelay(0)\n                    .setInterpolator(mConfig.fastOutLinearInInterpolator)\n                    .setDuration(mConfig.taskViewEnterFromAppDuration)\n                    .withLayer()\n                    .start();\n        }\n    }\n\n    /**\n     * Mark this task view that the user does has not interacted with the stack after a certain time.\n     */\n    void setNoUserInteractionState() {\n        if (mDismissButton.getVisibility() != View.VISIBLE) {\n            mDismissButton.animate().cancel();\n            mDismissButton.setVisibility(View.VISIBLE);\n            mDismissButton.setAlpha(1f);\n        }\n    }\n\n    /**\n     * Resets the state tracking that the user has not interacted with the stack after a certain time.\n     */\n    void resetNoUserInteractionState() {\n        mDismissButton.setVisibility(View.INVISIBLE);\n    }\n\n    @Override\n    protected int[] onCreateDrawableState(int extraSpace) {\n\n        // Don't forward our state to the drawable - we do it manually in onTaskViewFocusChanged.\n        // This is to prevent layer trashing when the view is pressed.\n        return new int[]{};\n    }\n\n    /**\n     * Notifies the associated TaskView has been focused.\n     */\n    void onTaskViewFocusChanged(boolean focused, boolean animateFocusedState) {\n        // If we are not animating the visible state, just return\n        if (!animateFocusedState) return;\n\n        boolean isRunning = false;\n        if (mFocusAnimator != null) {\n            isRunning = mFocusAnimator.isRunning();\n            DVUtils.cancelAnimationWithoutCallbacks(mFocusAnimator);\n        }\n\n        if (focused) {\n            int secondaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark);\n            int[][] states = new int[][]{\n                    new int[]{android.R.attr.state_enabled},\n                    new int[]{android.R.attr.state_pressed}\n            };\n            int[] newStates = new int[]{\n                    android.R.attr.state_enabled,\n                    android.R.attr.state_pressed\n            };\n            int[] colors = new int[]{\n                    secondaryColor,\n                    secondaryColor\n            };\n            mBackground.setColor(new ColorStateList(states, colors));\n            mBackground.setState(newStates);\n            // Pulse the background color\n            int currentColor = mBackgroundColor;\n            int lightPrimaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark);\n            ValueAnimator backgroundColor = ValueAnimator.ofObject(new ArgbEvaluator(),\n                    currentColor, lightPrimaryColor);\n            backgroundColor.addListener(new AnimatorListenerAdapter() {\n                @Override\n                public void onAnimationStart(Animator animation) {\n                    mBackground.setState(new int[]{});\n                }\n            });\n            backgroundColor.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {\n                @Override\n                public void onAnimationUpdate(ValueAnimator animation) {\n                    int color = (int) animation.getAnimatedValue();\n                    mBackgroundColorDrawable.setColor(color);\n                    mBackgroundColor = color;\n                }\n            });\n            backgroundColor.setRepeatCount(ValueAnimator.INFINITE);\n            backgroundColor.setRepeatMode(ValueAnimator.REVERSE);\n            // Pulse the translation\n            ObjectAnimator translation = ObjectAnimator.ofFloat(this, \"translationZ\", 15f);\n            translation.setRepeatCount(ValueAnimator.INFINITE);\n            translation.setRepeatMode(ValueAnimator.REVERSE);\n\n            mFocusAnimator = new AnimatorSet();\n            mFocusAnimator.playTogether(backgroundColor, translation);\n            mFocusAnimator.setStartDelay(750);\n            mFocusAnimator.setDuration(750);\n            mFocusAnimator.start();\n        } else {\n            if (isRunning) {\n                // Restore the background color\n                int currentColor = mBackgroundColor;\n                ValueAnimator backgroundColor = ValueAnimator.ofObject(new ArgbEvaluator(),\n                        currentColor, mCurrentPrimaryColor);\n                backgroundColor.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {\n                    @Override\n                    public void onAnimationUpdate(ValueAnimator animation) {\n                        int color = (int) animation.getAnimatedValue();\n                        mBackgroundColorDrawable.setColor(color);\n                        mBackgroundColor = color;\n                    }\n                });\n                // Restore the translation\n                ObjectAnimator translation = ObjectAnimator.ofFloat(this, \"translationZ\", 0f);\n\n                mFocusAnimator = new AnimatorSet();\n                mFocusAnimator.playTogether(backgroundColor, translation);\n                mFocusAnimator.setDuration(150);\n                mFocusAnimator.start();\n            } else {\n                mBackground.setState(new int[]{});\n                setTranslationZ(0f);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "deckview/src/main/java/com/appeaser/deckview/views/DeckChildViewThumbnail.java",
    "content": "package com.appeaser.deckview.views;\n\n/**\n * Created by Vikram on 02/04/2015.\n */\n\nimport android.animation.Animator;\nimport android.animation.AnimatorListenerAdapter;\nimport android.animation.ValueAnimator;\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapShader;\nimport android.graphics.Canvas;\nimport android.graphics.Color;\nimport android.graphics.LightingColorFilter;\nimport android.graphics.Matrix;\nimport android.graphics.Paint;\nimport android.graphics.Rect;\nimport android.graphics.RectF;\nimport android.graphics.Shader;\nimport android.util.AttributeSet;\nimport android.view.View;\n\nimport com.appeaser.deckview.helpers.DeckViewConfig;\nimport com.appeaser.deckview.utilities.DVUtils;\n\n/**\n * The task thumbnail view.  It implements an image view that allows for animating the dim and\n * alpha of the thumbnail image.\n */\npublic class DeckChildViewThumbnail extends View {\n\n    DeckViewConfig mConfig;\n\n    // Drawing\n    float mDimAlpha;\n    Matrix mScaleMatrix = new Matrix();\n    Paint mDrawPaint = new Paint();\n    RectF mBitmapRect = new RectF();\n    RectF mLayoutRect = new RectF();\n    BitmapShader mBitmapShader;\n    LightingColorFilter mLightingColorFilter = new LightingColorFilter(0xffffffff, 0);\n\n    // Thumbnail alpha\n    float mThumbnailAlpha;\n    ValueAnimator mThumbnailAlphaAnimator;\n    ValueAnimator.AnimatorUpdateListener mThumbnailAlphaUpdateListener\n            = new ValueAnimator.AnimatorUpdateListener() {\n        @Override\n        public void onAnimationUpdate(ValueAnimator animation) {\n            mThumbnailAlpha = (float) animation.getAnimatedValue();\n            updateThumbnailPaintFilter();\n        }\n    };\n\n    // Task bar clipping, the top of this thumbnail can be clipped against the opaque header\n    // bar that overlaps this thumbnail\n    View mTaskBar;\n    Rect mClipRect = new Rect();\n\n    // Visibility optimization, if the thumbnail height is less than the height of the header\n    // bar for the task view, then just mark this thumbnail view as invisible\n    boolean mInvisible;\n\n    public DeckChildViewThumbnail(Context context) {\n        this(context, null);\n    }\n\n    public DeckChildViewThumbnail(Context context, AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    public DeckChildViewThumbnail(Context context, AttributeSet attrs, int defStyleAttr) {\n        this(context, attrs, defStyleAttr, 0);\n    }\n\n    public DeckChildViewThumbnail(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {\n        super(context, attrs, defStyleAttr, defStyleRes);\n        mConfig = DeckViewConfig.getInstance();\n        mDrawPaint.setColorFilter(mLightingColorFilter);\n        mDrawPaint.setFilterBitmap(true);\n        mDrawPaint.setAntiAlias(true);\n    }\n\n    @Override\n    protected void onFinishInflate() {\n        mThumbnailAlpha = mConfig.taskViewThumbnailAlpha;\n        updateThumbnailPaintFilter();\n    }\n\n    @Override\n    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {\n        super.onLayout(changed, left, top, right, bottom);\n        if (changed) {\n            mLayoutRect.set(0, 0, getWidth(), getHeight());\n            updateThumbnailScale();\n        }\n    }\n\n    @Override\n    protected void onDraw(Canvas canvas) {\n        if (mInvisible) {\n            return;\n        }\n        // Draw the thumbnail with the rounded corners\n        canvas.drawRoundRect(0, 0, getWidth(), getHeight(),\n                mConfig.taskViewRoundedCornerRadiusPx,\n                mConfig.taskViewRoundedCornerRadiusPx, mDrawPaint);\n    }\n\n    /**\n     * Sets the thumbnail to a given bitmap.\n     */\n    void setThumbnail(Bitmap bm) {\n        mThumbnail = bm;\n\n        if (bm != null) {\n            mBitmapShader = new BitmapShader(bm, Shader.TileMode.CLAMP,\n                    Shader.TileMode.CLAMP);\n            mDrawPaint.setShader(mBitmapShader);\n            mBitmapRect.set(0, 0, bm.getWidth(), bm.getHeight());\n            updateThumbnailScale();\n        } else {\n            mBitmapShader = null;\n            mDrawPaint.setShader(null);\n        }\n        updateThumbnailPaintFilter();\n    }\n\n    /**\n     * Updates the paint to draw the thumbnail.\n     */\n    void updateThumbnailPaintFilter() {\n        if (mInvisible) {\n            return;\n        }\n        int mul = (int) ((1.0f - mDimAlpha) * mThumbnailAlpha * 255);\n        int add = (int) ((1.0f - mDimAlpha) * (1 - mThumbnailAlpha) * 255);\n        if (mBitmapShader != null) {\n            mLightingColorFilter =\n                    new LightingColorFilter(Color.argb(255, mul, mul, mul),\n                            Color.argb(0, add, add, add));\n            mDrawPaint.setColorFilter(mLightingColorFilter);\n            mDrawPaint.setColor(0xffffffff);\n        } else {\n            int grey = mul + add;\n            mDrawPaint.setColorFilter(null);\n            mDrawPaint.setColor(Color.argb(255, grey, grey, grey));\n        }\n        invalidate();\n    }\n\n    /**\n     * Updates the thumbnail shader's scale transform.\n     */\n    void updateThumbnailScale() {\n        if (mBitmapShader != null) {\n            mScaleMatrix.setRectToRect(mBitmapRect, mLayoutRect, Matrix.ScaleToFit.FILL);\n            mBitmapShader.setLocalMatrix(mScaleMatrix);\n        }\n    }\n\n    /**\n     * Updates the clip rect based on the given task bar.\n     */\n    void updateClipToTaskBar(View taskBar) {\n        mTaskBar = taskBar;\n        int top = (int) Math.max(0, taskBar.getTranslationY() +\n                taskBar.getMeasuredHeight() - 1);\n        mClipRect.set(0, top, getMeasuredWidth(), getMeasuredHeight());\n        setClipBounds(mClipRect);\n    }\n\n    /**\n     * Updates the visibility of the the thumbnail.\n     */\n    void updateThumbnailVisibility(int clipBottom) {\n        boolean invisible = mTaskBar != null && (getHeight() - clipBottom) <= mTaskBar.getHeight();\n        if (invisible != mInvisible) {\n            mInvisible = invisible;\n            if (!mInvisible) {\n                updateThumbnailPaintFilter();\n            }\n            invalidate();\n        }\n    }\n\n    /**\n     * Sets the dim alpha, only used when we are not using hardware layers.\n     * (see RecentsConfiguration.useHardwareLayers)\n     */\n    public void setDimAlpha(float dimAlpha) {\n        mDimAlpha = dimAlpha;\n        updateThumbnailPaintFilter();\n    }\n\n    /**\n     * Binds the thumbnail view to the task\n     */\n    //void rebindToTask(Task t) {\n    void rebindToTask(Bitmap thumbnail) {\n        if (thumbnail != null) {\n            setThumbnail(thumbnail);\n        } else {\n            setThumbnail(null);\n        }\n    }\n\n    /**\n     * Unbinds the thumbnail view from the task\n     */\n    void unbindFromTask() {\n        setThumbnail(null);\n    }\n\n    Bitmap mThumbnail;\n\n    public Bitmap getThumbnail() {\n        return mThumbnail;\n    }\n\n    /**\n     * Handles focus changes.\n     */\n    void onFocusChanged(boolean focused) {\n        if (focused) {\n            if (Float.compare(getAlpha(), 1f) != 0) {\n                startFadeAnimation(1f, 0, 150, null);\n            }\n        } else {\n            if (Float.compare(getAlpha(), mConfig.taskViewThumbnailAlpha) != 0) {\n                startFadeAnimation(mConfig.taskViewThumbnailAlpha, 0, 150, null);\n            }\n        }\n    }\n\n    /**\n     * Prepares for the enter recents animation, this gets called before the the view\n     * is first visible and will be followed by a startEnterRecentsAnimation() call.\n     */\n    void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask) {\n        if (isTaskViewLaunchTargetTask) {\n            mThumbnailAlpha = 1f;\n        } else {\n            mThumbnailAlpha = mConfig.taskViewThumbnailAlpha;\n        }\n        updateThumbnailPaintFilter();\n    }\n\n    /**\n     * Animates this task thumbnail as it enters Recents.\n     */\n    void startEnterRecentsAnimation(int delay, Runnable postAnimRunnable) {\n        startFadeAnimation(mConfig.taskViewThumbnailAlpha, delay,\n                mConfig.taskViewEnterFromAppDuration, postAnimRunnable);\n    }\n\n    /**\n     * Animates this task thumbnail as it exits Recents.\n     */\n    void startLaunchTaskAnimation(Runnable postAnimRunnable) {\n        startFadeAnimation(1f, 0, mConfig.taskViewExitToAppDuration, postAnimRunnable);\n    }\n\n    /**\n     * Starts a new thumbnail alpha animation.\n     */\n    void startFadeAnimation(float finalAlpha, int delay, int duration, final Runnable postAnimRunnable) {\n        DVUtils.cancelAnimationWithoutCallbacks(mThumbnailAlphaAnimator);\n        mThumbnailAlphaAnimator = ValueAnimator.ofFloat(mThumbnailAlpha, finalAlpha);\n        mThumbnailAlphaAnimator.setStartDelay(delay);\n        mThumbnailAlphaAnimator.setDuration(duration);\n        mThumbnailAlphaAnimator.setInterpolator(mConfig.fastOutSlowInInterpolator);\n        mThumbnailAlphaAnimator.addUpdateListener(mThumbnailAlphaUpdateListener);\n        if (postAnimRunnable != null) {\n            mThumbnailAlphaAnimator.addListener(new AnimatorListenerAdapter() {\n                @Override\n                public void onAnimationEnd(Animator animation) {\n                    postAnimRunnable.run();\n                }\n            });\n        }\n        mThumbnailAlphaAnimator.start();\n    }\n}\n"
  },
  {
    "path": "deckview/src/main/java/com/appeaser/deckview/views/DeckView.java",
    "content": "package com.appeaser.deckview.views;\n\nimport android.animation.ValueAnimator;\nimport android.content.Context;\nimport android.graphics.Matrix;\nimport android.graphics.Rect;\nimport android.util.AttributeSet;\nimport android.view.LayoutInflater;\nimport android.view.MotionEvent;\nimport android.view.View;\nimport android.view.WindowInsets;\nimport android.view.accessibility.AccessibilityEvent;\nimport android.widget.FrameLayout;\n\nimport com.appeaser.deckview.R;\nimport com.appeaser.deckview.helpers.DeckChildViewTransform;\nimport com.appeaser.deckview.helpers.DeckViewConfig;\nimport com.appeaser.deckview.utilities.DVConstants;\nimport com.appeaser.deckview.utilities.DVUtils;\nimport com.appeaser.deckview.utilities.DozeTrigger;\nimport com.appeaser.deckview.utilities.ReferenceCountedTrigger;\n\nimport java.lang.ref.WeakReference;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.Iterator;\n\n/**\n * Created by Vikram on 02/04/2015.\n */\n/* The visual representation of a task stack view */\npublic class DeckView<T> extends FrameLayout implements /*TaskStack.TaskStackCallbacks,*/\n        DeckChildView.DeckChildViewCallbacks<T>, DeckViewScroller.DeckViewScrollerCallbacks,\n        ViewPool.ViewPoolConsumer<DeckChildView<T>, T> {\n\n    DeckViewConfig mConfig;\n\n    DeckViewLayoutAlgorithm<T> mLayoutAlgorithm;\n    DeckViewScroller mStackScroller;\n    DeckViewTouchHandler mTouchHandler;\n    ViewPool<DeckChildView<T>, T> mViewPool;\n    ArrayList<DeckChildViewTransform> mCurrentTaskTransforms = new ArrayList<DeckChildViewTransform>();\n    DozeTrigger mUIDozeTrigger;\n    Rect mTaskStackBounds = new Rect();\n    int mFocusedTaskIndex = -1;\n    int mPrevAccessibilityFocusedIndex = -1;\n\n    // Optimizations\n    int mStackViewsAnimationDuration;\n    boolean mStackViewsDirty = true;\n    boolean mStackViewsClipDirty = true;\n    boolean mAwaitingFirstLayout = true;\n    boolean mStartEnterAnimationRequestedAfterLayout;\n    boolean mStartEnterAnimationCompleted;\n    ViewAnimation.TaskViewEnterContext mStartEnterAnimationContext;\n    int[] mTmpVisibleRange = new int[2];\n    float[] mTmpCoord = new float[2];\n    Matrix mTmpMatrix = new Matrix();\n    Rect mTmpRect = new Rect();\n    DeckChildViewTransform mTmpTransform = new DeckChildViewTransform();\n    HashMap<T, DeckChildView> mTmpTaskViewMap = new HashMap<T, DeckChildView>();\n    LayoutInflater mInflater;\n\n    // A convenience update listener to request updating clipping of tasks\n    ValueAnimator.AnimatorUpdateListener mRequestUpdateClippingListener =\n            new ValueAnimator.AnimatorUpdateListener() {\n                @Override\n                public void onAnimationUpdate(ValueAnimator animation) {\n                    requestUpdateStackViewsClip();\n                }\n            };\n\n    public DeckView(Context context) {\n        this(context, null);\n    }\n\n    public DeckView(Context context, AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    public DeckView(Context context, AttributeSet attrs, int defStyleAttr) {\n        this(context, attrs, defStyleAttr, 0);\n    }\n\n    public DeckView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {\n        super(context, attrs, defStyleAttr, defStyleRes);\n        DeckViewConfig.reinitialize(getContext());\n        mConfig = DeckViewConfig.getInstance();\n    }\n\n    public void initialize(Callback<T> callback) {\n        mCallback = callback;\n        requestLayout();\n\n        mViewPool = new ViewPool<DeckChildView<T>, T>(getContext(), this);\n        mInflater = LayoutInflater.from(getContext());\n        mLayoutAlgorithm = new DeckViewLayoutAlgorithm<T>(mConfig);\n        mStackScroller = new DeckViewScroller(getContext(), mConfig, mLayoutAlgorithm);\n        mStackScroller.setCallbacks(this);\n        mTouchHandler = new DeckViewTouchHandler(getContext(), this, mConfig, mStackScroller);\n\n        mUIDozeTrigger = new DozeTrigger(mConfig.taskBarDismissDozeDelaySeconds, new Runnable() {\n            @Override\n            public void run() {\n                // Show the task bar dismiss buttons\n                int childCount = getChildCount();\n                for (int i = 0; i < childCount; i++) {\n                    DeckChildView tv = (DeckChildView) getChildAt(i);\n                    tv.startNoUserInteractionAnimation();\n                }\n            }\n        });\n    }\n\n    /**\n     * Resets this TaskStackView for reuse.\n     */\n    void reset() {\n        // Reset the focused task\n        resetFocusedTask();\n\n        // Return all the views to the pool\n        int childCount = getChildCount();\n        for (int i = childCount - 1; i >= 0; i--) {\n            DeckChildView<T> tv = (DeckChildView) getChildAt(i);\n            mViewPool.returnViewToPool(tv);\n        }\n\n        // Mark each task view for relayout\n        if (mViewPool != null) {\n            Iterator<DeckChildView<T>> iter = mViewPool.poolViewIterator();\n            if (iter != null) {\n                while (iter.hasNext()) {\n                    DeckChildView tv = iter.next();\n                    tv.reset();\n                }\n            }\n        }\n\n        // Reset the stack state\n        mStackViewsDirty = true;\n        mStackViewsClipDirty = true;\n        mAwaitingFirstLayout = true;\n        mPrevAccessibilityFocusedIndex = -1;\n        if (mUIDozeTrigger != null) {\n            mUIDozeTrigger.stopDozing();\n            mUIDozeTrigger.resetTrigger();\n        }\n        mStackScroller.reset();\n    }\n\n    /**\n     * Requests that the views be synchronized with the model\n     */\n    void requestSynchronizeStackViewsWithModel() {\n        requestSynchronizeStackViewsWithModel(0);\n    }\n\n    void requestSynchronizeStackViewsWithModel(int duration) {\n        if (!mStackViewsDirty) {\n            invalidate();\n            mStackViewsDirty = true;\n        }\n        if (mAwaitingFirstLayout) {\n            // Skip the animation if we are awaiting first layout\n            mStackViewsAnimationDuration = 0;\n        } else {\n            mStackViewsAnimationDuration = Math.max(mStackViewsAnimationDuration, duration);\n        }\n    }\n\n    /**\n     * Requests that the views clipping be updated.\n     */\n    void requestUpdateStackViewsClip() {\n        if (!mStackViewsClipDirty) {\n            invalidate();\n            mStackViewsClipDirty = true;\n        }\n    }\n\n    /**\n     * Finds the child view given a specific task.\n     */\n    public DeckChildView getChildViewForTask(T key) {\n        int childCount = getChildCount();\n        for (int i = 0; i < childCount; i++) {\n            DeckChildView tv = (DeckChildView) getChildAt(i);\n            if (tv.getAttachedKey().equals(key)) {\n                return tv;\n            }\n        }\n        return null;\n    }\n\n    /**\n     * Returns the stack algorithm for this task stack.\n     */\n    public DeckViewLayoutAlgorithm getStackAlgorithm() {\n        return mLayoutAlgorithm;\n    }\n\n    /**\n     * Gets the stack transforms of a list of tasks, and returns the visible range of tasks.\n     */\n    private boolean updateStackTransforms(ArrayList<DeckChildViewTransform> taskTransforms,\n                                          ArrayList<T> data,\n                                          float stackScroll,\n                                          int[] visibleRangeOut,\n                                          boolean boundTranslationsToRect) {\n        int taskTransformCount = taskTransforms.size();\n        int taskCount = data.size();\n        int frontMostVisibleIndex = -1;\n        int backMostVisibleIndex = -1;\n\n        // We can reuse the task transforms where possible to reduce object allocation\n        if (taskTransformCount < taskCount) {\n            // If there are less transforms than tasks, then add as many transforms as necessary\n            for (int i = taskTransformCount; i < taskCount; i++) {\n                taskTransforms.add(new DeckChildViewTransform());\n            }\n        } else if (taskTransformCount > taskCount) {\n            // If there are more transforms than tasks, then just subset the transform list\n            taskTransforms.subList(0, taskCount);\n        }\n\n        // Update the stack transforms\n        DeckChildViewTransform prevTransform = null;\n        for (int i = taskCount - 1; i >= 0; i--) {\n            DeckChildViewTransform transform =\n                    mLayoutAlgorithm.getStackTransform(data.get(i),\n                            stackScroll, taskTransforms.get(i), prevTransform);\n            if (transform.visible) {\n                if (frontMostVisibleIndex < 0) {\n                    frontMostVisibleIndex = i;\n                }\n                backMostVisibleIndex = i;\n            } else {\n                if (backMostVisibleIndex != -1) {\n                    // We've reached the end of the visible range, so going down the rest of the\n                    // stack, we can just reset the transforms accordingly\n                    while (i >= 0) {\n                        taskTransforms.get(i).reset();\n                        i--;\n                    }\n                    break;\n                }\n            }\n\n            if (boundTranslationsToRect) {\n                transform.translationY = Math.min(transform.translationY,\n                        mLayoutAlgorithm.mViewRect.bottom);\n            }\n            prevTransform = transform;\n        }\n        if (visibleRangeOut != null) {\n            visibleRangeOut[0] = frontMostVisibleIndex;\n            visibleRangeOut[1] = backMostVisibleIndex;\n        }\n        return frontMostVisibleIndex != -1 && backMostVisibleIndex != -1;\n    }\n\n    /**\n     * Synchronizes the views with the model\n     */\n    boolean synchronizeStackViewsWithModel() {\n        if (mStackViewsDirty) {\n            // Get all the task transforms\n            ArrayList<T> data = mCallback.getData();\n            float stackScroll = mStackScroller.getStackScroll();\n            int[] visibleRange = mTmpVisibleRange;\n            boolean isValidVisibleRange = updateStackTransforms(mCurrentTaskTransforms,\n                    data, stackScroll, visibleRange, false);\n\n            // Return all the invisible children to the pool\n            mTmpTaskViewMap.clear();\n            int childCount = getChildCount();\n            for (int i = childCount - 1; i >= 0; i--) {\n                DeckChildView<T> tv = (DeckChildView) getChildAt(i);\n                T key = tv.getAttachedKey();\n                int taskIndex = data.indexOf(key);\n\n                if (visibleRange[1] <= taskIndex\n                        && taskIndex <= visibleRange[0]) {\n                    mTmpTaskViewMap.put(key, tv);\n                } else {\n                    mViewPool.returnViewToPool(tv);\n                }\n            }\n\n            for (int i = visibleRange[0]; isValidVisibleRange && i >= visibleRange[1]; i--) {\n                T key = data.get(i);\n                DeckChildViewTransform transform = mCurrentTaskTransforms.get(i);\n                DeckChildView tv = mTmpTaskViewMap.get(key);\n\n                if (tv == null) {\n                    // TODO Check\n                    tv = mViewPool.pickUpViewFromPool(key, key);\n\n                    if (mStackViewsAnimationDuration > 0) {\n                        // For items in the list, put them in start animating them from the\n                        // approriate ends of the list where they are expected to appear\n                        if (Float.compare(transform.p, 0f) <= 0) {\n                            mLayoutAlgorithm.getStackTransform(0f, 0f, mTmpTransform, null);\n                        } else {\n                            mLayoutAlgorithm.getStackTransform(1f, 0f, mTmpTransform, null);\n                        }\n                        tv.updateViewPropertiesToTaskTransform(mTmpTransform, 0);\n                    }\n                }\n\n                // Animate the task into place\n                tv.updateViewPropertiesToTaskTransform(mCurrentTaskTransforms.get(i),\n                        mStackViewsAnimationDuration, mRequestUpdateClippingListener);\n            }\n\n            // Reset the request-synchronize params\n            mStackViewsAnimationDuration = 0;\n            mStackViewsDirty = false;\n            mStackViewsClipDirty = true;\n            return true;\n        }\n        return false;\n    }\n\n    /**\n     * Updates the clip for each of the task views.\n     */\n    void clipTaskViews() {\n        // Update the clip on each task child\n        if (DVConstants.DebugFlags.App.EnableTaskStackClipping) {\n            int childCount = getChildCount();\n            for (int i = 0; i < childCount - 1; i++) {\n                DeckChildView tv = (DeckChildView) getChildAt(i);\n                DeckChildView nextTv = null;\n                DeckChildView tmpTv = null;\n                int clipBottom = 0;\n                if (tv.shouldClipViewInStack()) {\n                    // Find the next view to clip against\n                    int nextIndex = i;\n                    while (nextIndex < getChildCount()) {\n                        tmpTv = (DeckChildView) getChildAt(++nextIndex);\n                        if (tmpTv != null && tmpTv.shouldClipViewInStack()) {\n                            nextTv = tmpTv;\n                            break;\n                        }\n                    }\n\n                    // Clip against the next view, this is just an approximation since we are\n                    // stacked and we can make assumptions about the visibility of the this\n                    // task relative to the ones in front of it.\n                    if (nextTv != null) {\n                        // Map the top edge of next task view into the local space of the current\n                        // task view to find the clip amount in local space\n                        mTmpCoord[0] = mTmpCoord[1] = 0;\n                        DVUtils.mapCoordInDescendentToSelf(nextTv, this, mTmpCoord, false);\n                        DVUtils.mapCoordInSelfToDescendent(tv, this, mTmpCoord, mTmpMatrix);\n                        clipBottom = (int) Math.floor(tv.getMeasuredHeight() - mTmpCoord[1]\n                                - nextTv.getPaddingTop() - 1);\n                    }\n                }\n                tv.getViewBounds().setClipBottom(clipBottom);\n            }\n            if (getChildCount() > 0) {\n                // The front most task should never be clipped\n                DeckChildView tv = (DeckChildView) getChildAt(getChildCount() - 1);\n                tv.getViewBounds().setClipBottom(0);\n            }\n        }\n        mStackViewsClipDirty = false;\n    }\n\n    /**\n     * The stack insets to apply to the stack contents\n     */\n    public void setStackInsetRect(Rect r) {\n        mTaskStackBounds.set(r);\n    }\n\n    /**\n     * Updates the min and max virtual scroll bounds\n     */\n    void updateMinMaxScroll(boolean boundScrollToNewMinMax, boolean launchedWithAltTab,\n                            boolean launchedFromHome) {\n        // Compute the min and max scroll values\n        mLayoutAlgorithm.computeMinMaxScroll(mCallback.getData(), launchedWithAltTab, launchedFromHome);\n\n        // Debug logging\n        if (boundScrollToNewMinMax) {\n            mStackScroller.boundScroll();\n        }\n    }\n\n    /**\n     * Returns the scroller.\n     */\n    public DeckViewScroller getScroller() {\n        return mStackScroller;\n    }\n\n    /**\n     * Focuses the task at the specified index in the stack\n     */\n    void focusTask(int childIndex, boolean scrollToNewPosition, final boolean animateFocusedState) {\n        // Return early if the task is already focused\n        if (childIndex == mFocusedTaskIndex) return;\n\n        ArrayList<T> data = mCallback.getData();\n\n        if (0 <= childIndex && childIndex < data.size()) {\n            mFocusedTaskIndex = childIndex;\n\n            // Focus the view if possible, otherwise, focus the view after we scroll into position\n            T key = data.get(childIndex);\n            DeckChildView tv = getChildViewForTask(key);\n            Runnable postScrollRunnable = null;\n            if (tv != null) {\n                tv.setFocusedTask(animateFocusedState);\n            } else {\n                postScrollRunnable = new Runnable() {\n                    @Override\n                    public void run() {\n\n                        DeckChildView tv = getChildViewForTask(mCallback.getData().get(mFocusedTaskIndex));\n                        if (tv != null) {\n                            tv.setFocusedTask(animateFocusedState);\n                        }\n                    }\n                };\n            }\n\n            // Scroll the view into position (just center it in the curve)\n            if (scrollToNewPosition) {\n                float newScroll = mLayoutAlgorithm.getStackScrollForTask(key) - 0.5f;\n                newScroll = mStackScroller.getBoundedStackScroll(newScroll);\n                mStackScroller.animateScroll(mStackScroller.getStackScroll(), newScroll, postScrollRunnable);\n            } else {\n                if (postScrollRunnable != null) {\n                    postScrollRunnable.run();\n                }\n            }\n\n        }\n    }\n\n    /**\n     * Ensures that there is a task focused, if nothing is focused, then we will use the task\n     * at the center of the visible stack.\n     */\n    public boolean ensureFocusedTask() {\n        if (mFocusedTaskIndex < 0) {\n            // If there is no task focused, then find the task that is closes to the center\n            // of the screen and use that as the currently focused task\n            int x = mLayoutAlgorithm.mStackVisibleRect.centerX();\n            int y = mLayoutAlgorithm.mStackVisibleRect.centerY();\n            int childCount = getChildCount();\n            for (int i = childCount - 1; i >= 0; i--) {\n                DeckChildView tv = (DeckChildView) getChildAt(i);\n                tv.getHitRect(mTmpRect);\n                if (mTmpRect.contains(x, y)) {\n                    mFocusedTaskIndex = i;\n                    break;\n                }\n            }\n            // If we can't find the center task, then use the front most index\n            if (mFocusedTaskIndex < 0 && childCount > 0) {\n                mFocusedTaskIndex = childCount - 1;\n            }\n        }\n        return mFocusedTaskIndex >= 0;\n    }\n\n    /**\n     * Focuses the next task in the stack.\n     *\n     * @param animateFocusedState determines whether to actually draw the highlight along with\n     *                            the change in focus, as well as whether to scroll to fit the\n     *                            task into view.\n     */\n    public void focusNextTask(boolean forward, boolean animateFocusedState) {\n        // Find the next index to focus\n        int numTasks = mCallback.getData().size();\n        if (numTasks == 0) return;\n\n        int direction = (forward ? -1 : 1);\n        int newIndex = mFocusedTaskIndex + direction;\n        if (newIndex >= 0 && newIndex <= (numTasks - 1)) {\n            newIndex = Math.max(0, Math.min(numTasks - 1, newIndex));\n            focusTask(newIndex, true, animateFocusedState);\n        }\n    }\n\n    /**\n     * Dismisses the focused task.\n     */\n    public void dismissFocusedTask() {\n        // Return early if the focused task index is invalid\n        if (mFocusedTaskIndex < 0 || mFocusedTaskIndex >= mCallback.getData().size()) {\n            mFocusedTaskIndex = -1;\n            return;\n        }\n\n        //Long id = mAdapter.getItemId(mFocusedTaskIndex);\n        T key = mCallback.getData().get(mFocusedTaskIndex);\n        DeckChildView tv = getChildViewForTask(key);\n        tv.dismissTask();\n    }\n\n    /**\n     * Resets the focused task.\n     */\n    void resetFocusedTask() {\n        if ((0 <= mFocusedTaskIndex) && (mFocusedTaskIndex < mCallback.getData().size())) {\n            DeckChildView tv = getChildViewForTask(mCallback.getData().get(mFocusedTaskIndex));\n            if (tv != null) {\n                tv.unsetFocusedTask();\n            }\n        }\n        mFocusedTaskIndex = -1;\n    }\n\n    @Override\n    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {\n        super.onInitializeAccessibilityEvent(event);\n        int childCount = getChildCount();\n        if (childCount > 0) {\n            DeckChildView<T> backMostTask = (DeckChildView) getChildAt(0);\n            DeckChildView<T> frontMostTask = (DeckChildView) getChildAt(childCount - 1);\n            event.setFromIndex(mCallback.getData().indexOf(backMostTask.getAttachedKey()));\n            event.setToIndex(mCallback.getData().indexOf(frontMostTask.getAttachedKey()));\n        }\n        event.setItemCount(mCallback.getData().size());\n        event.setScrollY(mStackScroller.mScroller.getCurrY());\n        event.setMaxScrollY(mStackScroller.progressToScrollRange(mLayoutAlgorithm.mMaxScrollP));\n    }\n\n    @Override\n    public boolean onInterceptTouchEvent(MotionEvent ev) {\n        return mTouchHandler.onInterceptTouchEvent(ev);\n    }\n\n    @Override\n    public boolean onTouchEvent(MotionEvent ev) {\n        return mTouchHandler.onTouchEvent(ev);\n    }\n\n    @Override\n    public boolean onGenericMotionEvent(MotionEvent ev) {\n        return mTouchHandler.onGenericMotionEvent(ev);\n    }\n\n    @Override\n    public void computeScroll() {\n        mStackScroller.computeScroll();\n        // Synchronize the views\n        synchronizeStackViewsWithModel();\n        clipTaskViews();\n        // Notify accessibility\n        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED);\n    }\n\n    /**\n     * Computes the stack and task rects\n     */\n    public void computeRects(int windowWidth, int windowHeight, Rect taskStackBounds,\n                             boolean launchedWithAltTab, boolean launchedFromHome) {\n        // Compute the rects in the stack algorithm\n        mLayoutAlgorithm.computeRects(windowWidth, windowHeight, taskStackBounds);\n\n        // Update the scroll bounds\n        updateMinMaxScroll(false, launchedWithAltTab, launchedFromHome);\n    }\n\n    public int getCurrentChildIndex() {\n        if (getChildCount() == 0)\n            return -1;\n\n        DeckChildView<T> frontMostChild = (DeckChildView) getChildAt(getChildCount() / 2);\n\n        if (frontMostChild != null) {\n            return mCallback.getData().indexOf(frontMostChild.getAttachedKey());\n        }\n\n        return -1;\n    }\n\n    /**\n     * Focuses the task at the specified index in the stack\n     */\n    public void scrollToChild(int childIndex) {\n        if (getCurrentChildIndex() == childIndex)\n            return;\n\n        if (0 <= childIndex && childIndex < mCallback.getData().size()) {\n            // Scroll the view into position (just center it in the curve)\n            float newScroll = mLayoutAlgorithm.getStackScrollForTask(\n                    mCallback.getData().get(childIndex)) - 0.5f;\n            newScroll = mStackScroller.getBoundedStackScroll(newScroll);\n            mStackScroller.setStackScroll(newScroll);\n            //Alternate (animated) way\n            //mStackScroller.animateScroll(mStackScroller.getStackScroll(), newScroll, null);\n        }\n    }\n\n    /**\n     * Computes the maximum number of visible tasks and thumbnails.  Requires that\n     * updateMinMaxScrollForStack() is called first.\n     */\n    public DeckViewLayoutAlgorithm.VisibilityReport computeStackVisibilityReport() {\n        return mLayoutAlgorithm.computeStackVisibilityReport(mCallback.getData());\n    }\n\n    /**\n     * This is called with the full window width and height to allow stack view children to\n     * perform the full screen transition down.\n     */\n    @Override\n    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n        int width = MeasureSpec.getSize(widthMeasureSpec);\n        int height = MeasureSpec.getSize(heightMeasureSpec);\n\n        Rect _taskStackBounds = new Rect();\n        mConfig.getTaskStackBounds(width, height, mConfig.systemInsets.top,\n                mConfig.systemInsets.right, _taskStackBounds);\n\n        setStackInsetRect(_taskStackBounds);\n\n        // Compute our stack/task rects\n        Rect taskStackBounds = new Rect(mTaskStackBounds);\n        taskStackBounds.bottom -= mConfig.systemInsets.bottom;\n        computeRects(width, height, taskStackBounds, mConfig.launchedWithAltTab,\n                mConfig.launchedFromHome);\n\n        // If this is the first layout, then scroll to the front of the stack and synchronize the\n        // stack views immediately to load all the views\n        if (mAwaitingFirstLayout) {\n            mStackScroller.setStackScrollToInitialState();\n            requestSynchronizeStackViewsWithModel();\n            synchronizeStackViewsWithModel();\n        }\n\n        // Measure each of the TaskViews\n        int childCount = getChildCount();\n        for (int i = 0; i < childCount; i++) {\n            DeckChildView tv = (DeckChildView) getChildAt(i);\n            if (tv.getBackground() != null) {\n                tv.getBackground().getPadding(mTmpRect);\n            } else {\n                mTmpRect.setEmpty();\n            }\n            tv.measure(\n                    MeasureSpec.makeMeasureSpec(\n                            mLayoutAlgorithm.mTaskRect.width() + mTmpRect.left + mTmpRect.right,\n                            MeasureSpec.EXACTLY),\n                    MeasureSpec.makeMeasureSpec(\n                            mLayoutAlgorithm.mTaskRect.height() + mTmpRect.top + mTmpRect.bottom,\n                            MeasureSpec.EXACTLY));\n        }\n\n        setMeasuredDimension(width, height);\n    }\n\n    /**\n     * This is called with the size of the space not including the top or right insets, or the\n     * search bar height in portrait (but including the search bar width in landscape, since we want\n     * to draw under it.\n     */\n    @Override\n    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {\n        // Layout each of the children\n        int childCount = getChildCount();\n        for (int i = 0; i < childCount; i++) {\n            DeckChildView tv = (DeckChildView) getChildAt(i);\n            if (tv.getBackground() != null) {\n                tv.getBackground().getPadding(mTmpRect);\n            } else {\n                mTmpRect.setEmpty();\n            }\n            tv.layout(mLayoutAlgorithm.mTaskRect.left - mTmpRect.left,\n                    mLayoutAlgorithm.mTaskRect.top - mTmpRect.top,\n                    mLayoutAlgorithm.mTaskRect.right + mTmpRect.right,\n                    mLayoutAlgorithm.mTaskRect.bottom + mTmpRect.bottom);\n        }\n\n        if (mAwaitingFirstLayout) {\n            mAwaitingFirstLayout = false;\n            onFirstLayout();\n        }\n    }\n\n    /**\n     * Handler for the first layout.\n     */\n    void onFirstLayout() {\n        int offscreenY = mLayoutAlgorithm.mViewRect.bottom -\n                (mLayoutAlgorithm.mTaskRect.top - mLayoutAlgorithm.mViewRect.top);\n\n        int childCount = getChildCount();\n\n        // Prepare the first view for its enter animation\n        for (int i = childCount - 1; i >= 0; i--) {\n            DeckChildView tv = (DeckChildView) getChildAt(i);\n            // TODO: The false needs to go!\n            tv.prepareEnterRecentsAnimation(i == childCount - 1, false, offscreenY);\n        }\n\n        // If the enter animation started already and we haven't completed a layout yet, do the\n        // enter animation now\n        if (mStartEnterAnimationRequestedAfterLayout) {\n            startEnterRecentsAnimation(mStartEnterAnimationContext);\n            mStartEnterAnimationRequestedAfterLayout = false;\n            mStartEnterAnimationContext = null;\n        }\n\n        // When Alt-Tabbing, focus the previous task (but leave the animation until we finish the\n        // enter animation).\n        if (mConfig.launchedWithAltTab) {\n            if (mConfig.launchedFromAppWithThumbnail) {\n                focusTask(Math.max(0, mCallback.getData().size() - 2), false,\n                        mConfig.launchedHasConfigurationChanged);\n            } else {\n                focusTask(Math.max(0, mCallback.getData().size() - 1), false,\n                        mConfig.launchedHasConfigurationChanged);\n            }\n        }\n\n        // Start dozing\n        mUIDozeTrigger.startDozing();\n    }\n\n    void showDeck(Context context) {\n        // Try and start the enter animation (or restart it on configuration changed)\n        ReferenceCountedTrigger t = new ReferenceCountedTrigger(context, null, null, null);\n        ViewAnimation.TaskViewEnterContext ctx = new ViewAnimation.TaskViewEnterContext(t);\n\n        // We have to increment/decrement the post animation trigger in case there are no children\n        // to ensure that it runs\n        ctx.postAnimationTrigger.increment();\n        startEnterRecentsAnimation(ctx);\n        ctx.postAnimationTrigger.decrement();\n    }\n\n    /**\n     * Requests this task stacks to start it's enter-recents animation\n     */\n    public void startEnterRecentsAnimation(ViewAnimation.TaskViewEnterContext ctx) {\n        // If we are still waiting to layout, then just defer until then\n        if (mAwaitingFirstLayout) {\n            mStartEnterAnimationRequestedAfterLayout = true;\n            mStartEnterAnimationContext = ctx;\n            return;\n        }\n\n        if (mCallback.getData().size() > 0) {\n            int childCount = getChildCount();\n\n            // Animate all the task views into view\n            for (int i = childCount - 1; i >= 0; i--) {\n                DeckChildView<T> tv = (DeckChildView) getChildAt(i);\n                T key = tv.getAttachedKey();\n                ctx.currentTaskTransform = new DeckChildViewTransform();\n                ctx.currentStackViewIndex = i;\n                ctx.currentStackViewCount = childCount;\n                ctx.currentTaskRect = mLayoutAlgorithm.mTaskRect;\n                // TODO: this needs to go\n                ctx.currentTaskOccludesLaunchTarget = false;\n                ctx.updateListener = mRequestUpdateClippingListener;\n                mLayoutAlgorithm.getStackTransform(key, mStackScroller.getStackScroll(),\n                        ctx.currentTaskTransform, null);\n                tv.startEnterRecentsAnimation(ctx);\n            }\n\n            // Add a runnable to the post animation ref counter to clear all the views\n            ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {\n                @Override\n                public void run() {\n                    mStartEnterAnimationCompleted = true;\n                    // Poke the dozer to restart the trigger after the animation completes\n                    mUIDozeTrigger.poke();\n                }\n            });\n        }\n    }\n\n    @Override\n    public WindowInsets onApplyWindowInsets(WindowInsets insets) {\n        // Update the configuration with the latest system insets and trigger a relayout\n        // mConfig.updateSystemInsets(insets.getSystemWindowInsets());\n        mConfig.updateSystemInsets(new Rect(insets.getSystemWindowInsetLeft(),\n                insets.getSystemWindowInsetTop(),\n                insets.getSystemWindowInsetRight(),\n                insets.getSystemWindowInsetBottom()));\n        requestLayout();\n        return insets.consumeSystemWindowInsets();\n    }\n\n    void hideDeck(Context context, Runnable finishRunnable) {\n        ReferenceCountedTrigger exitTrigger = new ReferenceCountedTrigger(context,\n                null, finishRunnable, null);\n        ViewAnimation.TaskViewExitContext exitCtx =\n                new ViewAnimation.TaskViewExitContext(exitTrigger);\n\n        exitCtx.postAnimationTrigger.increment();\n        startExitToHomeAnimation(\n                new ViewAnimation.TaskViewExitContext(exitTrigger));\n        exitCtx.postAnimationTrigger.decrement();\n    }\n\n    /**\n     * Requests this task stacks to start it's exit-recents animation.\n     */\n    public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) {\n        // Stop any scrolling\n        mStackScroller.stopScroller();\n        mStackScroller.stopBoundScrollAnimation();\n        // Animate all the task views out of view\n        ctx.offscreenTranslationY = mLayoutAlgorithm.mViewRect.bottom -\n                (mLayoutAlgorithm.mTaskRect.top - mLayoutAlgorithm.mViewRect.top);\n        int childCount = getChildCount();\n        for (int i = 0; i < childCount; i++) {\n            DeckChildView tv = (DeckChildView) getChildAt(i);\n            tv.startExitToHomeAnimation(ctx);\n        }\n    }\n\n    /**\n     * Animates a task view in this stack as it launches.\n     */\n    public void startLaunchTaskAnimation(DeckChildView tv, Runnable r, boolean lockToTask) {\n        int childCount = getChildCount();\n        for (int i = 0; i < childCount; i++) {\n            DeckChildView t = (DeckChildView) getChildAt(i);\n            if (t == tv) {\n                t.setClipViewInStack(false);\n                t.startLaunchTaskAnimation(r, true, true, lockToTask);\n            } else {\n                // TODO: the false needs to go\n                t.startLaunchTaskAnimation(null, false, false, lockToTask);\n            }\n        }\n    }\n\n    /**\n     * Final callback after Recents is finally hidden.\n     */\n    void onRecentsHidden() {\n        reset();\n    }\n\n    public boolean isTransformedTouchPointInView(float x, float y, View child) {\n        // TODO: confirm if this is the right approach\n        if (child == null)\n            return false;\n\n        final Rect frame = new Rect();\n        child.getHitRect(frame);\n\n        return frame.contains((int) x, (int) y);\n    }\n\n    /**\n     * Pokes the dozer on user interaction.\n     */\n    void onUserInteraction() {\n        // Poke the doze trigger if it is dozing\n        mUIDozeTrigger.poke();\n    }\n\n    /**\n     * * ViewPoolConsumer Implementation ***\n     */\n\n    @Override\n    public DeckChildView createView(Context context) {\n        return (DeckChildView) mInflater.inflate(R.layout.deck_child_view, this, false);\n    }\n\n    @Override\n    public void prepareViewToEnterPool(DeckChildView<T> tv) {\n        T key = tv.getAttachedKey();\n\n        mCallback.unloadViewData(key);\n        tv.onTaskUnbound();\n        tv.onDataUnloaded();\n\n        // Detach the view from the hierarchy\n        detachViewFromParent(tv);\n\n        // Reset the view properties\n        tv.resetViewProperties();\n\n        // Reset the clip state of the task view\n        tv.setClipViewInStack(false);\n    }\n\n    @Override\n    public void prepareViewToLeavePool(DeckChildView<T> dcv, T key, boolean isNewView) {\n        // It is possible for a view to be returned to the view pool before it is laid out,\n        // which means that we will need to relayout the view when it is first used next.\n        boolean requiresRelayout = dcv.getWidth() <= 0 && !isNewView;\n\n        // Rebind the task and request that this task's data be filled into the TaskView\n        dcv.onTaskBound(key);\n\n        // Load the task data\n        mCallback.loadViewData(new WeakReference<DeckChildView<T>>(dcv), key);\n\n        // If the doze trigger has already fired, then update the state for this task view\n        if (mUIDozeTrigger.hasTriggered()) {\n            dcv.setNoUserInteractionState();\n        }\n\n        // If we've finished the start animation, then ensure we always enable the focus animations\n        if (mStartEnterAnimationCompleted) {\n            dcv.enableFocusAnimations();\n        }\n\n        // Find the index where this task should be placed in the stack\n        int insertIndex = -1;\n        int position = mCallback.getData().indexOf(key);\n        if (position != -1) {\n            int childCount = getChildCount();\n            for (int i = 0; i < childCount; i++) {\n                T otherKey = ((DeckChildView<T>) getChildAt(i)).getAttachedKey();\n                int pos = mCallback.getData().indexOf(otherKey);\n                if (position < pos) {\n                    insertIndex = i;\n                    break;\n                }\n            }\n        }\n\n\n        // Add/attach the view to the hierarchy\n        if (isNewView) {\n            addView(dcv, insertIndex);\n        } else {\n            attachViewToParent(dcv, insertIndex, dcv.getLayoutParams());\n            if (requiresRelayout) {\n                dcv.requestLayout();\n            }\n        }\n\n        // Set the new state for this view, including the callbacks and view clipping\n        dcv.setCallbacks(this);\n        dcv.setTouchEnabled(true);\n        dcv.setClipViewInStack(true);\n    }\n\n    @Override\n    public boolean hasPreferredData(DeckChildView<T> tv, T preferredData) {\n        return (tv.getAttachedKey() != null && tv.getAttachedKey().equals(preferredData));\n    }\n\n    /**\n     * * DeckChildCallbacks Implementation ***\n     */\n\n    @Override\n    public void onDeckChildViewAppIconClicked(DeckChildView tv) {\n        //\n    }\n\n    @Override\n    public void onDeckChildViewAppInfoClicked(DeckChildView tv) {\n        //\n    }\n\n    @Override\n    public void onDeckChildViewClicked(DeckChildView<T> dcv, T key) {\n        // Cancel any doze triggers\n        mUIDozeTrigger.stopDozing();\n        mCallback.onItemClick(key);\n    }\n\n    @Override\n    public void onDeckChildViewDismissed(DeckChildView<T> dcv) {\n        boolean taskWasFocused = dcv.isFocusedTask();\n\n        T key = dcv.getAttachedKey();\n        int taskIndex = mCallback.getData().indexOf(key);\n\n        onStackTaskRemoved(dcv);\n\n        // If the dismissed task was focused, then we should focus the new task in the same index\n        if (taskIndex != -1 && taskWasFocused) {\n            int nextTaskIndex = Math.min(mCallback.getData().size() - 1, taskIndex - 1);\n            if (nextTaskIndex >= 0) {\n                DeckChildView nextTv = getChildViewForTask(mCallback.getData().get(nextTaskIndex));\n                if (nextTv != null) {\n                    // Focus the next task, and only animate the visible state if we are launched\n                    // from Alt-Tab\n                    nextTv.setFocusedTask(mConfig.launchedWithAltTab);\n                }\n            }\n        }\n    }\n\n    public void onStackTaskRemoved(DeckChildView<T> removedView) {\n        // Remove the view associated with this task, we can't rely on updateTransforms\n        // to work here because the task is no longer in the list\n        if (removedView != null) {\n            T key = removedView.getAttachedKey();\n            int removedPosition = mCallback.getData().indexOf(key);\n            mViewPool.returnViewToPool(removedView);\n\n            // Notify the callback that we've removed the task and it can clean up after it\n            mCallback.onViewDismissed(key);\n        }\n\n        /*\n        // Get the stack scroll of the task to anchor to (since we are removing something, the front\n        // most task will be our anchor task)\n        T anchorTask = null;\n        float prevAnchorTaskScroll = 0;\n        boolean pullStackForward = mCallback.getData().size() > 0;\n        if (pullStackForward) {\n            anchorTask = mCallback.getData().get(mCallback.getData().size() - 1);\n            prevAnchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);\n        }\n\n        // Update the min/max scroll and animate other task views into their new positions\n        updateMinMaxScroll(true, mConfig.launchedWithAltTab, mConfig.launchedFromHome);\n\n        // Offset the stack by as much as the anchor task would otherwise move back\n        if (pullStackForward) {\n            float anchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);\n            mStackScroller.setStackScroll(mStackScroller.getStackScroll() + (anchorTaskScroll\n                    - prevAnchorTaskScroll));\n            mStackScroller.boundScroll();\n        }\n\n        // Animate all the tasks into place\n        requestSynchronizeStackViewsWithModel(200);\n\n        T newFrontMostTask = mCallback.getData().get(mCallback.getData().size() - 1);\n        // Update the new front most task\n        if (newFrontMostTask != null) {\n            DeckChildView<T> frontTv = getChildViewForTask(newFrontMostTask);\n            if (frontTv != null) {\n                frontTv.onTaskBound(newFrontMostTask);\n            }\n        }\n\n        // If there are no remaining tasks\n        if (mCallback.getData().size() == 0) {\n            mCallback.onNoViewsToDeck();\n        }\n        */\n    }\n\n    public void notifyDataSetChanged() {\n        // Get the stack scroll of the task to anchor to (since we are removing something, the front\n        // most task will be our anchor task)\n        T anchorTask = null;\n        float prevAnchorTaskScroll = 0;\n        boolean pullStackForward = mCallback.getData().size() > 0;\n        if (pullStackForward) {\n            anchorTask = mCallback.getData().get(mCallback.getData().size() - 1);\n            prevAnchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);\n        }\n\n        // Update the min/max scroll and animate other task views into their new positions\n        updateMinMaxScroll(true, mConfig.launchedWithAltTab, mConfig.launchedFromHome);\n\n        // Offset the stack by as much as the anchor task would otherwise move back\n        if (pullStackForward) {\n            float anchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);\n            mStackScroller.setStackScroll(mStackScroller.getStackScroll() + (anchorTaskScroll\n                    - prevAnchorTaskScroll));\n            mStackScroller.boundScroll();\n        }\n\n        // Animate all the tasks into place\n        requestSynchronizeStackViewsWithModel(200);\n\n        T newFrontMostTask = mCallback.getData().size() > 0 ?\n                mCallback.getData().get(mCallback.getData().size() - 1)\n                : null;\n        // Update the new front most task\n        if (newFrontMostTask != null) {\n            DeckChildView<T> frontTv = getChildViewForTask(newFrontMostTask);\n            if (frontTv != null) {\n                frontTv.onTaskBound(newFrontMostTask);\n            }\n        }\n\n        // If there are no remaining tasks\n        if (mCallback.getData().size() == 0) {\n            mCallback.onNoViewsToDeck();\n        }\n    }\n\n    @Override\n    public void onDeckChildViewClipStateChanged(DeckChildView tv) {\n        if (!mStackViewsDirty) {\n            invalidate();\n        }\n    }\n\n    @Override\n    public void onDeckChildViewFocusChanged(DeckChildView<T> tv, boolean focused) {\n        if (focused) {\n            mFocusedTaskIndex = mCallback.getData().indexOf(tv.getAttachedKey());\n        }\n    }\n\n    /**\n     * * TaskStackViewScroller.TaskStackViewScrollerCallbacks ***\n     */\n\n    @Override\n    public void onScrollChanged(float p) {\n        mUIDozeTrigger.poke();\n        requestSynchronizeStackViewsWithModel();\n        postInvalidateOnAnimation();\n    }\n\n    public void notifyDataSetChangedOld() {\n        ArrayList<T> data = mCallback.getData();\n\n        // Get the stack scroll of the task to anchor to (since we are removing something, the front\n        // most task will be our anchor task)\n        T anchorTask = null;\n        float prevAnchorTaskScroll = 0;\n        boolean pullStackForward = data.size() > 0;\n        if (pullStackForward) {\n            anchorTask = data.get(data.size() - 1);\n            prevAnchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);\n        }\n\n        // Update the min/max scroll and animate other task views into their new positions\n        updateMinMaxScroll(true, mConfig.launchedWithAltTab, mConfig.launchedFromHome);\n\n        // Offset the stack by as much as the anchor task would otherwise move back\n        if (pullStackForward) {\n            float anchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);\n            mStackScroller.setStackScroll(mStackScroller.getStackScroll() + (anchorTaskScroll\n                    - prevAnchorTaskScroll));\n            mStackScroller.boundScroll();\n        }\n\n        // Animate all the tasks into place\n        requestSynchronizeStackViewsWithModel(200);\n\n        T newFrontMostTask = data.get(data.size() - 1);\n        // Update the new front most task\n        if (newFrontMostTask != null) {\n            DeckChildView<T> frontTv = getChildViewForTask(newFrontMostTask);\n            if (frontTv != null) {\n                frontTv.onTaskBound(newFrontMostTask);\n            }\n        }\n\n        // If there are no remaining tasks\n        if (mCallback.getData().size() == 0)\n            mCallback.onNoViewsToDeck();\n    }\n\n    Callback<T> mCallback;\n\n    public interface Callback<T> {\n        public ArrayList<T> getData();\n\n        public void loadViewData(WeakReference<DeckChildView<T>> dcv, T item);\n\n        public void unloadViewData(T item);\n\n        public void onViewDismissed(T item);\n\n        public void onItemClick(T item);\n\n        public void onNoViewsToDeck();\n    }\n}\n"
  },
  {
    "path": "deckview/src/main/java/com/appeaser/deckview/views/DeckViewLayoutAlgorithm.java",
    "content": "package com.appeaser.deckview.views;\n\nimport android.graphics.Rect;\n\nimport com.appeaser.deckview.helpers.DeckChildViewTransform;\nimport com.appeaser.deckview.helpers.DeckViewConfig;\nimport com.appeaser.deckview.utilities.DVUtils;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\n\n/**\n * Created by Vikram on 02/04/2015.\n */\n/* The layout logic for a TaskStackView.\n *\n * We are using a curve that defines the curve of the tasks as that go back in the recents list.\n * The curve is defined such that at curve progress p = 0 is the end of the curve (the top of the\n * stack rect), and p = 1 at the start of the curve and the bottom of the stack rect.\n */\npublic class DeckViewLayoutAlgorithm<T> {\n\n    // These are all going to change\n    static final float StackPeekMinScale = 0.8f; // The min scale of the last card in the peek area\n\n    // A report of the visibility state of the stack\n    public class VisibilityReport {\n        public int numVisibleTasks;\n        public int numVisibleThumbnails;\n\n        /**\n         * Package level ctor\n         */\n        VisibilityReport(int tasks, int thumbnails) {\n            numVisibleTasks = tasks;\n            numVisibleThumbnails = thumbnails;\n        }\n    }\n\n    DeckViewConfig mConfig;\n\n    // The various rects that define the stack view\n    public Rect mViewRect = new Rect();\n    Rect mStackVisibleRect = new Rect();\n    Rect mStackRect = new Rect();\n    Rect mTaskRect = new Rect();\n\n    // The min/max scroll progress\n    float mMinScrollP;\n    float mMaxScrollP;\n    float mInitialScrollP;\n    int mWithinAffiliationOffset;\n    int mBetweenAffiliationOffset;\n    HashMap<T, Float> mTaskProgressMap = new HashMap<T, Float>();\n\n    // Log function\n    static final float XScale = 1.75f;  // The large the XScale, the longer the flat area of the curve\n    static final float LogBase = 3000;\n    static final int PrecisionSteps = 250;\n    static float[] xp;\n    static float[] px;\n\n    public DeckViewLayoutAlgorithm(DeckViewConfig config) {\n        mConfig = config;\n\n        // Precompute the path\n        initializeCurve();\n    }\n\n    /**\n     * Computes the stack and task rects\n     */\n    public void computeRects(int windowWidth, int windowHeight, Rect taskStackBounds) {\n        // Compute the stack rects\n        mViewRect.set(0, 0, windowWidth, windowHeight);\n        mStackRect.set(taskStackBounds);\n        mStackVisibleRect.set(taskStackBounds);\n        mStackVisibleRect.bottom = mViewRect.bottom;\n\n        int widthPadding = (int) (mConfig.taskStackWidthPaddingPct * mStackRect.width());\n        int heightPadding = mConfig.taskStackTopPaddingPx;\n        mStackRect.inset(widthPadding, heightPadding);\n\n        // Compute the task rect\n        int size = mStackRect.width();\n        int left = mStackRect.left + (mStackRect.width() - size) / 2;\n        mTaskRect.set(left, mStackRect.top,\n                left + size, mStackRect.top + size);\n\n        // Update the affiliation offsets\n        float visibleTaskPct = 0.5f;\n        mWithinAffiliationOffset = mConfig.taskBarHeight;\n        mBetweenAffiliationOffset = (int) (visibleTaskPct * mTaskRect.height());\n    }\n\n    /**\n     * Computes the minimum and maximum scroll progress values.  This method may be called before\n     * the RecentsConfiguration is set, so we need to pass in the alt-tab state.\n     */\n    void computeMinMaxScroll(ArrayList<T> data, boolean launchedWithAltTab,\n                             boolean launchedFromHome) {\n        // Clear the progress map\n        mTaskProgressMap.clear();\n\n        // Return early if we have no tasks\n        if (data.isEmpty()) {\n            mMinScrollP = mMaxScrollP = 0;\n            return;\n        }\n\n        // Note that we should account for the scale difference of the offsets at the screen bottom\n        int taskHeight = mTaskRect.height();\n        float pAtBottomOfStackRect = screenYToCurveProgress(mStackVisibleRect.bottom);\n        float pWithinAffiliateTop = screenYToCurveProgress(mStackVisibleRect.bottom -\n                mWithinAffiliationOffset);\n        float scale = curveProgressToScale(pWithinAffiliateTop);\n        int scaleYOffset = (int) (((1f - scale) * taskHeight) / 2);\n        pWithinAffiliateTop = screenYToCurveProgress(mStackVisibleRect.bottom -\n                mWithinAffiliationOffset + scaleYOffset);\n        float pWithinAffiliateOffset = pAtBottomOfStackRect - pWithinAffiliateTop;\n        float pBetweenAffiliateOffset = pAtBottomOfStackRect -\n                screenYToCurveProgress(mStackVisibleRect.bottom - mBetweenAffiliationOffset);\n        float pTaskHeightOffset = pAtBottomOfStackRect -\n                screenYToCurveProgress(mStackVisibleRect.bottom - taskHeight);\n        float pNavBarOffset = pAtBottomOfStackRect -\n                screenYToCurveProgress(mStackVisibleRect.bottom - (mStackVisibleRect.bottom -\n                        mStackRect.bottom));\n\n        // Update the task offsets\n        float pAtBackMostCardTop = 0.5f;\n        float pAtFrontMostCardTop = pAtBackMostCardTop;\n        int taskCount = data.size();\n        for (int i = 0; i < taskCount; i++) {\n            //Task task = tasks.get(i);\n            //mTaskProgressMap.put(task.key, pAtFrontMostCardTop);\n            mTaskProgressMap.put(data.get(i), pAtFrontMostCardTop);\n\n            if (i < (taskCount - 1)) {\n                // Increment the peek height\n                // TODO: Might need adjustments\n                //float pPeek = task.group.isFrontMostTask(task) ?\n                //pBetweenAffiliateOffset : pWithinAffiliateOffset;\n                float pPeek = pBetweenAffiliateOffset;\n                pAtFrontMostCardTop += pPeek;\n            }\n        }\n\n        mMaxScrollP = pAtFrontMostCardTop - ((1f - pTaskHeightOffset - pNavBarOffset));\n        mMinScrollP = data.size() == 1 ? Math.max(mMaxScrollP, 0f) : 0f;\n        if (launchedWithAltTab && launchedFromHome) {\n            // Center the top most task, since that will be focused first\n            mInitialScrollP = mMaxScrollP;\n        } else {\n            mInitialScrollP = pAtFrontMostCardTop - 0.825f;\n        }\n        mInitialScrollP = Math.min(mMaxScrollP, Math.max(0, mInitialScrollP));\n    }\n\n    /**\n     * Computes the maximum number of visible tasks and thumbnails.  Requires that\n     * computeMinMaxScroll() is called first.\n     */\n    public VisibilityReport computeStackVisibilityReport(ArrayList<T> data) {\n        if (data.size() <= 1) {\n            return new VisibilityReport(1, 1);\n        }\n\n        // Walk backwards in the task stack and count the number of tasks and visible thumbnails\n        int taskHeight = mTaskRect.height();\n        int numVisibleTasks = 1;\n        int numVisibleThumbnails = 1;\n        //float progress = mTaskProgressMap.get(tasks.get(tasks.size() - 1).key) - mInitialScrollP;\n\n        float progress = mTaskProgressMap.get(data.get(data.size() - 1)) - mInitialScrollP;\n        int prevScreenY = curveProgressToScreenY(progress);\n        for (int i = data.size() - 2; i >= 0; i--) {\n            //Task task = tasks.get(i);\n            //progress = mTaskProgressMap.get(task.key) - mInitialScrollP;\n            progress = mTaskProgressMap.get(data.get(i)) - mInitialScrollP;\n            if (progress < 0) {\n                break;\n            }\n\n            // TODO: Might need adjustments\n            //boolean isFrontMostTaskInGroup = task.group.isFrontMostTask(task);\n            boolean isFrontMostTaskInGroup = true;\n            if (isFrontMostTaskInGroup) {\n                float scaleAtP = curveProgressToScale(progress);\n                int scaleYOffsetAtP = (int) (((1f - scaleAtP) * taskHeight) / 2);\n                int screenY = curveProgressToScreenY(progress) + scaleYOffsetAtP;\n                boolean hasVisibleThumbnail = (prevScreenY - screenY) > mConfig.taskBarHeight;\n                if (hasVisibleThumbnail) {\n                    numVisibleThumbnails++;\n                    numVisibleTasks++;\n                    prevScreenY = screenY;\n                } else {\n                    // Once we hit the next front most task that does not have a visible thumbnail,\n                    // walk through remaining visible set\n                    for (int j = i; j >= 0; j--) {\n                        numVisibleTasks++;\n                        progress = mTaskProgressMap.get(data.get(i)) - mInitialScrollP;\n                        if (progress < 0) {\n                            break;\n                        }\n                    }\n                    break;\n                }\n            } else if (!isFrontMostTaskInGroup) {\n                // Affiliated task, no thumbnail\n                numVisibleTasks++;\n            }\n        }\n        return new VisibilityReport(numVisibleTasks, numVisibleThumbnails);\n    }\n\n    /**\n     * Update/get the transform\n     */\n    public DeckChildViewTransform getStackTransform(T key, float stackScroll,\n                                                    DeckChildViewTransform transformOut,\n                                                    DeckChildViewTransform prevTransform) {\n        // Return early if we have an invalid index\n        if (!mTaskProgressMap.containsKey(key)) {\n            transformOut.reset();\n            return transformOut;\n        }\n        return getStackTransform(mTaskProgressMap.get(key), stackScroll, transformOut,\n                prevTransform);\n    }\n\n    /**\n     * Update/get the transform\n     */\n    public DeckChildViewTransform getStackTransform(float taskProgress, float stackScroll,\n                                                    DeckChildViewTransform transformOut,\n                                                    DeckChildViewTransform prevTransform) {\n        float pTaskRelative = taskProgress - stackScroll;\n        float pBounded = Math.max(0, Math.min(pTaskRelative, 1f));\n        // If the task top is outside of the bounds below the screen, then immediately reset it\n        if (pTaskRelative > 1f) {\n            transformOut.reset();\n            transformOut.rect.set(mTaskRect);\n            return transformOut;\n        }\n        // The check for the top is trickier, since we want to show the next task if it is at all\n        // visible, even if p < 0.\n        if (pTaskRelative < 0f) {\n            if (prevTransform != null && Float.compare(prevTransform.p, 0f) <= 0) {\n                transformOut.reset();\n                transformOut.rect.set(mTaskRect);\n                return transformOut;\n            }\n        }\n        float scale = curveProgressToScale(pBounded);\n        int scaleYOffset = (int) (((1f - scale) * mTaskRect.height()) / 2);\n        int minZ = mConfig.taskViewTranslationZMinPx;\n        int maxZ = mConfig.taskViewTranslationZMaxPx;\n        transformOut.scale = scale;\n        transformOut.translationY = curveProgressToScreenY(pBounded) - mStackVisibleRect.top -\n                scaleYOffset;\n        transformOut.translationZ = Math.max(minZ, minZ + (pBounded * (maxZ - minZ)));\n        transformOut.rect.set(mTaskRect);\n        transformOut.rect.offset(0, transformOut.translationY);\n        DVUtils.scaleRectAboutCenter(transformOut.rect, transformOut.scale);\n        transformOut.visible = true;\n        transformOut.p = pTaskRelative;\n        return transformOut;\n    }\n\n    /**\n     * Returns the untransformed task view size.\n     */\n    public Rect getUntransformedTaskViewSize() {\n        Rect tvSize = new Rect(mTaskRect);\n        tvSize.offsetTo(0, 0);\n        return tvSize;\n    }\n\n    /**\n     * Returns the scroll to such task top = 1f;\n     */\n    public float getStackScrollForTask(T key) {\n        if (!mTaskProgressMap.containsKey(key)) return 0f;\n        return mTaskProgressMap.get(key);\n    }\n\n    /**\n     * Initializes the curve.\n     */\n    public static void initializeCurve() {\n        if (xp != null && px != null) return;\n        xp = new float[PrecisionSteps + 1];\n        px = new float[PrecisionSteps + 1];\n\n        // Approximate f(x)\n        float[] fx = new float[PrecisionSteps + 1];\n        float step = 1f / PrecisionSteps;\n        float x = 0;\n        for (int xStep = 0; xStep <= PrecisionSteps; xStep++) {\n            fx[xStep] = logFunc(x);\n            x += step;\n        }\n        // Calculate the arc length for x:1->0\n        float pLength = 0;\n        float[] dx = new float[PrecisionSteps + 1];\n        dx[0] = 0;\n        for (int xStep = 1; xStep < PrecisionSteps; xStep++) {\n            dx[xStep] = (float) Math.sqrt(Math.pow(fx[xStep] - fx[xStep - 1], 2) + Math.pow(step, 2));\n            pLength += dx[xStep];\n        }\n        // Approximate p(x), a function of cumulative progress with x, normalized to 0..1\n        float p = 0;\n        px[0] = 0f;\n        px[PrecisionSteps] = 1f;\n        for (int xStep = 1; xStep <= PrecisionSteps; xStep++) {\n            p += Math.abs(dx[xStep] / pLength);\n            px[xStep] = p;\n        }\n        // Given p(x), calculate the inverse function x(p). This assumes that x(p) is also a valid\n        // function.\n        int xStep = 0;\n        p = 0;\n        xp[0] = 0f;\n        xp[PrecisionSteps] = 1f;\n        for (int pStep = 0; pStep < PrecisionSteps; pStep++) {\n            // Walk forward in px and find the x where px <= p && p < px+1\n            while (xStep < PrecisionSteps) {\n                if (px[xStep] > p) break;\n                xStep++;\n            }\n            // Now, px[xStep-1] <= p < px[xStep]\n            if (xStep == 0) {\n                xp[pStep] = 0;\n            } else {\n                // Find x such that proportionally, x is correct\n                float fraction = (p - px[xStep - 1]) / (px[xStep] - px[xStep - 1]);\n                x = (xStep - 1 + fraction) * step;\n                xp[pStep] = x;\n            }\n            p += step;\n        }\n    }\n\n    /**\n     * Reverses and scales out x.\n     */\n    static float reverse(float x) {\n        return (-x * XScale) + 1;\n    }\n\n    /**\n     * The log function describing the curve.\n     */\n    static float logFunc(float x) {\n        return 1f - (float) (Math.pow(LogBase, reverse(x))) / (LogBase);\n    }\n\n    /**\n     * The inverse of the log function describing the curve.\n     */\n    float invLogFunc(float y) {\n        return (float) (Math.log((1f - reverse(y)) * (LogBase - 1) + 1) / Math.log(LogBase));\n    }\n\n    /**\n     * Converts from the progress along the curve to a screen coordinate.\n     */\n    int curveProgressToScreenY(float p) {\n        if (p < 0 || p > 1) return mStackVisibleRect.top + (int) (p * mStackVisibleRect.height());\n        float pIndex = p * PrecisionSteps;\n        int pFloorIndex = (int) Math.floor(pIndex);\n        int pCeilIndex = (int) Math.ceil(pIndex);\n        float xFraction = 0;\n        if (pFloorIndex < PrecisionSteps && (pCeilIndex != pFloorIndex)) {\n            float pFraction = (pIndex - pFloorIndex) / (pCeilIndex - pFloorIndex);\n            xFraction = (xp[pCeilIndex] - xp[pFloorIndex]) * pFraction;\n        }\n        float x = xp[pFloorIndex] + xFraction;\n        return mStackVisibleRect.top + (int) (x * mStackVisibleRect.height());\n    }\n\n    /**\n     * Converts from the progress along the curve to a scale.\n     */\n    float curveProgressToScale(float p) {\n        if (p < 0) return StackPeekMinScale;\n        if (p > 1) return 1f;\n        float scaleRange = (1f - StackPeekMinScale);\n        float scale = StackPeekMinScale + (p * scaleRange);\n        return scale;\n    }\n\n    /**\n     * Converts from a screen coordinate to the progress along the curve.\n     */\n    float screenYToCurveProgress(int screenY) {\n        float x = (float) (screenY - mStackVisibleRect.top) / mStackVisibleRect.height();\n        if (x < 0 || x > 1) return x;\n        float xIndex = x * PrecisionSteps;\n        int xFloorIndex = (int) Math.floor(xIndex);\n        int xCeilIndex = (int) Math.ceil(xIndex);\n        float pFraction = 0;\n        if (xFloorIndex < PrecisionSteps && (xCeilIndex != xFloorIndex)) {\n            float xFraction = (xIndex - xFloorIndex) / (xCeilIndex - xFloorIndex);\n            pFraction = (px[xCeilIndex] - px[xFloorIndex]) * xFraction;\n        }\n        return px[xFloorIndex] + pFraction;\n    }\n}"
  },
  {
    "path": "deckview/src/main/java/com/appeaser/deckview/views/DeckViewScroller.java",
    "content": "package com.appeaser.deckview.views;\n\nimport android.animation.Animator;\nimport android.animation.AnimatorListenerAdapter;\nimport android.animation.ObjectAnimator;\nimport android.animation.ValueAnimator;\nimport android.content.Context;\nimport android.widget.OverScroller;\n\nimport com.appeaser.deckview.helpers.DeckViewConfig;\nimport com.appeaser.deckview.utilities.DVUtils;\n\n/**\n * Created by Vikram on 02/04/2015.\n */\n/* The scrolling logic for a TaskStackView */\npublic class DeckViewScroller {\n    public interface DeckViewScrollerCallbacks {\n        public void onScrollChanged(float p);\n    }\n\n    DeckViewConfig mConfig;\n    DeckViewLayoutAlgorithm mLayoutAlgorithm;\n    DeckViewScrollerCallbacks mCb;\n\n    float mStackScrollP;\n\n    OverScroller mScroller;\n    ObjectAnimator mScrollAnimator;\n    float mFinalAnimatedScroll;\n\n    public DeckViewScroller(Context context, DeckViewConfig config,\n                            DeckViewLayoutAlgorithm layoutAlgorithm) {\n        mConfig = config;\n        mScroller = new OverScroller(context);\n        mLayoutAlgorithm = layoutAlgorithm;\n        setStackScroll(getStackScroll());\n    }\n\n    /**\n     * Resets the task scroller.\n     */\n    public void reset() {\n        mStackScrollP = 0f;\n    }\n\n    /**\n     * Sets the callbacks\n     */\n    public void setCallbacks(DeckViewScrollerCallbacks cb) {\n        mCb = cb;\n    }\n\n    /**\n     * Gets the current stack scroll\n     */\n    public float getStackScroll() {\n        return mStackScrollP;\n    }\n\n    /**\n     * Sets the current stack scroll\n     */\n    public void setStackScroll(float s) {\n        mStackScrollP = s;\n        if (mCb != null) {\n            mCb.onScrollChanged(mStackScrollP);\n        }\n    }\n\n    /**\n     * Sets the current stack scroll without calling the callback.\n     */\n    void setStackScrollRaw(float s) {\n        mStackScrollP = s;\n    }\n\n    /**\n     * Sets the current stack scroll to the initial state when you first enter recents.\n     *\n     * @return whether the stack progress changed.\n     */\n    public boolean setStackScrollToInitialState() {\n        float prevStackScrollP = mStackScrollP;\n        setStackScroll(getBoundedStackScroll(mLayoutAlgorithm.mInitialScrollP));\n        return Float.compare(prevStackScrollP, mStackScrollP) != 0;\n    }\n\n    /**\n     * Bounds the current scroll if necessary\n     */\n    public boolean boundScroll() {\n        float curScroll = getStackScroll();\n        float newScroll = getBoundedStackScroll(curScroll);\n        if (Float.compare(newScroll, curScroll) != 0) {\n            setStackScroll(newScroll);\n            return true;\n        }\n        return false;\n    }\n\n    /**\n     * Bounds the current scroll if necessary, but does not synchronize the stack view with the model.\n     */\n    public boolean boundScrollRaw() {\n        float curScroll = getStackScroll();\n        float newScroll = getBoundedStackScroll(curScroll);\n        if (Float.compare(newScroll, curScroll) != 0) {\n            setStackScrollRaw(newScroll);\n            return true;\n        }\n        return false;\n    }\n\n    /**\n     * Returns the bounded stack scroll\n     */\n    float getBoundedStackScroll(float scroll) {\n        return Math.max(mLayoutAlgorithm.mMinScrollP, Math.min(mLayoutAlgorithm.mMaxScrollP, scroll));\n    }\n\n    /**\n     * Returns the amount that the aboslute value of how much the scroll is out of bounds.\n     */\n    float getScrollAmountOutOfBounds(float scroll) {\n        if (scroll < mLayoutAlgorithm.mMinScrollP) {\n            return Math.abs(scroll - mLayoutAlgorithm.mMinScrollP);\n        } else if (scroll > mLayoutAlgorithm.mMaxScrollP) {\n            return Math.abs(scroll - mLayoutAlgorithm.mMaxScrollP);\n        }\n        return 0f;\n    }\n\n    /**\n     * Returns whether the specified scroll is out of bounds\n     */\n    boolean isScrollOutOfBounds() {\n        return Float.compare(getScrollAmountOutOfBounds(mStackScrollP), 0f) != 0;\n    }\n\n    /**\n     * Animates the stack scroll into bounds\n     */\n    ObjectAnimator animateBoundScroll() {\n        float curScroll = getStackScroll();\n        float newScroll = getBoundedStackScroll(curScroll);\n        if (Float.compare(newScroll, curScroll) != 0) {\n            // Start a new scroll animation\n            animateScroll(curScroll, newScroll, null);\n        }\n        return mScrollAnimator;\n    }\n\n    /**\n     * Animates the stack scroll\n     */\n    void animateScroll(float curScroll, float newScroll, final Runnable postRunnable) {\n        // Finish any current scrolling animations\n        if (mScrollAnimator != null && mScrollAnimator.isRunning()) {\n            setStackScroll(mFinalAnimatedScroll);\n            mScroller.startScroll(0, progressToScrollRange(mFinalAnimatedScroll), 0, 0, 0);\n        }\n        stopScroller();\n        stopBoundScrollAnimation();\n\n        mFinalAnimatedScroll = newScroll;\n        mScrollAnimator = ObjectAnimator.ofFloat(this, \"stackScroll\", curScroll, newScroll);\n        mScrollAnimator.setDuration(mConfig.taskStackScrollDuration);\n        mScrollAnimator.setInterpolator(mConfig.linearOutSlowInInterpolator);\n        mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {\n            @Override\n            public void onAnimationUpdate(ValueAnimator animation) {\n                setStackScroll((Float) animation.getAnimatedValue());\n            }\n        });\n        mScrollAnimator.addListener(new AnimatorListenerAdapter() {\n            @Override\n            public void onAnimationEnd(Animator animation) {\n                if (postRunnable != null) {\n                    postRunnable.run();\n                }\n                mScrollAnimator.removeAllListeners();\n            }\n        });\n        mScrollAnimator.start();\n    }\n\n    /**\n     * Aborts any current stack scrolls\n     */\n    void stopBoundScrollAnimation() {\n        DVUtils.cancelAnimationWithoutCallbacks(mScrollAnimator);\n    }\n\n    /**\n     * * OverScroller ***\n     */\n\n    int progressToScrollRange(float p) {\n        return (int) (p * mLayoutAlgorithm.mStackVisibleRect.height());\n    }\n\n    float scrollRangeToProgress(int s) {\n        return (float) s / mLayoutAlgorithm.mStackVisibleRect.height();\n    }\n\n    /**\n     * Called from the view draw, computes the next scroll.\n     */\n    boolean computeScroll() {\n        if (mScroller.computeScrollOffset()) {\n            float scroll = scrollRangeToProgress(mScroller.getCurrY());\n            setStackScrollRaw(scroll);\n            if (mCb != null) {\n                mCb.onScrollChanged(scroll);\n            }\n            return true;\n        }\n        return false;\n    }\n\n    /**\n     * Returns whether the overscroller is scrolling.\n     */\n    boolean isScrolling() {\n        return !mScroller.isFinished();\n    }\n\n    /**\n     * Stops the scroller and any current fling.\n     */\n    void stopScroller() {\n        if (!mScroller.isFinished()) {\n            mScroller.abortAnimation();\n        }\n    }\n}\n"
  },
  {
    "path": "deckview/src/main/java/com/appeaser/deckview/views/DeckViewTouchHandler.java",
    "content": "package com.appeaser.deckview.views;\n\nimport android.content.Context;\nimport android.view.InputDevice;\nimport android.view.MotionEvent;\nimport android.view.VelocityTracker;\nimport android.view.View;\nimport android.view.ViewConfiguration;\nimport android.view.ViewParent;\n\nimport com.appeaser.deckview.helpers.DeckViewConfig;\nimport com.appeaser.deckview.helpers.DeckViewSwipeHelper;\nimport com.appeaser.deckview.utilities.DVConstants;\n\n/**\n * Created by Vikram on 02/04/2015.\n */\n/* Handles touch events for a TaskStackView. */\npublic class DeckViewTouchHandler implements DeckViewSwipeHelper.Callback {\n    static int INACTIVE_POINTER_ID = -1;\n\n    DeckViewConfig mConfig;\n    DeckView mDeckView;\n    DeckViewScroller mScroller;\n    VelocityTracker mVelocityTracker;\n\n    boolean mIsScrolling;\n\n    float mInitialP;\n    float mLastP;\n    float mTotalPMotion;\n    int mInitialMotionX, mInitialMotionY;\n    int mLastMotionX, mLastMotionY;\n    int mActivePointerId = INACTIVE_POINTER_ID;\n    DeckChildView mActiveDeckChildView = null;\n\n    int mMinimumVelocity;\n    int mMaximumVelocity;\n    // The scroll touch slop is used to calculate when we start scrolling\n    int mScrollTouchSlop;\n    // The page touch slop is used to calculate when we start swiping\n    float mPagingTouchSlop;\n\n    DeckViewSwipeHelper mSwipeHelper;\n    boolean mInterceptedBySwipeHelper;\n\n    public DeckViewTouchHandler(Context context, DeckView dv,\n                                DeckViewConfig config, DeckViewScroller scroller) {\n        ViewConfiguration configuration = ViewConfiguration.get(context);\n        mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();\n        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();\n        mScrollTouchSlop = configuration.getScaledTouchSlop();\n        mPagingTouchSlop = configuration.getScaledPagingTouchSlop();\n        mDeckView = dv;\n        mScroller = scroller;\n        mConfig = config;\n\n        float densityScale = context.getResources().getDisplayMetrics().density;\n        mSwipeHelper = new DeckViewSwipeHelper(DeckViewSwipeHelper.X, this,\n                densityScale, mPagingTouchSlop);\n        mSwipeHelper.setMinAlpha(1f);\n    }\n\n    /**\n     * Velocity tracker helpers\n     */\n    void initOrResetVelocityTracker() {\n        if (mVelocityTracker == null) {\n            mVelocityTracker = VelocityTracker.obtain();\n        } else {\n            mVelocityTracker.clear();\n        }\n    }\n\n    void initVelocityTrackerIfNotExists() {\n        if (mVelocityTracker == null) {\n            mVelocityTracker = VelocityTracker.obtain();\n        }\n    }\n\n    void recycleVelocityTracker() {\n        if (mVelocityTracker != null) {\n            mVelocityTracker.recycle();\n            mVelocityTracker = null;\n        }\n    }\n\n    /**\n     * Returns the view at the specified coordinates\n     */\n    DeckChildView findViewAtPoint(int x, int y) {\n        int childCount = mDeckView.getChildCount();\n        for (int i = childCount - 1; i >= 0; i--) {\n            DeckChildView tv = (DeckChildView) mDeckView.getChildAt(i);\n            if (tv.getVisibility() == View.VISIBLE) {\n                if (mDeckView.isTransformedTouchPointInView(x, y, tv)) {\n                    return tv;\n                }\n            }\n        }\n        return null;\n    }\n\n    /**\n     * Constructs a simulated motion event for the current stack scroll.\n     */\n    MotionEvent createMotionEventForStackScroll(MotionEvent ev) {\n        MotionEvent pev = MotionEvent.obtainNoHistory(ev);\n        pev.setLocation(0, mScroller.progressToScrollRange(mScroller.getStackScroll()));\n        return pev;\n    }\n\n    /**\n     * Touch preprocessing for handling below\n     */\n    public boolean onInterceptTouchEvent(MotionEvent ev) {\n        // Return early if we have no children\n        boolean hasChildren = (mDeckView.getChildCount() > 0);\n        if (!hasChildren) {\n            return false;\n        }\n\n        // Pass through to swipe helper if we are swiping\n        mInterceptedBySwipeHelper = mSwipeHelper.onInterceptTouchEvent(ev);\n        if (mInterceptedBySwipeHelper) {\n            return true;\n        }\n\n        boolean wasScrolling = mScroller.isScrolling() ||\n                (mScroller.mScrollAnimator != null && mScroller.mScrollAnimator.isRunning());\n        int action = ev.getAction();\n        switch (action & MotionEvent.ACTION_MASK) {\n            case MotionEvent.ACTION_DOWN: {\n                // Save the touch down info\n                mInitialMotionX = mLastMotionX = (int) ev.getX();\n                mInitialMotionY = mLastMotionY = (int) ev.getY();\n                mInitialP = mLastP = mDeckView.getStackAlgorithm().screenYToCurveProgress(mLastMotionY);\n                mActivePointerId = ev.getPointerId(0);\n                mActiveDeckChildView = findViewAtPoint(mLastMotionX, mLastMotionY);\n                // Stop the current scroll if it is still flinging\n                mScroller.stopScroller();\n                mScroller.stopBoundScrollAnimation();\n                // Initialize the velocity tracker\n                initOrResetVelocityTracker();\n                mVelocityTracker.addMovement(createMotionEventForStackScroll(ev));\n                break;\n            }\n            case MotionEvent.ACTION_MOVE: {\n                if (mActivePointerId == INACTIVE_POINTER_ID) break;\n\n                // Initialize the velocity tracker if necessary\n                initVelocityTrackerIfNotExists();\n                mVelocityTracker.addMovement(createMotionEventForStackScroll(ev));\n\n                int activePointerIndex = ev.findPointerIndex(mActivePointerId);\n                int y = (int) ev.getY(activePointerIndex);\n                int x = (int) ev.getX(activePointerIndex);\n                if (Math.abs(y - mInitialMotionY) > mScrollTouchSlop) {\n                    // Save the touch move info\n                    mIsScrolling = true;\n                    // Disallow parents from intercepting touch events\n                    final ViewParent parent = mDeckView.getParent();\n                    if (parent != null) {\n                        parent.requestDisallowInterceptTouchEvent(true);\n                    }\n                }\n\n                mLastMotionX = x;\n                mLastMotionY = y;\n                mLastP = mDeckView.getStackAlgorithm().screenYToCurveProgress(mLastMotionY);\n                break;\n            }\n            case MotionEvent.ACTION_CANCEL:\n            case MotionEvent.ACTION_UP: {\n                // Animate the scroll back if we've cancelled\n                mScroller.animateBoundScroll();\n                // Reset the drag state and the velocity tracker\n                mIsScrolling = false;\n                mActivePointerId = INACTIVE_POINTER_ID;\n                mActiveDeckChildView = null;\n                mTotalPMotion = 0;\n                recycleVelocityTracker();\n                break;\n            }\n        }\n\n        return wasScrolling || mIsScrolling;\n    }\n\n    /**\n     * Handles touch events once we have intercepted them\n     */\n    public boolean onTouchEvent(MotionEvent ev) {\n        // Short circuit if we have no children\n        boolean hasChildren = (mDeckView.getChildCount() > 0);\n        if (!hasChildren) {\n            return false;\n        }\n\n        // Pass through to swipe helper if we are swiping\n        if (mInterceptedBySwipeHelper && mSwipeHelper.onTouchEvent(ev)) {\n            return true;\n        }\n\n        // Update the velocity tracker\n        initVelocityTrackerIfNotExists();\n\n        int action = ev.getAction();\n        switch (action & MotionEvent.ACTION_MASK) {\n            case MotionEvent.ACTION_DOWN: {\n                // Save the touch down info\n                mInitialMotionX = mLastMotionX = (int) ev.getX();\n                mInitialMotionY = mLastMotionY = (int) ev.getY();\n                mInitialP = mLastP = mDeckView.getStackAlgorithm().screenYToCurveProgress(mLastMotionY);\n                mActivePointerId = ev.getPointerId(0);\n                mActiveDeckChildView = findViewAtPoint(mLastMotionX, mLastMotionY);\n                // Stop the current scroll if it is still flinging\n                mScroller.stopScroller();\n                mScroller.stopBoundScrollAnimation();\n                // Initialize the velocity tracker\n                initOrResetVelocityTracker();\n                mVelocityTracker.addMovement(createMotionEventForStackScroll(ev));\n                // Disallow parents from intercepting touch events\n                final ViewParent parent = mDeckView.getParent();\n                if (parent != null) {\n                    parent.requestDisallowInterceptTouchEvent(true);\n                }\n                break;\n            }\n            case MotionEvent.ACTION_POINTER_DOWN: {\n                final int index = ev.getActionIndex();\n                mActivePointerId = ev.getPointerId(index);\n                mLastMotionX = (int) ev.getX(index);\n                mLastMotionY = (int) ev.getY(index);\n                mLastP = mDeckView.getStackAlgorithm().screenYToCurveProgress(mLastMotionY);\n                break;\n            }\n            case MotionEvent.ACTION_MOVE: {\n                if (mActivePointerId == INACTIVE_POINTER_ID) break;\n\n                mVelocityTracker.addMovement(createMotionEventForStackScroll(ev));\n\n                int activePointerIndex = ev.findPointerIndex(mActivePointerId);\n                int x = (int) ev.getX(activePointerIndex);\n                int y = (int) ev.getY(activePointerIndex);\n                int yTotal = Math.abs(y - mInitialMotionY);\n                float curP = mDeckView.getStackAlgorithm().screenYToCurveProgress(y);\n                float deltaP = mLastP - curP;\n                if (!mIsScrolling) {\n                    if (yTotal > mScrollTouchSlop) {\n                        mIsScrolling = true;\n                        // Disallow parents from intercepting touch events\n                        final ViewParent parent = mDeckView.getParent();\n                        if (parent != null) {\n                            parent.requestDisallowInterceptTouchEvent(true);\n                        }\n                    }\n                }\n                if (mIsScrolling) {\n                    float curStackScroll = mScroller.getStackScroll();\n                    float overScrollAmount = mScroller.getScrollAmountOutOfBounds(curStackScroll + deltaP);\n                    if (Float.compare(overScrollAmount, 0f) != 0) {\n                        // Bound the overscroll to a fixed amount, and inversely scale the y-movement\n                        // relative to how close we are to the max overscroll\n                        float maxOverScroll = mConfig.taskStackOverscrollPct;\n                        deltaP *= (1f - (Math.min(maxOverScroll, overScrollAmount)\n                                / maxOverScroll));\n                    }\n                    mScroller.setStackScroll(curStackScroll + deltaP);\n                }\n                mLastMotionX = x;\n                mLastMotionY = y;\n                mLastP = mDeckView.getStackAlgorithm().screenYToCurveProgress(mLastMotionY);\n                mTotalPMotion += Math.abs(deltaP);\n                break;\n            }\n            case MotionEvent.ACTION_UP: {\n                mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);\n                int velocity = (int) mVelocityTracker.getYVelocity(mActivePointerId);\n                if (mIsScrolling && (Math.abs(velocity) > mMinimumVelocity)) {\n                    float overscrollRangePct = Math.abs((float) velocity / mMaximumVelocity);\n                    int overscrollRange = (int) (Math.min(1f, overscrollRangePct) *\n                            (DVConstants.Values.DView.TaskStackMaxOverscrollRange -\n                                    DVConstants.Values.DView.TaskStackMinOverscrollRange));\n                    mScroller.mScroller.fling(0,\n                            mScroller.progressToScrollRange(mScroller.getStackScroll()),\n                            0, velocity,\n                            0, 0,\n                            mScroller.progressToScrollRange(mDeckView.getStackAlgorithm().mMinScrollP),\n                            mScroller.progressToScrollRange(mDeckView.getStackAlgorithm().mMaxScrollP),\n                            0, DVConstants.Values.DView.TaskStackMinOverscrollRange +\n                                    overscrollRange);\n                    // Invalidate to kick off computeScroll\n                    mDeckView.invalidate();\n                } else if (mScroller.isScrollOutOfBounds()) {\n                    // Animate the scroll back into bounds\n                    mScroller.animateBoundScroll();\n                }\n\n                mActivePointerId = INACTIVE_POINTER_ID;\n                mIsScrolling = false;\n                mTotalPMotion = 0;\n                recycleVelocityTracker();\n                break;\n            }\n            case MotionEvent.ACTION_POINTER_UP: {\n                int pointerIndex = ev.getActionIndex();\n                int pointerId = ev.getPointerId(pointerIndex);\n                if (pointerId == mActivePointerId) {\n                    // Select a new active pointer id and reset the motion state\n                    final int newPointerIndex = (pointerIndex == 0) ? 1 : 0;\n                    mActivePointerId = ev.getPointerId(newPointerIndex);\n                    mLastMotionX = (int) ev.getX(newPointerIndex);\n                    mLastMotionY = (int) ev.getY(newPointerIndex);\n                    mLastP = mDeckView.getStackAlgorithm().screenYToCurveProgress(mLastMotionY);\n                    mVelocityTracker.clear();\n                }\n                break;\n            }\n            case MotionEvent.ACTION_CANCEL: {\n                if (mScroller.isScrollOutOfBounds()) {\n                    // Animate the scroll back into bounds\n                    mScroller.animateBoundScroll();\n                }\n                mActivePointerId = INACTIVE_POINTER_ID;\n                mIsScrolling = false;\n                mTotalPMotion = 0;\n                recycleVelocityTracker();\n                break;\n            }\n        }\n        return true;\n    }\n\n    /**\n     * Handles generic motion events\n     */\n    public boolean onGenericMotionEvent(MotionEvent ev) {\n        if ((ev.getSource() & InputDevice.SOURCE_CLASS_POINTER) ==\n                InputDevice.SOURCE_CLASS_POINTER) {\n            int action = ev.getAction();\n            switch (action & MotionEvent.ACTION_MASK) {\n                case MotionEvent.ACTION_SCROLL:\n                    // Find the front most task and scroll the next task to the front\n                    float vScroll = ev.getAxisValue(MotionEvent.AXIS_VSCROLL);\n                    if (vScroll > 0) {\n                        if (mDeckView.ensureFocusedTask()) {\n                            mDeckView.focusNextTask(true, false);\n                        }\n                    } else {\n                        if (mDeckView.ensureFocusedTask()) {\n                            mDeckView.focusNextTask(false, false);\n                        }\n                    }\n                    return true;\n            }\n        }\n        return false;\n    }\n\n    /**\n     * * SwipeHelper Implementation ***\n     */\n\n    @Override\n    public View getChildAtPosition(MotionEvent ev) {\n        return findViewAtPoint((int) ev.getX(), (int) ev.getY());\n    }\n\n    @Override\n    public boolean canChildBeDismissed(View v) {\n        return true;\n    }\n\n    @Override\n    public void onBeginDrag(View v) {\n        DeckChildView tv = (DeckChildView) v;\n        // Disable clipping with the stack while we are swiping\n        tv.setClipViewInStack(false);\n        // Disallow touch events from this task view\n        tv.setTouchEnabled(false);\n        // Disallow parents from intercepting touch events\n        final ViewParent parent = mDeckView.getParent();\n        if (parent != null) {\n            parent.requestDisallowInterceptTouchEvent(true);\n        }\n    }\n\n    @Override\n    public void onSwipeChanged(View v, float delta) {\n        // Do nothing\n    }\n\n    @Override\n    public void onChildDismissed(View v) {\n        DeckChildView tv = (DeckChildView) v;\n        // Re-enable clipping with the stack (we will reuse this view)\n        tv.setClipViewInStack(true);\n        // Re-enable touch events from this task view\n        tv.setTouchEnabled(true);\n        // Remove the task view from the stack\n        mDeckView.onDeckChildViewDismissed(tv);\n    }\n\n    @Override\n    public void onSnapBackCompleted(View v) {\n        DeckChildView tv = (DeckChildView) v;\n        // Re-enable clipping with the stack\n        tv.setClipViewInStack(true);\n        // Re-enable touch events from this task view\n        tv.setTouchEnabled(true);\n    }\n\n    @Override\n    public void onDragCancelled(View v) {\n        // Do nothing\n    }\n}"
  },
  {
    "path": "deckview/src/main/java/com/appeaser/deckview/views/FixedSizeImageView.java",
    "content": "package com.appeaser.deckview.views;\n\n/**\n * Created by Vikram on 01/04/2015.\n */\n\nimport android.content.Context;\nimport android.graphics.drawable.BitmapDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.util.AttributeSet;\nimport android.widget.ImageView;\n\n/**\n * This is an optimized ImageView that does not trigger a requestLayout() or invalidate() when\n * setting the image to Null.\n */\npublic class FixedSizeImageView extends ImageView {\n\n    boolean mAllowRelayout = true;\n    boolean mAllowInvalidate = true;\n\n    public FixedSizeImageView(Context context) {\n        this(context, null);\n    }\n\n    public FixedSizeImageView(Context context, AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    public FixedSizeImageView(Context context, AttributeSet attrs, int defStyleAttr) {\n        this(context, attrs, defStyleAttr, 0);\n    }\n\n    public FixedSizeImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {\n        super(context, attrs, defStyleAttr, defStyleRes);\n    }\n\n    @Override\n    public void requestLayout() {\n        if (mAllowRelayout) {\n            super.requestLayout();\n        }\n    }\n\n    @Override\n    public void invalidate() {\n        if (mAllowInvalidate) {\n            super.invalidate();\n        }\n    }\n\n    @Override\n    public void setImageDrawable(Drawable drawable) {\n        boolean isNullBitmapDrawable = (drawable instanceof BitmapDrawable) &&\n                (((BitmapDrawable) drawable).getBitmap() == null);\n        if (drawable == null || isNullBitmapDrawable) {\n            mAllowRelayout = false;\n            mAllowInvalidate = false;\n        }\n        super.setImageDrawable(drawable);\n        mAllowRelayout = true;\n        mAllowInvalidate = true;\n    }\n}\n"
  },
  {
    "path": "deckview/src/main/java/com/appeaser/deckview/views/ViewAnimation.java",
    "content": "package com.appeaser.deckview.views;\n\nimport android.animation.ValueAnimator;\nimport android.graphics.Rect;\n\nimport com.appeaser.deckview.helpers.DeckChildViewTransform;\nimport com.appeaser.deckview.utilities.ReferenceCountedTrigger;\n\n/**\n * Created by Vikram on 01/04/2015.\n */\n/* Common code related to view animations */\npublic class ViewAnimation {\n\n    /* The animation context for a task view animation into Recents */\n    public static class TaskViewEnterContext {\n        // A trigger to run some logic when all the animations complete.  This works around the fact\n        // that it is difficult to coordinate ViewPropertyAnimators\n        public ReferenceCountedTrigger postAnimationTrigger;\n        // An update listener to notify as the enter animation progresses (used for the home transition)\n        public ValueAnimator.AnimatorUpdateListener updateListener;\n\n        // These following properties are updated for each task view we start the enter animation on\n\n        // Whether or not the current task occludes the launch target\n        boolean currentTaskOccludesLaunchTarget;\n        // The task rect for the current stack\n        Rect currentTaskRect;\n        // The transform of the current task view\n        public DeckChildViewTransform currentTaskTransform;\n        // The view index of the current task view\n        public int currentStackViewIndex;\n        // The total number of task views\n        public int currentStackViewCount;\n\n        public TaskViewEnterContext(ReferenceCountedTrigger t) {\n            postAnimationTrigger = t;\n        }\n    }\n\n    /* The animation context for a task view animation out of Recents */\n    public static class TaskViewExitContext {\n        // A trigger to run some logic when all the animations complete.  This works around the fact\n        // that it is difficult to coordinate ViewPropertyAnimators\n        public ReferenceCountedTrigger postAnimationTrigger;\n\n        // The translationY to apply to a TaskView to move it off the bottom of the task stack\n        public int offscreenTranslationY;\n\n        public TaskViewExitContext(ReferenceCountedTrigger t) {\n            postAnimationTrigger = t;\n        }\n    }\n\n}\n"
  },
  {
    "path": "deckview/src/main/java/com/appeaser/deckview/views/ViewPool.java",
    "content": "package com.appeaser.deckview.views;\n\nimport android.content.Context;\n\nimport java.util.Iterator;\nimport java.util.LinkedList;\n\n/**\n * Created by Vikram on 01/04/2015.\n */\n/* A view pool to manage more views than we can visibly handle */\npublic class ViewPool<V, T> {\n\n    /* An interface to the consumer of a view pool */\n    public interface ViewPoolConsumer<V, T> {\n        public V createView(Context context);\n\n        public void prepareViewToEnterPool(V v);\n\n        public void prepareViewToLeavePool(V v, T prepareData, boolean isNewView);\n\n        public boolean hasPreferredData(V v, T preferredData);\n    }\n\n    Context mContext;\n    ViewPoolConsumer<V, T> mViewCreator;\n    LinkedList<V> mPool = new LinkedList<V>();\n\n    /**\n     * Initializes the pool with a fixed predetermined pool size\n     */\n    public ViewPool(Context context, ViewPoolConsumer<V, T> viewCreator) {\n        mContext = context;\n        mViewCreator = viewCreator;\n    }\n\n    /**\n     * Returns a view into the pool\n     */\n    void returnViewToPool(V v) {\n        mViewCreator.prepareViewToEnterPool(v);\n        mPool.push(v);\n    }\n\n    /**\n     * Gets a view from the pool and prepares it\n     */\n    V pickUpViewFromPool(T preferredData, T prepareData) {\n        V v = null;\n        boolean isNewView = false;\n        if (mPool.isEmpty()) {\n            v = mViewCreator.createView(mContext);\n            isNewView = true;\n        } else {\n            // Try and find a preferred view\n            Iterator<V> iter = mPool.iterator();\n            while (iter.hasNext()) {\n                V vpv = iter.next();\n                if (mViewCreator.hasPreferredData(vpv, preferredData)) {\n                    v = vpv;\n                    iter.remove();\n                    break;\n                }\n            }\n            // Otherwise, just grab the first view\n            if (v == null) {\n                v = mPool.pop();\n            }\n        }\n        mViewCreator.prepareViewToLeavePool(v, prepareData, isNewView);\n        return v;\n    }\n\n    /**\n     * Returns an iterator to the list of the views in the pool.\n     */\n    Iterator<V> poolViewIterator() {\n        if (mPool != null) {\n            return mPool.iterator();\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "deckview/src/main/res/drawable-v21/deck_child_view_button_bg.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!-- Copyright (C) 2014 The Android Open Source Project\n\n     Licensed under the Apache License, Version 2.0 (the \"License\");\n     you may not use this file except in compliance with the License.\n     You may obtain a copy of the License at\n\n          http://www.apache.org/licenses/LICENSE-2.0\n\n     Unless required by applicable law or agreed to in writing, software\n     distributed under the License is distributed on an \"AS IS\" BASIS,\n     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n     See the License for the specific language governing permissions and\n     limitations under the License.\n-->\n\n<ripple xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:color=\"#40ffffff\"></ripple>"
  },
  {
    "path": "deckview/src/main/res/drawable-v21/deck_child_view_dismiss_dark.xml",
    "content": "<!--\nCopyright (C) 2014 The Android Open Source Project\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n    you may not use this file except in compliance with the License.\n    You may obtain a copy of the License at\n\n         http://www.apache.org/licenses/LICENSE-2.0\n\n    Unless required by applicable law or agreed to in writing, software\n    distributed under the License is distributed on an \"AS IS\" BASIS,\n    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n    See the License for the specific language governing permissions and\n    limitations under the License.\n-->\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"48.0dp\"\n    android:height=\"48.0dp\"\n    android:viewportWidth=\"48.0\"\n    android:viewportHeight=\"48.0\">\n    <path\n        android:fillColor=\"#99000000\"\n        android:pathData=\"M38.000000,12.800000l-2.799999,-2.800000 -11.200001,11.200001 -11.200000,-11.200001 -2.800000,2.800000 11.200001,11.200000 -11.200001,11.200001 2.800000,2.799999 11.200000,-11.200001 11.200001,11.200001 2.799999,-2.799999 -11.200001,-11.200001z\" />\n</vector>"
  },
  {
    "path": "deckview/src/main/res/drawable-v21/deck_child_view_dismiss_light.xml",
    "content": "<!--\nCopyright (C) 2014 The Android Open Source Project\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n    you may not use this file except in compliance with the License.\n    You may obtain a copy of the License at\n\n         http://www.apache.org/licenses/LICENSE-2.0\n\n    Unless required by applicable law or agreed to in writing, software\n    distributed under the License is distributed on an \"AS IS\" BASIS,\n    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n    See the License for the specific language governing permissions and\n    limitations under the License.\n-->\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"48.0dp\"\n    android:height=\"48.0dp\"\n    android:viewportWidth=\"48.0\"\n    android:viewportHeight=\"48.0\">\n    <path\n        android:fillColor=\"#ffeeeeee\"\n        android:pathData=\"M38.000000,12.800000l-2.799999,-2.800000 -11.200001,11.200001 -11.200000,-11.200001 -2.800000,2.800000 11.200001,11.200000 -11.200001,11.200001 2.800000,2.799999 11.200000,-11.200001 11.200001,11.200001 2.799999,-2.799999 -11.200001,-11.200001z\" />\n</vector>"
  },
  {
    "path": "deckview/src/main/res/drawable-v21/deck_child_view_header_bg.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!-- Copyright (C) 2014 The Android Open Source Project\n\n     Licensed under the Apache License, Version 2.0 (the \"License\");\n     you may not use this file except in compliance with the License.\n     You may obtain a copy of the License at\n\n          http://www.apache.org/licenses/LICENSE-2.0\n\n     Unless required by applicable law or agreed to in writing, software\n     distributed under the License is distributed on an \"AS IS\" BASIS,\n     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n     See the License for the specific language governing permissions and\n     limitations under the License.\n-->\n\n<ripple xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:color=\"?android:attr/colorControlHighlight\">\n    <item android:drawable=\"@android:color/transparent\" />\n</ripple>"
  },
  {
    "path": "deckview/src/main/res/drawable-v21/deck_child_view_header_bg_color.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!-- Copyright (C) 2014 The Android Open Source Project\n\n     Licensed under the Apache License, Version 2.0 (the \"License\");\n     you may not use this file except in compliance with the License.\n     You may obtain a copy of the License at\n\n          http://www.apache.org/licenses/LICENSE-2.0\n\n     Unless required by applicable law or agreed to in writing, software\n     distributed under the License is distributed on an \"AS IS\" BASIS,\n     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n     See the License for the specific language governing permissions and\n     limitations under the License.\n-->\n\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n    <corners\n        android:topLeftRadius=\"2dp\"\n        android:topRightRadius=\"2dp\" />\n    <solid android:color=\"#00000000\" />\n</shape>"
  },
  {
    "path": "deckview/src/main/res/interpolator-v21/decelerate_quint.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n/*\n** Copyright 2010, The Android Open Source Project\n**\n** Licensed under the Apache License, Version 2.0 (the \"License\"); \n** you may not use this file except in compliance with the License. \n** You may obtain a copy of the License at \n**\n**     http://www.apache.org/licenses/LICENSE-2.0 \n**\n** Unless required by applicable law or agreed to in writing, software \n** distributed under the License is distributed on an \"AS IS\" BASIS, \n** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. \n** See the License for the specific language governing permissions and \n** limitations under the License.\n*/\n-->\n\n<decelerateInterpolator xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:factor=\"2.5\" />"
  },
  {
    "path": "deckview/src/main/res/interpolator-v21/fast_out_linear_in.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Copyright (C) 2014 The Android Open Source Project\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~      http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License\n  -->\n\n<pathInterpolator xmlns:android=\"http://schemas.android.com/apk/res/android\" android:controlX1=\"0.4\"\n    android:controlY1=\"0\" android:controlX2=\"1\" android:controlY2=\"1\" />"
  },
  {
    "path": "deckview/src/main/res/interpolator-v21/fast_out_slow_in.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Copyright (C) 2014 The Android Open Source Project\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~      http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License\n  -->\n\n<pathInterpolator xmlns:android=\"http://schemas.android.com/apk/res/android\" android:controlX1=\"0.4\"\n    android:controlY1=\"0\" android:controlX2=\"0.2\" android:controlY2=\"1\" />"
  },
  {
    "path": "deckview/src/main/res/interpolator-v21/linear_out_slow_in.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Copyright (C) 2014 The Android Open Source Project\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~      http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License\n  -->\n\n<pathInterpolator xmlns:android=\"http://schemas.android.com/apk/res/android\" android:controlX1=\"0\"\n    android:controlY1=\"0\" android:controlX2=\"0.2\" android:controlY2=\"1\" />"
  },
  {
    "path": "deckview/src/main/res/layout/deck_child_view.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!-- Copyright (C) 2014 The Android Open Source Project\n\n     Licensed under the Apache License, Version 2.0 (the \"License\");\n     you may not use this file except in compliance with the License.\n     You may obtain a copy of the License at\n  \n          http://www.apache.org/licenses/LICENSE-2.0\n  \n     Unless required by applicable law or agreed to in writing, software\n     distributed under the License is distributed on an \"AS IS\" BASIS,\n     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n     See the License for the specific language governing permissions and\n     limitations under the License.\n-->\n<com.appeaser.deckview.views.DeckChildView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:focusable=\"true\">\n\n    <FrameLayout\n        android:id=\"@+id/task_view_content\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n\n        <com.appeaser.deckview.views.DeckChildViewThumbnail\n            android:id=\"@+id/task_view_thumbnail\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\" />\n\n        <include layout=\"@layout/deck_child_view_header\" />\n    </FrameLayout>\n</com.appeaser.deckview.views.DeckChildView>"
  },
  {
    "path": "deckview/src/main/res/layout/deck_child_view_header.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!-- Copyright (C) 2014 The Android Open Source Project\n\n     Licensed under the Apache License, Version 2.0 (the \"License\");\n     you may not use this file except in compliance with the License.\n     You may obtain a copy of the License at\n  \n          http://www.apache.org/licenses/LICENSE-2.0\n  \n     Unless required by applicable law or agreed to in writing, software\n     distributed under the License is distributed on an \"AS IS\" BASIS,\n     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n     See the License for the specific language governing permissions and\n     limitations under the License.\n-->\n<com.appeaser.deckview.views.DeckChildViewHeader xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/task_view_bar\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"@dimen/deck_child_header_bar_height\"\n    android:layout_gravity=\"top|center_horizontal\">\n\n    <com.appeaser.deckview.views.FixedSizeImageView\n        android:id=\"@+id/application_icon\"\n        android:layout_width=\"@dimen/task_view_application_icon_size\"\n        android:layout_height=\"@dimen/task_view_application_icon_size\"\n        android:layout_marginStart=\"8dp\"\n        android:layout_gravity=\"center_vertical|start\"\n        android:padding=\"8dp\"\n        android:background=\"@drawable/deck_child_view_button_bg\" />\n\n    <TextView\n        android:id=\"@+id/activity_description\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center_vertical|start\"\n        android:layout_marginStart=\"64dp\"\n        android:layout_marginEnd=\"64dp\"\n        android:textSize=\"16sp\"\n        android:textColor=\"#ffffffff\"\n        android:text=\"@string/deck_view_empty_message\"\n        android:fontFamily=\"sans-serif-medium\"\n        android:singleLine=\"true\"\n        android:maxLines=\"2\"\n        android:ellipsize=\"marquee\"\n        android:fadingEdge=\"horizontal\" />\n\n    <com.appeaser.deckview.views.FixedSizeImageView\n        android:id=\"@+id/dismiss_task\"\n        android:layout_width=\"48dp\"\n        android:layout_height=\"48dp\"\n        android:layout_marginEnd=\"4dp\"\n        android:layout_gravity=\"center_vertical|end\"\n        android:padding=\"12dp\"\n        android:background=\"@drawable/deck_child_view_button_bg\"\n        android:visibility=\"invisible\"\n        android:src=\"@drawable/deck_child_view_dismiss_light\" />\n</com.appeaser.deckview.views.DeckChildViewHeader>"
  },
  {
    "path": "deckview/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <!-- The default recents task bar background color. -->\n    <color name=\"task_bar_default_background_color\">#ffe6e6e6</color>\n\n    <!-- The recents task bar light text color to be drawn on top of dark backgrounds. -->\n    <color name=\"task_bar_light_text_color\">#ffeeeeee</color>\n\n    <!-- The recents task bar dark text color to be drawn on top of light backgrounds. -->\n    <color name=\"task_bar_dark_text_color\">#cc000000</color>\n\n    <!-- The recents task bar highlight color. -->\n    <color name=\"task_bar_highlight_color\">#28ffffff</color>\n\n    <!-- Shadow color for the first pixels around the fake shadow for recents. -->\n    <color name=\"fake_shadow_start_color\">#44000000</color>\n\n    <!-- Shadow color for the furthest pixels around the fake shadow for recents. -->\n    <color name=\"fake_shadow_end_color\">#03000000</color>\n\n</resources>"
  },
  {
    "path": "deckview/src/main/res/values/config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <!-- The min animation duration for animating views that are currently visible. -->\n    <integer name=\"filter_animate_current_views_duration\">250</integer>\n\n    <!-- The min animation duration for animating views that are newly visible. -->\n    <integer name=\"filter_animate_new_views_duration\">250</integer>\n\n    <!-- The animation duration for scrolling the stack to a particular item. -->\n    <integer name=\"animate_deck_scroll_duration\">225</integer>\n\n    <!-- The minimum alpha for the dim applied to cards that go deeper into the stack. -->\n    <integer name=\"max_deck_view_dim\">96</integer>\n\n    <!-- The duration of the window transition when coming to Recents from an app.\n         In order to defer the in-app animations until after the transition is complete,\n         we also need to use this value as the starting delay when animating the first\n         task decorations in. -->\n    <integer name=\"enter_from_app_transition_duration\">325</integer>\n\n    <!-- The duration of the window transition when coming to Recents from the Launcher.\n         In order to defer the in-app animations until after the transition is complete,\n         we also need to use this value as the starting delay when animating the task views\n         in from the bottom of the screen. -->\n    <integer name=\"enter_from_home_transition_duration\">100</integer>\n\n    <!-- The duration for animating the task decorations in after transitioning from an app. -->\n    <integer name=\"task_enter_from_app_duration\">200</integer>\n\n    <!-- The duration for animating the task from the bottom of the screen when transitioning\n     from home. -->\n    <integer name=\"task_enter_from_home_duration\">225</integer>\n\n    <!-- The stagger for each task when animating the task from the bottom of the screen when\n     transitioning from home. -->\n    <integer name=\"task_enter_from_home_stagger_delay\">12</integer>\n\n    <!-- The duration for animating the task decorations out before transitioning to an app. -->\n    <integer name=\"task_exit_to_app_duration\">125</integer>\n\n    <!-- The duration of the animation of the tasks to the bottom of the screen when leaving\n     Recents to go back to the Launcher. -->\n    <integer name=\"task_exit_to_home_duration\">225</integer>\n\n    <!-- The animation duration for animating the removal of a task view. -->\n    <integer name=\"animate_task_view_remove_duration\">250</integer>\n\n    <!-- The duration in seconds to wait before the dismiss buttons are shown. -->\n    <integer name=\"task_bar_dismiss_delay_seconds\">1</integer>\n\n    <!-- The min animation duration for animating the nav bar scrim in. -->\n    <integer name=\"nav_bar_scrim_enter_duration\">400</integer>\n\n    <!-- Whether recents should use hardware layers for its taskviews. This flag can be enabled\n    for devices where the java drawing of round rects may be slow -->\n    <bool name=\"config_use_hardware_layers\">false</bool>\n\n    <!-- The delay to enforce between each alt-tab key press. -->\n    <integer name=\"deck_alt_tab_key_delay\">200</integer>\n\n    <!-- Whether to use cheap, less good looking shadows for recents -->\n    <bool name=\"config_fake_shadows\">false</bool>\n\n    <!-- Svelte specific logic, see RecentsConfiguration.SVELTE_* constants. -->\n    212\n    <integer name=\"deck_svelte_level\">0</integer>\n\n</resources>"
  },
  {
    "path": "deckview/src/main/res/values/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <!-- Used to calculate the translation animation duration, the expected amount of movement\n         in dps over one second of time. -->\n    <dimen name=\"animation_movement_in_dps_per_second\">800dp</dimen>\n\n    <!-- The side padding for the deck as a percentage of the width. -->\n    <item name=\"deck_width_padding_percentage\" format=\"float\" type=\"dimen\">0.03333</item>\n\n    <!-- The overscroll percentage allowed on the deck. -->\n    <item name=\"deck_overscroll_percentage\" format=\"float\" type=\"dimen\">0.0875</item>\n\n    <!-- The top offset for the deck. -->\n    <dimen name=\"deck_top_padding\">16dp</dimen>\n\n    <!-- The amount to translate when animating the removal of a task. -->\n    <dimen name=\"task_view_remove_anim_translation_x\">100dp</dimen>\n\n    <!-- The radius of the rounded corners on a task view. -->\n    <dimen name=\"task_view_rounded_corners_radius\">2dp</dimen>\n\n    <!-- The amount of highlight to make on each task view. -->\n    <dimen name=\"task_view_highlight\">1.5dp</dimen>\n\n    <!-- The min translation in the Z index for the last task. -->\n    <dimen name=\"task_view_z_min\">20dp</dimen>\n\n    <!-- The max translation in the Z index for the last task. -->\n    <dimen name=\"task_view_z_max\">80dp</dimen>\n\n    <!-- The amount to offset when animating into an affiliate group. -->\n    <dimen name=\"task_view_affiliate_group_enter_offset\">64dp</dimen>\n\n    <!-- The min alpha to apply to a task affiliation group color. -->\n    <item name=\"task_affiliation_color_min_alpha_percentage\" format=\"float\" type=\"dimen\">0.6</item>\n\n    <!-- The height of a task view bar. -->\n    <dimen name=\"deck_child_header_bar_height\">56dp</dimen>\n\n    <!-- The alpha to apply to a task thumbnail. -->\n    <item name=\"task_view_thumbnail_alpha\" format=\"float\" type=\"dimen\">0.9</item>\n\n    <!-- Inset shadow for FakeShadowDrawable. It is used to avoid gaps between the card\n         and the shadow. -->\n    <dimen name=\"fake_shadow_inset\">1dp</dimen>\n\n    <dimen name=\"fake_shadow_size\">8dp</dimen>\n\n    <!-- The size of the application icon in the recents task view. -->\n    <dimen name=\"task_view_application_icon_size\">48dp</dimen>\n\n</resources>"
  },
  {
    "path": "deckview/src/main/res/values/strings.xml",
    "content": "<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n    <string name=\"app_name\">DeckViewLibrary</string>\n\n    <string name=\"deck_view_empty_message\">Nothing here</string>\n\n    <!-- Content description to tell the user that this button will remove an application from recents -->\n    <string name=\"accessibility_item_will_be_dismissed\">Dismiss <xliff:g id=\"app\" example=\"Calendar\">%s</xliff:g>.</string>\n</resources>\n"
  },
  {
    "path": "deckview/src/main/res/values-land/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <!-- The side padding for the deck as a percentage of the width. -->\n    <item name=\"deck_width_padding_percentage\" format=\"float\" type=\"dimen\">0.26</item>\n\n</resources>"
  },
  {
    "path": "deckview/src/main/res/values-sw600dp/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <!-- The side padding for the deck as a percentage of the width. -->\n    <item name=\"deck_width_padding_percentage\" format=\"float\" type=\"dimen\">0.075</item>\n\n</resources>"
  },
  {
    "path": "deckview/src/main/res/values-sw600dp-land/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <!-- The side padding for the deck as a percentage of the width. -->\n    <item name=\"deck_width_padding_percentage\" format=\"float\" type=\"dimen\">0.25</item>\n\n</resources>"
  },
  {
    "path": "deckview/src/main/res/values-sw720dp/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <!-- The radius of the rounded corners on a task view. -->\n    <dimen name=\"task_view_rounded_corners_radius\">3dp</dimen>\n\n</resources>"
  },
  {
    "path": "deckviewsample/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "deckviewsample/build.gradle",
    "content": "apply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion 22\n    buildToolsVersion \"22.0.1\"\n\n    defaultConfig {\n        applicationId \"com.appeaser.deckviewsample\"\n        minSdkVersion 21\n        targetSdkVersion 22\n        versionCode 1\n        versionName \"1.0\"\n    }\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n}\n\ndependencies {\n    compile fileTree(dir: 'libs', include: ['*.jar'])\n    compile 'com.squareup.picasso:picasso:2.5.2'\n    compile project(':deckview')\n}\n"
  },
  {
    "path": "deckviewsample/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in C:/Users/Vikram/Documents/Android/adt-bundle-windows-x86-20130219/sdk/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n"
  },
  {
    "path": "deckviewsample/src/androidTest/java/com/appeaser/deckviewsample/ApplicationTest.java",
    "content": "package com.appeaser.deckviewsample;\n\nimport android.app.Application;\nimport android.test.ApplicationTestCase;\n\n/**\n * <a href=\"http://d.android.com/tools/testing/testing_android.html\">Testing Fundamentals</a>\n */\npublic class ApplicationTest extends ApplicationTestCase<Application> {\n    public ApplicationTest() {\n        super(Application.class);\n    }\n}"
  },
  {
    "path": "deckviewsample/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.appeaser.deckviewsample\">\n\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n\n    <application\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:theme=\"@style/AppTheme\">\n        <activity\n            android:name=\".DeckViewSampleActivity\"\n            android:label=\"@string/app_name\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "deckviewsample/src/main/java/com/appeaser/deckviewsample/Datum.java",
    "content": "package com.appeaser.deckviewsample;\n\nimport android.os.Parcel;\nimport android.os.Parcelable;\n\nimport com.squareup.picasso.Target;\n\n/**\n * Simple model class\n * One important requirement for DeckView to function\n * is that all items in the dataset *must be* uniquely\n * identifiable. No two items can be such\n * that `item1.equals(item2)` returns `true`.\n * See equals() implementation below.\n * `id` is generated using `DeckViewSampleActivity#generateuniqueKey()`\n * Implementing `Parcelable` serves only one purpose - to persist data\n * on configuration change.\n */\npublic class Datum implements Parcelable {\n\n    public int id;\n    public String headerTitle, link;\n    public Target target;\n\n    public Datum() {\n        // Nothing\n    }\n\n    @Override\n    public int describeContents() {\n        return 0;\n    }\n\n    public Datum(Parcel in) {\n        readFromParcel(in);\n    }\n\n    public void readFromParcel(Parcel in) {\n        id = in.readInt();\n        headerTitle = in.readString();\n        link = in.readString();\n    }\n\n    @Override\n    public void writeToParcel(Parcel dest, int flags) {\n        dest.writeInt(id);\n        dest.writeString(headerTitle);\n        dest.writeString(link);\n    }\n\n    public static final Creator<Datum> CREATOR = new Creator<Datum>() {\n        public Datum createFromParcel(Parcel in) {\n            return new Datum(in);\n        }\n\n        public Datum[] newArray(int size) {\n            return new Datum[size];\n        }\n    };\n\n    @Override\n    public boolean equals(Object o) {\n        return ((Datum) o).id == this.id;\n    }\n}"
  },
  {
    "path": "deckviewsample/src/main/java/com/appeaser/deckviewsample/DeckViewSampleActivity.java",
    "content": "package com.appeaser.deckviewsample;\n\nimport android.app.Activity;\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.graphics.Color;\nimport android.graphics.drawable.Drawable;\nimport android.os.Bundle;\nimport android.view.Menu;\nimport android.view.MenuItem;\nimport android.widget.Toast;\n\nimport com.appeaser.deckview.views.DeckChildView;\nimport com.appeaser.deckview.views.DeckView;\nimport com.squareup.picasso.Picasso;\nimport com.squareup.picasso.Target;\n\nimport java.lang.ref.WeakReference;\nimport java.util.ArrayList;\nimport java.util.Random;\n\n/**\n * Basic sample for DeckView.\n * Images are downloaded and cached using\n * Picasso \"http://square.github.io/picasso/\".\n * DeckView is *very* young & can only\n * afford basic functionality.\n */\npublic class DeckViewSampleActivity extends Activity {\n\n    // View that stacks its children like a deck of cards\n    DeckView<Datum> mDeckView;\n\n    Drawable mDefaultHeaderIcon;\n    ArrayList<Datum> mEntries;\n\n    // Placeholder for when the image is being downloaded\n    Bitmap mDefaultThumbnail;\n\n    // Retain position on configuration change\n    // imageSize to pass to http://lorempixel.com\n    int scrollToChildIndex = -1, imageSize = 500;\n\n    // SavedInstance bundle keys\n    final String CURRENT_SCROLL = \"current.scroll\", CURRENT_LIST = \"current.list\";\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_deck_view_sample);\n\n        mDeckView = (DeckView) findViewById(R.id.deckview);\n        mDefaultThumbnail = BitmapFactory.decodeResource(getResources(),\n                R.drawable.default_thumbnail);\n        mDefaultHeaderIcon = getResources().getDrawable(R.drawable.default_header_icon);\n\n        if (savedInstanceState != null) {\n            if (savedInstanceState.containsKey(CURRENT_LIST)) {\n                mEntries = savedInstanceState.getParcelableArrayList(CURRENT_LIST);\n            }\n\n            if (savedInstanceState.containsKey(CURRENT_SCROLL)) {\n                scrollToChildIndex = savedInstanceState.getInt(CURRENT_SCROLL);\n            }\n        }\n\n        if (mEntries == null) {\n            mEntries = new ArrayList<>();\n\n            for (int i = 1; i < 100; i++) {\n                Datum datum = new Datum();\n                datum.id = generateUniqueKey();\n                datum.link = \"http://lorempixel.com/\" + imageSize + \"/\" + imageSize\n                        + \"/sports/\" + \"ID \" + datum.id + \"/\";\n                datum.headerTitle = \"Image ID \" + datum.id;\n                mEntries.add(datum);\n            }\n        }\n\n        // Callback implementation\n        DeckView.Callback<Datum> deckViewCallback = new DeckView.Callback<Datum>() {\n            @Override\n            public ArrayList<Datum> getData() {\n                return mEntries;\n            }\n\n            @Override\n            public void loadViewData(WeakReference<DeckChildView<Datum>> dcv, Datum item) {\n                loadViewDataInternal(item, dcv);\n            }\n\n            @Override\n            public void unloadViewData(Datum item) {\n                Picasso.with(DeckViewSampleActivity.this).cancelRequest(item.target);\n            }\n\n            @Override\n            public void onViewDismissed(Datum item) {\n                mEntries.remove(item);\n                mDeckView.notifyDataSetChanged();\n            }\n\n            @Override\n            public void onItemClick(Datum item) {\n                Toast.makeText(DeckViewSampleActivity.this,\n                        \"Item with title: '\" + item.headerTitle + \"' clicked\",\n                        Toast.LENGTH_SHORT).show();\n            }\n\n            @Override\n            public void onNoViewsToDeck() {\n                Toast.makeText(DeckViewSampleActivity.this,\n                        \"No views to show\",\n                        Toast.LENGTH_SHORT).show();\n            }\n        };\n\n        mDeckView.initialize(deckViewCallback);\n\n        if (scrollToChildIndex != -1) {\n            mDeckView.post(new Runnable() {\n                @Override\n                public void run() {\n                    // Restore scroll position\n                    mDeckView.scrollToChild(scrollToChildIndex);\n                }\n            });\n        }\n    }\n\n    void loadViewDataInternal(final Datum datum,\n                              final WeakReference<DeckChildView<Datum>> weakView) {\n        // datum.target can be null\n        Picasso.with(DeckViewSampleActivity.this).cancelRequest(datum.target);\n\n        datum.target = new Target() {\n            @Override\n            public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {\n                // Pass loaded Bitmap to view\n                if (weakView.get() != null) {\n                    weakView.get().onDataLoaded(datum, bitmap,\n                            mDefaultHeaderIcon, datum.headerTitle, Color.DKGRAY);\n                }\n            }\n\n            @Override\n            public void onBitmapFailed(Drawable errorDrawable) {\n                // Loading failed. Pass default thumbnail instead\n                if (weakView.get() != null) {\n                    weakView.get().onDataLoaded(datum, mDefaultThumbnail,\n                            mDefaultHeaderIcon, datum.headerTitle + \" Failed\", Color.DKGRAY);\n                }\n            }\n\n            @Override\n            public void onPrepareLoad(Drawable placeHolderDrawable) {\n                // Pass the default thumbnail for now. It will\n                // be replaced once the target Bitmap has been loaded\n                if (weakView.get() != null) {\n                    weakView.get().onDataLoaded(datum, mDefaultThumbnail,\n                            mDefaultHeaderIcon, \"Loading...\", Color.DKGRAY);\n                }\n            }\n        };\n\n        // Begin loading\n        Picasso.with(DeckViewSampleActivity.this).load(datum.link).into(datum.target);\n    }\n\n\n    @Override\n    public boolean onCreateOptionsMenu(Menu menu) {\n        // Inflate the menu; this adds items to the action bar if it is present.\n        getMenuInflater().inflate(R.menu.menu_deck_view_sample, menu);\n        return true;\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(MenuItem item) {\n        // Handle action bar item clicks here. The action bar will\n        // automatically handle clicks on the Home/Up button, so long\n        // as you specify a parent activity in AndroidManifest.xml.\n        int id = item.getItemId();\n\n        // Add a new item to the end of the list\n        if (id == R.id.action_add) {\n            Datum datum = new Datum();\n            datum.id = generateUniqueKey();\n            datum.headerTitle = \"(New) Image ID \" + datum.id;\n            datum.link = \"http://lorempixel.com/\" + imageSize + \"/\" + imageSize\n                    + \"/sports/\" + \"ID \" + datum.id + \"/\";\n            mEntries.add(datum);\n            mDeckView.notifyDataSetChanged();\n            return true;\n        } else if (id == R.id.action_add_multiple) {\n            // Add multiple items (between 5 & 10 items)\n            // at random indices\n            Random rand = new Random();\n\n            // adding between 5 and 10 items\n            int numberOfItemsToAdd = rand.nextInt(6) + 5;\n\n            for (int i = 0; i < numberOfItemsToAdd; i++) {\n                int atIndex = mEntries.size() > 0 ?\n                        rand.nextInt(mEntries.size()) : 0;\n\n                Datum datum = new Datum();\n                datum.id = generateUniqueKey();\n                datum.link = \"http://lorempixel.com/\" + imageSize + \"/\" + imageSize\n                        + \"/sports/\" + \"ID \" + datum.id + \"/\";\n                datum.headerTitle = \"(New) Image ID \" + datum.id;\n                mEntries.add(atIndex, datum);\n            }\n\n            mDeckView.notifyDataSetChanged();\n            return true;\n        }\n\n        return super.onOptionsItemSelected(item);\n    }\n\n    @Override\n    protected void onSaveInstanceState(Bundle outState) {\n        // Save current scroll and the list\n        int currentChildIndex = mDeckView.getCurrentChildIndex();\n        outState.putInt(CURRENT_SCROLL, currentChildIndex);\n        outState.putParcelableArrayList(CURRENT_LIST, mEntries);\n\n        super.onSaveInstanceState(outState);\n    }\n\n    // Generates a key that will remain unique\n    // during the application's lifecycle\n    private static int generateUniqueKey() {\n        return ++KEY;\n    }\n\n    private static int KEY = 0;\n}\n"
  },
  {
    "path": "deckviewsample/src/main/res/drawable-v21/box.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n/* //device/apps/common/res/drawable/box.xml\n**\n** Copyright 2007, The Android Open Source Project\n**\n** Licensed under the Apache License, Version 2.0 (the \"License\"); \n** you may not use this file except in compliance with the License. \n** You may obtain a copy of the License at \n**\n**     http://www.apache.org/licenses/LICENSE-2.0 \n**\n** Unless required by applicable law or agreed to in writing, software \n** distributed under the License is distributed on an \"AS IS\" BASIS, \n** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. \n** See the License for the specific language governing permissions and \n** limitations under the License.\n*/\n-->\n\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <solid android:color=\"#00000000\" />\n    <stroke\n        android:width=\"1dp\"\n        color=\"#ff000000\" />\n    <padding\n        android:left=\"1dp\"\n        android:top=\"1dp\"\n        android:right=\"1dp\"\n        android:bottom=\"1dp\" />\n</shape>\n"
  },
  {
    "path": "deckviewsample/src/main/res/layout/activity_deck_view_sample.xml",
    "content": "<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    tools:context=\".DeckViewSampleActivity\">\n\n    <com.appeaser.deckview.views.DeckView\n        android:id=\"@+id/deckview\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n</FrameLayout>"
  },
  {
    "path": "deckviewsample/src/main/res/menu/menu_deck_view_sample.xml",
    "content": "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    tools:context=\".DeckViewSampleActivity\">\n    <item\n        android:id=\"@+id/action_add\"\n        android:title=\"Add\"\n        android:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/action_add_multiple\"\n        android:title=\"Add multiple\"\n        android:showAsAction=\"never\" />\n</menu>\n"
  },
  {
    "path": "deckviewsample/src/main/res/values/dimens.xml",
    "content": "<resources>\n    <!-- Default screen margins, per the Android Design guidelines. -->\n    <dimen name=\"activity_horizontal_margin\">16dp</dimen>\n    <dimen name=\"activity_vertical_margin\">16dp</dimen>\n</resources>\n"
  },
  {
    "path": "deckviewsample/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">DeckViewSample</string>\n\n    <string name=\"hello_world\">Hello world!</string>\n    <string name=\"action_settings\">Settings</string>\n</resources>\n"
  },
  {
    "path": "deckviewsample/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"android:Theme.Holo.Light.DarkActionBar\">\n        <!-- Customize your theme here. -->\n    </style>\n\n</resources>\n"
  },
  {
    "path": "deckviewsample/src/main/res/values-v21/styles.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <style name=\"AppTheme\" parent=\"android:Theme.Material.Light\"></style>\n</resources>\n"
  },
  {
    "path": "deckviewsample/src/main/res/values-w820dp/dimens.xml",
    "content": "<resources>\n    <!-- Example customization of dimensions originally defined in res/values/dimens.xml\n         (such as screen margins) for screens with more than 820dp of available width. This\n         would include 7\" and 10\" devices in landscape (~960dp and ~1280dp respectively). -->\n    <dimen name=\"activity_horizontal_margin\">64dp</dimen>\n</resources>\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "#Wed Apr 10 15:27:10 PDT 2013\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-2.2.1-all.zip\n"
  },
  {
    "path": "gradle.properties",
    "content": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\n# Default value: -Xmx10248m -XX:MaxPermSize=256m\n# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8\n\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn ( ) {\n    echo \"$*\"\n}\n\ndie ( ) {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\nesac\n\n# For Cygwin, ensure paths are in UNIX format before anything is touched.\nif $cygwin ; then\n    [ -n \"$JAVA_HOME\" ] && JAVA_HOME=`cygpath --unix \"$JAVA_HOME\"`\nfi\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >&-\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >&-\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules\nfunction splitJvmOpts() {\n    JVM_OPTS=(\"$@\")\n}\neval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\nJVM_OPTS[${#JVM_OPTS[*]}]=\"-Dorg.gradle.appname=$APP_BASE_NAME\"\n\nexec \"$JAVACMD\" \"${JVM_OPTS[@]}\" -classpath \"$CLASSPATH\" org.gradle.wrapper.GradleWrapperMain \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif \"%ERRORLEVEL%\" == \"0\" goto init\n\necho.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto init\n\necho.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:init\n@rem Get command-line arguments, handling Windowz variants\n\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\nif \"%@eval[2+2]\" == \"4\" goto 4NT_args\n\n:win9xME_args\n@rem Slurp the command line arguments.\nset CMD_LINE_ARGS=\nset _SKIP=2\n\n:win9xME_args_slurp\nif \"x%~1\" == \"x\" goto execute\n\nset CMD_LINE_ARGS=%*\ngoto execute\n\n:4NT_args\n@rem Get arguments from the 4NT Shell from JP Software\nset CMD_LINE_ARGS=%$\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\n\n:end\n@rem End local scope for the variables with windows NT shell\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\nexit /b 1\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "settings.gradle",
    "content": "include ':app', ':deckview', ':deckviewsample'\n"
  }
]