[
  {
    "path": ".gitignore",
    "content": "*.iml\n.gradle\n/local.properties\n/.idea/workspace.xml\n/.idea/libraries\n.DS_Store\n/build\n/captures\n.externalNativeBuild\n"
  },
  {
    "path": "README.md",
    "content": "# Deprecated, Not maintain anymore.\n\n# Style Live Wallpaper\n\nA live wallpaper project for Android\n====================\n<a href=\"https://play.google.com/store/apps/details?id=com.kinglloy.album\" target=\"_blank\">\n<img src=\"https://play.google.com/intl/en_us/badges/images/generic/en-play-badge.png\" alt=\"Get it on Google Play\" height=\"90\"/></a>\n\nFor developer\n=========\n\nStyle offers a sdk that allows you to build your own live wallpaper.\n\n**[Develop Doc](https://github.com/jinkg/style-develop-sdk)**&nbsp;&nbsp;•&nbsp;&nbsp;**[For more examples](https://github.com/jinkg/style-sdk)**\n\n![bezier](https://github.com/jinkg/Screenshots/blob/master/Style/bezier.gif)\n![boids](https://github.com/jinkg/Screenshots/blob/master/Style/boids.gif)\n![botz](https://github.com/jinkg/Screenshots/blob/master/Style/botz.gif)\n![flower](https://github.com/jinkg/Screenshots/blob/master/Style/flower.gif)\n![rainbow](https://github.com/jinkg/Screenshots/blob/master/Style/rainbow.gif)\n![shark](https://github.com/jinkg/Screenshots/blob/master/Style/shark.gif)\n![stride](https://github.com/jinkg/Screenshots/blob/master/Style/stride.gif)\n![yinyang](https://github.com/jinkg/Screenshots/blob/master/Style/yinyang.gif)\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:2.3.1'\n        classpath 'com.google.gms:google-services:3.0.0'\n        classpath \"org.jetbrains.kotlin:kotlin-gradle-plugin:$KOTLIN_VERSION\"\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\ntask clean(type: Delete) {\n    delete rootProject.buildDir\n}\n"
  },
  {
    "path": "buildApk.bat",
    "content": "@echo off\necho Start Build Apk\n\ncall gradlew clean\ncall gradlew assembleProductionRelease -Dchannel=kinglloy\ncall gradlew assembleProductionRelease -Dchannel=yingyongbao\ncall gradlew assembleProductionRelease -Dchannel=360 -Dapp_name=Style艺术壁纸\ncall gradlew assembleProductionRelease -Dchannel=vivo\ncall gradlew assembleProductionRelease -Dchannel=flyme\ncall gradlew assembleProductionRelease -Dchannel=wandoujia\ncall gradlew assembleProductionRelease -Dchannel=baidu -Dapp_name=Style艺术壁纸\ncall gradlew assembleProductionRelease -Dchannel=google\ncall gradlew assembleProductionRelease -Dchannel=huawei -Dapp_name=Style艺术壁纸\n\necho Build Apk Complete"
  },
  {
    "path": "buildApk.sh",
    "content": "#!/usr/bin/env bash\necho \"Start Build Apk\"\n\n./gradlew clean\n./gradlew assembleProductionRelease -Dchannel=kinglloy\n./gradlew assembleProductionRelease -Dchannel=yingyongbao\n./gradlew assembleProductionRelease -Dchannel=360 -Dapp_name=Style艺术壁纸\n./gradlew assembleProductionRelease -Dchannel=vivo\n./gradlew assembleProductionRelease -Dchannel=flyme\n./gradlew assembleProductionRelease -Dchannel=wandoujia\n./gradlew assembleProductionRelease -Dchannel=baidu -Dapp_name=Style艺术壁纸\n./gradlew assembleProductionRelease -Dchannel=google\n./gradlew assembleProductionRelease -Dchannel=huawei -Dapp_name=Style艺术壁纸\n\necho \"Build Apk Complete\"\n"
  },
  {
    "path": "data/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "data/CMakeLists.txt",
    "content": "# Sets the minimum version of CMake required to build the native\n# library. You should either keep the default value or only pass a\n# value of 3.4.0 or lower.\n\ncmake_minimum_required(VERSION 3.4.1)\n\n# Creates and names a library, sets it as either STATIC\n# or SHARED, and provides the relative paths to its source code.\n# You can define multiple libraries, and CMake builds it for you.\n# Gradle automatically packages shared libraries with your APK.\n\nadd_library( # Sets the name of the library.\n             facet_id-lib\n\n             # Sets the library as a shared library.\n             SHARED\n\n             # Provides a relative path to your source file(s).\n             # Associated headers in the same location as their source\n             # file are automatically included.\n             src/main/cpp/facet_id-lib.cpp )\n\n# Searches for a specified prebuilt library and stores the path as a\n# variable. Because system libraries are included in the search path by\n# default, you only need to specify the name of the public NDK library\n# you want to add. CMake verifies that the library exists before\n# completing its build.\n\nfind_library( # Sets the name of the path variable.\n              log-lib\n\n              # Specifies the name of the NDK library that\n              # you want CMake to locate.\n              log )\n\n# Specifies libraries CMake should link to your target library. You\n# can link multiple libraries, such as libraries you define in the\n# build script, prebuilt third-party libraries, or system libraries.\n\ntarget_link_libraries( # Specifies the target library.\n                       facet_id-lib\n\n                       # Links the target library to the log library\n                       # included in the NDK.\n                       ${log-lib} )\n"
  },
  {
    "path": "data/build.gradle",
    "content": "buildscript {\n    repositories {\n        mavenCentral()\n    }\n    dependencies {\n        classpath 'me.tatarka:gradle-retrolambda:3.2.3'\n    }\n}\n\napply plugin: 'com.android.library'\napply plugin: 'me.tatarka.retrolambda'\napply plugin: 'kotlin-android'\n\nProperties properties = new Properties()\nproperties.load(project.rootProject.file('switch.properties').newDataInputStream())\n\ndef ENABLE_EXTERNAL_LOG = properties.get('ENABLE_EXTERNAL_LOG')\ndef LOG_ENABLE = properties.get('LOG_ENABLE')\n\ndef CHANNEL = System.getProperty(\"channel\", \"default\")\n\nandroid {\n    compileSdkVersion COMPILE_SDK_VERSION as int\n    buildToolsVersion BUILD_TOOLS_VERSION as String\n\n    defaultConfig {\n        minSdkVersion MIN_SDK_VERSION as int\n        targetSdkVersion TARGET_SDK_VERSION as int\n        versionCode APP_VERSION_CODE as int\n        versionName APP_VERSION_NAME as String\n\n        testInstrumentationRunner \"android.support.test.runner.AndroidJUnitRunner\"\n        externalNativeBuild {\n            cmake {\n                cppFlags \"\"\n            }\n        }\n        buildConfigField(\"boolean\", \"LOG_ENABLE\", \"${LOG_ENABLE}\")\n        buildConfigField(\"String\", \"CHANNEL\", \"\\\"${CHANNEL}\\\"\")\n    }\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n    publishNonDefault true\n    productFlavors {\n        demo {\n            buildConfigField(\"boolean\", \"DEMO_MODE\", \"true\")\n            buildConfigField(\"boolean\", \"ENABLE_EXTERNAL_LOG\", \"true\")\n            buildConfigField(\"String\", \"SERVER_WALLPAPER_ENDPOINT\", \"\\\"${DEMO_API_WALLPAPER_ENDPOINT}\\\"\")\n        }\n        production {\n            buildConfigField(\"boolean\", \"DEMO_MODE\", \"false\")\n            buildConfigField(\"boolean\", \"ENABLE_EXTERNAL_LOG\", \"${ENABLE_EXTERNAL_LOG}\")\n            buildConfigField(\"String\", \"SERVER_WALLPAPER_ENDPOINT\", \"\\\"${PRODUCTION_API_WALLPAPER_ENDPOINT}\\\"\")\n        }\n    }\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_1_8\n        targetCompatibility JavaVersion.VERSION_1_8\n    }\n    externalNativeBuild {\n        cmake {\n            path \"CMakeLists.txt\"\n        }\n    }\n}\n\ndependencies {\n    compile fileTree(dir: 'libs', include: ['*.jar'])\n    compile \"com.android.support:appcompat-v7:${SUPPORT_LIBRARY_VERSION}\"\n    compile \"com.android.support:exifinterface:${SUPPORT_LIBRARY_VERSION}\"\n    compile \"org.jetbrains.kotlin:kotlin-stdlib:${KOTLIN_VERSION}\"\n    compile \"org.jetbrains.anko:anko-common:${ANKO_VESION}\"\n    compile \"org.jetbrains.anko:anko-sqlite:${ANKO_VESION}\"\n    compile \"com.google.code.gson:gson:${GSON_VERSION}\"\n    compile \"com.squareup.okhttp3:okhttp:${OK_HTTP_VERSION}\"\n    compile project(':domain')\n\n    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {\n        exclude group: 'com.android.support', module: 'support-annotations'\n    })\n    testCompile 'junit:junit:4.12'\n\n}\n"
  },
  {
    "path": "data/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /Users/jinyalin/Library/Android/sdk/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific liked 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\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you liked the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n"
  },
  {
    "path": "data/src/androidTest/java/com/yalin/data/ExampleInstrumentedTest.java",
    "content": "package com.yalin.data;\n\nimport android.content.Context;\nimport android.support.test.InstrumentationRegistry;\nimport android.support.test.runner.AndroidJUnit4;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.junit.Assert.*;\n\n/**\n * Instrumentation test, which will execute on an Android device.\n *\n * @see <a href=\"http://d.android.com/tools/testing\">Testing documentation</a>\n */\n@RunWith(AndroidJUnit4.class)\npublic class ExampleInstrumentedTest {\n    @Test\n    public void useAppContext() throws Exception {\n        // Context of the app under test.\n        Context appContext = InstrumentationRegistry.getTargetContext();\n\n        assertEquals(\"com.yalin.data.test\", appContext.getPackageName());\n    }\n}\n"
  },
  {
    "path": "data/src/main/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.yalin.style.data\">\n\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n    <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />\n    <uses-permission android:name=\"android.permission.WRITE_SYNC_SETTINGS\" />\n    <uses-permission android:name=\"android.permission.READ_SYNC_SETTINGS\" />\n    <uses-permission android:name=\"android.permission.READ_SYNC_STATS\" />\n    <uses-permission android:name=\"android.permission.AUTHENTICATE_ACCOUNTS\" />\n\n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />\n\n    <application\n        android:allowBackup=\"true\"\n        android:supportsRtl=\"true\">\n\n        <service\n            android:name=\".repository.datasource.sync.SyncService\"\n            android:exported=\"false\">\n            <intent-filter>\n                <action android:name=\"android.content.SyncAdapter\" />\n            </intent-filter>\n            <meta-data\n                android:name=\"android.content.SyncAdapter\"\n                android:resource=\"@xml/syncadapter\" />\n        </service>\n\n        <service\n            android:name=\".repository.datasource.sync.account.AuthenticatorService\"\n            android:exported=\"false\">\n            <intent-filter>\n                <action android:name=\"android.accounts.AccountAuthenticator\" />\n            </intent-filter>\n\n            <meta-data\n                android:name=\"android.accounts.AccountAuthenticator\"\n                android:resource=\"@xml/authenticator\" />\n        </service>\n\n        <service\n            android:name=\".repository.datasource.sync.gallery.GalleryScheduleService\"\n            android:exported=\"false\">\n            <intent-filter>\n                <action android:name=\"com.yalin.style.ACTION_START_UP\" />\n                <action android:name=\"com.yalin.style.ACTION_SHUT_DOWN\" />\n                <action android:name=\"com.yalin.style.ACTION_SCHEDULE\" />\n                <action android:name=\"com.yalin.style.ACTION_SET_INTERVAL\" />\n            </intent-filter>\n\n        </service>\n\n        <provider\n            android:name=\".repository.datasource.provider.StyleProvider\"\n            android:authorities=\"com.yalin.style\"\n            android:exported=\"false\"\n            android:syncable=\"true\" />\n\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "data/src/main/cpp/facet_id-lib.cpp",
    "content": "#include <jni.h>\n#include <string>\n#include <android/log.h>\n\n#define LOG_TAG (\"facet_id_check\")\n#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)\n\nint LOG_ENABLE = 0;\nconst char *LOG = \"LOG\";\n\nint check_facet_id(JNIEnv *, jstring);\n\njstring get_facet_id(JNIEnv *, jobject, jint);\n\nvoid printLog(const char *);\n\nextern \"C\"\njboolean\nJava_com_yalin_style_data_utils_FacetIdUtil_checkCurrentFacetId__Landroid_content_Context_2I(\n        JNIEnv *env, jclass, jobject context, jint uId) {\n    jstring facet_id = get_facet_id(env, context, uId);;\n    int check_result = check_facet_id(env, facet_id);\n    bool result = check_result == 0;\n    return (jboolean) result;\n}\n\nextern \"C\"\njstring\nJava_com_yalin_style_data_utils_FacetIdUtil_getFacetId__Landroid_content_Context_2I(\n        JNIEnv *env, jclass, jobject context, jint uId) {\n    return get_facet_id(env, context, uId);\n}\n\njstring get_facet_id(JNIEnv *env, jobject context, jint uId) {\n\n    jclass contextClazz = env->FindClass(\"android/content/Context\");\n    jmethodID getPackageManagerMethodId = env->GetMethodID(contextClazz, \"getPackageManager\",\n                                                           \"()Landroid/content/pm/PackageManager;\");\n    jobject packageManager = env->CallObjectMethod(context, getPackageManagerMethodId);\n\n    printLog(\"PackageManager obtained.\");\n\n    jclass packageManagerClazz = env->FindClass(\"android/content/pm/PackageManager\");\n    jmethodID getPackageForUidMethodId = env->GetMethodID(packageManagerClazz, \"getPackagesForUid\",\n                                                          \"(I)[Ljava/lang/String;\");\n\n    jobjectArray packageNames = (jobjectArray) (jarray) env->CallObjectMethod(packageManager,\n                                                                              getPackageForUidMethodId,\n                                                                              uId);\n    jstring packageName = (jstring) env->GetObjectArrayElement(packageNames, 0);\n\n    printLog(\"packageName obtained.\");\n\n    jmethodID getPackageInfoMethodId = env->GetMethodID(packageManagerClazz, \"getPackageInfo\",\n                                                        \"(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;\");\n    jfieldID flag = env->GetStaticFieldID(packageManagerClazz, \"GET_SIGNATURES\", \"I\");\n\n    jint signaturesFlag = env->GetStaticIntField(packageManagerClazz, flag);\n\n    jobject packageInfo = env->CallObjectMethod(packageManager, getPackageInfoMethodId,\n                                                packageName, signaturesFlag);\n\n    printLog(\"packageInfo obtained.\");\n\n    jclass packageInfoClazz = env->FindClass(\"android/content/pm/PackageInfo\");\n\n    jfieldID signatureFieldId = env->GetFieldID(packageInfoClazz, \"signatures\",\n                                                \"[Landroid/content/pm/Signature;\");\n    jobjectArray signatures = (jobjectArray) env->GetObjectField(packageInfo, signatureFieldId);\n\n    printLog(\"signatures obtained.\");\n\n    jclass signatureClazz = env->FindClass(\"android/content/pm/Signature\");\n    jmethodID toByteArrayMethodId = env->GetMethodID(signatureClazz, \"toByteArray\", \"()[B\");\n\n    jbyteArray cert = (jbyteArray) env->CallObjectMethod(env->GetObjectArrayElement(signatures, 0),\n                                                         toByteArrayMethodId);\n\n    printLog(\"cert obtained.\");\n\n    jclass inputStreamClazz = env->FindClass(\"java/io/ByteArrayInputStream\");\n    jmethodID inputStreamConstructorMethodId = env->GetMethodID(inputStreamClazz, \"<init>\",\n                                                                \"([B)V\");\n    jobject inputStream = env->NewObject(inputStreamClazz, inputStreamConstructorMethodId, cert);\n\n    printLog(\"inputStream obtained.\");\n\n    jclass certificateFactoryClazz = env->FindClass(\"java/security/cert/CertificateFactory\");\n    jmethodID certificateFactoryGetInstanceMethodId = env->GetStaticMethodID(\n            certificateFactoryClazz,\n            \"getInstance\",\n            \"(Ljava/lang/String;)Ljava/security/cert/CertificateFactory;\");\n    jstring x509String = env->NewStringUTF(\"X509\");\n    jobject certificateFactory = env->CallStaticObjectMethod(certificateFactoryClazz,\n                                                             certificateFactoryGetInstanceMethodId,\n                                                             x509String);\n\n    printLog(\"certificateFactory obtained.\");\n\n    jmethodID generateCertificateMethodId = env->GetMethodID(certificateFactoryClazz,\n                                                             \"generateCertificate\",\n                                                             \"(Ljava/io/InputStream;)Ljava/security/cert/Certificate;\");\n    jclass x509CertificateClazz = env->FindClass(\"java/security/cert/X509Certificate\");\n\n    jobject x509Certificate = env->CallObjectMethod(certificateFactory, generateCertificateMethodId,\n                                                    inputStream);\n\n    printLog(\"x509Certificate obtained.\");\n\n    jclass messageDigestClazz = env->FindClass(\"java/security/MessageDigest\");\n    jmethodID messageDigestInstanceMethodId = env->GetStaticMethodID(messageDigestClazz,\n                                                                     \"getInstance\",\n                                                                     \"(Ljava/lang/String;)Ljava/security/MessageDigest;\");\n\n    jstring sha1 = env->NewStringUTF(\"SHA1\");\n    jobject messageDigest = env->CallStaticObjectMethod(messageDigestClazz,\n                                                        messageDigestInstanceMethodId, sha1);\n\n    printLog(\"messageDigest obtained.\");\n\n    jmethodID certificateEncodeMethodId = env->GetMethodID(x509CertificateClazz, \"getEncoded\",\n                                                           \"()[B\");\n    jbyteArray certEncode = (jbyteArray) env->CallObjectMethod(x509Certificate,\n                                                               certificateEncodeMethodId);\n\n    printLog(\"certEncode obtained.\");\n\n    jmethodID messageDigestMethodId = env->GetMethodID(messageDigestClazz, \"digest\", \"([B)[B\");\n    jbyteArray digestArray = (jbyteArray) env->CallObjectMethod(messageDigest,\n                                                                messageDigestMethodId,\n                                                                certEncode);\n\n    printLog(\"digestArray obtained.\");\n\n    jclass base64Clazz = env->FindClass(\"android/util/Base64\");\n    jmethodID encodeToStringMethodId = env->GetStaticMethodID(base64Clazz, \"encodeToString\",\n                                                              \"([BI)Ljava/lang/String;\");\n\n    jfieldID base64FlagFiledId = env->GetStaticFieldID(base64Clazz, \"DEFAULT\",\n                                                       \"I\");\n    jint base64DefaultFlag = env->GetStaticIntField(base64Clazz, base64FlagFiledId);\n\n    jstring result = (jstring) env->CallStaticObjectMethod(base64Clazz, encodeToStringMethodId,\n                                                           digestArray, base64DefaultFlag);\n\n    printLog(\"result obtained.\");\n\n    env->DeleteLocalRef(packageManager);\n    env->DeleteLocalRef(packageNames);\n    env->DeleteLocalRef(packageInfo);\n    env->DeleteLocalRef(signatures);\n    env->DeleteLocalRef(cert);\n    env->DeleteLocalRef(inputStream);\n    env->DeleteLocalRef(certificateFactory);\n    env->DeleteLocalRef(x509Certificate);\n    env->DeleteLocalRef(messageDigest);\n    env->DeleteLocalRef(certEncode);\n    env->DeleteLocalRef(digestArray);\n\n    return result;\n}\n\nint check_facet_id(JNIEnv *env, jstring facet_id) {\n    // modify to your app's facet wallpaperId\n    jstring valid_facet_id_string = env->NewStringUTF(\"unkown\");\n\n    const char *valid_facet_id = env->GetStringUTFChars(valid_facet_id_string, 0);\n    char *target_facet_id = (char *) env->GetStringUTFChars(facet_id, 0);\n\n//    target_facet_id[strcspn(target_facet_id, \"\\r\\n\")] = '\\0';\n\n    printLog(target_facet_id);\n    printLog(valid_facet_id);\n\n    int result = strcmp(target_facet_id, valid_facet_id);\n\n    char tmp[128];\n    sprintf(tmp, \"check complete: result=%d\", result);\n    printLog(tmp);\n\n    env->DeleteLocalRef(valid_facet_id_string);\n    return result;\n}\n\nvoid printLog(const char *str) {\n    if (LOG_ENABLE == 0) {\n        return;\n    }\n\n    LOGI(str, LOG);\n}"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/SyncConfig.java",
    "content": "package com.yalin.style.data;\n\nimport java.util.concurrent.TimeUnit;\n\n/**\n * YaLin 2017/1/3.\n */\n\npublic class SyncConfig {\n\n  public static final int DEFAULT_DOWNLOAD_TIMEOUT = 120; // in seconds\n  public static final int DEFAULT_READ_TIMEOUT = 30; // in seconds\n  public static final int DEFAULT_CONNECT_TIMEOUT = 15; // in seconds\n\n\n  public static final long DEBUG_AUTO_SYNC_INTERVAL_LONG =\n      TimeUnit.MILLISECONDS.convert(5L, TimeUnit.MINUTES);\n\n  public static final long AUTO_SYNC_INTERVAL_LONG =\n      TimeUnit.MILLISECONDS.convert(16L, TimeUnit.HOURS);\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/cache/AdvanceWallpaperCache.kt",
    "content": "package com.yalin.style.data.cache\n\nimport com.yalin.style.data.entity.AdvanceWallpaperEntity\n\n/**\n * @author jinyalin\n * @since 2017/8/4.\n */\ninterface AdvanceWallpaperCache {\n    fun put(wallpapers: List<AdvanceWallpaperEntity>)\n\n    fun getSelectedWallpaper(): AdvanceWallpaperEntity?\n\n    fun selectWallpaper(wallpaperId: String)\n\n    fun getWallpapers(): List<AdvanceWallpaperEntity>\n\n    fun getWallpaper(wallpaperId: String): AdvanceWallpaperEntity?\n\n    fun readAd(wallpaperId: String)\n\n    fun evictAll()\n\n    fun isCached(wallpaperId: String): Boolean\n\n    fun isDirty(): Boolean\n\n    fun makeDirty()\n}"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/cache/AdvanceWallpaperCacheImpl.kt",
    "content": "package com.yalin.style.data.cache\n\nimport android.text.TextUtils\nimport com.yalin.style.data.entity.AdvanceWallpaperEntity\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n/**\n * @author jinyalin\n * @since 2017/8/4.\n */\n@Singleton\nclass AdvanceWallpaperCacheImpl @Inject constructor() : AdvanceWallpaperCache {\n    private var wallpapers: List<AdvanceWallpaperEntity>? = null\n\n    @Synchronized override fun put(wallpapers: List<AdvanceWallpaperEntity>) {\n        this.wallpapers = wallpapers\n    }\n\n    override fun getSelectedWallpaper(): AdvanceWallpaperEntity? {\n        if (isDirty()) {\n            return null\n        }\n        return wallpapers!!.firstOrNull { it.isSelected }\n    }\n\n    override fun selectWallpaper(wallpaperId: String) {\n        if (isDirty()) {\n            throw IllegalStateException(\"Cache is dirty.\")\n        }\n        for (wallpaper in wallpapers!!) {\n            wallpaper.isSelected = TextUtils.equals(wallpaperId, wallpaper.wallpaperId)\n        }\n    }\n\n    override fun getWallpapers(): List<AdvanceWallpaperEntity> {\n        if (isDirty()) {\n            throw IllegalStateException(\"Cache is dirty.\")\n        }\n        return ArrayList(wallpapers!!)\n    }\n\n    override fun getWallpaper(wallpaperId: String): AdvanceWallpaperEntity? {\n        if (!isCached(wallpaperId)) {\n            throw IllegalStateException(\"WallpaperId $wallpaperId is not cached.\")\n        }\n        return wallpapers!!.firstOrNull { TextUtils.equals(wallpaperId, it.wallpaperId) }\n    }\n\n    override fun readAd(wallpaperId: String) {\n        if (isCached(wallpaperId)) {\n            getWallpaper(wallpaperId)?.needAd = false\n        }\n    }\n\n    @Synchronized override fun evictAll() {\n        wallpapers = null\n    }\n\n    override fun isCached(wallpaperId: String): Boolean {\n        if (isDirty()) {\n            return false\n        }\n        return wallpapers!!.any { TextUtils.equals(wallpaperId, it.wallpaperId) }\n    }\n\n    override fun isDirty(): Boolean {\n        return wallpapers == null || wallpapers!!.isEmpty()\n    }\n\n    override fun makeDirty() {\n        evictAll()\n    }\n\n}"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/cache/GalleryWallpaperCache.kt",
    "content": "package com.yalin.style.data.cache\n\nimport com.yalin.style.data.entity.GalleryWallpaperEntity\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n\n/**\n * @author jinyalin\n * @since 2017/5/26.\n */\n@Singleton\nclass GalleryWallpaperCache @Inject constructor() {\n    private var wallpaperEntities: List<GalleryWallpaperEntity>? = null\n\n    fun put(wallpaperEntities: List<GalleryWallpaperEntity>) {\n        this.wallpaperEntities = wallpaperEntities\n    }\n\n    fun get() = wallpaperEntities\n\n    fun isCached() = wallpaperEntities != null && wallpaperEntities!!.isNotEmpty()\n\n    fun isDirty() = wallpaperEntities == null || wallpaperEntities!!.isEmpty()\n\n    fun evictAll() {\n        wallpaperEntities = null\n    }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/cache/SourcesCache.kt",
    "content": "package com.yalin.style.data.cache\n\nimport android.content.Context\nimport com.yalin.style.data.entity.SourceEntity\nimport io.reactivex.Observable\n\n/**\n * @author jinyalin\n * @since 2017/5/22.\n */\ninterface SourcesCache {\n    fun getSources(ctx: Context): Observable<List<SourceEntity>>\n\n    fun selectSource(selectSourceId: Int, tempSelect: Boolean): Boolean\n\n    fun getUsedSourceId(): Int\n}"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/cache/SourcesCacheImpl.kt",
    "content": "package com.yalin.style.data.cache\n\nimport android.content.Context\nimport android.graphics.Color\nimport com.yalin.style.data.R\nimport com.yalin.style.data.entity.SourceEntity\nimport com.yalin.style.data.extensions.DelegateExt\nimport com.yalin.style.data.repository.datasource.provider.StyleContract\nimport com.yalin.style.data.repository.datasource.sync.gallery.GalleryScheduleService\nimport com.yalin.style.domain.repository.SourcesRepository.*\nimport io.reactivex.Observable\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n/**\n * @author jinyalin\n * @since 2017/5/22.\n */\n@Singleton\nclass SourcesCacheImpl @Inject\nconstructor(val ctx: Context) : SourcesCache {\n    var selectedId: Int by DelegateExt.preferences(ctx, \"selected_source_id\", SOURCE_ID_STYLE)\n\n    val advanceSource: SourceEntity\n    val featureSource: SourceEntity\n    val gallerySource: SourceEntity\n\n    init {\n        advanceSource = SourceEntity(SOURCE_ID_ADVANCE).apply {\n            title = ctx.getString(R.string.advance_source_title)\n            iconId = R.drawable.style_ic_source\n            description = ctx.getString(R.string.advance_source_description)\n            color = Color.WHITE\n            isSelected = selectedId == id\n            isHasSetting = true\n        }\n        featureSource = SourceEntity(SOURCE_ID_STYLE).apply {\n            title = ctx.getString(R.string.featuredart_source_title)\n            iconId = R.drawable.style_ic_source\n            description = ctx.getString(R.string.featuredart_source_description)\n            color = Color.WHITE\n            isSelected = selectedId == id\n            isHasSetting = false\n        }\n\n        gallerySource = SourceEntity(SOURCE_ID_CUSTOM).apply {\n            title = ctx.getString(R.string.gallery_title)\n            iconId = R.drawable.gallery_ic_source\n            description = ctx.getString(R.string.gallery_description)\n            color = 0x4db6ac\n            isSelected = selectedId == id\n            isHasSetting = true\n        }\n\n        if (getUsedSourceId() == SOURCE_ID_CUSTOM) {\n            GalleryScheduleService.startUp(ctx)\n        }\n    }\n\n    override fun getSources(ctx: Context): Observable<List<SourceEntity>> {\n        return Observable.create { emitter ->\n            val sources = ArrayList<SourceEntity>()\n            sources.add(advanceSource)\n            sources.add(featureSource)\n            sources.add(gallerySource)\n            emitter.onNext(sources)\n            emitter.onComplete()\n        }\n    }\n\n    override fun selectSource(selectSourceId: Int, tempSelect: Boolean): Boolean {\n        var success = false\n        if (featureSource.id == selectSourceId) {\n            featureSource.isSelected = true\n            gallerySource.isSelected = false\n            advanceSource.isSelected = false\n            success = true\n\n            GalleryScheduleService.shutDown(ctx)\n        } else if (gallerySource.id == selectSourceId) {\n            featureSource.isSelected = false\n            gallerySource.isSelected = true\n            advanceSource.isSelected = false\n            success = true\n\n            GalleryScheduleService.startUp(ctx)\n        } else if (advanceSource.id == selectSourceId) {\n            featureSource.isSelected = false\n            gallerySource.isSelected = false\n            advanceSource.isSelected = true\n\n            success = true\n\n            GalleryScheduleService.shutDown(ctx)\n        }\n        selectedId = selectSourceId\n        if (success) {\n            notifyChanged()\n        }\n        return success\n    }\n\n    override fun getUsedSourceId(): Int {\n        return selectedId\n    }\n\n    private fun notifyChanged() {\n        ctx.contentResolver.notifyChange(StyleContract.Source.CONTENT_URI, null)\n    }\n}"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/cache/WallpaperCache.java",
    "content": "package com.yalin.style.data.cache;\n\nimport com.yalin.style.data.entity.WallpaperEntity;\n\nimport java.util.Queue;\n\nimport io.reactivex.Observable;\n\n/**\n * @author jinyalin\n * @since 2017/4/20.\n */\n\npublic interface WallpaperCache {\n\n  /**\n   * return the first\n   *\n   * @return wallpaperEntity\n   */\n  Observable<WallpaperEntity> get();\n\n  Observable<WallpaperEntity> getNext();\n\n  Observable<Integer> getWallpaperCount();\n\n  void likeWallpaper(String wallpaperId);\n\n  void put(Queue<WallpaperEntity> wallpaperEntities);\n\n  boolean isCached();\n\n  boolean isCached(String wallpaperId);\n\n  boolean isDirty();\n\n  void evictAll();\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/cache/WallpaperCacheImpl.java",
    "content": "package com.yalin.style.data.cache;\n\nimport android.text.TextUtils;\n\nimport com.fernandocejas.arrow.checks.Preconditions;\nimport com.yalin.style.data.entity.WallpaperEntity;\n\nimport java.util.Queue;\n\nimport javax.inject.Inject;\nimport javax.inject.Singleton;\n\nimport io.reactivex.Observable;\n\n/**\n * @author jinyalin\n * @since 2017/4/20.\n */\n@Singleton\npublic class WallpaperCacheImpl implements WallpaperCache {\n\n    private Queue<WallpaperEntity> wallpaperEntities;\n\n    @Inject\n    public WallpaperCacheImpl() {\n    }\n\n    @Override\n    public Observable<WallpaperEntity> get() {\n        Preconditions.checkNotNull(wallpaperEntities, \"There is not cached wallpaper.\");\n        Preconditions.checkArgument(!wallpaperEntities.isEmpty(), \"There is not cached wallpaper.\");\n        return Observable.create(emitter -> {\n            emitter.onNext(wallpaperEntities.peek());\n            emitter.onComplete();\n        });\n    }\n\n    @Override\n    public Observable<WallpaperEntity> getNext() {\n        Preconditions.checkNotNull(wallpaperEntities, \"There is not cached wallpaper.\");\n        Preconditions.checkArgument(!wallpaperEntities.isEmpty(), \"There is not cached wallpaper.\");\n        return Observable.create(emitter -> {\n            WallpaperEntity entity = wallpaperEntities.poll();\n            wallpaperEntities.offer(entity);\n            emitter.onNext(wallpaperEntities.peek());\n            emitter.onComplete();\n        });\n    }\n\n    @Override\n    public Observable<Integer> getWallpaperCount() {\n        Preconditions.checkNotNull(wallpaperEntities, \"There is not cached wallpaper.\");\n        return Observable.create(emitter -> {\n            emitter.onNext(wallpaperEntities.size());\n            emitter.onComplete();\n        });\n    }\n\n    @Override\n    public void likeWallpaper(String wallpaperId) {\n        Preconditions.checkNotNull(wallpaperId, \"There is not cached wallpaper.\");\n        if (isCached(wallpaperId)) {\n            WallpaperEntity entity = get(wallpaperId);\n            if (entity != null) {\n                entity.liked = !entity.liked;\n            }\n        }\n    }\n\n    @Override\n    public synchronized void put(Queue<WallpaperEntity> wallpaperEntities) {\n        this.wallpaperEntities = wallpaperEntities;\n    }\n\n    @Override\n    public boolean isCached() {\n        return wallpaperEntities != null && !wallpaperEntities.isEmpty();\n    }\n\n    @Override\n    public boolean isCached(String wallpaperId) {\n        if (isDirty()) {\n            return false;\n        }\n        for (WallpaperEntity entity : wallpaperEntities) {\n            if (TextUtils.equals(entity.wallpaperId, wallpaperId)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    @Override\n    public boolean isDirty() {\n        return wallpaperEntities == null;\n    }\n\n    @Override\n    public synchronized void evictAll() {\n        if (wallpaperEntities != null) {\n            wallpaperEntities.clear();\n        }\n        wallpaperEntities = null;\n    }\n\n    private WallpaperEntity get(String wallpaperId) {\n        for (WallpaperEntity entity : wallpaperEntities) {\n            if (TextUtils.equals(entity.wallpaperId, wallpaperId)) {\n                return entity;\n            }\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/entity/AdvanceWallpaperEntity.java",
    "content": "package com.yalin.style.data.entity;\n\nimport android.database.Cursor;\nimport android.text.TextUtils;\n\nimport com.yalin.style.data.log.LogUtil;\nimport com.yalin.style.data.repository.datasource.provider.StyleContract;\n\nimport java.io.File;\nimport java.io.FileNotFoundException;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * @author jinyalin\n * @since 2017/7/28.\n */\n\npublic class AdvanceWallpaperEntity {\n    private static final String TAG = \"AdvanceWallpaperEntity\";\n\n    public int id;\n    public String wallpaperId;\n    public String link;\n    public String name;\n    public String author;\n    public String iconUrl;\n    public String downloadUrl;\n    public String providerName;\n\n    public boolean lazyDownload;\n    public boolean needAd;\n\n    public String storePath;\n    public String checkSum;\n\n    public boolean isDefault = false;\n\n    public boolean isSelected = false;\n\n    public static List<AdvanceWallpaperEntity> readCursor(Cursor cursor) {\n        List<AdvanceWallpaperEntity> validWallpapers = new ArrayList<>();\n        while (cursor != null && cursor.moveToNext()) {\n            AdvanceWallpaperEntity wallpaperEntity = readEntityFromCursor(cursor);\n            try {\n                if (!wallpaperEntity.lazyDownload\n                        && !new File(wallpaperEntity.storePath).exists()) {\n                    throw new FileNotFoundException(\"Component not found.\");\n                }\n                validWallpapers.add(wallpaperEntity);\n            } catch (Exception e) {\n                LogUtil.D(TAG, \"File not found with wallpaper wallpaperId : \"\n                        + wallpaperEntity.wallpaperId);\n            }\n        }\n        return validWallpapers;\n    }\n\n    public static AdvanceWallpaperEntity readEntityFromCursor(Cursor cursor) {\n        AdvanceWallpaperEntity wallpaperEntity = new AdvanceWallpaperEntity();\n\n        wallpaperEntity.id = cursor.getInt(cursor.getColumnIndex(\n                StyleContract.AdvanceWallpaper._ID));\n        wallpaperEntity.name = cursor.getString(cursor.getColumnIndex(\n                StyleContract.AdvanceWallpaper.COLUMN_NAME_NAME));\n        wallpaperEntity.wallpaperId = cursor.getString(cursor.getColumnIndex(\n                StyleContract.AdvanceWallpaper.COLUMN_NAME_WALLPAPER_ID));\n        wallpaperEntity.iconUrl = cursor.getString(cursor.getColumnIndex(\n                StyleContract.AdvanceWallpaper.COLUMN_NAME_ICON_URL));\n        wallpaperEntity.link = cursor.getString(cursor.getColumnIndex(\n                StyleContract.AdvanceWallpaper.COLUMN_NAME_LINK));\n        wallpaperEntity.author = cursor.getString(cursor.getColumnIndex(\n                StyleContract.AdvanceWallpaper.COLUMN_NAME_AUTHOR));\n        wallpaperEntity.downloadUrl = cursor.getString(cursor.getColumnIndex(\n                StyleContract.AdvanceWallpaper.COLUMN_NAME_DOWNLOAD_URL));\n        wallpaperEntity.checkSum = cursor.getString(cursor.getColumnIndex(\n                StyleContract.AdvanceWallpaper.COLUMN_NAME_CHECKSUM));\n        wallpaperEntity.storePath = cursor.getString(cursor.getColumnIndex(\n                StyleContract.AdvanceWallpaper.COLUMN_NAME_STORE_PATH));\n        wallpaperEntity.providerName = cursor.getString(cursor.getColumnIndex(\n                StyleContract.AdvanceWallpaper.COLUMN_NAME_PROVIDER_NAME));\n        wallpaperEntity.isSelected = cursor.getInt(cursor.getColumnIndex(\n                StyleContract.AdvanceWallpaper.COLUMN_NAME_SELECTED)) == 1;\n        wallpaperEntity.lazyDownload = cursor.getInt(cursor.getColumnIndex(\n                StyleContract.AdvanceWallpaper.COLUMN_NAME_LAZY_DOWNLOAD)) == 1;\n        wallpaperEntity.needAd = cursor.getInt(cursor.getColumnIndex(\n                StyleContract.AdvanceWallpaper.COLUMN_NAME_NEED_AD)) == 1;\n\n        return wallpaperEntity;\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        if (obj instanceof AdvanceWallpaperEntity) {\n            if (TextUtils.equals(((AdvanceWallpaperEntity) obj).name, name)\n                    && TextUtils.equals(((AdvanceWallpaperEntity) obj).checkSum, checkSum)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    @Override\n    public int hashCode() {\n        int result = 0;\n        result = 31 * result + name.hashCode();\n        result = 31 * result + checkSum.hashCode();\n        return result;\n    }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/entity/DeviceInfo.java",
    "content": "package com.yalin.style.data.entity;\n\nimport com.yalin.style.data.BuildConfig;\n\n/**\n * @author jinyalin\n * @since 2017/4/25.\n */\n\npublic class DeviceInfo {\n    private int sdkVersion;\n    private String androidId;\n    private String manufacturer;\n    private String brand;\n    private String model;\n    private String type;\n    private String versionName;\n    private int versionCode;\n    private String channel;\n\n    public DeviceInfo(int sdkVersion, String androidId, String manufacturer,\n                      String brand, String model) {\n        this.sdkVersion = sdkVersion;\n        this.androidId = androidId;\n        this.manufacturer = manufacturer;\n        this.brand = brand;\n        this.model = model;\n        type = \"Android\";\n        versionName = BuildConfig.VERSION_NAME;\n        versionCode = BuildConfig.VERSION_CODE;\n        channel = BuildConfig.CHANNEL;\n    }\n\n    public int getSdkVersion() {\n        return sdkVersion;\n    }\n\n    public String getAndroidId() {\n        return androidId;\n    }\n\n    public String getManufacturer() {\n        return manufacturer;\n    }\n\n    public String getModel() {\n        return model;\n    }\n\n    public String getType() {\n        return type;\n    }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/entity/GalleryWallpaperEntity.kt",
    "content": "package com.yalin.style.data.entity\n\nimport android.content.Context\nimport android.database.Cursor\nimport android.net.Uri\nimport android.os.Build\nimport com.yalin.style.data.log.LogUtil\nimport com.yalin.style.data.repository.datasource.provider.StyleContract\nimport com.yalin.style.data.utils.getCacheFileForUri\nimport com.yalin.style.data.utils.getImagesFromTreeUri\nimport java.io.InputStream\nimport java.util.ArrayList\n\n/**\n * @author jinyalin\n * @since 2017/5/24.\n */\nclass GalleryWallpaperEntity {\n    var id: Long = 0\n    var uri: String? = null\n    var isTreeUri: Boolean = false\n    var dateTime: Long = 0\n    var location: String? = null\n    var hasMetadata: Boolean = false\n\n    companion object {\n        private val TAG = \"GalleryWallpaperEntity\"\n\n        fun readCursor(context: Context, cursor: Cursor?): ArrayList<GalleryWallpaperEntity> {\n            val validWallpapers = ArrayList<GalleryWallpaperEntity>(5)\n            while (cursor != null && cursor.moveToNext()) {\n                val entity = readEntityFromCursor(cursor)\n                var inputStream: InputStream? = null\n                try {\n                    // valid input stream\n                    if (!entity.isTreeUri) {\n                        inputStream = context.contentResolver.openInputStream(\n                                Uri.parse(entity.uri))\n                    }\n                    validWallpapers.add(entity)\n                } catch (e: Exception) {\n                    LogUtil.D(TAG, \"Cannot open inputStream for uri : \"\n                            + entity.uri)\n                    val cacheFile = getCacheFileForUri(context, entity.uri!!)\n                    if ((cacheFile != null && cacheFile.exists())) {\n                        // has cache file\n                        validWallpapers.add(entity)\n                    }\n                } finally {\n                    inputStream?.close()\n                }\n            }\n            return validWallpapers;\n        }\n\n        fun readEntityFromCursor(cursor: Cursor): GalleryWallpaperEntity {\n            val entity = GalleryWallpaperEntity()\n\n            entity.id = cursor.getLong(cursor.getColumnIndex(\n                    StyleContract.GalleryWallpaper._ID))\n            entity.uri = cursor.getString(cursor.getColumnIndex(\n                    StyleContract.GalleryWallpaper.COLUMN_NAME_CUSTOM_URI))\n            entity.isTreeUri = cursor.getInt(cursor.getColumnIndex(\n                    StyleContract.GalleryWallpaper.COLUMN_NAME_IS_TREE_URI)) == 1\n            entity.dateTime = cursor.getLong(cursor.getColumnIndex(\n                    StyleContract.GalleryWallpaper.COLUMN_NAME_DATE_TIME))\n            entity.location = cursor.getString(cursor.getColumnIndex(\n                    StyleContract.GalleryWallpaper.COLUMN_NAME_LOCATION))\n            entity.hasMetadata = cursor.getInt(cursor.getColumnIndex(\n                    StyleContract.GalleryWallpaper.COLUMN_NAME_HAS_METADATA)) == 1\n            return entity\n        }\n    }\n}"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/entity/HttpRequestBody.java",
    "content": "package com.yalin.style.data.entity;\n\nimport android.content.Context;\n\nimport com.yalin.style.data.utils.FacetIdUtil;\n\n/**\n * @author jinyalin\n * @since 2017/4/25.\n */\n\npublic class HttpRequestBody {\n    private DeviceInfo deviceInfo;\n    private String facetId;\n\n    public HttpRequestBody(Context context, DeviceInfo deviceInfo) {\n        this.deviceInfo = deviceInfo;\n        this.facetId = FacetIdUtil.getFacetId(context);\n    }\n\n    public DeviceInfo getDeviceInfo() {\n        return deviceInfo;\n    }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/entity/SourceEntity.kt",
    "content": "package com.yalin.style.data.entity\n\n\n/**\n * @author jinyalin\n * *\n * @since 2017/5/23.\n */\n\nclass SourceEntity(var id: Int) {\n    var title: String? = null\n    var description: String? = null\n    var iconId: Int = 0\n    var isSelected: Boolean = false\n    var isHasSetting: Boolean = false\n    var color: Int = 0\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/entity/WallpaperEntity.java",
    "content": "package com.yalin.style.data.entity;\n\nimport android.content.Context;\nimport android.database.Cursor;\nimport android.text.TextUtils;\n\nimport com.yalin.style.data.log.LogUtil;\nimport com.yalin.style.data.repository.datasource.provider.StyleContract;\nimport com.yalin.style.data.repository.datasource.provider.StyleContract.Wallpaper;\n\nimport java.io.InputStream;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * @author jinyalin\n * @since 2017/4/18.\n */\n\npublic class WallpaperEntity {\n\n    private static final String TAG = \"WallpaperEntity\";\n\n    public int id;\n    public String wallpaperId;\n    public String imageUri;\n    public String title;\n    public String byline;\n    public String attribution;\n\n    public boolean canLike;\n    public boolean liked;\n    public boolean isDefault;\n\n    public String checksum;\n\n    public WallpaperEntity() {\n    }\n\n    public static List<WallpaperEntity> readCursor(Context context, Cursor cursor) {\n        List<WallpaperEntity> validWallpapers = new ArrayList<>(5);\n        while (cursor != null && cursor.moveToNext()) {\n            WallpaperEntity wallpaperEntity = WallpaperEntity.readEntityFromCursor(cursor);\n            try {\n                // valid input stream\n                InputStream is = context.getContentResolver().openInputStream(\n                        StyleContract.Wallpaper.buildWallpaperUri(\n                                wallpaperEntity.wallpaperId));\n                validWallpapers.add(wallpaperEntity);\n                if (is != null) {\n                    is.close();\n                }\n            } catch (Exception e) {\n                LogUtil.D(TAG, \"File not found with wallpaper wallpaperId : \"\n                        + wallpaperEntity.wallpaperId);\n            }\n        }\n        return validWallpapers;\n    }\n\n    public static WallpaperEntity readEntityFromCursor(Cursor cursor) {\n        WallpaperEntity wallpaperEntity = new WallpaperEntity();\n\n        wallpaperEntity.id = cursor.getInt(cursor.getColumnIndex(\n                Wallpaper._ID));\n        wallpaperEntity.title = cursor.getString(cursor.getColumnIndex(\n                Wallpaper.COLUMN_NAME_TITLE));\n        wallpaperEntity.wallpaperId = cursor.getString(cursor.getColumnIndex(\n                Wallpaper.COLUMN_NAME_WALLPAPER_ID));\n        wallpaperEntity.imageUri = cursor.getString(cursor.getColumnIndex(\n                Wallpaper.COLUMN_NAME_IMAGE_URI));\n        wallpaperEntity.byline = cursor.getString(cursor.getColumnIndex(\n                Wallpaper.COLUMN_NAME_BYLINE));\n        wallpaperEntity.attribution = cursor.getString(cursor.getColumnIndex(\n                Wallpaper.COLUMN_NAME_ATTRIBUTION));\n        wallpaperEntity.liked = cursor.getInt(cursor.getColumnIndex(\n                Wallpaper.COLUMN_NAME_LIKED)) == 1;\n        wallpaperEntity.checksum = cursor.getString(cursor.getColumnIndex(\n                Wallpaper.COLUMN_NAME_CHECKSUM));\n\n        return wallpaperEntity;\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        if (obj instanceof WallpaperEntity) {\n            if (TextUtils.equals(((WallpaperEntity) obj).title, title)\n                    && TextUtils.equals(((WallpaperEntity) obj).checksum, checksum)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    @Override\n    public int hashCode() {\n        int result = 0;\n        result = 31 * result + title.hashCode();\n        result = 31 * result + checksum.hashCode();\n        return result;\n    }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/entity/mapper/AdvanceWallpaperEntityMapper.java",
    "content": "package com.yalin.style.data.entity.mapper;\n\nimport com.fernandocejas.arrow.checks.Preconditions;\nimport com.yalin.style.data.entity.AdvanceWallpaperEntity;\nimport com.yalin.style.domain.AdvanceWallpaper;\nimport com.yalin.style.domain.Wallpaper;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport javax.inject.Inject;\nimport javax.inject.Singleton;\n\n/**\n * @author jinyalin\n * @since 2017/7/28.\n */\n@Singleton\npublic class AdvanceWallpaperEntityMapper {\n    @Inject\n    public AdvanceWallpaperEntityMapper() {\n\n    }\n\n    public AdvanceWallpaper transform(AdvanceWallpaperEntity wallpaperEntity) {\n        Preconditions.checkNotNull(wallpaperEntity, \"Wallpaper can not be null.\");\n        AdvanceWallpaper wallpaper = new AdvanceWallpaper();\n        wallpaper.id = wallpaperEntity.id;\n        wallpaper.wallpaperId = wallpaperEntity.wallpaperId;\n        wallpaper.link = wallpaperEntity.link;\n        wallpaper.name = wallpaperEntity.name;\n        wallpaper.author = wallpaperEntity.author;\n        wallpaper.iconUrl = wallpaperEntity.iconUrl;\n        wallpaper.downloadUrl = wallpaperEntity.downloadUrl;\n        wallpaper.providerName = wallpaperEntity.providerName;\n        wallpaper.storePath = wallpaperEntity.storePath;\n        wallpaper.isDefault = wallpaperEntity.isDefault;\n        wallpaper.isSelected = wallpaperEntity.isSelected;\n        wallpaper.lazyDownload = wallpaperEntity.lazyDownload;\n        wallpaper.needAd = wallpaperEntity.needAd;\n\n        return wallpaper;\n    }\n\n    public List<AdvanceWallpaper> transformList(List<AdvanceWallpaperEntity> wallpaperEntities) {\n        Preconditions.checkNotNull(wallpaperEntities, \"SourceEntity can not be null.\");\n        List<AdvanceWallpaper> sources = new ArrayList<>();\n        for (AdvanceWallpaperEntity entity : wallpaperEntities) {\n            sources.add(transform(entity));\n        }\n        return sources;\n    }\n\n    public Wallpaper mapToWallpaper(AdvanceWallpaperEntity wallpaperEntity) {\n        Preconditions.checkNotNull(wallpaperEntity, \"Wallpaper can not be null.\");\n        Wallpaper wallpaper = new Wallpaper();\n        wallpaper.wallpaperId = wallpaperEntity.wallpaperId;\n        wallpaper.byline = wallpaperEntity.author;\n        wallpaper.title = wallpaperEntity.name;\n        wallpaper.attribution = wallpaperEntity.link;\n        wallpaper.canLike = false;\n        return wallpaper;\n    }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/entity/mapper/WallpaperEntityMapper.java",
    "content": "package com.yalin.style.data.entity.mapper;\n\nimport com.fernandocejas.arrow.checks.Preconditions;\nimport com.yalin.style.data.entity.GalleryWallpaperEntity;\nimport com.yalin.style.data.entity.SourceEntity;\nimport com.yalin.style.data.entity.WallpaperEntity;\nimport com.yalin.style.domain.GalleryWallpaper;\nimport com.yalin.style.domain.Source;\nimport com.yalin.style.domain.Wallpaper;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport javax.inject.Inject;\nimport javax.inject.Singleton;\n\n/**\n * @author jinyalin\n * @since 2017/4/18.\n */\n@Singleton\npublic class WallpaperEntityMapper {\n\n    @Inject\n    public WallpaperEntityMapper() {\n    }\n\n    public Wallpaper transform(WallpaperEntity wallpaperEntity) {\n        Preconditions.checkNotNull(wallpaperEntity, \"Wallpaper can not be null.\");\n        Wallpaper wallpaper = new Wallpaper();\n        wallpaper.title = wallpaperEntity.title;\n        wallpaper.attribution = wallpaperEntity.attribution;\n        wallpaper.byline = wallpaperEntity.byline;\n        wallpaper.imageUri = wallpaperEntity.imageUri;\n        wallpaper.wallpaperId = wallpaperEntity.wallpaperId;\n        wallpaper.liked = wallpaperEntity.liked;\n        wallpaper.isDefault = wallpaperEntity.isDefault;\n        wallpaper.canLike = wallpaperEntity.canLike;\n        return wallpaper;\n    }\n\n    public List<Source> transformSources(List<SourceEntity> sourceEntities) {\n        Preconditions.checkNotNull(sourceEntities, \"SourceEntity can not be null.\");\n        List<Source> sources = new ArrayList<>();\n        for (SourceEntity entity : sourceEntities) {\n            sources.add(transformSource(entity));\n        }\n        return sources;\n    }\n\n    public Source transformSource(SourceEntity sourceEntity) {\n        Preconditions.checkNotNull(sourceEntity, \"SourceEntity can not be null.\");\n        Source source = new Source();\n        source.id = sourceEntity.getId();\n        source.title = sourceEntity.getTitle();\n        source.description = sourceEntity.getDescription();\n        source.iconId = sourceEntity.getIconId();\n        source.selected = sourceEntity.isSelected();\n        source.hasSetting = sourceEntity.isHasSetting();\n        source.color = sourceEntity.getColor();\n        return source;\n    }\n\n    public List<GalleryWallpaper> transformGalleryWallpaper(\n            List<GalleryWallpaperEntity> galleryWallpaperEntities) {\n        Preconditions.checkNotNull(galleryWallpaperEntities,\n                \"GalleryWallpaperEntity can not be null.\");\n        List<GalleryWallpaper> entities = new ArrayList<>();\n        for (GalleryWallpaperEntity entity : galleryWallpaperEntities) {\n            entities.add(transformGalleryWallpaper(entity));\n        }\n        return entities;\n    }\n\n    public GalleryWallpaper transformGalleryWallpaper(\n            GalleryWallpaperEntity galleryWallpaperEntity) {\n        Preconditions.checkNotNull(galleryWallpaperEntity,\n                \"GalleryWallpaperEntity can not be null.\");\n        GalleryWallpaper galleryWallpaper = new GalleryWallpaper();\n        galleryWallpaper.id = galleryWallpaperEntity.getId();\n        galleryWallpaper.isTreeUri = galleryWallpaperEntity.isTreeUri();\n        galleryWallpaper.uri = galleryWallpaperEntity.getUri();\n        galleryWallpaper.dateTime = galleryWallpaperEntity.getDateTime();\n        galleryWallpaper.location = galleryWallpaperEntity.getLocation();\n        galleryWallpaper.hasMetadata = galleryWallpaperEntity.getHasMetadata();\n        return galleryWallpaper;\n    }\n\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/exception/LikeException.java",
    "content": "package com.yalin.style.data.exception;\n\n/**\n * @author jinyalin\n * @since 2017/4/29.\n */\n\npublic class LikeException extends Exception {\n    public LikeException() {\n        super();\n    }\n\n    public LikeException(final Throwable cause) {\n        super(cause);\n    }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/exception/NetworkConnectionException.java",
    "content": "package com.yalin.style.data.exception;\n\n/**\n * @author jinyalin\n * @since 2017/4/29.\n */\n\npublic class NetworkConnectionException extends Exception {\n    public NetworkConnectionException() {\n        super();\n    }\n\n    public NetworkConnectionException(final Throwable cause) {\n        super(cause);\n    }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/exception/NoContentException.java",
    "content": "package com.yalin.style.data.exception;\n\n/**\n * @author jinyalin\n * @since 2017/7/31.\n */\n\npublic class NoContentException extends Exception {\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/exception/RemoteServerException.java",
    "content": "package com.yalin.style.data.exception;\n\n/**\n * @author jinyalin\n * @since 2017/8/4.\n */\n\npublic class RemoteServerException extends Exception {\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/exception/ReswitchException.java",
    "content": "package com.yalin.style.data.exception;\n\n/**\n * @author jinyalin\n * @since 2017/4/29.\n */\n\npublic class ReswitchException extends Exception {\n    public ReswitchException() {\n        super();\n    }\n\n    public ReswitchException(final Throwable cause) {\n        super(cause);\n    }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/executor/JobExecutor.java",
    "content": "package com.yalin.style.data.executor;\n\nimport android.support.annotation.NonNull;\n\nimport com.yalin.style.domain.executor.ThreadExecutor;\n\nimport java.util.concurrent.LinkedBlockingQueue;\nimport java.util.concurrent.ThreadFactory;\nimport java.util.concurrent.ThreadPoolExecutor;\nimport java.util.concurrent.TimeUnit;\n\nimport javax.inject.Inject;\nimport javax.inject.Singleton;\n\n/**\n * @author jinyalin\n * @since 2017/4/18.\n */\n@Singleton\npublic class JobExecutor implements ThreadExecutor {\n    protected final ThreadPoolExecutor threadPoolExecutor;\n\n    @Inject\n    JobExecutor() {\n        threadPoolExecutor = new ThreadPoolExecutor(3, 5, 10, TimeUnit.SECONDS,\n                new LinkedBlockingQueue<>(), new JobThreadFactory());\n    }\n\n    @Override\n    public void execute(@NonNull Runnable command) {\n        threadPoolExecutor.execute(command);\n    }\n\n    private static class JobThreadFactory implements ThreadFactory {\n        private int counter = 0;\n\n        @Override\n        public Thread newThread(@NonNull Runnable runnable) {\n            return new Thread(runnable, \"android_\" + counter);\n        }\n    }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/executor/SerialJobExecutor.java",
    "content": "package com.yalin.style.data.executor;\n\nimport android.support.annotation.NonNull;\n\n\nimport com.yalin.style.domain.executor.SerialThreadExecutor;\n\nimport java.util.ArrayDeque;\n\nimport javax.inject.Inject;\nimport javax.inject.Singleton;\n\n/**\n * @author jinyalin\n * @since 2017/4/18.\n */\n@Singleton\npublic class SerialJobExecutor extends JobExecutor implements SerialThreadExecutor {\n    final ArrayDeque<Runnable> mTasks = new ArrayDeque<>();\n    Runnable mActive;\n\n    @Inject\n    SerialJobExecutor() {\n        super();\n    }\n\n    @Override\n    public synchronized void execute(@NonNull Runnable command) {\n        mTasks.offer(() -> {\n            try {\n                command.run();\n            } finally {\n                scheduleNext();\n            }\n        });\n        if (mActive == null) {\n            scheduleNext();\n        }\n    }\n\n    protected synchronized void scheduleNext() {\n        if ((mActive = mTasks.poll()) != null) {\n            threadPoolExecutor.execute(mActive);\n        }\n    }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/extensions/DelegateExt.kt",
    "content": "package com.yalin.style.data.extensions\n\nimport android.content.Context\nimport android.content.SharedPreferences\nimport java.lang.IllegalArgumentException\nimport kotlin.reflect.KProperty\n\n/**\n * @author jinyalin\n * @since 2017/5/22.\n */\nobject DelegateExt {\n    fun <T> preferences(context: Context, name: String, default: T) =\n            Preferences(context, name, default)\n}\n\nclass Preferences<T>(val context: Context, val name: String, val default: T) {\n    companion object {\n        val PREFERENCE_NAME = \"style_preference\"\n    }\n\n    val prefs: SharedPreferences by lazy {\n        context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE)\n    }\n\n    operator fun getValue(thisRef: Any?, property: KProperty<*>): T = findPreference(name, default)\n\n    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) =\n            putPreference(name, value)\n\n    @Suppress(\"UNCHECKED_CAST\")\n    private fun findPreference(name: String, default: T): T = with(prefs) {\n        val res: Any = when (default) {\n            is Long -> getLong(name, default)\n            is String -> getString(name, default)\n            is Int -> getInt(name, default)\n            is Boolean -> getBoolean(name, default)\n            is Float -> getFloat(name, default)\n            else -> throw IllegalArgumentException(\"This type can not be saved into Preferences\")\n        }\n\n        res as T\n    }\n\n    private fun putPreference(name: String, value: T) = with(prefs.edit()) {\n        when (value) {\n            is Long -> putLong(name, value)\n            is String -> putString(name, value)\n            is Int -> putInt(name, value)\n            is Boolean -> putBoolean(name, value)\n            is Float -> putFloat(name, value)\n            else -> throw IllegalArgumentException(\"This type can't be saved into Preferences\")\n        }.apply()\n    }\n}"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/lock/LikeWallpaperLock.java",
    "content": "package com.yalin.style.data.lock;\n\nimport com.yalin.style.domain.executor.ThreadExecutor;\nimport com.yalin.style.domain.interactor.DefaultObserver;\n\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport javax.inject.Inject;\nimport javax.inject.Singleton;\n\nimport io.reactivex.Observable;\nimport io.reactivex.schedulers.Schedulers;\n\n/**\n * YaLin\n * On 2017/4/30.\n */\n@Singleton\npublic class LikeWallpaperLock extends ResourceLock {\n\n    @Inject\n    public LikeWallpaperLock(ThreadExecutor threadExecutor) {\n        super(threadExecutor);\n    }\n\n    @Override\n    protected Observable<Void> appendDelay(Observable<Void> observable) {\n        return observable.delaySubscription(500, TimeUnit.MILLISECONDS);\n    }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/lock/OpenInputStreamLock.java",
    "content": "package com.yalin.style.data.lock;\n\nimport com.yalin.style.domain.executor.ThreadExecutor;\n\nimport io.reactivex.Observable;\n\nimport java.util.concurrent.TimeUnit;\n\nimport javax.inject.Inject;\nimport javax.inject.Singleton;\n\n/**\n * YaLin\n * On 2017/4/30.\n */\n@Singleton\npublic class OpenInputStreamLock extends ResourceLock {\n\n    @Inject\n    public OpenInputStreamLock(ThreadExecutor threadExecutor) {\n        super(threadExecutor);\n    }\n\n    @Override\n    protected Observable<Void> appendDelay(Observable<Void> observable) {\n        return observable.delaySubscription(1, TimeUnit.SECONDS);\n    }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/lock/ResourceLock.java",
    "content": "package com.yalin.style.data.lock;\n\nimport com.yalin.style.domain.executor.ThreadExecutor;\nimport com.yalin.style.domain.interactor.DefaultObserver;\n\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport io.reactivex.Observable;\nimport io.reactivex.schedulers.Schedulers;\n\n/**\n * YaLin\n * On 2017/4/30.\n */\n\npublic abstract class ResourceLock {\n    private AtomicBoolean lock = new AtomicBoolean(false);\n\n    private final ThreadExecutor threadExecutor;\n\n    private DefaultObserver<Void> releaseObserver;\n\n    public ResourceLock(ThreadExecutor threadExecutor) {\n        this.threadExecutor = threadExecutor;\n    }\n\n    public synchronized boolean obtain() {\n        if (lock.get()) {\n            return false;\n        } else {\n            lock.set(true);\n            return true;\n        }\n    }\n\n    public synchronized void release() {\n        if (releaseObserver != null) {\n            releaseObserver.dispose();\n        }\n        releaseObserver = new DefaultObserver<>();\n        Observable<Void> observable = Observable.create(e -> {\n            lock.set(false);\n            e.onComplete();\n        });\n        observable = appendDelay(observable);\n        observable.subscribeOn(Schedulers.from(threadExecutor))\n                .subscribeWith(releaseObserver);\n    }\n\n    protected Observable<Void> appendDelay(Observable<Void> observable) {\n        return observable;\n    }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/lock/SelectSourceLock.java",
    "content": "package com.yalin.style.data.lock;\n\nimport com.yalin.style.domain.executor.ThreadExecutor;\n\nimport java.util.concurrent.TimeUnit;\n\nimport javax.inject.Inject;\nimport javax.inject.Singleton;\n\nimport io.reactivex.Observable;\n\n/**\n * YaLin\n * On 2017/4/30.\n */\n@Singleton\npublic class SelectSourceLock extends ResourceLock {\n\n    @Inject\n    public SelectSourceLock(ThreadExecutor threadExecutor) {\n        super(threadExecutor);\n    }\n\n    @Override\n    protected Observable<Void> appendDelay(Observable<Void> observable) {\n        return observable.delaySubscription(1, TimeUnit.SECONDS);\n    }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/log/LogUtil.java",
    "content": "package com.yalin.style.data.log;\n\nimport android.os.Environment;\nimport android.os.Process;\nimport android.util.Log;\n\nimport com.yalin.style.data.BuildConfig;\n\nimport java.io.BufferedWriter;\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.OutputStreamWriter;\nimport java.util.Calendar;\nimport java.util.Locale;\n\n/**\n * YaLin 2016/11/23.\n */\n\npublic class LogUtil {\n\n    private static final String FILE_NAME = \"Style/stat_log.txt\";\n\n    public static final boolean LOG_ENABLE = BuildConfig.DEMO_MODE || BuildConfig.LOG_ENABLE;\n\n    public static synchronized boolean isExternalLogEnabled() {\n        //noinspection SimplifiableIfStatement\n        if (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {\n            return false;\n        }\n        return LOG_ENABLE && BuildConfig.ENABLE_EXTERNAL_LOG;\n    }\n\n    public static void F(String tag, String msg, Throwable throwable) {\n        if (!LOG_ENABLE) {\n            return;\n        }\n        String stackTraces = Log.getStackTraceString(throwable);\n        if (msg == null) {\n            msg = \"\";\n        }\n        F(tag, msg + \" :\\n \" + stackTraces);\n    }\n\n    public static void F(String tag, String msg) {\n        if (!LOG_ENABLE) {\n            return;\n        }\n        String procInfo = getProcessInfo();\n\n        Log.e(tag, procInfo + msg);\n\n        if (!isExternalLogEnabled()) {\n            return;\n        }\n        tag = tag + \":debug\";\n        writeLog(tag, procInfo + msg);\n    }\n\n    public static void E(String tag, String msg, Throwable throwable) {\n        if (!LOG_ENABLE) {\n            return;\n        }\n        String stackTraces = Log.getStackTraceString(throwable);\n        if (msg == null) {\n            msg = \"\";\n        }\n        E(tag, msg + \" :\\n \" + stackTraces);\n    }\n\n    public static void E(String tag, String msg) {\n        if (!LOG_ENABLE) {\n            return;\n        }\n        String procInfo = getProcessInfo();\n\n        Log.e(tag, procInfo + msg);\n    }\n\n    public static void D(String tag, String msg) {\n        if (!LOG_ENABLE) {\n            return;\n        }\n        String procInfo = getProcessInfo();\n\n        Log.d(tag, procInfo + msg);\n    }\n\n    private static String getProcessInfo() {\n        return \"Process wallpaperId: \" + Process.myPid()\n                + \" Thread wallpaperId: \" + Thread.currentThread().getId() + \" \";\n    }\n\n    private static synchronized void writeLog(String tag, String msg) {\n        internalWriteLog(FILE_NAME, tag, msg);\n    }\n\n    private static synchronized void internalWriteLog(String filename, String tag, String msg) {\n        try {\n            if (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {\n                return;\n            }\n\n            File file = new File(Environment.getExternalStorageDirectory(), filename);\n\n            //noinspection ResultOfMethodCallIgnored\n            file.getParentFile().mkdirs();\n\n            BufferedWriter bw = new BufferedWriter(\n                    new OutputStreamWriter(new FileOutputStream(file, true)));\n\n            String time = getCurrentTime();\n            bw.write(time + \" \" + tag + \" \\t\" + msg + \"\\r\\n\");\n\n            bw.close();\n        } catch (Exception e) {\n            // ignore\n        }\n    }\n\n    private static String getCurrentTime() {\n        Calendar c = Calendar.getInstance();\n        return String.format(Locale.getDefault(), \"%d-%02d-%02d %02d:%02d:%02d.%03d\",\n                c.get(Calendar.YEAR), c.get(Calendar.MONTH) + 1, c.get(Calendar.DAY_OF_MONTH),\n                c.get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE), c.get(Calendar.SECOND),\n                c.get(Calendar.MILLISECOND));\n    }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/observable/SourcesObservableImpl.kt",
    "content": "package com.yalin.style.data.observable\n\nimport android.content.Context\nimport android.database.ContentObserver\nimport android.net.Uri\nimport android.os.Handler\nimport com.yalin.style.data.cache.SourcesCache\nimport com.yalin.style.data.log.LogUtil\nimport com.yalin.style.data.repository.datasource.provider.StyleContract\nimport com.yalin.style.domain.interactor.DefaultObserver\nimport com.yalin.style.domain.observable.SourcesObservable\nimport java.util.HashSet\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n/**\n * @author jinyalin\n * @since 2017/5/23.\n */\n@Singleton\nclass SourcesObservableImpl @Inject\nconstructor(val context: Context,\n            val sourcesCache: SourcesCache) : SourcesObservable {\n\n    companion object {\n        val TAG = \"WallpaperObservableImpl\"\n    }\n\n    private val mObserverSet = HashSet<DefaultObserver<Void>>()\n    private val mContentObserver = object : ContentObserver(Handler()) {\n        override fun onChange(selfChange: Boolean, uri: Uri) {\n            LogUtil.D(TAG, \"Source changed notify observer to reload.\")\n            notifyObserver()\n        }\n    }\n\n    private var observerRegistered = false\n\n    override fun registerObserver(observer: DefaultObserver<Void>) {\n        synchronized(mObserverSet) {\n            mObserverSet.add(observer)\n            if (!observerRegistered) {\n                context.contentResolver\n                        .registerContentObserver(StyleContract.Source.CONTENT_URI,\n                                true, mContentObserver)\n                observerRegistered = true\n            }\n        }\n    }\n\n    override fun unregisterObserver(observer: DefaultObserver<Void>) {\n        synchronized(mObserverSet) {\n            mObserverSet.remove(observer)\n            if (mObserverSet.isEmpty()) {\n                context.contentResolver.unregisterContentObserver(mContentObserver)\n                observerRegistered = false\n            }\n        }\n    }\n\n\n    private fun notifyObserver() {\n        for (observer in mObserverSet) {\n            observer.onNext(null)\n            observer.onComplete()\n        }\n    }\n\n}"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/observable/WallpaperObservableImpl.kt",
    "content": "package com.yalin.style.data.observable\n\nimport android.content.Context\nimport android.database.ContentObserver\nimport android.net.Uri\nimport android.os.Handler\nimport android.text.TextUtils\nimport com.yalin.style.data.cache.SourcesCache\nimport com.yalin.style.data.log.LogUtil\nimport com.yalin.style.data.repository.datasource.AdvanceWallpaperDataStoreFactory\nimport com.yalin.style.data.repository.datasource.StyleWallpaperDataStoreFactory\nimport com.yalin.style.data.repository.datasource.provider.StyleContract\nimport com.yalin.style.domain.interactor.DefaultObserver\nimport com.yalin.style.domain.observable.SourcesObservable\nimport com.yalin.style.domain.observable.WallpaperObservable\nimport com.yalin.style.domain.repository.SourcesRepository.SOURCE_ID_CUSTOM\nimport com.yalin.style.domain.repository.SourcesRepository.SOURCE_ID_STYLE\nimport com.yalin.style.domain.repository.SourcesRepository.SOURCE_ID_ADVANCE\nimport java.util.HashSet\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n/**\n * @author jinyalin\n * @since 2017/5/23.\n */\n@Singleton\nclass WallpaperObservableImpl @Inject\nconstructor(val context: Context,\n            val sourcesCache: SourcesCache,\n            val styleWallpaperDataStoreFactory: StyleWallpaperDataStoreFactory,\n            val advanceWallpaperDataStoreFactory: AdvanceWallpaperDataStoreFactory,\n            val sourcesObservable: SourcesObservable) :\n        WallpaperObservable {\n\n    companion object {\n        val TAG = \"WallpaperObservableImpl\"\n    }\n\n    private val mObserverSet = HashSet<DefaultObserver<Void>>()\n    private val mWallpaperObserver = object : ContentObserver(Handler()) {\n        override fun onChange(selfChange: Boolean, uri: Uri) {\n            LogUtil.D(TAG, \"Wallpaper data changed notify observer to reload.\")\n            if (sourcesCache.getUsedSourceId() == SOURCE_ID_CUSTOM\n                    && TextUtils.equals(uri.toString(),\n                    StyleContract.GalleryWallpaper.CONTENT_URI.toString())) {\n                notifyObserver()\n            } else if (sourcesCache.getUsedSourceId() == SOURCE_ID_STYLE\n                    && TextUtils.equals(uri.toString(),\n                    StyleContract.Wallpaper.CONTENT_URI.toString())) {\n                notifyObserver()\n            } else if (sourcesCache.getUsedSourceId() == SOURCE_ID_ADVANCE\n                    && TextUtils.equals(uri.toString(),\n                    StyleContract.AdvanceWallpaper.CONTENT_URI.toString())) {\n                notifyObserver()\n            }\n            styleWallpaperDataStoreFactory.onDataRefresh()\n            advanceWallpaperDataStoreFactory.onDataRefresh()\n        }\n    }\n\n    private val sourceObserver = object : DefaultObserver<Void>() {\n        override fun onComplete() {\n            notifyObserver()\n        }\n    }\n\n    init {\n        context.contentResolver\n                .registerContentObserver(StyleContract.Wallpaper.CONTENT_URI,\n                        true, mWallpaperObserver)\n        context.contentResolver\n                .registerContentObserver(StyleContract.GalleryWallpaper.CONTENT_URI,\n                        true, mWallpaperObserver)\n        context.contentResolver\n                .registerContentObserver(StyleContract.AdvanceWallpaper.CONTENT_URI,\n                        true, mWallpaperObserver)\n\n        sourcesObservable.registerObserver(sourceObserver)\n    }\n\n    override fun registerObserver(observer: DefaultObserver<Void>) {\n        synchronized(mObserverSet) {\n            mObserverSet.add(observer)\n        }\n    }\n\n    override fun unregisterObserver(observer: DefaultObserver<Void>) {\n        synchronized(mObserverSet) {\n            mObserverSet.remove(observer)\n        }\n    }\n\n\n    private fun notifyObserver() {\n        for (observer in mObserverSet) {\n            observer.onNext(null)\n            observer.onComplete()\n        }\n    }\n\n}"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/repository/AdvanceWallpaperDataRepository.kt",
    "content": "package com.yalin.style.data.repository\n\nimport android.content.Context\nimport com.yalin.style.data.entity.mapper.AdvanceWallpaperEntityMapper\nimport com.yalin.style.data.extensions.DelegateExt\nimport com.yalin.style.data.repository.datasource.AdvanceWallpaperDataStoreFactory\nimport com.yalin.style.domain.AdvanceWallpaper\nimport com.yalin.style.domain.GalleryWallpaper\nimport com.yalin.style.domain.Wallpaper\nimport com.yalin.style.domain.repository.WallpaperRepository\nimport io.reactivex.Observable\nimport java.io.InputStream\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n/**\n * @author jinyalin\n * @since 2017/7/27.\n */\n@Singleton\nclass AdvanceWallpaperDataRepository\n@Inject constructor(val context: Context,\n                    val factory: AdvanceWallpaperDataStoreFactory,\n                    val wallpaperMapper: AdvanceWallpaperEntityMapper,\n                    val styleRepository: StyleWallpaperDataRepository)\n    : WallpaperRepository {\n    var needRollback: Boolean by DelegateExt.preferences(context, \"is_need_rollback\",\n            false)\n\n    override fun getWallpaper(): Observable<Wallpaper> {\n        return Observable.create<Wallpaper> { emitter ->\n            emitter.onNext(wallpaperMapper.mapToWallpaper(factory.create().getWallpaperEntity()))\n            emitter.onComplete()\n        }\n    }\n\n    override fun switchWallpaper(): Observable<Wallpaper> {\n        return Observable.create<Wallpaper> { emitter ->\n            emitter.onError(IllegalStateException(\n                    \"StyleWallpaperRepository have not gallery wallpapers.\"))\n        }\n    }\n\n    override fun openInputStream(wallpaperId: String?): Observable<InputStream> {\n        var id: String? = null\n        styleRepository.wallpaper.subscribe(\n                { wallpaper -> id = wallpaper.wallpaperId })\n\n        return styleRepository.openInputStream(id)\n    }\n\n    override fun getWallpaperCount(): Observable<Int> {\n        return Observable.create<Int> { emitter ->\n            emitter.onNext(1)\n            emitter.onComplete()\n        }\n    }\n\n    override fun likeWallpaper(wallpaperId: String?): Observable<Boolean> {\n        return Observable.create<Boolean> { emitter ->\n            emitter.onError(IllegalStateException(\n                    \"StyleWallpaperRepository have not gallery wallpapers.\"))\n        }\n    }\n\n    override fun addGalleryWallpaperUris(uris: MutableList<GalleryWallpaper>?): Observable<Boolean> {\n        return Observable.create<Boolean> { emitter ->\n            emitter.onError(IllegalStateException(\n                    \"StyleWallpaperRepository have not gallery wallpapers.\"))\n        }\n    }\n\n    override fun removeGalleryWallpaperUris(uris: MutableList<GalleryWallpaper>?): Observable<Boolean> {\n        return Observable.create<Boolean> { emitter ->\n            emitter.onError(IllegalStateException(\n                    \"StyleWallpaperRepository have not gallery wallpapers.\"))\n        }\n    }\n\n    override fun getGalleryWallpapers(): Observable<List<GalleryWallpaper>> {\n        return Observable.create<List<GalleryWallpaper>> { emitter ->\n            emitter.onError(IllegalStateException(\n                    \"StyleWallpaperRepository have not gallery wallpapers.\"))\n        }\n    }\n\n    override fun getAdvanceWallpapers(): Observable<List<AdvanceWallpaper>> {\n        return factory.create().getAdvanceWallpapers().map(wallpaperMapper::transformList)\n    }\n\n    override fun loadAdvanceWallpapers(): Observable<List<AdvanceWallpaper>> {\n        return factory.createRemoteDataStore().getAdvanceWallpapers()\n                .map(wallpaperMapper::transformList)\n    }\n\n    override fun downloadAdvanceWallpaper(wallpaperId: String): Observable<Long> {\n        return factory.createRemoteDataStore().downloadWallpaper(wallpaperId)\n    }\n\n    override fun selectAdvanceWallpaper(wallpaperId: String, tempSelect: Boolean):\n            Observable<Boolean> {\n        return factory.create().selectWallpaper(wallpaperId, tempSelect)\n    }\n\n    override fun getAdvanceWallpaper(): AdvanceWallpaper {\n        return wallpaperMapper.transform(factory.create().getWallpaperEntity())\n    }\n\n    override fun readAdvanceAd(wallpaperId: String): Observable<Boolean> {\n        return factory.create().readAd(wallpaperId)\n    }\n\n    override fun foreNow(wallpaperUri: String?): Observable<Boolean> {\n        return Observable.create<Boolean> { emitter ->\n            emitter.onError(IllegalStateException(\n                    \"StyleWallpaperRepository have not gallery wallpapers.\"))\n        }\n    }\n\n    override fun setGalleryUpdateInterval(intervalMin: Int): Observable<Boolean> {\n        return Observable.create<Boolean> { emitter ->\n            emitter.onError(IllegalStateException(\n                    \"StyleWallpaperRepository have not gallery wallpapers.\"))\n        }\n    }\n\n    override fun getGalleryUpdateInterval(): Observable<Int> {\n        return Observable.create<Int> { emitter ->\n            emitter.onError(IllegalStateException(\n                    \"StyleWallpaperRepository have not gallery wallpapers.\"))\n        }\n    }\n\n    fun markRollback() {\n        needRollback = true\n    }\n\n    fun maybeRollback() {\n        if (needRollback) {\n            factory.create().rollback()\n        }\n    }\n}"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/repository/GalleryWallpaperDataRepository.kt",
    "content": "package com.yalin.style.data.repository\n\nimport com.yalin.style.data.entity.mapper.WallpaperEntityMapper\nimport com.yalin.style.data.repository.datasource.GalleryWallpaperDataStoreFactory\nimport com.yalin.style.domain.AdvanceWallpaper\nimport com.yalin.style.domain.GalleryWallpaper\nimport com.yalin.style.domain.Wallpaper\nimport com.yalin.style.domain.repository.WallpaperRepository\nimport io.reactivex.Observable\nimport java.io.InputStream\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n/**\n * @author jinyalin\n * @since 2017/5/24.\n */\n@Singleton\nclass GalleryWallpaperDataRepository @Inject\nconstructor(val galleryWallpaperDataStoreFactory: GalleryWallpaperDataStoreFactory,\n            val wallpaperEntityMapper: WallpaperEntityMapper) :\n        WallpaperRepository {\n\n    override fun getWallpaper(): Observable<Wallpaper> =\n            galleryWallpaperDataStoreFactory.create()\n                    .wallPaperEntity.map(wallpaperEntityMapper::transform)\n\n    override fun switchWallpaper(): Observable<Wallpaper> =\n            galleryWallpaperDataStoreFactory.create()\n                    .switchWallPaperEntity().map(wallpaperEntityMapper::transform)\n\n    override fun openInputStream(wallpaperId: String?): Observable<InputStream> =\n            galleryWallpaperDataStoreFactory.create()\n                    .openInputStream(wallpaperId)\n\n    override fun getWallpaperCount(): Observable<Int> =\n            galleryWallpaperDataStoreFactory.create()\n                    .wallpaperCount\n\n    override fun likeWallpaper(wallpaperId: String): Observable<Boolean> =\n            galleryWallpaperDataStoreFactory.create()\n                    .likeWallpaper(wallpaperId)\n\n    override fun addGalleryWallpaperUris(uris: List<GalleryWallpaper>): Observable<Boolean> =\n            galleryWallpaperDataStoreFactory.create()\n                    .addGalleryWallpaperUris(uris)\n\n    override fun removeGalleryWallpaperUris(uris: List<GalleryWallpaper>): Observable<Boolean> =\n            galleryWallpaperDataStoreFactory.create()\n                    .removeGalleryWallpaperUris(uris)\n\n    override fun getGalleryWallpapers(): Observable<List<GalleryWallpaper>> =\n            galleryWallpaperDataStoreFactory.create()\n                    .getGalleryWallpaperUris().map(wallpaperEntityMapper::transformGalleryWallpaper)\n\n    override fun getAdvanceWallpapers(): Observable<List<AdvanceWallpaper>> {\n        return Observable.create<List<AdvanceWallpaper>> { emitter ->\n            emitter.onError(IllegalStateException(\n                    \"GalleryWallpaperDataRepository have not advance wallpapers.\"))\n        }\n    }\n\n    override fun loadAdvanceWallpapers(): Observable<List<AdvanceWallpaper>> {\n        return Observable.create<List<AdvanceWallpaper>> { emitter ->\n            emitter.onError(IllegalStateException(\n                    \"GalleryWallpaperDataRepository cannot load advance wallpapers.\"))\n        }\n    }\n\n    override fun downloadAdvanceWallpaper(wallpaperId: String): Observable<Long> {\n        return Observable.create<Long> { emitter ->\n            emitter.onError(IllegalStateException(\n                    \"GalleryWallpaperDataRepository cannot download advance wallpapers.\"))\n        }\n    }\n\n    override fun selectAdvanceWallpaper(wallpaperId: String, tempSelect: Boolean):\n            Observable<Boolean> {\n        return Observable.create<Boolean> { emitter ->\n            emitter.onError(IllegalStateException(\n                    \"GalleryWallpaperDataRepository cannot select advance wallpapers.\"))\n        }\n    }\n\n    override fun getAdvanceWallpaper(): AdvanceWallpaper {\n        throw IllegalStateException(\n                \"GalleryWallpaperDataRepository cannot get advance wallpapers.\")\n    }\n\n    override fun readAdvanceAd(wallpaperId: String?): Observable<Boolean> {\n        throw IllegalStateException(\n                \"GalleryWallpaperDataRepository cannot read advance ad.\")\n    }\n\n    override fun foreNow(wallpaperUri: String): Observable<Boolean> =\n            galleryWallpaperDataStoreFactory.create().forceNow(wallpaperUri)\n\n    override fun setGalleryUpdateInterval(intervalMin: Int): Observable<Boolean> =\n            galleryWallpaperDataStoreFactory.create().setUpdateIntervalMin(intervalMin)\n\n    override fun getGalleryUpdateInterval(): Observable<Int> =\n            galleryWallpaperDataStoreFactory.create().getUpdateIntervalMin()\n}"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/repository/SourcesDataRepository.kt",
    "content": "package com.yalin.style.data.repository\n\nimport android.content.Context\nimport com.yalin.style.data.entity.mapper.WallpaperEntityMapper\nimport com.yalin.style.data.repository.datasource.SourcesDataStoreFactory\nimport com.yalin.style.domain.Source\nimport com.yalin.style.domain.repository.SourcesRepository\nimport com.yalin.style.domain.repository.SourcesRepository.SOURCE_ID_ADVANCE\nimport com.yalin.style.domain.repository.SourcesRepository.SOURCE_ID_CUSTOM\nimport com.yalin.style.domain.repository.WallpaperRepository\nimport io.reactivex.Observable\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n/**\n * @author jinyalin\n * @since 2017/5/23.\n */\n@Singleton\nclass SourcesDataRepository @Inject\nconstructor(val context: Context,\n            val sourcesDataStoreFactory: SourcesDataStoreFactory,\n            val wallpaperEntityMapper: WallpaperEntityMapper,\n            var styleWallpaperDataRepository: StyleWallpaperDataRepository,\n            var customWallpaperDataRepository: GalleryWallpaperDataRepository,\n            var advanceWallpaperDataRepository: AdvanceWallpaperDataRepository) : SourcesRepository {\n    override fun getSources(): Observable<List<Source>> {\n        val sourcesDataStore = sourcesDataStoreFactory.create()\n        return sourcesDataStore.getSources().map(wallpaperEntityMapper::transformSources)\n    }\n\n    override fun selectSource(sourceId: Int, tempSelect: Boolean): Observable<Boolean> {\n        val sourcesDataStore = sourcesDataStoreFactory.create()\n        return sourcesDataStore.selectSource(sourceId, tempSelect)\n    }\n\n    override fun getWallpaperRepository(): WallpaperRepository {\n        val sourcesDataStore = sourcesDataStoreFactory.create()\n\n        when (sourcesDataStore.getUsedSourceId()) {\n            SOURCE_ID_CUSTOM -> return customWallpaperDataRepository\n            SOURCE_ID_ADVANCE -> return advanceWallpaperDataRepository\n            else -> return styleWallpaperDataRepository\n        }\n    }\n\n    override fun getSelectedSource(): Int {\n        val sourcesDataStore = sourcesDataStoreFactory.create()\n        return sourcesDataStore.getUsedSourceId()\n    }\n}"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/repository/StyleWallpaperDataRepository.java",
    "content": "package com.yalin.style.data.repository;\n\nimport android.content.Context;\nimport android.text.TextUtils;\n\nimport com.fernandocejas.arrow.checks.Preconditions;\nimport com.yalin.style.data.entity.mapper.WallpaperEntityMapper;\nimport com.yalin.style.data.repository.datasource.WallpaperDataStore;\nimport com.yalin.style.data.repository.datasource.StyleWallpaperDataStoreFactory;\nimport com.yalin.style.data.repository.datasource.sync.SyncHelper;\nimport com.yalin.style.data.repository.datasource.sync.account.Account;\nimport com.yalin.style.domain.AdvanceWallpaper;\nimport com.yalin.style.domain.GalleryWallpaper;\nimport com.yalin.style.domain.Wallpaper;\nimport com.yalin.style.domain.repository.WallpaperRepository;\n\n\nimport java.io.InputStream;\nimport java.util.List;\n\nimport javax.inject.Inject;\nimport javax.inject.Singleton;\n\nimport io.reactivex.Observable;\n\n/**\n * @author jinyalin\n * @since 2017/4/18.\n */\n@Singleton\npublic class StyleWallpaperDataRepository implements WallpaperRepository {\n\n    private final Context context;\n    private final StyleWallpaperDataStoreFactory styleWallpaperDataStoreFactory;\n    private final WallpaperEntityMapper wallpaperEntityMapper;\n\n    @Inject\n    StyleWallpaperDataRepository(Context context,\n                                 StyleWallpaperDataStoreFactory styleWallpaperDataStoreFactory,\n                                 WallpaperEntityMapper wallpaperEntityMapper) {\n        this.context = context;\n        this.styleWallpaperDataStoreFactory = styleWallpaperDataStoreFactory;\n        this.wallpaperEntityMapper = wallpaperEntityMapper;\n\n        Account.createSyncAccount(context);\n        SyncHelper.updateSyncInterval(context);\n    }\n\n    @Override\n    public Observable<Wallpaper> getWallpaper() {\n        final WallpaperDataStore dataStore = styleWallpaperDataStoreFactory.create();\n        return dataStore.getWallPaperEntity().map(wallpaperEntityMapper::transform);\n    }\n\n    @Override\n    public Observable<Wallpaper> switchWallpaper() {\n        final WallpaperDataStore dataStore = styleWallpaperDataStoreFactory.create();\n        return dataStore.switchWallPaperEntity().map(wallpaperEntityMapper::transform);\n    }\n\n    @Override\n    public Observable<InputStream> openInputStream(String wallpaperId) {\n        Preconditions.checkArgument(!TextUtils.isEmpty(wallpaperId), \"WallpaperId cannot be null\");\n        final WallpaperDataStore dataStore = styleWallpaperDataStoreFactory.createDbDataStore();\n        return dataStore.openInputStream(wallpaperId);\n    }\n\n    @Override\n    public Observable<Integer> getWallpaperCount() {\n        final WallpaperDataStore dataStore = styleWallpaperDataStoreFactory.create();\n        return dataStore.getWallpaperCount();\n    }\n\n    @Override\n    public Observable<Boolean> likeWallpaper(String wallpaperId) {\n        Preconditions.checkArgument(!TextUtils.isEmpty(wallpaperId), \"WallpaperId cannot be null\");\n        final WallpaperDataStore dataStore = styleWallpaperDataStoreFactory.createDbDataStore();\n        return dataStore.likeWallpaper(wallpaperId);\n    }\n\n    @Override\n    public Observable<Boolean> addGalleryWallpaperUris(List<GalleryWallpaper> uris) {\n        return Observable.create(emitter ->\n                emitter.onError(new IllegalStateException(\n                        \"StyleWallpaperRepository can not add gallery wallpaper.\"))\n        );\n    }\n\n    @Override\n    public Observable<Boolean> removeGalleryWallpaperUris(List<GalleryWallpaper> uris) {\n        return Observable.create(emitter ->\n                emitter.onError(new IllegalStateException(\n                        \"StyleWallpaperRepository can not remove gallery wallpaper.\"))\n        );\n    }\n\n    @Override\n    public Observable<List<GalleryWallpaper>> getGalleryWallpapers() {\n        return Observable.create(emitter ->\n                emitter.onError(new IllegalStateException(\n                        \"StyleWallpaperRepository have not gallery wallpapers.\"))\n        );\n    }\n\n    @Override\n    public Observable<List<AdvanceWallpaper>> getAdvanceWallpapers() {\n        return Observable.create(emitter ->\n                emitter.onError(new IllegalStateException(\n                        \"StyleWallpaperRepository cannot get advance wallpapers.\"))\n        );\n    }\n\n    @Override\n    public Observable<List<AdvanceWallpaper>> loadAdvanceWallpapers() {\n        return Observable.create(emitter ->\n                emitter.onError(new IllegalStateException(\n                        \"StyleWallpaperRepository cannot load advance wallpapers.\"))\n        );\n    }\n\n    @Override\n    public Observable<Long> downloadAdvanceWallpaper(String wallpaperId) {\n        return Observable.create(emitter ->\n                emitter.onError(new IllegalStateException(\n                        \"StyleWallpaperRepository cannot download advance wallpapers.\"))\n        );\n    }\n\n    @Override\n    public Observable<Boolean> selectAdvanceWallpaper(String wallpaperId, boolean tempSelect) {\n        return Observable.create(emitter ->\n                emitter.onError(new IllegalStateException(\n                        \"StyleWallpaperRepository cannot select advance wallpapers.\"))\n        );\n    }\n\n    public AdvanceWallpaper getAdvanceWallpaper() {\n        throw new IllegalStateException(\n                \"StyleWallpaperRepository cannot get advance wallpapers.\");\n    }\n\n    @Override\n    public Observable<Boolean> readAdvanceAd(String wallpaperId) {\n        throw new IllegalStateException(\n                \"StyleWallpaperRepository cannot read advance ad.\");\n    }\n\n    @Override\n    public Observable<Boolean> foreNow(String wallpaperUri) {\n        return Observable.create(emitter ->\n                emitter.onError(new IllegalStateException(\n                        \"StyleWallpaperRepository can not foreNow gallery wallpaper.\"))\n        );\n    }\n\n    @Override\n    public Observable<Boolean> setGalleryUpdateInterval(int intervalMin) {\n        return Observable.create(emitter ->\n                emitter.onError(new IllegalStateException(\n                        \"StyleWallpaperRepository can not setGalleryUpdateInterval gallery wallpaper.\"))\n        );\n    }\n\n    @Override\n    public Observable<Integer> getGalleryUpdateInterval() {\n        return Observable.create(emitter ->\n                emitter.onError(new IllegalStateException(\n                        \"StyleWallpaperRepository can not getGalleryUpdateInterval gallery wallpaper.\"))\n        );\n    }\n\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/repository/datasource/AdvanceWallpaperDataStore.kt",
    "content": "package com.yalin.style.data.repository.datasource\n\nimport com.yalin.style.data.entity.AdvanceWallpaperEntity\nimport io.reactivex.Observable\n\n/**\n * @author jinyalin\n * @since 2017/7/28.\n */\ninterface AdvanceWallpaperDataStore {\n\n    fun getWallpaperEntity(): AdvanceWallpaperEntity\n\n    fun getAdvanceWallpapers(): Observable<List<AdvanceWallpaperEntity>>\n\n    fun selectWallpaper(wallpaperId: String, tempSelect: Boolean): Observable<Boolean>\n\n    fun downloadWallpaper(wallpaperId: String): Observable<Long>\n\n    fun readAd(wallpaperId: String): Observable<Boolean>\n\n    fun rollback()\n}"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/repository/datasource/AdvanceWallpaperDataStoreFactory.kt",
    "content": "package com.yalin.style.data.repository.datasource\n\nimport android.content.Context\nimport com.yalin.style.data.cache.AdvanceWallpaperCache\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n/**\n * @author jinyalin\n * @since 2017/7/28.\n */\n@Singleton\nclass AdvanceWallpaperDataStoreFactory @Inject\nconstructor(val context: Context,\n            val advanceWallpaperCache: AdvanceWallpaperCache) {\n\n    fun create(): AdvanceWallpaperDataStore {\n        return AdvanceWallpaperDataStoreImpl(context, advanceWallpaperCache)\n    }\n\n    fun createRemoteDataStore(): AdvanceWallpaperDataStore {\n        return RemoteAdvanceWallpaperDataStore(context,\n                AdvanceWallpaperDataStoreImpl(context, advanceWallpaperCache))\n    }\n\n    fun onDataRefresh() {\n        advanceWallpaperCache.evictAll()\n    }\n}"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/repository/datasource/AdvanceWallpaperDataStoreImpl.kt",
    "content": "package com.yalin.style.data.repository.datasource\n\nimport android.content.ContentValues\nimport android.content.Context\nimport android.database.Cursor\nimport com.yalin.style.data.cache.AdvanceWallpaperCache\nimport com.yalin.style.data.entity.AdvanceWallpaperEntity\nimport com.yalin.style.data.repository.datasource.provider.StyleContract\nimport com.yalin.style.data.utils.notifyChange\nimport io.reactivex.Observable\n\n/**\n * @author jinyalin\n * @since 2017/7/28.\n */\nclass AdvanceWallpaperDataStoreImpl(val context: Context,\n                                    val advanceWallpaperCache: AdvanceWallpaperCache)\n    : AdvanceWallpaperDataStore {\n    companion object {\n        val TAG = \"AdvanceDataStore\"\n\n        val DEFAULT_WALLPAPER_ID = \"-1\"\n    }\n\n    @Synchronized override fun getWallpaperEntity(): AdvanceWallpaperEntity {\n        if (!advanceWallpaperCache.isDirty()) {\n            val selected = advanceWallpaperCache.getSelectedWallpaper()\n            if (selected != null) {\n                return selected\n            }\n        }\n        var cursor: Cursor? = null\n        var entity: AdvanceWallpaperEntity? = null\n        try {\n            val contentResolver = context.contentResolver\n            cursor = contentResolver.query(StyleContract.AdvanceWallpaper.CONTENT_SELECTED_URI,\n                    null, null, null, null)\n            if (cursor != null && cursor.moveToFirst()) {\n                entity = AdvanceWallpaperEntity.readEntityFromCursor(cursor)\n            }\n        } finally {\n            if (cursor != null) {\n                cursor.close()\n            }\n        }\n        if (entity == null) {\n            entity = buildDefaultWallpaper()\n        }\n        return entity\n    }\n\n    override fun getAdvanceWallpapers(): Observable<List<AdvanceWallpaperEntity>> {\n        return createAdvanceWallpapersFromDB().doOnNext(advanceWallpaperCache::put)\n    }\n\n    override fun selectWallpaper(wallpaperId: String, tempSelect: Boolean):\n            Observable<Boolean> {\n        return Observable.create { emitter ->\n            val selectValue = ContentValues()\n            selectValue.put(StyleContract.AdvanceWallpaper.COLUMN_NAME_SELECTED, 1)\n            val unselectedValue = ContentValues()\n            unselectedValue.put(StyleContract.AdvanceWallpaper.COLUMN_NAME_SELECTED, 0)\n            // unselected old\n            context.contentResolver.update(\n                    StyleContract.AdvanceWallpaper.CONTENT_SELECTED_URI,\n                    unselectedValue, null, null)\n            // select new\n            val uri = StyleContract.AdvanceWallpaper.buildWallpaperUri(wallpaperId)\n            val selectedCount = context.contentResolver.update(uri, selectValue, null, null)\n            if (selectedCount > 0) {\n                emitter.onNext(true)\n            } else {\n                emitter.onNext(false)\n            }\n            synchronized(advanceWallpaperCache) {\n                if (!advanceWallpaperCache.isDirty()) {\n                    advanceWallpaperCache.selectWallpaper(wallpaperId)\n                }\n            }\n\n            emitter.onComplete()\n            notifyChange(context, StyleContract.AdvanceWallpaper.CONTENT_URI)\n        }\n    }\n\n    override fun downloadWallpaper(wallpaperId: String): Observable<Long> {\n        throw UnsupportedOperationException(\"Local data store not support download wallpaper.\")\n    }\n\n    fun loadWallpaperEntity(wallpaperId: String): AdvanceWallpaperEntity {\n        var entity: AdvanceWallpaperEntity? = null\n        synchronized(advanceWallpaperCache) {\n            if (!advanceWallpaperCache.isDirty()\n                    && advanceWallpaperCache.isCached(wallpaperId)) {\n                entity = advanceWallpaperCache.getWallpaper(wallpaperId)\n            } else {\n                entity = loadWallpaperEntityFromDB(wallpaperId)\n            }\n            if (entity == null) {\n                entity = loadWallpaperEntityFromDB(wallpaperId)\n            }\n        }\n        return entity!!\n    }\n\n    override fun readAd(wallpaperId: String): Observable<Boolean> {\n        return Observable.create { emitter ->\n            if (advanceWallpaperCache.isCached(wallpaperId)) {\n                advanceWallpaperCache.readAd(wallpaperId)\n            }\n\n            val uri = StyleContract.AdvanceWallpaper.buildWallpaperUri(wallpaperId)\n            val contentValues = ContentValues()\n            contentValues.put(StyleContract.AdvanceWallpaper.COLUMN_NAME_NEED_AD, 0)\n            context.contentResolver.update(uri, contentValues, null, null)\n            emitter.onNext(true)\n            emitter.onComplete()\n        }\n    }\n\n    override fun rollback() {\n        advanceWallpaperCache.evictAll()\n\n        val unselectedValue = ContentValues()\n        unselectedValue.put(StyleContract.AdvanceWallpaper.COLUMN_NAME_SELECTED, 0)\n        // unselected all\n        context.contentResolver.update(\n                StyleContract.AdvanceWallpaper.CONTENT_SELECTED_URI,\n                unselectedValue, null, null)\n    }\n\n    private fun loadWallpaperEntityFromDB(wallpaperId: String): AdvanceWallpaperEntity? {\n        var cursor: Cursor? = null\n        try {\n            val contentResolver = context.contentResolver\n            cursor = contentResolver.query(StyleContract.AdvanceWallpaper\n                    .buildWallpaperUri(wallpaperId),\n                    null, null, null, null)\n            if (cursor != null && cursor.moveToFirst()) {\n                val entity = AdvanceWallpaperEntity.readEntityFromCursor(cursor)\n                return entity\n            }\n            return null\n        } finally {\n            cursor?.close()\n        }\n    }\n\n    private fun createAdvanceWallpapersFromDB(): Observable<List<AdvanceWallpaperEntity>> {\n        return Observable.create { emitter ->\n            var cursor: Cursor? = null\n            val validWallpapers = ArrayList<AdvanceWallpaperEntity>()\n            try {\n                val contentResolver = context.contentResolver\n                cursor = contentResolver.query(StyleContract.AdvanceWallpaper.CONTENT_URI,\n                        null, null, null, null)\n                validWallpapers.addAll(AdvanceWallpaperEntity.readCursor(cursor))\n            } finally {\n                cursor?.close()\n            }\n\n            emitter.onNext(validWallpapers)\n            emitter.onComplete()\n        }\n    }\n\n    private fun buildDefaultWallpaper(): AdvanceWallpaperEntity {\n        val entity = AdvanceWallpaperEntity()\n        entity.isDefault = true\n        entity.id = -1\n        entity.wallpaperId = DEFAULT_WALLPAPER_ID\n        entity.author = \"Yalin\"\n        entity.link = \"kinglloy.com\"\n        entity.name = \"Rainbow\"\n\n        return entity\n    }\n}"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/repository/datasource/CacheWallpaperDataStore.java",
    "content": "package com.yalin.style.data.repository.datasource;\n\nimport com.yalin.style.data.cache.WallpaperCache;\nimport com.yalin.style.data.entity.WallpaperEntity;\n\nimport com.yalin.style.data.exception.ReswitchException;\nimport com.yalin.style.data.lock.OpenInputStreamLock;\n\nimport java.io.InputStream;\n\nimport io.reactivex.Observable;\n\n/**\n * @author jinyalin\n * @since 2017/4/20.\n */\n\npublic class CacheWallpaperDataStore implements WallpaperDataStore {\n\n    private final WallpaperCache wallpaperCache;\n    private final OpenInputStreamLock openInputStreamLock;\n\n    public CacheWallpaperDataStore(WallpaperCache wallpaperCache,\n                                   OpenInputStreamLock openInputStreamLock) {\n        this.wallpaperCache = wallpaperCache;\n        this.openInputStreamLock = openInputStreamLock;\n    }\n\n    @Override\n    public Observable<WallpaperEntity> getWallPaperEntity() {\n        return wallpaperCache.get();\n    }\n\n    @Override\n    public Observable<WallpaperEntity> switchWallPaperEntity() {\n        if (openInputStreamLock.obtain()) {\n            openInputStreamLock.release();\n            return wallpaperCache.getNext();\n        } else {\n            return Observable.create(emitter ->\n                    emitter.onError(new ReswitchException()));\n        }\n    }\n\n    @Override\n    public Observable<InputStream> openInputStream(String wallpaperId) {\n        throw new UnsupportedOperationException(\"Cache data store not support open input stream.\");\n    }\n\n    @Override\n    public Observable<Integer> getWallpaperCount() {\n        return wallpaperCache.getWallpaperCount();\n    }\n\n    @Override\n    public Observable<Boolean> likeWallpaper(String wallpaperId) {\n        throw new UnsupportedOperationException(\"Cache data store not support open input stream.\");\n    }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/repository/datasource/DbWallpaperDataStore.java",
    "content": "package com.yalin.style.data.repository.datasource;\n\nimport android.content.ContentResolver;\nimport android.content.ContentValues;\nimport android.content.Context;\nimport android.database.Cursor;\n\nimport com.yalin.style.data.cache.WallpaperCache;\nimport com.yalin.style.data.entity.WallpaperEntity;\nimport com.yalin.style.data.exception.LikeException;\nimport com.yalin.style.data.lock.LikeWallpaperLock;\nimport com.yalin.style.data.lock.OpenInputStreamLock;\nimport com.yalin.style.data.log.LogUtil;\nimport com.yalin.style.data.repository.datasource.provider.StyleContract;\n\nimport com.yalin.style.data.repository.datasource.provider.StyleContract.Wallpaper;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.LinkedList;\nimport java.util.Queue;\n\nimport io.reactivex.Observable;\n\n/**\n * @author jinyalin\n * @since 2017/4/18.\n */\n\npublic class DbWallpaperDataStore implements WallpaperDataStore {\n\n    private static final String TAG = \"DbWallpaperDataStore\";\n\n    public static final String DEFAULT_WALLPAPER_ID = \"-1\";\n\n    private final Context context;\n\n    private final WallpaperCache wallpaperCache;\n    private final OpenInputStreamLock openInputStreamLock;\n    private final LikeWallpaperLock likeWallpaperLock;\n\n    public DbWallpaperDataStore(Context context, WallpaperCache wallpaperCache,\n                                OpenInputStreamLock openInputStreamLock,\n                                LikeWallpaperLock likeWallpaperLock) {\n        this.context = context;\n        this.wallpaperCache = wallpaperCache;\n        this.openInputStreamLock = openInputStreamLock;\n        this.likeWallpaperLock = likeWallpaperLock;\n    }\n\n    @Override\n    public Observable<WallpaperEntity> getWallPaperEntity() {\n        return createEntitiesObservable().doOnNext(wallpaperCache::put).map(Queue::peek);\n    }\n\n    @Override\n    public Observable<WallpaperEntity> switchWallPaperEntity() {\n        return getWallPaperEntity();\n    }\n\n    @Override\n    public Observable<InputStream> openInputStream(String wallpaperId) {\n        return Observable.create(emitter -> {\n            try {\n                openInputStreamLock.obtain();\n                InputStream inputStream;\n                if (DEFAULT_WALLPAPER_ID.equals(wallpaperId)) {\n                    inputStream = context.getAssets().open(\"painterly-architectonic.jpg\");\n                } else {\n                    inputStream = context.getContentResolver().openInputStream(\n                            StyleContract.Wallpaper.buildWallpaperUri(wallpaperId));\n                }\n                emitter.onNext(inputStream);\n                emitter.onComplete();\n            } catch (IOException e) {\n                LogUtil.D(TAG, \"Open input stream failed for wallpaperId : \" + wallpaperId);\n                emitter.onError(e);\n            } finally {\n                openInputStreamLock.release();\n            }\n        });\n    }\n\n    @Override\n    public Observable<Integer> getWallpaperCount() {\n        return Observable.create(emitter -> {\n            Cursor cursor = null;\n            int count = 0;\n            ContentResolver contentResolver = context.getContentResolver();\n            cursor = contentResolver.query(StyleContract.Wallpaper.CONTENT_URI,\n                    null, null, null, null);\n            if (cursor != null) {\n                // contain default\n                count = cursor.getCount() + 1;\n                cursor.close();\n            }\n            emitter.onNext(count);\n            emitter.onComplete();\n        });\n    }\n\n    @Override\n    public Observable<Boolean> likeWallpaper(String wallpaperId) {\n        if (!likeWallpaperLock.obtain()) {\n            return Observable.create(emitter -> emitter.onError(new LikeException()));\n        }\n        wallpaperCache.likeWallpaper(wallpaperId);\n        return Observable.create(emitter -> {\n            Cursor cursor = null;\n            try {\n                ContentResolver contentResolver = context.getContentResolver();\n                cursor = contentResolver.query(StyleContract.Wallpaper.buildWallpaperUri(wallpaperId),\n                        null, null, null, null);\n                if (cursor != null && cursor.moveToFirst()) {\n                    WallpaperEntity entity = WallpaperEntity.readEntityFromCursor(cursor);\n                    entity.liked = !entity.liked;\n                    int columnCount = contentResolver\n                            .update(StyleContract.Wallpaper.buildWallpaperLikeUri(wallpaperId),\n                                    buildKeepContentValue(entity), null, null);\n                    if (columnCount > 0) {\n                        emitter.onNext(entity.liked);\n                    } else {\n                        throw new LikeException();\n                    }\n                } else {\n                    throw new LikeException();\n                }\n            } catch (Exception e) {\n                emitter.onError(e);\n            } finally {\n                likeWallpaperLock.release();\n                if (cursor != null) {\n                    cursor.close();\n                }\n            }\n        });\n    }\n\n    private Observable<Queue<WallpaperEntity>> createEntitiesObservable() {\n        return Observable.create(emitter -> {\n            Cursor cursor = null;\n            Queue<WallpaperEntity> validWallpapers = new LinkedList<>();\n            try {\n                ContentResolver contentResolver = context.getContentResolver();\n                cursor = contentResolver.query(Wallpaper.CONTENT_URI,\n                        null, null, null, null);\n                validWallpapers.addAll(WallpaperEntity.readCursor(context, cursor));\n            } finally {\n                if (cursor != null) {\n                    cursor.close();\n                }\n            }\n            // from db all wallpaper can be liked\n            for (WallpaperEntity entity : validWallpapers) {\n                entity.canLike = true;\n            }\n\n            validWallpapers.add(buildDefaultWallpaper());\n\n            emitter.onNext(validWallpapers);\n            emitter.onComplete();\n        });\n    }\n\n\n    public static WallpaperEntity buildDefaultWallpaper() {\n        WallpaperEntity wallpaperEntity = new WallpaperEntity();\n        wallpaperEntity.id = -1;\n        wallpaperEntity.attribution = \"kinglloy.com\";\n        wallpaperEntity.byline = \"Lyubov Popova, 1918\";\n        wallpaperEntity.imageUri = \"imageUri\";\n        wallpaperEntity.title = \"Painterly Architectonic\";\n        wallpaperEntity.wallpaperId = DEFAULT_WALLPAPER_ID;\n        wallpaperEntity.liked = false;\n        wallpaperEntity.isDefault = true;\n        wallpaperEntity.canLike = false;\n        return wallpaperEntity;\n    }\n\n    private ContentValues buildKeepContentValue(WallpaperEntity entity) {\n        ContentValues contentValues = new ContentValues();\n        contentValues.put(Wallpaper.COLUMN_NAME_LIKED, entity.liked ? 1 : 0);\n        return contentValues;\n    }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/repository/datasource/GalleryWallpaperDataStore.kt",
    "content": "package com.yalin.style.data.repository.datasource\n\nimport android.content.ContentProviderOperation\nimport android.content.Context\nimport android.database.Cursor\nimport android.net.Uri\nimport android.os.Build\nimport android.text.TextUtils\nimport android.text.format.DateUtils\nimport com.fernandocejas.arrow.checks.Preconditions\nimport com.yalin.style.data.R\nimport com.yalin.style.data.cache.GalleryWallpaperCache\nimport com.yalin.style.data.entity.GalleryWallpaperEntity\nimport com.yalin.style.data.entity.WallpaperEntity\nimport com.yalin.style.data.exception.ReswitchException\nimport com.yalin.style.data.extensions.DelegateExt\nimport com.yalin.style.data.lock.OpenInputStreamLock\nimport com.yalin.style.data.repository.datasource.io.GalleryWallpapersHandler\nimport com.yalin.style.data.repository.datasource.io.RemoveGalleryWallpapersHandler\nimport com.yalin.style.data.repository.datasource.provider.StyleContract\nimport com.yalin.style.data.repository.datasource.sync.gallery.GalleryScheduleService\nimport com.yalin.style.data.utils.getCacheFileForUri\nimport com.yalin.style.data.utils.getImagesFromTreeUri\nimport com.yalin.style.data.utils.notifyChange\nimport com.yalin.style.domain.GalleryWallpaper\nimport io.reactivex.Observable\nimport java.io.FileInputStream\nimport java.io.IOException\nimport java.io.InputStream\nimport java.util.*\nimport kotlin.collections.ArrayList\n\n/**\n * @author jinyalin\n * @since 2017/5/22.\n */\nclass GalleryWallpaperDataStore(val context: Context,\n                                val openInputStreamLock: OpenInputStreamLock,\n                                val galleryWallpaperCache: GalleryWallpaperCache) :\n        WallpaperDataStore {\n\n    var currentGalleryWallpaperId: Long by DelegateExt.preferences(context,\n            GalleryScheduleService.PREF_CURRENT_SHOW_WALLPAPER_ID, -1)\n\n    var rotateIntervalMin: Int by DelegateExt.preferences(context,\n            GalleryScheduleService.PREF_ROTATE_INTERVAL_MIN,\n            GalleryScheduleService.DEFAULT_ROTATE_INTERVAL_MIN)\n\n    override fun getWallPaperEntity(): Observable<WallpaperEntity> {\n        if (galleryWallpaperCache.isCached()) {\n            val entities = galleryWallpaperCache.get()!!\n            return Observable.create { emitter ->\n                emitter.onNext(peekValid(entities))\n                emitter.onComplete()\n            }\n        } else {\n            return createEntitiesObservable().doOnNext(galleryWallpaperCache::put).map { entities ->\n                return@map peekValid(entities)\n            }\n        }\n    }\n\n    override fun switchWallPaperEntity(): Observable<WallpaperEntity> {\n        if (openInputStreamLock.obtain()) {\n            openInputStreamLock.release()\n            return doSwitch()\n        } else {\n            return Observable.create<WallpaperEntity> {\n                emitter ->\n                emitter.onError(ReswitchException())\n            }\n        }\n    }\n\n    override fun openInputStream(wallpaperId: String?): Observable<InputStream> {\n        Preconditions.checkArgument(!TextUtils.isEmpty(wallpaperId))\n        return Observable.create<InputStream> { emitter ->\n            var cursor: Cursor? = null\n            try {\n                openInputStreamLock.obtain()\n                var inputStream: InputStream? = null\n                if (DbWallpaperDataStore.DEFAULT_WALLPAPER_ID == wallpaperId) {\n                    inputStream = context.assets.open(\"painterly-architectonic.jpg\")\n                } else {\n                    cursor = context.contentResolver.query(\n                            StyleContract.GalleryWallpaper.buildGalleryWallpaperUri(\n                                    wallpaperId!!.toLong()), null, null, null, null)\n                    if (cursor != null && cursor.moveToFirst()) {\n                        val uriString = cursor.getString(cursor.getColumnIndex(\n                                StyleContract.GalleryWallpaper.COLUMN_NAME_CUSTOM_URI))\n                        val isTreeUri = cursor.getInt(cursor.getColumnIndex(\n                                StyleContract.GalleryWallpaper.COLUMN_NAME_IS_TREE_URI)) == 1\n\n                        if (isTreeUri && Build.VERSION.SDK_INT >= 21) {\n                            val images = getImagesFromTreeUri(context, Uri.parse(uriString),\n                                    Int.MAX_VALUE)\n                            inputStream = context.contentResolver.openInputStream(\n                                    images[Random().nextInt(images.size)])\n                        } else {\n                            try {\n                                inputStream = context.contentResolver.openInputStream(\n                                        Uri.parse(uriString))\n                            } catch (e: Exception) {\n                                // if cached file exist then use cached file\n                                val cacheFile = getCacheFileForUri(context, uriString)\n                                if ((cacheFile != null && cacheFile.exists())) {\n                                    inputStream = FileInputStream(cacheFile)\n                                } else {\n                                    throw IOException(\"Cannot open gallery uri : \" + uriString)\n                                }\n                            }\n                        }\n                    }\n                }\n                emitter.onNext(inputStream!!)\n                emitter.onComplete()\n            } catch (e: IOException) {\n                emitter.onError(e)\n            } finally {\n                cursor?.close()\n                openInputStreamLock.release()\n            }\n        }\n    }\n\n    override fun getWallpaperCount(): Observable<Int> {\n        return Observable.create { emitter ->\n            var totalCount: Int = 0\n            if (galleryWallpaperCache.isCached()) {\n                totalCount = galleryWallpaperCache.get()!!.size\n            } else {\n                var cursor: Cursor? = null\n                try {\n                    cursor = context.contentResolver.query(StyleContract.GalleryWallpaper.CONTENT_URI,\n                            null, null, null, null)\n                    if (cursor != null) {\n                        totalCount = cursor.count\n                    }\n                } finally {\n                    cursor?.close()\n                }\n            }\n\n            emitter.onNext(totalCount)\n            emitter.onComplete()\n        }\n    }\n\n    override fun likeWallpaper(wallpaperId: String?): Observable<Boolean> {\n        throw UnsupportedOperationException(\"Gallery data store not support like.\")\n    }\n\n    fun addGalleryWallpaperUris(uris: List<GalleryWallpaper>): Observable<Boolean> {\n        return Observable.create<Boolean> { emitter ->\n            galleryWallpaperCache.evictAll()\n            var success = true\n            try {\n                val wallpaperHandler = GalleryWallpapersHandler(context, uris)\n                val contentOperators = ArrayList<ContentProviderOperation>()\n                wallpaperHandler.makeContentProviderOperations(contentOperators)\n                if (contentOperators.size > 0) {\n                    context.contentResolver.applyBatch(StyleContract.AUTHORITY, contentOperators)\n                }\n            } catch (e: Exception) {\n                success = false\n                emitter.onError(e)\n            }\n            if (success) {\n                emitter.onNext(true)\n                emitter.onComplete()\n                GalleryScheduleService.publish(context)\n            }\n        }\n    }\n\n    fun removeGalleryWallpaperUris(uris: List<GalleryWallpaper>): Observable<Boolean> {\n        return Observable.create<Boolean> { emitter ->\n            galleryWallpaperCache.evictAll()\n            var success = true\n            try {\n                val removeHandler = RemoveGalleryWallpapersHandler(context, uris)\n                val contentOperators = ArrayList<ContentProviderOperation>()\n                removeHandler.makeContentProviderOperations(contentOperators)\n                if (contentOperators.size > 0) {\n                    context.contentResolver.applyBatch(StyleContract.AUTHORITY, contentOperators)\n                }\n            } catch (e: Exception) {\n                success = false\n                emitter.onError(e)\n            }\n            if (success) {\n                emitter.onNext(true)\n                emitter.onComplete()\n                GalleryScheduleService.publish(context)\n            }\n        }\n    }\n\n    fun getGalleryWallpaperUris(): Observable<List<GalleryWallpaperEntity>> {\n        return createEntitiesObservable().doOnNext(galleryWallpaperCache::put)\n    }\n\n    fun forceNow(wallpaperUri: String): Observable<Boolean> {\n        return Observable.create { emitter ->\n            if (galleryWallpaperCache.isCached()) {\n                for (entity in galleryWallpaperCache.get()!!) {\n                    if (TextUtils.equals(entity.uri, wallpaperUri)) {\n                        currentGalleryWallpaperId = entity.id\n                        break\n                    }\n                }\n            } else {\n                var cursor: Cursor? = null\n                try {\n                    cursor = context.contentResolver.query(StyleContract.GalleryWallpaper.CONTENT_URI,\n                            arrayOf(StyleContract.GalleryWallpaper._ID),\n                            StyleContract.GalleryWallpaper.COLUMN_NAME_CUSTOM_URI + \" = ? \",\n                            arrayOf(wallpaperUri), null)\n                    if (cursor != null && cursor.moveToFirst()) {\n                        currentGalleryWallpaperId = cursor.getLong(0)\n                    }\n                } finally {\n                    cursor?.close()\n                }\n            }\n            emitter.onNext(true)\n            emitter.onComplete()\n            notifyChange(context, StyleContract.GalleryWallpaper.CONTENT_URI)\n        }\n    }\n\n    fun setUpdateIntervalMin(intervalMin: Int): Observable<Boolean> {\n        return Observable.create { emitter ->\n            GalleryScheduleService.setInterval(context, intervalMin)\n            emitter.onNext(true)\n            emitter.onComplete()\n        }\n    }\n\n    fun getUpdateIntervalMin(): Observable<Int> {\n        return Observable.create { emitter ->\n            emitter.onNext(rotateIntervalMin)\n            emitter.onComplete()\n        }\n    }\n\n    private fun createEntitiesObservable(): Observable<List<GalleryWallpaperEntity>> {\n        return Observable.create<List<GalleryWallpaperEntity>> { emitter ->\n            var cursor: Cursor? = null\n            val validWallpapers = ArrayList<GalleryWallpaperEntity>()\n            try {\n                val contentResolver = context.contentResolver\n                cursor = contentResolver.query(StyleContract.GalleryWallpaper.CONTENT_URI,\n                        null, null, null, null)\n                validWallpapers.addAll(GalleryWallpaperEntity.readCursor(context, cursor))\n            } finally {\n                cursor?.close()\n            }\n\n            emitter.onNext(validWallpapers)\n            emitter.onComplete()\n        }\n    }\n\n    private fun peekValid(entities: List<GalleryWallpaperEntity>): WallpaperEntity {\n        if (entities.isNotEmpty()) {\n            if (currentGalleryWallpaperId == -1L) {\n                val selectedEntity: GalleryWallpaperEntity\n                if (entities.size == 1) {\n                    selectedEntity = entities[0]\n                    currentGalleryWallpaperId = selectedEntity.id\n                } else {\n                    val random = Random()\n                    selectedEntity = entities[random.nextInt(entities.size)]\n                    currentGalleryWallpaperId = selectedEntity.id\n                }\n                return switchToWallpaperEntity(selectedEntity)\n            } else {\n                entities.filter { it.id == currentGalleryWallpaperId }\n                        .forEach { return switchToWallpaperEntity(it) }\n            }\n        }\n        return DbWallpaperDataStore.buildDefaultWallpaper()\n    }\n\n    private fun switchToWallpaperEntity(galleryWallpaperEntity: GalleryWallpaperEntity)\n            : WallpaperEntity {\n        val wallpaperEntity = WallpaperEntity()\n        wallpaperEntity.isDefault = false\n        wallpaperEntity.canLike = false\n        wallpaperEntity.title = getTitle(galleryWallpaperEntity.dateTime)\n        wallpaperEntity.byline = getByline(galleryWallpaperEntity.location)\n        wallpaperEntity.attribution = \"kinglloy.com\"\n        wallpaperEntity.imageUri = galleryWallpaperEntity.uri\n        wallpaperEntity.wallpaperId = galleryWallpaperEntity.id.toString()\n\n        return wallpaperEntity\n    }\n\n    private fun getTitle(dateTime: Long): String {\n        if (dateTime > 0)\n            return DateUtils.formatDateTime(context, dateTime,\n                    DateUtils.FORMAT_SHOW_DATE\n                            or DateUtils.FORMAT_SHOW_YEAR\n                            or DateUtils.FORMAT_SHOW_WEEKDAY)\n        else return context.getString(R.string.gallery_from_gallery)\n    }\n\n    private fun getByline(location: String?): String {\n        if (location.isNullOrEmpty())\n            return context.getString(R.string.gallery_touch_to_view)\n        else return location!!\n    }\n\n    private fun doSwitch(): Observable<WallpaperEntity> {\n        if (galleryWallpaperCache.isCached()) {\n            return Observable.create<WallpaperEntity> { emitter ->\n                val entities = galleryWallpaperCache.get()\n                emitter.onNext(peekOne(entities!!))\n                emitter.onComplete()\n            }\n        } else {\n            return createEntitiesObservable().doOnNext(galleryWallpaperCache::put)\n                    .map { entities ->\n                        return@map peekOne(entities)\n                    }\n        }\n    }\n\n    private fun peekOne(entities: List<GalleryWallpaperEntity>): WallpaperEntity {\n        var selectedEntity: GalleryWallpaperEntity? = null\n        if (entities.size > 1) {\n            val random = Random()\n            while (true) {\n                selectedEntity = entities[random.nextInt(entities.size)]\n                if (selectedEntity.id != currentGalleryWallpaperId) {\n                    currentGalleryWallpaperId = selectedEntity.id\n                    break\n                }\n            }\n        } else if (entities.size == 1) {\n            if (currentGalleryWallpaperId != entities[0].id) {\n                currentGalleryWallpaperId = entities[0].id\n            }\n            selectedEntity = entities[0]\n        } else {\n            if (currentGalleryWallpaperId != -1L) {\n                currentGalleryWallpaperId = -1\n            }\n        }\n        if (selectedEntity != null) {\n            return switchToWallpaperEntity(selectedEntity)\n        }\n        return DbWallpaperDataStore.buildDefaultWallpaper()\n    }\n}"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/repository/datasource/GalleryWallpaperDataStoreFactory.kt",
    "content": "package com.yalin.style.data.repository.datasource\n\nimport android.content.Context\nimport com.yalin.style.data.cache.GalleryWallpaperCache\nimport com.yalin.style.data.lock.OpenInputStreamLock\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n/**\n * @author jinyalin\n * @since 2017/5/24.\n */\n@Singleton\nclass GalleryWallpaperDataStoreFactory @Inject\nconstructor(val context: Context,\n            val openInputStreamLock: OpenInputStreamLock,\n            val galleryWallpaperCache: GalleryWallpaperCache) {\n\n    fun create(): GalleryWallpaperDataStore {\n        return GalleryWallpaperDataStore(context, openInputStreamLock, galleryWallpaperCache)\n    }\n}"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/repository/datasource/RemoteAdvanceWallpaperDataStore.kt",
    "content": "package com.yalin.style.data.repository.datasource\n\nimport android.content.ContentProviderOperation\nimport android.content.ContentResolver\nimport android.content.Context\nimport android.content.OperationApplicationException\nimport android.database.Cursor\nimport android.os.RemoteException\nimport com.google.gson.JsonParser\nimport com.yalin.style.data.R\nimport com.yalin.style.data.entity.AdvanceWallpaperEntity\nimport com.yalin.style.data.exception.NoContentException\nimport com.yalin.style.data.exception.RemoteServerException\nimport com.yalin.style.data.log.LogUtil\nimport com.yalin.style.data.repository.datasource.io.AdvanceWallpaperHandler\nimport com.yalin.style.data.repository.datasource.net.RemoteAdvanceWallpaperFetcher\nimport com.yalin.style.data.repository.datasource.provider.StyleContract\nimport com.yalin.style.data.repository.datasource.sync.account.Account\nimport com.yalin.style.domain.interactor.DefaultObserver\nimport io.reactivex.Observable\n\n/**\n * @author jinyalin\n * @since 2017/7/31.\n */\nclass RemoteAdvanceWallpaperDataStore(val context: Context,\n                                      val localDataStore: AdvanceWallpaperDataStoreImpl)\n    : AdvanceWallpaperDataStore {\n    companion object {\n        val TAG = \"RemoteAdvanceWallpaper\"\n    }\n\n    val wallpaperHandler = AdvanceWallpaperHandler(context)\n\n    override fun getWallpaperEntity(): AdvanceWallpaperEntity {\n        throw UnsupportedOperationException(\"Remote data store not support get wallpaper.\")\n    }\n\n    override fun getAdvanceWallpapers(): Observable<List<AdvanceWallpaperEntity>> {\n        return Observable.create { emitter ->\n            val account = Account.getAccount()\n            val authority = context.getString(R.string.authority)\n            ContentResolver.cancelSync(account, authority)\n\n            val batch = ArrayList<ContentProviderOperation>()\n            try {\n                val wallpapers = RemoteAdvanceWallpaperFetcher(context).fetchDataIfNewer()\n                val parser = JsonParser()\n                val handler = AdvanceWallpaperHandler(context)\n                handler.process(parser.parse(wallpapers))\n                handler.makeContentProviderOperations(batch)\n            } catch (e: Exception) {\n                emitter.onError(RemoteServerException())\n                return@create\n            }\n\n            try {\n                val operations = batch.size\n                if (operations > 0) {\n                    context.contentResolver.applyBatch(StyleContract.AUTHORITY, batch)\n                }\n            } catch (ex: RemoteException) {\n                LogUtil.D(TAG, \"RemoteException while applying content provider operations.\")\n                throw RuntimeException(\"Error executing content provider batch operation\", ex)\n            } catch (ex: OperationApplicationException) {\n                LogUtil.D(TAG, \"OperationApplicationException while applying content provider operations.\")\n                throw RuntimeException(\"Error executing content provider batch operation\", ex)\n            }\n\n            var cursor: Cursor? = null\n            val validWallpapers = ArrayList<AdvanceWallpaperEntity>()\n            try {\n                val contentResolver = context.contentResolver\n                cursor = contentResolver.query(StyleContract.AdvanceWallpaper.CONTENT_URI,\n                        null, null, null, null)\n                validWallpapers.addAll(AdvanceWallpaperEntity.readCursor(cursor))\n            } finally {\n                if (cursor != null) {\n                    cursor.close()\n                }\n            }\n\n            if (validWallpapers.isEmpty()) {\n                emitter.onError(NoContentException())\n            } else {\n                emitter.onNext(validWallpapers)\n            }\n            emitter.onComplete()\n        }\n    }\n\n    override fun selectWallpaper(wallpaperId: String, tempSelect: Boolean): Observable<Boolean> {\n        throw UnsupportedOperationException(\"Remote data store not support select wallpaper.\")\n    }\n\n    override fun downloadWallpaper(wallpaperId: String): Observable<Long> {\n        return Observable.create { emitter ->\n            val entity = localDataStore.loadWallpaperEntity(wallpaperId)\n            wallpaperHandler.downloadWallpaperComponent(entity, object : DefaultObserver<Long>() {\n                override fun onNext(downloadedLength: Long) {\n                    emitter.onNext(downloadedLength)\n                }\n\n                override fun onComplete() {\n                    emitter.onComplete()\n                }\n\n                override fun onError(exception: Throwable) {\n                    emitter.onError(exception)\n                }\n\n            })\n        }\n    }\n\n    override fun readAd(wallpaperId: String): Observable<Boolean> {\n        throw UnsupportedOperationException(\"Remote data store not support read wallpaper ad.\")\n\n    }\n\n    override fun rollback() {\n        // do nothing\n    }\n}"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/repository/datasource/SourcesDataStore.kt",
    "content": "package com.yalin.style.data.repository.datasource\n\nimport com.yalin.style.data.entity.SourceEntity\nimport io.reactivex.Observable\n\n/**\n * @author jinyalin\n * @since 2017/5/23.\n */\ninterface SourcesDataStore {\n\n    fun getSources(): Observable<List<SourceEntity>>\n\n    fun selectSource(sourceId: Int, tempSelect: Boolean): Observable<Boolean>\n\n    fun getUsedSourceId(): Int\n}"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/repository/datasource/SourcesDataStoreFactory.kt",
    "content": "package com.yalin.style.data.repository.datasource\n\nimport android.content.Context\nimport com.yalin.style.data.cache.SourcesCache\nimport com.yalin.style.data.lock.SelectSourceLock\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n/**\n * @author jinyalin\n * @since 2017/5/23.\n */\n@Singleton\nclass SourcesDataStoreFactory @Inject\nconstructor(val context: Context,\n            val sourcesCache: SourcesCache,\n            val selectSourceLock: SelectSourceLock) {\n\n    fun create(): SourcesDataStore {\n        return SourcesDataStoreImpl(context, sourcesCache, selectSourceLock)\n    }\n}"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/repository/datasource/SourcesDataStoreImpl.kt",
    "content": "package com.yalin.style.data.repository.datasource\n\nimport android.content.Context\nimport com.yalin.style.data.cache.SourcesCache\nimport com.yalin.style.data.entity.SourceEntity\nimport com.yalin.style.data.lock.SelectSourceLock\nimport io.reactivex.Observable\n\n/**\n * @author jinyalin\n * @since 2017/5/23.\n */\nclass SourcesDataStoreImpl(val context: Context,\n                           val sourcesCache: SourcesCache,\n                           val selectSourceLock: SelectSourceLock) : SourcesDataStore {\n\n    override fun selectSource(sourceId: Int, tempSelect: Boolean): Observable<Boolean> {\n        return Observable.create { emitter ->\n            try {\n                if ((!tempSelect && selectSourceLock.obtain()) || tempSelect) {\n                    emitter.onNext(sourcesCache.selectSource(sourceId, tempSelect))\n                } else {\n                    emitter.onNext(false)\n                }\n                emitter.onComplete()\n            } finally {\n                selectSourceLock.release()\n            }\n        }\n    }\n\n    override fun getSources(): Observable<List<SourceEntity>> {\n        return sourcesCache.getSources(context)\n    }\n\n    override fun getUsedSourceId() = sourcesCache.getUsedSourceId()\n\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/repository/datasource/StyleWallpaperDataStoreFactory.java",
    "content": "package com.yalin.style.data.repository.datasource;\n\n\nimport android.content.Context;\n\nimport com.yalin.style.data.cache.WallpaperCache;\n\nimport com.yalin.style.data.lock.LikeWallpaperLock;\nimport com.yalin.style.data.lock.OpenInputStreamLock;\n\nimport javax.inject.Inject;\nimport javax.inject.Singleton;\n\n/**\n * @author jinyalin\n * @since 2017/4/18.\n */\n\n@Singleton\npublic class StyleWallpaperDataStoreFactory {\n\n    private final Context context;\n    private final WallpaperCache wallpaperCache;\n    private final OpenInputStreamLock openInputStreamLock;\n    private final LikeWallpaperLock likeWallpaperLock;\n\n    @Inject\n    StyleWallpaperDataStoreFactory(Context context, WallpaperCache wallpaperCache,\n                                   OpenInputStreamLock openInputStreamLock,\n                                   LikeWallpaperLock likeWallpaperLock) {\n        this.context = context;\n        this.wallpaperCache = wallpaperCache;\n        this.openInputStreamLock = openInputStreamLock;\n        this.likeWallpaperLock = likeWallpaperLock;\n    }\n\n    public WallpaperDataStore create() {\n        WallpaperDataStore wallpaperDataStore;\n        if (!wallpaperCache.isDirty() && wallpaperCache.isCached()) {\n            wallpaperDataStore = new CacheWallpaperDataStore(wallpaperCache, openInputStreamLock);\n        } else {\n            wallpaperDataStore = createDbDataStore();\n        }\n        return wallpaperDataStore;\n    }\n\n    public WallpaperDataStore createDbDataStore() {\n        return new DbWallpaperDataStore(context, wallpaperCache,\n                openInputStreamLock, likeWallpaperLock);\n    }\n\n    public void onDataRefresh() {\n        wallpaperCache.evictAll();\n    }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/repository/datasource/WallpaperDataStore.java",
    "content": "package com.yalin.style.data.repository.datasource;\n\nimport com.yalin.style.data.entity.WallpaperEntity;\n\nimport java.io.InputStream;\n\nimport io.reactivex.Observable;\n\n/**\n * @author jinyalin\n * @since 2017/4/18.\n */\n\npublic interface WallpaperDataStore {\n\n    Observable<WallpaperEntity> getWallPaperEntity();\n\n    Observable<WallpaperEntity> switchWallPaperEntity();\n\n    Observable<InputStream> openInputStream(String wallpaperId);\n\n    Observable<Integer> getWallpaperCount();\n\n    Observable<Boolean> likeWallpaper(String wallpaperId);\n}"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/repository/datasource/io/AdvanceWallpaperHandler.kt",
    "content": "package com.yalin.style.data.repository.datasource.io\n\nimport android.content.ContentProviderOperation\nimport android.content.Context\nimport android.database.Cursor\nimport com.google.gson.Gson\nimport com.google.gson.JsonElement\nimport com.yalin.style.data.SyncConfig\nimport com.yalin.style.data.entity.AdvanceWallpaperEntity\nimport com.yalin.style.data.exception.NetworkConnectionException\nimport com.yalin.style.data.exception.RemoteServerException\nimport com.yalin.style.data.log.LogUtil\nimport com.yalin.style.data.repository.datasource.provider.StyleContract\nimport com.yalin.style.data.repository.datasource.provider.StyleContractHelper\nimport com.yalin.style.data.utils.WallpaperFileHelper\nimport com.yalin.style.domain.interactor.DefaultObserver\nimport okhttp3.OkHttpClient\nimport okhttp3.Request\nimport java.io.*\nimport java.net.URL\nimport java.util.ArrayList\nimport java.util.HashSet\nimport java.util.concurrent.TimeUnit\nimport java.util.concurrent.locks.ReentrantLock\n\n/**\n * @author jinyalin\n * @since 2017/7/28.\n */\nclass AdvanceWallpaperHandler(context: Context) : JSONHandler(context) {\n    companion object {\n        val TAG = \"AdvanceWallpaperHandler\"\n        val downloadLock = ReentrantLock()\n    }\n\n    private var wallpapers: ArrayList<AdvanceWallpaperEntity> = ArrayList()\n\n    override fun makeContentProviderOperations(list: ArrayList<ContentProviderOperation>) {\n        val uri = StyleContractHelper.setUriAsCalledFromSyncAdapter(\n                StyleContract.AdvanceWallpaper.CONTENT_URI)\n        list.add(ContentProviderOperation.newDelete(uri).build())\n\n        val validFiles = HashSet<String>()\n        val selectedEntities = querySelectedWallpapers()\n        validFiles.addAll(getWallpaperNameSet(selectedEntities))\n        for (wallpaper in this.wallpapers) {\n            wallpaper.storePath = makeStorePath(wallpaper)\n            if (!selectedEntities.contains(wallpaper)) {\n                if (wallpaper.lazyDownload || (downloadWallpaperComponent(wallpaper)\n                        && WallpaperFileHelper.ensureChecksumValid(mContext,\n                        wallpaper.checkSum, wallpaper.storePath))) {\n                    LogUtil.D(TAG, \"download wallpaper component \"\n                            + \" success, do output wallpaper.\")\n                    outputWallpaper(wallpaper, list)\n                    validFiles.add(makeFilename(wallpaper))\n                }\n            }\n        }\n        // delete old wallpapers\n        WallpaperFileHelper.deleteOldComponent(mContext, validFiles)\n    }\n\n    override fun process(element: JsonElement) {\n        val wallpapers = Gson().fromJson(element, Array<AdvanceWallpaperEntity>::class.java)\n        this.wallpapers.ensureCapacity(wallpapers.size)\n        this.wallpapers.addAll(wallpapers)\n    }\n\n    private fun makeFilename(wallpaper: AdvanceWallpaperEntity): String {\n        return wallpaper.hashCode().toString() + \"_component.apk\"\n    }\n\n    private fun makeStorePath(wallpaper: AdvanceWallpaperEntity): String {\n        val outputDir = WallpaperFileHelper.getAdvanceWallpaperDir(mContext)\n        return File(outputDir, makeFilename(wallpaper)).absolutePath\n    }\n\n    private fun getWallpaperNameSet(entities: List<AdvanceWallpaperEntity>): Set<String> {\n        val ids = HashSet<String>()\n        for (entity in entities) {\n            ids.add(makeFilename(entity))\n        }\n        return ids\n    }\n\n    private fun outputWallpaper(wallpaper: AdvanceWallpaperEntity,\n                                list: ArrayList<ContentProviderOperation>) {\n        val uri = StyleContractHelper.setUriAsCalledFromSyncAdapter(\n                StyleContract.AdvanceWallpaper.CONTENT_URI)\n        val builder = ContentProviderOperation.newInsert(uri)\n        builder.withValue(StyleContract.AdvanceWallpaper.COLUMN_NAME_WALLPAPER_ID, wallpaper.wallpaperId)\n        builder.withValue(StyleContract.AdvanceWallpaper.COLUMN_NAME_AUTHOR, wallpaper.author)\n        builder.withValue(StyleContract.AdvanceWallpaper.COLUMN_NAME_DOWNLOAD_URL, wallpaper.downloadUrl)\n        builder.withValue(StyleContract.AdvanceWallpaper.COLUMN_NAME_ICON_URL, wallpaper.iconUrl)\n        builder.withValue(StyleContract.AdvanceWallpaper.COLUMN_NAME_LINK, wallpaper.link)\n        builder.withValue(StyleContract.AdvanceWallpaper.COLUMN_NAME_NAME, wallpaper.name)\n        builder.withValue(StyleContract.AdvanceWallpaper.COLUMN_NAME_CHECKSUM, wallpaper.checkSum)\n        builder.withValue(StyleContract.AdvanceWallpaper.COLUMN_NAME_STORE_PATH, wallpaper.storePath)\n        builder.withValue(StyleContract.AdvanceWallpaper.COLUMN_NAME_PROVIDER_NAME, wallpaper.providerName)\n        builder.withValue(StyleContract.AdvanceWallpaper.COLUMN_NAME_SELECTED, 0)\n        builder.withValue(StyleContract.AdvanceWallpaper.COLUMN_NAME_LAZY_DOWNLOAD,\n                if (wallpaper.lazyDownload) 1 else 0)\n        builder.withValue(StyleContract.AdvanceWallpaper.COLUMN_NAME_NEED_AD,\n                if (wallpaper.needAd) 1 else 0)\n\n        list.add(builder.build())\n    }\n\n    private fun querySelectedWallpapers(): List<AdvanceWallpaperEntity> {\n        var cursor: Cursor? = null\n        try {\n            cursor = mContext.contentResolver.query(\n                    StyleContract.AdvanceWallpaper.CONTENT_SELECTED_URI, null, null, null, null)\n            return AdvanceWallpaperEntity.readCursor(cursor)\n        } finally {\n            if (cursor != null) {\n                cursor.close()\n            }\n        }\n    }\n\n    private fun downloadWallpaperComponent(wallpaper: AdvanceWallpaperEntity): Boolean {\n        return downloadWallpaperComponent(wallpaper, null)\n    }\n\n    fun downloadWallpaperComponent(wallpaper: AdvanceWallpaperEntity,\n                                   observer: DefaultObserver<Long>?): Boolean {\n        observer?.onNext(0)\n        LogUtil.D(TAG, \"Start download wallpaper component to \" + wallpaper.storePath)\n        val outputFile = File(wallpaper.storePath)\n        if (outputFile.exists()) {\n            if (WallpaperFileHelper.ensureChecksumValid(mContext,\n                    wallpaper.checkSum, wallpaper.storePath)) {\n                observer?.onComplete()\n                return true\n            }\n        }\n        synchronized(downloadLock) {\n            var os: OutputStream? = null\n            var _is: InputStream? = null\n            try {\n                if (outputFile.exists()) {\n                    if (WallpaperFileHelper.ensureChecksumValid(mContext,\n                            wallpaper.checkSum, wallpaper.storePath)) {\n                        observer?.onComplete()\n                        return true\n                    } else {\n                        outputFile.delete()\n                    }\n                }\n\n                val storePath = outputFile.parentFile\n                storePath.mkdirs()\n                os = FileOutputStream(outputFile)\n                val httpClient = OkHttpClient.Builder()\n                        .connectTimeout(SyncConfig.DEFAULT_CONNECT_TIMEOUT.toLong(), TimeUnit.SECONDS)\n                        .readTimeout(SyncConfig.DEFAULT_DOWNLOAD_TIMEOUT.toLong(), TimeUnit.SECONDS)\n                        .build()\n                val request = Request.Builder().url(URL(wallpaper.downloadUrl)).build()\n\n                val response = httpClient.newCall(request).execute()\n                val responseCode = response.code()\n                if (responseCode in 200..299) {\n                    _is = response.body().byteStream()\n                    val buffer = ByteArray(1024)\n                    var bytesRead: Int\n                    var writeLength = 0L\n                    bytesRead = _is.read(buffer)\n                    while (bytesRead > 0) {\n                        os.write(buffer, 0, bytesRead)\n                        writeLength += bytesRead\n                        observer?.onNext(writeLength)\n                        bytesRead = _is.read(buffer)\n                    }\n                    os.flush()\n                    observer?.onComplete()\n                    return true\n                } else {\n                    LogUtil.E(TAG, \"Download wallpaper component \" + wallpaper.name + \" failed.\")\n                    observer?.onError(RemoteServerException())\n                    return false\n\n                }\n            } catch (e: IOException) {\n                e.printStackTrace()\n                observer?.onError(NetworkConnectionException())\n                LogUtil.E(TAG, \"Download wallpaper component\" + wallpaper.name + \" failed.\", e)\n                return false\n            } finally {\n                ensureChecksum(outputFile, wallpaper.checkSum)\n                try {\n                    os?.close()\n                    _is?.close()\n                } catch (e: IOException) {\n                    // ignore\n                }\n            }\n        }\n    }\n\n    private fun ensureChecksum(file: File, checkSum: String) {\n        if (file.exists()) {\n            if (!WallpaperFileHelper.ensureChecksumValid(mContext,\n                    checkSum, file.absolutePath)) {\n                file.delete()\n            }\n        }\n    }\n}"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/repository/datasource/io/GalleryWallpapersHandler.kt",
    "content": "package com.yalin.style.data.repository.datasource.io\n\nimport android.annotation.SuppressLint\nimport android.content.ContentProviderOperation\nimport android.content.Context\nimport android.location.Address\nimport android.location.Geocoder\nimport android.net.Uri\nimport android.support.media.ExifInterface\nimport android.text.TextUtils\nimport com.google.gson.JsonElement\nimport com.yalin.style.data.log.LogUtil\nimport com.yalin.style.data.repository.datasource.provider.StyleContract\nimport com.yalin.style.data.utils.isTreeUri\nimport com.yalin.style.data.utils.processUriPermission\nimport com.yalin.style.domain.GalleryWallpaper\nimport java.text.SimpleDateFormat\nimport java.util.ArrayList\n\n/**\n * @author jinyalin\n * @since 2017/5/24.\n */\nclass GalleryWallpapersHandler(val context: Context,\n                               val uris: List<GalleryWallpaper>) : JSONHandler(context) {\n\n    companion object {\n        private val TAG = \"GalleryWallpapersHandler\"\n\n        @SuppressLint(\"SimpleDateFormat\")\n        private val sExifDateFormat = SimpleDateFormat(\"yyyy:MM:dd HH:mm:ss\")\n    }\n\n    private var mGeocoder: Geocoder = Geocoder(context)\n\n    override fun makeContentProviderOperations(list: ArrayList<ContentProviderOperation>) {\n        val uri = StyleContract.GalleryWallpaper.CONTENT_URI\n        for (wallpaperEntity in uris) {\n            if (TextUtils.isEmpty(wallpaperEntity.uri)) {\n                continue\n            }\n            wallpaperEntity.isTreeUri = isTreeUri(Uri.parse(wallpaperEntity.uri))\n            processUriPermission(context, wallpaperEntity)\n\n            val builder = ContentProviderOperation.newInsert(uri)\n            builder.withValue(StyleContract.GalleryWallpaper.COLUMN_NAME_CUSTOM_URI,\n                    wallpaperEntity.uri)\n            builder.withValue(StyleContract.GalleryWallpaper.COLUMN_NAME_IS_TREE_URI,\n                    if (wallpaperEntity.isTreeUri) 1 else 0)\n\n            readMetaData(wallpaperEntity.uri, builder)\n            list.add(builder.build())\n        }\n    }\n\n    override fun process(element: JsonElement) {\n\n    }\n\n    private fun readMetaData(uriString: String, builder: ContentProviderOperation.Builder) {\n        try {\n            val uri = Uri.parse(uriString)\n            var hasMetadata = false\n            context.contentResolver.openInputStream(uri).use({ `in` ->\n                if (`in` == null) {\n                    return\n                }\n                val exifInterface = ExifInterface(`in`)\n                val dateString = exifInterface.getAttribute(ExifInterface.TAG_DATETIME)\n                if (!TextUtils.isEmpty(dateString)) {\n                    val date = sExifDateFormat.parse(dateString)\n                    builder.withValue(StyleContract.GalleryWallpaper.COLUMN_NAME_DATE_TIME,\n                            date.time)\n                    hasMetadata = true\n                }\n\n                val latlong = exifInterface.latLong\n                if (latlong != null) {\n                    // Reverse geocode\n                    var addresses: List<Address>? = null\n                    try {\n                        addresses = mGeocoder.getFromLocation(latlong[0], latlong[1], 1)\n                    } catch (e: IllegalArgumentException) {\n                        LogUtil.E(TAG, \"Invalid latitude/longitude, skipping location metadata\", e)\n                    }\n\n                    if (addresses != null && addresses.isNotEmpty()) {\n                        val addr = addresses[0]\n                        val locality = addr.locality\n                        val adminArea = addr.adminArea\n                        val countryCode = addr.countryCode\n                        val sb = StringBuilder()\n                        if (!TextUtils.isEmpty(locality)) {\n                            sb.append(locality)\n                        }\n                        if (!TextUtils.isEmpty(adminArea)) {\n                            if (sb.isNotEmpty()) {\n                                sb.append(\", \")\n                            }\n                            sb.append(adminArea)\n                        }\n                        if (!TextUtils.isEmpty(countryCode)) {\n                            if (sb.isNotEmpty()) {\n                                sb.append(\", \")\n                            }\n                            sb.append(countryCode)\n                        }\n                        builder.withValue(StyleContract.GalleryWallpaper.COLUMN_NAME_LOCATION,\n                                sb.toString())\n                        hasMetadata = true\n                    }\n                }\n            })\n            if (hasMetadata) {\n                builder.withValue(StyleContract.GalleryWallpaper.COLUMN_NAME_HAS_METADATA,\n                        if (hasMetadata) 1 else 0)\n            }\n        } catch (e: Exception) {\n            LogUtil.E(TAG, \"Couldn't read image metadata.\", e)\n        }\n    }\n}"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/repository/datasource/io/JSONHandler.java",
    "content": "package com.yalin.style.data.repository.datasource.io;\n\nimport android.content.ContentProviderOperation;\nimport android.content.Context;\nimport com.google.gson.JsonElement;\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.io.Reader;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.nio.charset.Charset;\nimport java.util.ArrayList;\n\n/**\n * YaLin 2017/1/3.\n */\n\npublic abstract class JSONHandler {\n\n  protected Context mContext;\n\n  public JSONHandler(Context context) {\n    mContext = context;\n  }\n\n  public abstract void makeContentProviderOperations(ArrayList<ContentProviderOperation> list);\n\n  public abstract void process(JsonElement element);\n\n  /**\n   * Loads a raw resource and returns its content as a String.\n   *\n   * @throws IOException If any error was encountered, such as an incorrect resource ID, or\n   * inaccessible file.\n   */\n  public static String parseResource(Context context, int resource) throws IOException {\n    InputStream stream = null;\n    String data;\n    try {\n      stream = context.getResources().openRawResource(resource);\n      data = parseStream(stream);\n    } finally {\n      if (stream != null) {\n        try {\n          stream.close();\n        } catch (IOException e) {\n          // Ignore exceptions during stream close, other exceptions thrown earlier will\n          // be handled by the calling methods\n        }\n      }\n    }\n\n    return data;\n  }\n\n  private static String parseStream(final InputStream stream) throws IOException {\n    Reader reader = null;\n    Writer writer = new StringWriter();\n    char[] buffer = new char[1024];\n\n    // IO errors are passed up to the calling method and must be caught there.\n    try {\n      reader = new BufferedReader(new InputStreamReader(stream, Charset.forName(\"UTF-8\")));\n      int n;\n      while ((n = reader.read(buffer)) != -1) {\n        writer.write(buffer, 0, n);\n      }\n    } finally {\n      if (reader != null) {\n        try {\n          reader.close();\n        } catch (IOException e) {\n          // Ignore exceptions during stream close, other exceptions thrown earlier will\n          // be handled by the calling methods\n        }\n      }\n    }\n\n    return writer.toString();\n  }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/repository/datasource/io/RemoveGalleryWallpapersHandler.kt",
    "content": "package com.yalin.style.data.repository.datasource.io\n\nimport android.content.ContentProviderOperation\nimport android.content.Context\nimport android.text.TextUtils\nimport com.google.gson.JsonElement\nimport com.yalin.style.data.repository.datasource.provider.StyleContract\nimport com.yalin.style.domain.GalleryWallpaper\nimport java.util.ArrayList\n\n/**\n * YaLin\n * On 2017/5/26.\n */\n\nclass RemoveGalleryWallpapersHandler(val context: Context,\n                                     val uris: List<GalleryWallpaper>) : JSONHandler(context) {\n\n    override fun makeContentProviderOperations(list: ArrayList<ContentProviderOperation>) {\n        uris.filterNot { TextUtils.isEmpty(it.uri) }\n                .map {\n                    StyleContract.GalleryWallpaper\n                            .buildGalleryWallpaperDeleteUri(it.uri)\n                }\n                .map { ContentProviderOperation.newDelete(it) }\n                .mapTo(list) { it.build() }\n    }\n\n    override fun process(element: JsonElement) {\n\n    }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/repository/datasource/io/WallpapersHandler.java",
    "content": "package com.yalin.style.data.repository.datasource.io;\n\nimport android.content.ContentProviderOperation;\nimport android.content.Context;\nimport android.database.Cursor;\nimport android.net.Uri;\n\nimport com.google.gson.Gson;\nimport com.google.gson.JsonElement;\nimport com.yalin.style.data.SyncConfig;\nimport com.yalin.style.data.entity.WallpaperEntity;\nimport com.yalin.style.data.log.LogUtil;\nimport com.yalin.style.data.repository.datasource.provider.StyleContract.Wallpaper;\nimport com.yalin.style.data.repository.datasource.provider.StyleContractHelper;\nimport com.yalin.style.data.utils.TimeUtil;\nimport com.yalin.style.data.utils.WallpaperFileHelper;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.net.URL;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.concurrent.TimeUnit;\n\nimport okhttp3.OkHttpClient;\nimport okhttp3.Request;\nimport okhttp3.Response;\n\n/**\n * YaLin 2017/1/3.\n */\n\npublic class WallpapersHandler extends JSONHandler {\n\n    private static final String TAG = \"WallpapersHandler\";\n\n    private ArrayList<WallpaperEntity> mWallpapers = new ArrayList<>();\n\n    public WallpapersHandler(Context context) {\n        super(context);\n    }\n\n    @Override\n    public void makeContentProviderOperations(ArrayList<ContentProviderOperation> list) {\n        Uri uri = StyleContractHelper.setUriAsCalledFromSyncAdapter(Wallpaper.CONTENT_URI);\n        list.add(ContentProviderOperation.newDelete(uri).build());\n\n        Set<String> validFiles = new HashSet<>();\n        List<WallpaperEntity> keepedEntity = queryKeepedWallpapers();\n        validFiles.addAll(getWallpaperIdSet(keepedEntity));\n        for (WallpaperEntity wallpaper : mWallpapers) {\n            Uri wallpaperUri = Wallpaper.buildWallpaperSaveUri(wallpaper.wallpaperId);\n            if (!keepedEntity.contains(wallpaper) && downloadWallpaper(wallpaper, wallpaperUri)\n                    && WallpaperFileHelper.ensureWallpaperChecksumValid(mContext,\n                    wallpaper.checksum, wallpaper.wallpaperId)) {\n                LogUtil.D(TAG, \"download wallpaper \" + wallpaperUri\n                        + \" success, do output wallpaper.\");\n                outputWallpaper(wallpaper, list, wallpaperUri.toString());\n                validFiles.add(wallpaper.wallpaperId);\n            }\n        }\n        // delete old wallpapers\n        WallpaperFileHelper.deleteOldWallpapers(mContext, validFiles);\n    }\n\n    @Override\n    public void process(JsonElement element) {\n        WallpaperEntity[] wallpapers = new Gson().fromJson(element, WallpaperEntity[].class);\n        mWallpapers.ensureCapacity(wallpapers.length);\n        Collections.addAll(mWallpapers, wallpapers);\n    }\n\n    private Set<String> getWallpaperIdSet(List<WallpaperEntity> entities) {\n        Set<String> ids = new HashSet<>();\n        for (WallpaperEntity entity : entities) {\n            ids.add(entity.wallpaperId);\n        }\n        return ids;\n    }\n\n    private List<WallpaperEntity> queryKeepedWallpapers() {\n        Cursor cursor = null;\n        try {\n            cursor = mContext.getContentResolver().query(Wallpaper.CONTENT_KEEPED_URI,\n                    null, null, null, null);\n            return WallpaperEntity.readCursor(mContext, cursor);\n        } finally {\n            if (cursor != null) {\n                cursor.close();\n            }\n        }\n    }\n\n    private void outputWallpaper(WallpaperEntity wallpaper,\n                                 ArrayList<ContentProviderOperation> list, String uriString) {\n        Uri uri = StyleContractHelper.setUriAsCalledFromSyncAdapter(Wallpaper.CONTENT_URI);\n        ContentProviderOperation.Builder builder = ContentProviderOperation.newInsert(uri);\n        builder.withValue(Wallpaper.COLUMN_NAME_WALLPAPER_ID, wallpaper.wallpaperId);\n        builder.withValue(Wallpaper.COLUMN_NAME_TITLE, wallpaper.title);\n        builder.withValue(Wallpaper.COLUMN_NAME_IMAGE_URI, uriString);\n        builder.withValue(Wallpaper.COLUMN_NAME_BYLINE, wallpaper.byline);\n        builder.withValue(Wallpaper.COLUMN_NAME_ATTRIBUTION, wallpaper.attribution);\n        builder.withValue(Wallpaper.COLUMN_NAME_ADD_DATE, TimeUtil.getCurrentTime(mContext));\n        builder.withValue(Wallpaper.COLUMN_NAME_LIKED, wallpaper.liked ? 1 : 0);\n        builder.withValue(Wallpaper.COLUMN_NAME_CHECKSUM, wallpaper.checksum);\n\n        list.add(builder.build());\n    }\n\n    private boolean downloadWallpaper(WallpaperEntity wallpaper, Uri uri) {\n        LogUtil.D(TAG, \"Start download wallpaper uri = \" + uri);\n        OutputStream os = null;\n        InputStream is = null;\n        try {\n            os = mContext.getContentResolver().openOutputStream(uri);\n            if (os == null) {\n                return false;\n            }\n            OkHttpClient httpClient = new OkHttpClient.Builder()\n                    .connectTimeout(SyncConfig.DEFAULT_CONNECT_TIMEOUT, TimeUnit.SECONDS)\n                    .readTimeout(SyncConfig.DEFAULT_DOWNLOAD_TIMEOUT, TimeUnit.SECONDS)\n                    .build();\n            Request request = new Request.Builder().url(new URL(wallpaper.imageUri)).build();\n\n            Response response = httpClient.newCall(request).execute();\n            int responseCode = response.code();\n            if (responseCode >= 200 && responseCode < 300) {\n                is = response.body().byteStream();\n                byte[] buffer = new byte[1024];\n                int bytesRead;\n                while ((bytesRead = is.read(buffer)) > 0) {\n                    os.write(buffer, 0, bytesRead);\n                }\n                os.flush();\n                return true;\n            } else {\n                LogUtil.E(TAG, \"Download wallpaper \" + wallpaper.title + \" failed.\");\n                return false;\n            }\n        } catch (IOException e) {\n            e.printStackTrace();\n            LogUtil.E(TAG, \"Download wallpaper \" + wallpaper.title + \" failed.\", e);\n            return false;\n        } finally {\n            try {\n                if (os != null) {\n                    os.close();\n                }\n                if (is != null) {\n                    is.close();\n                }\n            } catch (IOException e) {\n                // ignore\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/repository/datasource/net/DataFetcher.java",
    "content": "package com.yalin.style.data.repository.datasource.net;\n\nimport android.content.Context;\nimport android.text.TextUtils;\n\nimport com.yalin.style.data.BuildConfig;\nimport com.yalin.style.data.SyncConfig;\nimport com.yalin.style.data.log.LogUtil;\nimport com.yalin.style.data.utils.HttpRequestUtil;\n\nimport java.io.IOException;\nimport java.net.HttpURLConnection;\nimport java.net.URL;\nimport java.util.concurrent.TimeUnit;\n\nimport okhttp3.OkHttpClient;\nimport okhttp3.Request;\nimport okhttp3.RequestBody;\nimport okhttp3.Response;\n\n/**\n * @author jinyalin\n * @since 2017/7/28.\n */\n\npublic abstract class DataFetcher {\n    private static final String TAG = \"DataFetcher\";\n\n    private final Context mContext;\n\n    private final String mWallpaperUrl;\n\n    public DataFetcher(Context context) {\n        mContext = context;\n        mWallpaperUrl = getUrl();\n    }\n\n    public String fetchDataIfNewer() throws IOException {\n        OkHttpClient httpClient = new OkHttpClient.Builder()\n                .connectTimeout(SyncConfig.DEFAULT_CONNECT_TIMEOUT, TimeUnit.SECONDS)\n                .readTimeout(SyncConfig.DEFAULT_READ_TIMEOUT, TimeUnit.SECONDS)\n                .build();\n\n        Request request = new Request.Builder()\n                .url(new URL(mWallpaperUrl))\n                .post(RequestBody.create(null, HttpRequestUtil.getRequestBody(mContext)))\n                .build();\n        Response response = httpClient.newCall(request).execute();\n        if (response == null) {\n            LogUtil.F(TAG, \"Request for wallpaper returned null response.\");\n            throw new IOException(\"Request for data wallpaper returned null response.\");\n        }\n        int status = response.code();\n        if (status == HttpURLConnection.HTTP_OK) {\n            LogUtil.D(TAG, \"Server return HTTP_OK, so new data is available.\");\n            String body = response.body().string();\n            if (TextUtils.isEmpty(body)) {\n                LogUtil.F(TAG, \"Request for wallpaper returned empty data.\");\n                throw new IOException(\"Error fetching wallpaper data : no data.\");\n            }\n            LogUtil.D(TAG, \"Wallpaper \" + mWallpaperUrl + \" read, contents: \" + body);\n            return body;\n        } else if (status == HttpURLConnection.HTTP_NOT_MODIFIED) {\n            LogUtil.D(TAG, \"HTTP_NOT_MODIFIED: data has not changed since\");\n            return null;\n        } else {\n            LogUtil.D(TAG, \"Error fetching conference data: HTTP status \" + status + \" and manifest \" +\n                    mWallpaperUrl);\n            throw new IOException(\"Error fetching conference data: HTTP status \" + status);\n        }\n    }\n\n    protected abstract String getUrl();\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/repository/datasource/net/RemoteAdvanceWallpaperFetcher.java",
    "content": "package com.yalin.style.data.repository.datasource.net;\n\nimport android.content.Context;\n\nimport com.yalin.style.data.BuildConfig;\n\n\n/**\n * @author jinyalin\n * @since 2017/7/28.\n */\n\npublic class RemoteAdvanceWallpaperFetcher extends DataFetcher {\n\n    public RemoteAdvanceWallpaperFetcher(Context context) {\n        super(context);\n    }\n\n\n    protected String getUrl() {\n        return BuildConfig.SERVER_WALLPAPER_ENDPOINT + \"/advance\";\n    }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/repository/datasource/provider/StyleContract.java",
    "content": "package com.yalin.style.data.repository.datasource.provider;\n\nimport android.net.Uri;\nimport android.provider.BaseColumns;\n\n/**\n * YaLin 2016/12/30.\n */\n\npublic class StyleContract {\n\n    public static final String AUTHORITY = \"com.yalin.style\";\n\n    private static final String SCHEME = \"content://\";\n\n    interface WallpaperColumns {\n\n        /**\n         * Type: TEXT\n         */\n        String COLUMN_NAME_WALLPAPER_ID = \"wallpaper_id\";\n        /**\n         * Type: TEXT\n         */\n        String COLUMN_NAME_IMAGE_URI = \"image_uri\";\n        /**\n         * Type: TEXT\n         */\n        String COLUMN_NAME_TITLE = \"title\";\n        /**\n         * Type: TEXT\n         */\n        String COLUMN_NAME_BYLINE = \"byline\";\n        /**\n         * Type: TEXT\n         */\n        String COLUMN_NAME_ATTRIBUTION = \"attribution\";\n        /**\n         * Type: long\n         */\n        String COLUMN_NAME_ADD_DATE = \"add_date\";\n        /**\n         * Type: SHORT\n         */\n        String COLUMN_NAME_LIKED = \"keep\";\n        /**\n         * Type: TEXT\n         */\n        String COLUMN_NAME_CHECKSUM = \"checksum\";\n    }\n\n    interface GalleryColumns {\n\n        /**\n         * Type: TEXT\n         */\n        String COLUMN_NAME_CUSTOM_URI = \"custom_wallpaper_uri\";\n\n        /**\n         * Type: INTEGER\n         */\n        String COLUMN_NAME_IS_TREE_URI = \"is_tree_uri\";\n\n        /**\n         * Type: INTEGER\n         */\n        String COLUMN_NAME_DATE_TIME = \"date_time\";\n\n        /**\n         * Type: TEXT\n         */\n        String COLUMN_NAME_LOCATION = \"location\";\n\n        /**\n         * Type: INTEGER\n         */\n        String COLUMN_NAME_HAS_METADATA = \"has_metadata\";\n    }\n\n    interface AdvanceWallpaperColumns {\n        /**\n         * Type: TEXT\n         */\n        String COLUMN_NAME_WALLPAPER_ID = \"wallpaper_id\";\n        /**\n         * Type: TEXT\n         */\n        String COLUMN_NAME_ICON_URL = \"icon_url\";\n        /**\n         * Type: TEXT\n         */\n        String COLUMN_NAME_NAME = \"name\";\n        /**\n         * Type: TEXT\n         */\n        String COLUMN_NAME_LINK = \"link\";\n        /**\n         * Type: TEXT\n         */\n        String COLUMN_NAME_AUTHOR = \"author\";\n        /**\n         * Type: TEXT\n         */\n        String COLUMN_NAME_DOWNLOAD_URL = \"download_url\";\n        /**\n         * Type: TEXT\n         */\n        String COLUMN_NAME_CHECKSUM = \"checksum\";\n        /**\n         * Type: TEXT\n         */\n        String COLUMN_NAME_STORE_PATH = \"store_path\";\n        /**\n         * Type: TEXT\n         */\n        String COLUMN_NAME_PROVIDER_NAME = \"provider_name\";\n        /**\n         * Type: INTEGER\n         */\n        String COLUMN_NAME_SELECTED = \"selected\";\n\n        /**\n         * Type: INTEGER\n         */\n        String COLUMN_NAME_LAZY_DOWNLOAD = \"lazy_download\";\n\n        /**\n         * Type: INTEGER\n         */\n        String COLUMN_NAME_NEED_AD = \"need_ad\";\n    }\n\n    public static final Uri BASE_CONTENT_URI = Uri.parse(SCHEME + AUTHORITY);\n\n    private static final String PATH_SOURCE = \"source\";\n    private static final String PATH_WALLPAPER = \"wallpaper\";\n    private static final String PATH_GALLERY = \"gallery\";\n    private static final String PATH_ADVANCE_WALLPAPER = \"advance_wallpaper\";\n\n    public static final String[] TOP_LEVEL_PATHS = {\n            PATH_WALLPAPER\n    };\n\n    public static final class Source {\n\n        public static final Uri CONTENT_URI =\n                BASE_CONTENT_URI.buildUpon().appendPath(PATH_SOURCE).build();\n    }\n\n    public static final class Wallpaper implements WallpaperColumns, BaseColumns {\n\n        public static final String TABLE_NAME = \"wallpaper\";\n\n        public static final String PATH_LIKE_WALLPAPER = \"like\";\n        public static final String PATH_LIKED_WALLPAPER = \"liked\";\n        public static final String PATH_SAVE_WALLPAPER = \"save\";\n\n        public static final Uri CONTENT_URI =\n                BASE_CONTENT_URI.buildUpon().appendPath(PATH_WALLPAPER).build();\n\n        public static final Uri CONTENT_KEEPED_URI =\n                BASE_CONTENT_URI.buildUpon().appendPath(PATH_WALLPAPER)\n                        .appendPath(PATH_LIKED_WALLPAPER).build();\n\n        public static Uri buildWallpaperUri(String wallpaperId) {\n            return CONTENT_URI.buildUpon().appendPath(wallpaperId).build();\n        }\n\n        public static Uri buildWallpaperSaveUri(String wallpaperId) {\n            return CONTENT_URI.buildUpon()\n                    .appendPath(PATH_SAVE_WALLPAPER).appendPath(wallpaperId).build();\n        }\n\n        public static Uri buildWallpaperLikeUri(String wallpaperId) {\n            return CONTENT_URI.buildUpon()\n                    .appendPath(PATH_LIKE_WALLPAPER).appendPath(wallpaperId).build();\n        }\n\n\n        public static String getWallpaperId(Uri uri) {\n            return uri.getPathSegments().get(1);\n        }\n\n        public static String getWallpaperSaveId(Uri uri) {\n            return uri.getPathSegments().get(2);\n        }\n\n        public static String getWallpaperLikeId(Uri uri) {\n            return uri.getPathSegments().get(2);\n        }\n    }\n\n    public static final class GalleryWallpaper implements GalleryColumns, BaseColumns {\n\n        public static final String TABLE_NAME = \"gallery_wallpaper\";\n\n        public static final String PATH_URI = \"uri\";\n\n        public static final Uri CONTENT_URI =\n                BASE_CONTENT_URI.buildUpon().appendPath(PATH_GALLERY).build();\n\n        public static Uri buildGalleryWallpaperUri(long insertId) {\n            return CONTENT_URI.buildUpon().appendPath(String.valueOf(insertId)).build();\n        }\n\n        public static String getGalleryWallpaperId(Uri uri) {\n            return uri.getPathSegments().get(1);\n        }\n\n        public static Uri buildGalleryWallpaperDeleteUri(String uri) {\n            return CONTENT_URI.buildUpon().appendPath(PATH_URI)\n                    .appendPath(String.valueOf(uri)).build();\n        }\n\n        public static String getGalleryWallpaperDeleteUri(Uri uri) {\n            return uri.getPathSegments().get(2);\n        }\n    }\n\n    public static final class AdvanceWallpaper implements AdvanceWallpaperColumns, BaseColumns {\n        public static final String TABLE_NAME = \"advance_wallpaper\";\n\n        public static final String PATH_SELECTED_WALLPAPER = \"selected\";\n\n        public static final Uri CONTENT_URI =\n                BASE_CONTENT_URI.buildUpon().appendEncodedPath(PATH_ADVANCE_WALLPAPER).build();\n\n        public static final Uri CONTENT_SELECTED_URI =\n                BASE_CONTENT_URI.buildUpon().appendPath(PATH_ADVANCE_WALLPAPER)\n                        .appendPath(PATH_SELECTED_WALLPAPER).build();\n\n\n        public static Uri buildWallpaperUri(String wallpaperId) {\n            return CONTENT_URI.buildUpon().appendPath(wallpaperId).build();\n        }\n\n        public static String getWallpaperId(Uri uri) {\n            return uri.getPathSegments().get(1);\n        }\n    }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/repository/datasource/provider/StyleContractHelper.java",
    "content": "package com.yalin.style.data.repository.datasource.provider;\n\nimport android.net.Uri;\n\n/**\n * YaLin 2017/1/3.\n */\n\npublic class StyleContractHelper {\n\n  private static final String QUERY_PARAMETER_CALLER_IS_SYNC_ADAPTER = \"callerIsSyncAdapter\";\n\n  public static Uri setUriAsCalledFromSyncAdapter(Uri uri) {\n    return uri.buildUpon().appendQueryParameter(QUERY_PARAMETER_CALLER_IS_SYNC_ADAPTER, \"true\")\n        .build();\n  }\n\n  public static boolean isUriCalledFromSyncAdapter(Uri uri) {\n    return uri.getBooleanQueryParameter(QUERY_PARAMETER_CALLER_IS_SYNC_ADAPTER, false);\n  }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/repository/datasource/provider/StyleDatabase.java",
    "content": "package com.yalin.style.data.repository.datasource.provider;\n\nimport android.content.ContentResolver;\nimport android.content.Context;\nimport android.database.sqlite.SQLiteDatabase;\nimport android.database.sqlite.SQLiteOpenHelper;\nimport android.provider.BaseColumns;\n\nimport com.yalin.style.data.R;\nimport com.yalin.style.data.log.LogUtil;\nimport com.yalin.style.data.repository.datasource.provider.StyleContract.AdvanceWallpaper;\nimport com.yalin.style.data.repository.datasource.provider.StyleContract.Wallpaper;\nimport com.yalin.style.data.repository.datasource.sync.account.Account;\n\n/**\n * YaLin 2016/12/30.\n */\n\npublic class StyleDatabase extends SQLiteOpenHelper {\n\n    private static final String TAG = \"StyleDatabase\";\n    private static final String DATABASE_NAME = \"style.db\";\n\n    private static final int VERSION_2016_12_30 = 1;\n    private static final int VERSION_2017_4_30 = 2;\n    private static final int VERSION_2017_5_24 = 3;\n    private static final int VERSION_2017_7_28 = 4;\n    private static final int VERSION_2017_8_11 = 5;\n    private static final int CUR_DATABASE_VERSION = VERSION_2017_8_11;\n\n    private final Context mContext;\n\n    interface Tables {\n\n        String WALLPAPER = StyleContract.Wallpaper.TABLE_NAME;\n        String GALLERY = StyleContract.GalleryWallpaper.TABLE_NAME;\n        String ADVANCE_WALLPAPER = AdvanceWallpaper.TABLE_NAME;\n    }\n\n    public StyleDatabase(Context context) {\n        super(context, DATABASE_NAME, null, CUR_DATABASE_VERSION);\n        mContext = context;\n    }\n\n    @Override\n    public void onCreate(SQLiteDatabase db) {\n        db.execSQL(\"CREATE TABLE \" + Tables.WALLPAPER + \" (\"\n                + BaseColumns._ID + \" INTEGER PRIMARY KEY AUTOINCREMENT,\"\n                + StyleContract.Wallpaper.COLUMN_NAME_WALLPAPER_ID + \" TEXT,\"\n                + StyleContract.Wallpaper.COLUMN_NAME_TITLE + \" TEXT,\"\n                + StyleContract.Wallpaper.COLUMN_NAME_IMAGE_URI + \" TEXT,\"\n                + StyleContract.Wallpaper.COLUMN_NAME_ATTRIBUTION + \" TEXT,\"\n                + StyleContract.Wallpaper.COLUMN_NAME_BYLINE + \" TEXT,\"\n                + StyleContract.Wallpaper.COLUMN_NAME_ADD_DATE + \" INTEGER);\");\n\n        upgradeFrom20161230to20170430(db);\n        upgradeFrom20170430to20170524(db);\n        upgradeFrom20170525to20170728(db);\n        upgradeFrom20170728to20170811(db);\n    }\n\n    @Override\n    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {\n        LogUtil.D(TAG, \"onUpgrade() from \" + oldVersion + \" to \" + newVersion);\n        // Cancel any sync currently in progress\n        android.accounts.Account account = Account.getAccount();\n        if (account != null) {\n            LogUtil.D(TAG, \"Cancelling any pending syncs for account\");\n            ContentResolver.cancelSync(account, mContext.getString(R.string.authority));\n        }\n        int version = oldVersion;\n        if (version == VERSION_2016_12_30) {\n            upgradeFrom20161230to20170430(db);\n            version = VERSION_2017_4_30;\n        }\n        if (version == VERSION_2017_4_30) {\n            upgradeFrom20170430to20170524(db);\n            version = VERSION_2017_5_24;\n        }\n        if (version == VERSION_2017_5_24) {\n            upgradeFrom20170525to20170728(db);\n            version = VERSION_2017_7_28;\n        }\n        if (version == VERSION_2017_7_28) {\n            upgradeFrom20170728to20170811(db);\n            version = VERSION_2017_8_11;\n        }\n        if (version != CUR_DATABASE_VERSION) {\n            LogUtil.E(TAG, \"Upgrade unsuccessful -- destroying old data during upgrade\");\n\n            db.execSQL(\"DROP TABLE IF EXISTS \" + Tables.WALLPAPER);\n            db.execSQL(\"DROP TABLE IF EXISTS \" + Tables.GALLERY);\n            onCreate(db);\n            version = CUR_DATABASE_VERSION;\n        }\n    }\n\n    private void upgradeFrom20161230to20170430(SQLiteDatabase db) {\n        db.execSQL(\"ALTER TABLE \" + Tables.WALLPAPER\n                + \" ADD COLUMN \" + Wallpaper.COLUMN_NAME_LIKED + \" INTEGER NOT NULL DEFAULT 0\");\n        db.execSQL(\"ALTER TABLE \" + Tables.WALLPAPER\n                + \" ADD COLUMN \" + Wallpaper.COLUMN_NAME_CHECKSUM + \" TEXT\");\n    }\n\n    private void upgradeFrom20170430to20170524(SQLiteDatabase db) {\n        db.execSQL(\"CREATE TABLE \" + Tables.GALLERY + \" (\"\n                + BaseColumns._ID + \" INTEGER PRIMARY KEY AUTOINCREMENT,\"\n                + StyleContract.GalleryWallpaper.COLUMN_NAME_CUSTOM_URI + \" TEXT NOT NULL,\"\n                + StyleContract.GalleryWallpaper.COLUMN_NAME_IS_TREE_URI + \" INTEGER,\"\n                + StyleContract.GalleryWallpaper.COLUMN_NAME_DATE_TIME + \" INTEGER,\"\n                + StyleContract.GalleryWallpaper.COLUMN_NAME_LOCATION + \" TEXT,\"\n                + StyleContract.GalleryWallpaper.COLUMN_NAME_HAS_METADATA + \" INTEGER DEFAULT 0,\"\n                + \"UNIQUE (\" + StyleContract.GalleryWallpaper.COLUMN_NAME_CUSTOM_URI\n                + \") ON CONFLICT REPLACE);\");\n    }\n\n    private void upgradeFrom20170525to20170728(SQLiteDatabase db) {\n        db.execSQL(\"CREATE TABLE \" + Tables.ADVANCE_WALLPAPER + \" (\"\n                + BaseColumns._ID + \" INTEGER PRIMARY KEY AUTOINCREMENT,\"\n                + AdvanceWallpaper.COLUMN_NAME_WALLPAPER_ID + \" TEXT,\"\n                + AdvanceWallpaper.COLUMN_NAME_ICON_URL + \" TEXT,\"\n                + AdvanceWallpaper.COLUMN_NAME_DOWNLOAD_URL + \" TEXT,\"\n                + AdvanceWallpaper.COLUMN_NAME_NAME + \" TEXT,\"\n                + AdvanceWallpaper.COLUMN_NAME_AUTHOR + \" TEXT,\"\n                + AdvanceWallpaper.COLUMN_NAME_STORE_PATH + \" TEXT,\"\n                + AdvanceWallpaper.COLUMN_NAME_LINK + \" TEXT,\"\n                + AdvanceWallpaper.COLUMN_NAME_PROVIDER_NAME + \" TEXT,\"\n                + AdvanceWallpaper.COLUMN_NAME_CHECKSUM + \" TEXT,\"\n                + AdvanceWallpaper.COLUMN_NAME_SELECTED + \" INTEGER DEFAULT 0);\");\n    }\n\n    private void upgradeFrom20170728to20170811(SQLiteDatabase db) {\n        db.execSQL(\"ALTER TABLE \" + Tables.ADVANCE_WALLPAPER\n                + \" ADD COLUMN \" + AdvanceWallpaper.COLUMN_NAME_LAZY_DOWNLOAD\n                + \" INTEGER NOT NULL DEFAULT 0\");\n        db.execSQL(\"ALTER TABLE \" + Tables.ADVANCE_WALLPAPER\n                + \" ADD COLUMN \" + AdvanceWallpaper.COLUMN_NAME_NEED_AD\n                + \" INTEGER NOT NULL DEFAULT 0\");\n    }\n\n    public static void deleteDatabase(Context context) {\n        context.deleteDatabase(DATABASE_NAME);\n    }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/repository/datasource/provider/StyleProvider.java",
    "content": "package com.yalin.style.data.repository.datasource.provider;\n\nimport android.content.ContentProvider;\nimport android.content.ContentProviderOperation;\nimport android.content.ContentProviderResult;\nimport android.content.ContentValues;\nimport android.content.Context;\nimport android.content.OperationApplicationException;\nimport android.database.Cursor;\nimport android.database.sqlite.SQLiteDatabase;\nimport android.net.Uri;\nimport android.os.ParcelFileDescriptor;\nimport android.provider.BaseColumns;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\n\nimport com.yalin.style.data.log.LogUtil;\nimport com.yalin.style.data.repository.datasource.provider.StyleContract.AdvanceWallpaper;\nimport com.yalin.style.data.repository.datasource.provider.StyleContract.GalleryWallpaper;\nimport com.yalin.style.data.repository.datasource.provider.StyleContract.Wallpaper;\nimport com.yalin.style.data.utils.SelectionBuilder;\nimport com.yalin.style.data.utils.WallpaperFileHelper;\n\nimport java.io.FileNotFoundException;\nimport java.util.ArrayList;\nimport java.util.Arrays;\n\n/**\n * YaLin 2016/12/30.\n */\n\npublic class StyleProvider extends ContentProvider {\n\n    private static final String TAG = \"StyleProvider\";\n\n    private StyleDatabase mOpenHelper;\n\n    private StyleProviderUriMatcher mUriMatcher;\n\n    @Override\n    public boolean onCreate() {\n        mOpenHelper = new StyleDatabase(getContext());\n        mUriMatcher = new StyleProviderUriMatcher();\n        return true;\n    }\n\n    private void deleteDatabase() {\n        mOpenHelper.close();\n        Context context = getContext();\n        StyleDatabase.deleteDatabase(context);\n        mOpenHelper = new StyleDatabase(context);\n    }\n\n    @NonNull\n    @Override\n    public ContentProviderResult[] applyBatch(@NonNull ArrayList<ContentProviderOperation> operations)\n            throws OperationApplicationException {\n        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();\n        db.beginTransaction();\n        try {\n            final int numOperations = operations.size();\n            final ContentProviderResult[] results = new ContentProviderResult[numOperations];\n            for (int i = 0; i < numOperations; i++) {\n                results[i] = operations.get(i).apply(this, results, i);\n            }\n            db.setTransactionSuccessful();\n            return results;\n        } finally {\n            db.endTransaction();\n        }\n    }\n\n    @Nullable\n    @Override\n    public Cursor query(@NonNull Uri uri, String[] projection, String selection,\n                        String[] selectionArgs,\n                        String sortOrder) {\n        final SQLiteDatabase db = mOpenHelper.getReadableDatabase();\n\n        StyleUriEnum uriEnum = mUriMatcher.matchUri(uri);\n\n        LogUtil.D(TAG, \"uri=\" + uri + \" code=\" + uriEnum.code + \" proj=\" +\n                Arrays.toString(projection) + \" selection=\" + selection + \" args=\"\n                + Arrays.toString(selectionArgs) + \")\");\n\n        switch (uriEnum) {\n            case WALLPAPER:\n            case WALLPAPER_ID:\n            case WALLPAPER_LIKED:\n            case ADVANCE_WALLPAPER:\n            case ADVANCE_WALLPAPER_ID:\n            case ADVANCE_WALLPAPER_SELECTED: {\n                final SelectionBuilder builder = buildSimpleSelection(uri);\n                return builder.query(db, projection, BaseColumns._ID + \" DESC\");\n            }\n            case GALLERY:\n            case GALLERY_ID: {\n                final SelectionBuilder builder = buildSimpleSelection(uri);\n                return builder.query(db, projection, GalleryWallpaper._ID + \" DESC\");\n            }\n            default: {\n                final SelectionBuilder builder = buildExpandedSelection(uri, uriEnum.code);\n\n                return builder.query(db, projection, null);\n            }\n        }\n    }\n\n    @Nullable\n    @Override\n    public String getType(@NonNull Uri uri) {\n        return null;\n    }\n\n    @Nullable\n    @Override\n    public Uri insert(@NonNull Uri uri, ContentValues values) {\n        LogUtil.D(TAG, \"insert(uri=\" + uri + \", values=\" + values.toString()\n                + \")\");\n\n        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();\n        StyleUriEnum uriEnum = mUriMatcher.matchUri(uri);\n        long id = db.insertOrThrow(uriEnum.table, null, values);\n\n        switch (uriEnum) {\n            case WALLPAPER:\n                return StyleContract.Wallpaper.buildWallpaperUri(\n                        values.getAsString(StyleContract.Wallpaper.COLUMN_NAME_WALLPAPER_ID));\n            case GALLERY:\n                return StyleContract.GalleryWallpaper.buildGalleryWallpaperUri(id);\n            case ADVANCE_WALLPAPER: {\n                return AdvanceWallpaper.buildWallpaperUri(\n                        values.getAsString(AdvanceWallpaper.COLUMN_NAME_WALLPAPER_ID));\n            }\n            default: {\n                throw new UnsupportedOperationException(\"Unknown insert uri: \" + uri);\n            }\n        }\n    }\n\n    @Override\n    public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) {\n        LogUtil.D(TAG, \"delete(uri=\" + uri + \")\");\n        if (uri == StyleContract.BASE_CONTENT_URI) {\n            deleteDatabase();\n            return 1;\n        }\n        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();\n        StyleUriEnum uriEnum = mUriMatcher.matchUri(uri);\n        final SelectionBuilder builder = buildSimpleSelection(uri);\n        switch (uriEnum) {\n            case WALLPAPER: {\n                builder.where(Wallpaper.COLUMN_NAME_LIKED + \" = ?\", \"0\");\n                break;\n            }\n            case ADVANCE_WALLPAPER: {\n                builder.where(AdvanceWallpaper.COLUMN_NAME_SELECTED + \" = ?\", \"0\");\n                break;\n            }\n            case GALLERY_URI: {\n                return builder.delete(db);\n            }\n        }\n        return builder.where(selection, selectionArgs).delete(db);\n    }\n\n    @Override\n    public int update(@NonNull Uri uri, ContentValues values, String selection,\n                      String[] selectionArgs) {\n        LogUtil.D(TAG, \"update(uri=\" + uri + \")\");\n        if (uri == StyleContract.BASE_CONTENT_URI) {\n            deleteDatabase();\n            return 1;\n        }\n        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();\n        final SelectionBuilder builder = buildSimpleSelection(uri);\n        return builder.where(selection, selectionArgs).update(db, values);\n    }\n\n    @Nullable\n    @Override\n    public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode)\n            throws FileNotFoundException {\n        LogUtil.D(TAG, \"openReadFile(uri=\" + uri + \",mode=\" + mode + \")\");\n\n        StyleUriEnum uriEnum = mUriMatcher.matchUri(uri);\n        switch (uriEnum) {\n            case WALLPAPER:\n                return WallpaperFileHelper.openReadFile(getContext(), queryUriForShow(), mode);\n            case WALLPAPER_ID:\n                return WallpaperFileHelper.openReadFile(getContext(), uri, mode);\n            case WALLPAPER_SAVE:\n                return WallpaperFileHelper.openWriteFile(getContext(), uri, mode);\n            default:\n                throw new FileNotFoundException(\"Cannot match uri : \" + uri);\n        }\n    }\n\n    private Uri queryUriForShow() {\n        Cursor cursor = query(Wallpaper.CONTENT_URI,\n                new String[]{StyleContract.Wallpaper.COLUMN_NAME_IMAGE_URI},\n                null, null, null);\n        try {\n            if (cursor != null && cursor.moveToFirst()) {\n                return Uri.parse(cursor.getString(0));\n            }\n            return null;\n        } finally {\n            if (cursor != null) {\n                cursor.close();\n            }\n        }\n    }\n\n    private SelectionBuilder buildSimpleSelection(Uri uri) {\n        final SelectionBuilder builder = new SelectionBuilder();\n        StyleUriEnum uriEnum = mUriMatcher.matchUri(uri);\n\n        switch (uriEnum) {\n            case WALLPAPER:\n            case ADVANCE_WALLPAPER: {\n                return builder.table(uriEnum.table);\n            }\n            case WALLPAPER_ID: {\n                String wallpaperId = StyleContract.Wallpaper.getWallpaperId(uri);\n                return builder.table(StyleDatabase.Tables.WALLPAPER)\n                        .where(Wallpaper.COLUMN_NAME_WALLPAPER_ID + \" = ?\", wallpaperId);\n            }\n            case WALLPAPER_LIKE: {\n                String wallpaperId = StyleContract.Wallpaper.getWallpaperLikeId(uri);\n                return builder.table(StyleDatabase.Tables.WALLPAPER)\n                        .where(Wallpaper.COLUMN_NAME_WALLPAPER_ID + \" = ?\", wallpaperId);\n            }\n            case WALLPAPER_LIKED: {\n                return builder.table(StyleDatabase.Tables.WALLPAPER)\n                        .where(Wallpaper.COLUMN_NAME_LIKED + \" = ?\", \"1\");\n            }\n            case GALLERY: {\n                return builder.table(StyleDatabase.Tables.GALLERY);\n            }\n            case GALLERY_ID: {\n                String galleryWallpaperId\n                        = StyleContract.GalleryWallpaper.getGalleryWallpaperId(uri);\n                return builder.table(StyleDatabase.Tables.GALLERY)\n                        .where(GalleryWallpaper._ID + \" = ?\", galleryWallpaperId);\n            }\n            case GALLERY_URI: {\n                String uriString\n                        = StyleContract.GalleryWallpaper.getGalleryWallpaperDeleteUri(uri);\n                return builder.table(StyleDatabase.Tables.GALLERY)\n                        .where(GalleryWallpaper.COLUMN_NAME_CUSTOM_URI + \" = ?\", uriString);\n            }\n            case ADVANCE_WALLPAPER_ID: {\n                String wallpaperId = AdvanceWallpaper.getWallpaperId(uri);\n                return builder.table(StyleDatabase.Tables.ADVANCE_WALLPAPER)\n                        .where(AdvanceWallpaper.COLUMN_NAME_WALLPAPER_ID + \" = ?\", wallpaperId);\n            }\n            case ADVANCE_WALLPAPER_SELECTED: {\n                return builder.table(StyleDatabase.Tables.ADVANCE_WALLPAPER)\n                        .where(AdvanceWallpaper.COLUMN_NAME_SELECTED + \" = ?\", String.valueOf(1));\n            }\n            default: {\n                throw new UnsupportedOperationException(\"Unknown uri for \" + uri);\n            }\n        }\n    }\n\n    private SelectionBuilder buildExpandedSelection(Uri uri, int match) {\n        final SelectionBuilder builder = new SelectionBuilder();\n        StyleUriEnum uriEnum = mUriMatcher.matchUri(uri);\n        if (uriEnum == null) {\n            throw new UnsupportedOperationException(\"Unknown uri: \" + uri);\n        }\n        switch (uriEnum) {\n            default: {\n                throw new UnsupportedOperationException(\"Unknown uri: \" + uri);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/repository/datasource/provider/StyleProviderUriMatcher.java",
    "content": "package com.yalin.style.data.repository.datasource.provider;\n\nimport android.content.UriMatcher;\nimport android.net.Uri;\nimport android.util.SparseArray;\n\n/**\n * YaLin 2016/12/30.\n */\n\npublic class StyleProviderUriMatcher {\n\n  private UriMatcher mUriMatcher;\n\n  private SparseArray<StyleUriEnum> mEnumsMap = new SparseArray<>();\n\n  public StyleProviderUriMatcher() {\n    mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);\n    buildUriMatcher();\n  }\n\n  private void buildUriMatcher() {\n    final String authority = StyleContract.AUTHORITY;\n\n    StyleUriEnum[] uris = StyleUriEnum.values();\n    for (StyleUriEnum uri : uris) {\n      mUriMatcher.addURI(authority, uri.path, uri.code);\n    }\n    buildEnumsMap();\n  }\n\n  private void buildEnumsMap() {\n    StyleUriEnum[] uris = StyleUriEnum.values();\n    for (StyleUriEnum uri : uris) {\n      mEnumsMap.put(uri.code, uri);\n    }\n  }\n\n  public StyleUriEnum matchUri(Uri uri) {\n    final int code = mUriMatcher.match(uri);\n    try {\n      return matchCode(code);\n    } catch (UnsupportedOperationException e) {\n      throw new UnsupportedOperationException(\"Unknown uri \" + uri);\n    }\n  }\n\n  public StyleUriEnum matchCode(int code) {\n    StyleUriEnum uriEnum = mEnumsMap.get(code);\n    if (uriEnum != null) {\n      return uriEnum;\n    } else {\n      throw new UnsupportedOperationException(\"Unknown uri with code \" + code);\n    }\n  }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/repository/datasource/provider/StyleUriEnum.java",
    "content": "package com.yalin.style.data.repository.datasource.provider;\n\n/**\n * YaLin 2016/12/30.\n */\n\npublic enum StyleUriEnum {\n    WALLPAPER(100, \"wallpaper\", StyleDatabase.Tables.WALLPAPER),\n    WALLPAPER_SAVE(103, \"wallpaper/save/*\", null),\n    WALLPAPER_LIKE(104, \"wallpaper/like/*\", null),\n    WALLPAPER_LIKED(105, \"wallpaper/liked\", null),\n    WALLPAPER_ID(102, \"wallpaper/*\", null),\n\n    GALLERY(200, \"gallery\", StyleDatabase.Tables.GALLERY),\n    GALLERY_URI(202, \"gallery/uri/*\", null),\n    GALLERY_ID(201, \"gallery/*\", null),\n\n    ADVANCE_WALLPAPER(300, \"advance_wallpaper\", StyleDatabase.Tables.ADVANCE_WALLPAPER),\n    ADVANCE_WALLPAPER_SELECTED(302, \"advance_wallpaper/selected\", null),\n    ADVANCE_WALLPAPER_ID(301, \"advance_wallpaper/*\", null);\n\n\n    public int code;\n    public String path;\n    public String table;\n\n    StyleUriEnum(int code, String path, String table) {\n        this.code = code;\n        this.path = path;\n        this.table = table;\n    }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/repository/datasource/sync/RemoteStyleDataFetcher.java",
    "content": "package com.yalin.style.data.repository.datasource.sync;\n\nimport android.content.Context;\n\nimport com.yalin.style.data.BuildConfig;\nimport com.yalin.style.data.repository.datasource.net.DataFetcher;\n\n/**\n * YaLin 2017/1/3.\n */\n\npublic class RemoteStyleDataFetcher extends DataFetcher {\n\n\n    public RemoteStyleDataFetcher(Context context) {\n        super(context);\n    }\n\n    @Override\n    protected String getUrl() {\n        return BuildConfig.SERVER_WALLPAPER_ENDPOINT;\n    }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/repository/datasource/sync/StyleDataHandler.java",
    "content": "package com.yalin.style.data.repository.datasource.sync;\n\nimport android.content.ContentProviderOperation;\nimport android.content.ContentResolver;\nimport android.content.Context;\nimport android.content.OperationApplicationException;\nimport android.net.Uri;\nimport android.os.RemoteException;\n\nimport com.google.gson.JsonParser;\nimport com.google.gson.stream.JsonReader;\nimport com.yalin.style.data.log.LogUtil;\nimport com.yalin.style.data.repository.datasource.io.AdvanceWallpaperHandler;\nimport com.yalin.style.data.repository.datasource.io.JSONHandler;\nimport com.yalin.style.data.repository.datasource.io.WallpapersHandler;\nimport com.yalin.style.data.repository.datasource.provider.StyleContract;\n\nimport java.io.IOException;\nimport java.io.StringReader;\nimport java.util.ArrayList;\nimport java.util.HashMap;\n\n/**\n * YaLin 2017/1/3.\n */\n\npublic class StyleDataHandler {\n\n    private static final String TAG = \"StyleDataHandler\";\n\n    private static final String DATA_KEY_WALLPAPER = \"wallpapers\";\n    private static final String DATA_KEY_ADVANCE_WALLPAPER = \"advance_wallpapers\";\n\n    private static final String[] DATA_KEYS_IN_ORDER = {\n            DATA_KEY_WALLPAPER,\n            DATA_KEY_ADVANCE_WALLPAPER\n    };\n\n    Context mContext = null;\n\n    WallpapersHandler mWallpapersHandler = null;\n    AdvanceWallpaperHandler mAdvanceWallpapersHandler = null;\n\n    HashMap<String, JSONHandler> mHandlerForKey = new HashMap<>();\n\n    private int mContentProviderOperationsDone = 0;\n\n    public StyleDataHandler(Context context) {\n        mContext = context;\n    }\n\n    public void applyStyleData(String[] dataBodies) throws IOException {\n        LogUtil.D(TAG, \"Applying data from \" + dataBodies.length + \" files\");\n\n        mHandlerForKey.put(DATA_KEY_WALLPAPER, mWallpapersHandler = new WallpapersHandler(mContext));\n        mHandlerForKey.put(DATA_KEY_ADVANCE_WALLPAPER,\n                mAdvanceWallpapersHandler = new AdvanceWallpaperHandler(mContext));\n\n        LogUtil.D(TAG, \"Processing \" + dataBodies.length + \" JSON objects.\");\n        for (int i = 0; i < dataBodies.length; i++) {\n            LogUtil.D(TAG, \"Processing json object #\" + (i + 1) + \" of \" + dataBodies.length);\n            processDataBody(dataBodies[i]);\n        }\n\n        ArrayList<ContentProviderOperation> batch = new ArrayList<>();\n        for (String key : DATA_KEYS_IN_ORDER) {\n            LogUtil.D(TAG, \"Building content provider operations for: \" + key);\n            mHandlerForKey.get(key).makeContentProviderOperations(batch);\n            LogUtil.D(TAG, \"Content provider operations so far: \" + batch.size());\n        }\n\n        LogUtil.D(TAG, \"Applying \" + batch.size() + \" content provider operations.\");\n        try {\n            int operations = batch.size();\n            if (operations > 0) {\n                mContext.getContentResolver().applyBatch(StyleContract.AUTHORITY, batch);\n            }\n            LogUtil.D(TAG, \"Successfully applied \" + operations + \" content provider operations.\");\n            mContentProviderOperationsDone += operations;\n        } catch (RemoteException ex) {\n            LogUtil.D(TAG, \"RemoteException while applying content provider operations.\");\n            throw new RuntimeException(\"Error executing content provider batch operation\", ex);\n        } catch (OperationApplicationException ex) {\n            LogUtil.D(TAG, \"OperationApplicationException while applying content provider operations.\");\n            throw new RuntimeException(\"Error executing content provider batch operation\", ex);\n        }\n\n        LogUtil.D(TAG, \"Notifying changes on all top-level paths on Content Resolver.\");\n        ContentResolver resolver = mContext.getContentResolver();\n        for (String path : StyleContract.TOP_LEVEL_PATHS) {\n            Uri uri = StyleContract.BASE_CONTENT_URI.buildUpon().appendPath(path).build();\n            resolver.notifyChange(uri, null);\n        }\n\n        LogUtil.D(TAG, \"Done applying conference data.\");\n    }\n\n    private void processDataBody(String dataBody) throws IOException {\n        JsonParser parser = new JsonParser();\n        try (JsonReader reader = new JsonReader(new StringReader(dataBody))) {\n            reader.setLenient(true); // To err is human\n\n            // the whole file is a single JSON object\n            reader.beginObject();\n\n            while (reader.hasNext()) {\n                String key = reader.nextName();\n                if (mHandlerForKey.containsKey(key)) {\n                    LogUtil.D(TAG, \"Processing key in conference data json: \" + key);\n                    mHandlerForKey.get(key).process(parser.parse(reader));\n                } else {\n                    LogUtil.D(TAG, \"Skipping unknown key in conference data json: \" + key);\n                    reader.skipValue();\n                }\n            }\n            reader.endObject();\n        }\n    }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/repository/datasource/sync/SyncAdapter.java",
    "content": "package com.yalin.style.data.repository.datasource.sync;\n\nimport android.accounts.Account;\nimport android.content.AbstractThreadedSyncAdapter;\nimport android.content.ContentProviderClient;\nimport android.content.Context;\nimport android.content.SyncResult;\nimport android.os.Bundle;\n\nimport com.yalin.style.data.log.LogUtil;\n\n/**\n * YaLin 2017/1/3.\n */\n\npublic class SyncAdapter extends AbstractThreadedSyncAdapter {\n\n    private static final String TAG = \"SyncAdapter\";\n\n    public static final String SYNC_MANUALLY = \"syn_manually\";\n\n    private final Context mContext;\n\n    public SyncAdapter(Context context, boolean autoInitialize) {\n        super(context, autoInitialize);\n        mContext = context;\n\n        Thread.setDefaultUncaughtExceptionHandler((thread, throwable) ->\n                LogUtil.F(TAG, \"Uncaught sync exception, suppressing UI in release build.\",\n                        throwable));\n\n    }\n\n    @Override\n    public void onPerformSync(Account account, Bundle extras, String authority,\n                              ContentProviderClient provider, SyncResult syncResult) {\n        LogUtil.F(TAG, \"PerformSync.\");\n        if (extras.getBoolean(SYNC_MANUALLY)) {\n            LogUtil.D(TAG, \"Manually sync.\");\n        }\n        new SyncHelper(mContext).performSync(syncResult, extras);\n    }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/repository/datasource/sync/SyncHelper.java",
    "content": "package com.yalin.style.data.repository.datasource.sync;\n\n\nimport android.accounts.Account;\nimport android.content.ContentResolver;\nimport android.content.Context;\nimport android.content.SyncResult;\nimport android.net.ConnectivityManager;\nimport android.os.Bundle;\nimport android.text.TextUtils;\n\nimport com.yalin.style.data.BuildConfig;\nimport com.yalin.style.data.SyncConfig;\nimport com.yalin.style.data.log.LogUtil;\nimport com.yalin.style.data.repository.datasource.provider.StyleContract;\nimport java.io.IOException;\n\n/**\n * YaLin 2017/1/3.\n */\n\npublic class SyncHelper {\n\n  private static final String TAG = \"SyncHelper\";\n  private final Context mContext;\n\n  private StyleDataHandler mDataHandler;\n\n  public SyncHelper(Context context) {\n    mContext = context;\n    mDataHandler = new StyleDataHandler(mContext);\n  }\n\n  public boolean performSync(SyncResult syncResult, Bundle extras) {\n    try {\n      doStyleSync();\n      return true;\n    } catch (IOException e) {\n      e.printStackTrace();\n    }\n    return false;\n  }\n\n  private boolean doStyleSync() throws IOException {\n    if (!isOnline()) {\n      LogUtil.D(TAG, \"Not attempting remote sync because device is OFFLINE\");\n      return false;\n    }\n\n    LogUtil.D(TAG, \"Starting remote sync.\");\n\n    String data = new RemoteStyleDataFetcher(mContext).fetchDataIfNewer();\n    if (!TextUtils.isEmpty(data)) {\n      mDataHandler.applyStyleData(new String[]{data});\n    }\n    return true;\n  }\n\n  private boolean isOnline() {\n    ConnectivityManager cm = (ConnectivityManager) mContext\n        .getSystemService(Context.CONNECTIVITY_SERVICE);\n    return cm.getActiveNetworkInfo() != null &&\n        cm.getActiveNetworkInfo().isConnectedOrConnecting();\n  }\n\n  public static void updateSyncInterval(final Context context) {\n    Account account = com.yalin.style.data.repository.datasource.sync.account.Account.getAccount();\n    LogUtil.D(TAG, \"Checking sync interval\");\n    long recommended = calculateRecommendedSyncInterval(context);\n    LogUtil.D(TAG, \"Setting up sync for account, interval \" + recommended + \"ms\");\n    ContentResolver.setIsSyncable(account, StyleContract.AUTHORITY, 1);\n    ContentResolver.setSyncAutomatically(account, StyleContract.AUTHORITY, true);\n    ContentResolver\n        .addPeriodicSync(account, StyleContract.AUTHORITY, new Bundle(), recommended / 1000L);\n\n  }\n\n  private static long calculateRecommendedSyncInterval(final Context context) {\n    if (BuildConfig.DEMO_MODE) {\n      return SyncConfig.DEBUG_AUTO_SYNC_INTERVAL_LONG;\n    } else {\n      return SyncConfig.AUTO_SYNC_INTERVAL_LONG;\n    }\n  }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/repository/datasource/sync/SyncService.java",
    "content": "package com.yalin.style.data.repository.datasource.sync;\n\nimport android.app.Service;\nimport android.content.Intent;\nimport android.os.IBinder;\nimport android.support.annotation.Nullable;\n\n/**\n * YaLin 2017/1/3.\n */\n\npublic class SyncService extends Service {\n\n  private static final Object sSyncAdapterLock = new Object();\n  private static SyncAdapter sSyncAdapter = null;\n\n  @Override\n  public void onCreate() {\n    super.onCreate();\n    synchronized (sSyncAdapterLock) {\n      if (sSyncAdapter == null) {\n        sSyncAdapter = new SyncAdapter(getApplicationContext(), false);\n      }\n    }\n  }\n\n  @Nullable\n  @Override\n  public IBinder onBind(Intent intent) {\n    return sSyncAdapter.getSyncAdapterBinder();\n  }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/repository/datasource/sync/account/Account.java",
    "content": "package com.yalin.style.data.repository.datasource.sync.account;\n\nimport android.accounts.AccountManager;\nimport android.content.Context;\n\nimport com.yalin.style.data.log.LogUtil;\n\n/**\n * YaLin 2017/1/3.\n */\n\npublic class Account {\n\n  public static final String ACCOUNT_TYPE = \"com.yalin.style\";\n\n  public static final String ACCOUNT_NAME = \"Sync Account\";\n\n  private static final String TAG = \"Account\";\n  private static android.accounts.Account mAccount;\n\n  public static android.accounts.Account createSyncAccount(Context context) {\n    AccountManager accountManager = (AccountManager) context\n        .getSystemService(Context.ACCOUNT_SERVICE);\n\n    android.accounts.Account account = getAccount();\n    if (accountManager.addAccountExplicitly(account, null, null)) {\n      return account;\n    } else {\n      LogUtil.D(TAG, \"Unable to create account\");\n      return null;\n    }\n  }\n\n  public static android.accounts.Account getAccount() {\n    if (mAccount == null) {\n      mAccount = new android.accounts.Account(ACCOUNT_NAME, ACCOUNT_TYPE);\n    }\n    return mAccount;\n  }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/repository/datasource/sync/account/Authenticator.java",
    "content": "package com.yalin.style.data.repository.datasource.sync.account;\n\nimport android.accounts.*;\nimport android.accounts.Account;\nimport android.content.Context;\nimport android.os.Bundle;\n\n/**\n * YaLin 2017/1/3.\n */\n\npublic class Authenticator extends AbstractAccountAuthenticator {\n\n  public Authenticator(Context context) {\n    super(context);\n  }\n\n  @Override\n  public Bundle editProperties(\n      AccountAuthenticatorResponse response, String accountType) {\n    throw new UnsupportedOperationException();\n  }\n\n  // Don't add additional accounts\n  @Override\n  public Bundle addAccount(\n      AccountAuthenticatorResponse response,\n      String accountType,\n      String authTokenType,\n      String[] requiredFeatures,\n      Bundle options) throws NetworkErrorException {\n    return null;\n  }\n\n  // Ignore attempts to confirm credentials\n  @Override\n  public Bundle confirmCredentials(\n      AccountAuthenticatorResponse response,\n      Account account,\n      Bundle options) throws NetworkErrorException {\n    return null;\n  }\n\n  // Getting an authentication token is not supported\n  @Override\n  public Bundle getAuthToken(\n      AccountAuthenticatorResponse response,\n      Account account,\n      String authTokenType,\n      Bundle options) throws NetworkErrorException {\n    throw new UnsupportedOperationException();\n  }\n\n  // Getting a label for the auth token is not supported\n  @Override\n  public String getAuthTokenLabel(String authTokenType) {\n    throw new UnsupportedOperationException();\n  }\n\n  // Updating user credentials is not supported\n  @Override\n  public Bundle updateCredentials(\n      AccountAuthenticatorResponse response,\n      Account account,\n      String authTokenType, Bundle options) throws NetworkErrorException {\n    throw new UnsupportedOperationException();\n  }\n\n  // Checking features for the account is not supported\n  @Override\n  public Bundle hasFeatures(\n      AccountAuthenticatorResponse response,\n      Account account, String[] options) throws NetworkErrorException {\n    throw new UnsupportedOperationException();\n  }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/repository/datasource/sync/account/AuthenticatorService.java",
    "content": "package com.yalin.style.data.repository.datasource.sync.account;\n\nimport android.app.Service;\nimport android.content.Intent;\nimport android.os.IBinder;\nimport android.support.annotation.Nullable;\n\n/**\n * YaLin 2017/1/3.\n */\n\npublic class AuthenticatorService extends Service {\n\n  private Authenticator mAuthenticator;\n\n  @Override\n  public void onCreate() {\n    super.onCreate();\n    mAuthenticator = new Authenticator(this);\n  }\n\n  @Nullable\n  @Override\n  public IBinder onBind(Intent intent) {\n    return mAuthenticator.getIBinder();\n  }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/repository/datasource/sync/gallery/GalleryScheduleService.kt",
    "content": "package com.yalin.style.data.repository.datasource.sync.gallery\n\nimport android.app.AlarmManager\nimport android.app.IntentService\nimport android.app.PendingIntent\nimport android.content.ComponentName\nimport android.content.Context\nimport android.content.Intent\nimport android.database.Cursor\nimport com.yalin.style.data.entity.GalleryWallpaperEntity\nimport com.yalin.style.data.extensions.DelegateExt\nimport com.yalin.style.data.log.LogUtil\nimport com.yalin.style.data.repository.datasource.provider.StyleContract\nimport com.yalin.style.data.utils.notifyChange\nimport java.util.*\n\n/**\n * @author jinyalin\n * @since 2017/6/9.\n */\nclass GalleryScheduleService : IntentService(TAG) {\n    companion object {\n        val TAG = \"GalleryScheduleService\"\n\n        val PREF_ROTATE_INTERVAL_MIN = \"rotate_interval_min\"\n        val PREF_CURRENT_SHOW_WALLPAPER_ID = \"current_gallery_wallpaper_id\"\n\n        val DEFAULT_ROTATE_INTERVAL_MIN = 60 * 6\n\n        val ACTION_START_UP = \"com.yalin.style.ACTION_START_UP\"\n        val ACTION_SHUT_DOWN = \"com.yalin.style.ACTION_SHUT_DOWN\"\n        val ACTION_SCHEDULE = \"com.yalin.style.ACTION_SCHEDULE\"\n        val ACTION_SET_INTERVAL = \"com.yalin.style.ACTION_SET_INTERVAL\"\n\n        val INTERVAL_KEY = \"interval\"\n\n        fun startUp(context: Context) {\n            val intent = Intent(ACTION_START_UP).setComponent(ComponentName(context,\n                    GalleryScheduleService::class.java))\n            context.startService(intent)\n        }\n\n        fun shutDown(context: Context) {\n            val intent = Intent(ACTION_SHUT_DOWN).setComponent(ComponentName(context,\n                    GalleryScheduleService::class.java))\n            context.startService(intent)\n        }\n\n        fun publish(context: Context) {\n            val intent = Intent(ACTION_SCHEDULE).setComponent(ComponentName(context,\n                    GalleryScheduleService::class.java))\n            context.startService(intent)\n        }\n\n        fun setInterval(context: Context, intervalMin: Int) {\n            val intent = Intent(ACTION_SET_INTERVAL).setComponent(ComponentName(context,\n                    GalleryScheduleService::class.java))\n            intent.putExtra(INTERVAL_KEY, intervalMin)\n            context.startService(intent)\n        }\n    }\n\n    var rotateIntervalMin: Int by DelegateExt.preferences(this,\n            PREF_ROTATE_INTERVAL_MIN, DEFAULT_ROTATE_INTERVAL_MIN)\n\n    var currentShowWallpaperId: Long by DelegateExt.preferences(this,\n            PREF_CURRENT_SHOW_WALLPAPER_ID, -1)\n\n    override fun onCreate() {\n        super.onCreate()\n    }\n\n    override fun onHandleIntent(intent: Intent?) {\n        if (intent != null) {\n            handleCommand(intent, intent.action)\n        }\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n    }\n\n    private fun handleCommand(intent: Intent, action: String) {\n        when (action) {\n            ACTION_START_UP -> startUp()\n            ACTION_SCHEDULE -> scheduleNext()\n            ACTION_SHUT_DOWN -> shutDown()\n            ACTION_SET_INTERVAL -> setInterval(intent.getIntExtra(INTERVAL_KEY,\n                    DEFAULT_ROTATE_INTERVAL_MIN))\n        }\n    }\n\n    private fun startUp() {\n        LogUtil.D(TAG, \"Start up gallery schedule service.\")\n        setNextAlarm()\n    }\n\n    private fun scheduleNext() {\n        LogUtil.D(TAG, \"Schedule next gallery wallpaper.\")\n        setNextAlarm()\n        publicNextWallpaper()\n    }\n\n    private fun shutDown() {\n        LogUtil.D(TAG, \"Shut down gallery schedule service.\")\n        cancelAlarm()\n        stopSelf()\n    }\n\n    private fun setInterval(intervalMin: Int) {\n        rotateIntervalMin = intervalMin\n        LogUtil.D(TAG, \"Set schedule interval $rotateIntervalMin = $intervalMin\")\n        setNextAlarm()\n    }\n\n    private fun setNextAlarm() {\n        if (rotateIntervalMin > 0) {\n            setUpdateAlarm(System.currentTimeMillis() + rotateIntervalMin * 60 * 1000)\n        } else {\n            cancelAlarm()\n        }\n    }\n\n    private fun setUpdateAlarm(nextTimeMillis: Long) {\n        if (nextTimeMillis < System.currentTimeMillis()) {\n            LogUtil.D(TAG, \"Refusing to schedule next artwork in the past\")\n            return\n        }\n\n        val am = getSystemService(Context.ALARM_SERVICE) as AlarmManager\n        am.set(AlarmManager.RTC, nextTimeMillis, getHandleNextCommandPendingIntent(this))\n        LogUtil.D(TAG, \"Scheduling next gallery at \" + Date(nextTimeMillis))\n    }\n\n    private fun cancelAlarm() {\n        LogUtil.D(TAG, \"Cancel schedule alarm\")\n        val am = getSystemService(Context.ALARM_SERVICE) as AlarmManager\n        am.cancel(getHandleNextCommandPendingIntent(this))\n    }\n\n    private fun getHandleNextCommandPendingIntent(context: Context): PendingIntent {\n        return PendingIntent.getService(context, 0,\n                Intent(ACTION_SCHEDULE).setComponent(ComponentName(context, javaClass)),\n                PendingIntent.FLAG_UPDATE_CURRENT)\n    }\n\n    private fun publicNextWallpaper() {\n        var cursor: Cursor? = null\n        val validWallpapers = ArrayList<GalleryWallpaperEntity>()\n        try {\n            cursor = contentResolver.query(StyleContract.GalleryWallpaper.CONTENT_URI,\n                    null, null, null, null)\n            validWallpapers.addAll(GalleryWallpaperEntity.readCursor(this, cursor))\n        } finally {\n            if (cursor != null) {\n                cursor.close()\n            }\n        }\n\n        if (validWallpapers.size > 1) {\n            val random = Random()\n            while (true) {\n                val selectedEntity = validWallpapers[random.nextInt(validWallpapers.size)]\n                if (selectedEntity.id != currentShowWallpaperId) {\n                    currentShowWallpaperId = selectedEntity.id\n                    break\n                }\n            }\n        } else if (validWallpapers.size == 1) {\n            if (currentShowWallpaperId != validWallpapers[0].id) {\n                currentShowWallpaperId = validWallpapers[0].id\n            }\n        } else {\n            if (currentShowWallpaperId != -1L) {\n                currentShowWallpaperId = -1\n            }\n        }\n\n        LogUtil.D(TAG, \"Current select wallpaper wallpaperId : $currentShowWallpaperId \")\n        notifyChange(this, StyleContract.GalleryWallpaper.CONTENT_URI)\n    }\n}"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/utils/ChecksumUtil.java",
    "content": "package com.yalin.style.data.utils;\n\nimport android.util.Base64;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.InputStream;\nimport java.security.DigestInputStream;\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\n\n/**\n * @author jinyalin\n * @since 2017/4/25.\n */\n\npublic class ChecksumUtil {\n    public static String getChecksum(File file) {\n        try {\n            MessageDigest md = MessageDigest.getInstance(\"MD5\");\n            try (InputStream is = new FileInputStream(file);\n                 DigestInputStream dis = new DigestInputStream(is, md)) {\n                byte[] buffer = new byte[2048];\n                //noinspection StatementWithEmptyBody\n                while (dis.read(buffer) > 0) {\n\n                }\n                byte[] digest = dis.getMessageDigest().digest();\n                return Base64.encodeToString(digest, Base64.URL_SAFE).trim();\n            } catch (Exception e) {\n                return null;\n            }\n        } catch (NoSuchAlgorithmException e) {\n            e.printStackTrace();\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/utils/DeviceUtil.java",
    "content": "package com.yalin.style.data.utils;\n\nimport android.annotation.SuppressLint;\nimport android.content.Context;\nimport android.os.Build;\nimport android.provider.Settings;\n\n/**\n * @author jinyalin\n * @since 2017/4/25.\n */\n\npublic class DeviceUtil {\n\n    private DeviceUtil() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    public static int getSDKVersion() {\n        return android.os.Build.VERSION.SDK_INT;\n    }\n\n    @SuppressLint(\"HardwareIds\")\n    public static String getAndroidID(Context context) {\n        return Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);\n    }\n\n    public static String getManufacturer() {\n        return Build.MANUFACTURER;\n    }\n\n    public static String getModel() {\n        String model = Build.MODEL;\n        if (model != null) {\n            model = model.trim().replaceAll(\"\\\\s*\", \"\");\n        } else {\n            model = \"\";\n        }\n        return model;\n    }\n\n    public static String getBrand() {\n        String brand = Build.BRAND;\n        if (brand != null) {\n            brand = brand.trim().replaceAll(\"\\\\s*\", \"\");\n        } else {\n            brand = \"\";\n        }\n        return brand;\n    }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/utils/FacetIdUtil.java",
    "content": "package com.yalin.style.data.utils;\n\n\nimport android.content.Context;\n\n/**\n * @author jinyalin\n * @since 2017/4/26.\n */\npublic class FacetIdUtil {\n    static {\n        System.loadLibrary(\"facet_id-lib\");\n    }\n\n    public static boolean checkCurrentFacetId(Context context) {\n        return checkCurrentFacetId(context, getUid(context));\n    }\n\n    public static String getFacetId(Context context) {\n        return getFacetId(context, getUid(context));\n    }\n\n    private static native boolean checkCurrentFacetId(Context context, int uId);\n\n    private static native String getFacetId(Context context, int uId);\n\n    private static int getUid(Context context) {\n        if (context != null) {\n            return context.getApplicationInfo().uid;\n        } else {\n            return -1;\n        }\n    }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/utils/HttpRequestUtil.java",
    "content": "package com.yalin.style.data.utils;\n\nimport android.content.Context;\n\nimport com.google.gson.Gson;\nimport com.yalin.style.data.entity.DeviceInfo;\nimport com.yalin.style.data.entity.HttpRequestBody;\n\n/**\n * @author jinyalin\n * @since 2017/4/25.\n */\n\npublic class HttpRequestUtil {\n    private static Gson gson = new Gson();\n\n    public static String getRequestBody(Context context) {\n        HttpRequestBody requestBody = new HttpRequestBody(context, getDeviceJson(context));\n        return gson.toJson(requestBody);\n    }\n\n    private static DeviceInfo getDeviceJson(Context context) {\n        int sdkVersion = DeviceUtil.getSDKVersion();\n        String androidId = DeviceUtil.getAndroidID(context);\n        String manufacturer = DeviceUtil.getManufacturer();\n        String brand = DeviceUtil.getBrand();\n        String model = DeviceUtil.getModel();\n\n        return new DeviceInfo(sdkVersion, androidId, manufacturer, brand, model);\n    }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/utils/NativeFileHelper.kt",
    "content": "package com.yalin.style.data.utils\n\nimport android.content.Context\nimport android.text.TextUtils\nimport com.yalin.style.data.log.LogUtil\nimport java.io.File\n\n/**\n * @author jinyalin\n * @since 2017/8/10.\n */\n\nprivate val TAG = \"NativeFileHelper\"\nprivate var nativePath: String? = null\n\nfun getNativeDir(context: Context): File {\n    if (TextUtils.isEmpty(nativePath)) {\n        val cacheDir = context.cacheDir\n        val nativeDir = File(cacheDir.parent, \"lib\")\n        nativeDir.mkdirs()\n        nativePath = nativeDir.absolutePath\n    }\n    return File(nativePath)\n}\n\nfun getNativeFileName(componentPath: String, libName: String): String {\n    return \"plugin_\" + getComponentName(componentPath) + \"_\" + libName + \".so\"\n}\n\nfun clearNativeFiles(context: Context, componentPath: String) {\n    val files = getNativeFiles(context, componentPath)\n    for (file in files) {\n        file.delete()\n    }\n}\n\nprivate fun getNativeFiles(context: Context, componentPath: String): Array<File> {\n    val nativePrefix = \"plugin_\" + getComponentName(componentPath)\n    return getNativeDir(context).listFiles({ file -> file.name.contains(nativePrefix) })\n}\n\nprivate fun getComponentName(componentPath: String): String {\n    var componentName: String\n    try {\n        val tmp = componentPath.split(\"/\".toRegex())\n        componentName = tmp[tmp.size - 1].split(\"\\\\.\".toRegex())[0]\n    } catch (e: Exception) {\n        componentName = componentPath.hashCode().toString()\n    }\n    LogUtil.D(TAG, \"getComponentName for $componentPath result :$componentName\")\n    return componentName\n}"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/utils/NetworkUtil.java",
    "content": "package com.yalin.style.data.utils;\n\nimport android.content.Context;\nimport android.net.ConnectivityManager;\nimport android.net.NetworkInfo;\n\n/**\n * @author jinyalin\n * @since 2017/4/29.\n */\n\npublic class NetworkUtil {\n    public static boolean isThereInternetConnection(Context context) {\n        boolean isConnected;\n\n        ConnectivityManager connectivityManager =\n                (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);\n        NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();\n        isConnected = (networkInfo != null && networkInfo.isConnectedOrConnecting());\n\n        return isConnected;\n    }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/utils/SelectionBuilder.java",
    "content": "package com.yalin.style.data.utils;\n\nimport android.content.ContentValues;\nimport android.database.Cursor;\nimport android.database.sqlite.SQLiteDatabase;\nimport android.text.TextUtils;\n\nimport com.yalin.style.data.log.LogUtil;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * YaLin 2016/12/30.\n */\n\npublic class SelectionBuilder {\n\n  private static final String TAG = \"SelectionBuilder\";\n\n  private String mTable = null;\n  private Map<String, String> mProjectionMap = new HashMap<>();\n  private StringBuilder mSelection = new StringBuilder();\n  private ArrayList<String> mSelectionArgs = new ArrayList<>();\n  private String mGroupBy = null;\n  private String mHaving = null;\n\n  /**\n   * Reset any internal state, allowing this builder to be recycled.\n   */\n  public SelectionBuilder reset() {\n    mTable = null;\n    mGroupBy = null;\n    mHaving = null;\n    mSelection.setLength(0);\n    mSelectionArgs.clear();\n    return this;\n  }\n\n  /**\n   * Append the given selection clause to the internal state. Each clause is surrounded with\n   * parenthesis and combined using {@code AND}.\n   */\n  public SelectionBuilder where(String selection, String... selectionArgs) {\n    if (TextUtils.isEmpty(selection)) {\n      if (selectionArgs != null && selectionArgs.length > 0) {\n        throw new IllegalArgumentException(\n            \"Valid selection required when including arguments=\");\n      }\n\n      // Shortcut when clause is empty\n      return this;\n    }\n\n    if (mSelection.length() > 0) {\n      mSelection.append(\" AND \");\n    }\n\n    mSelection.append(\"(\").append(selection).append(\")\");\n    if (selectionArgs != null) {\n      Collections.addAll(mSelectionArgs, selectionArgs);\n    }\n\n    return this;\n  }\n\n  public SelectionBuilder groupBy(String groupBy) {\n    mGroupBy = groupBy;\n    return this;\n  }\n\n  public SelectionBuilder having(String having) {\n    mHaving = having;\n    return this;\n  }\n\n  public SelectionBuilder table(String table) {\n    mTable = table;\n    return this;\n  }\n\n  /**\n   * Replace positional params in table. Use for JOIN ON conditions.\n   */\n  public SelectionBuilder table(String table, String... tableParams) {\n    if (tableParams != null && tableParams.length > 0) {\n      String[] parts = table.split(\"[?]\", tableParams.length + 1);\n      StringBuilder sb = new StringBuilder(parts[0]);\n      for (int i = 1; i < parts.length; i++) {\n        sb.append('\"').append(tableParams[i - 1]).append('\"')\n            .append(parts[i]);\n      }\n      mTable = sb.toString();\n    } else {\n      mTable = table;\n    }\n    return this;\n  }\n\n  private void assertTable() {\n    if (mTable == null) {\n      throw new IllegalStateException(\"Table not specified\");\n    }\n  }\n\n  public SelectionBuilder mapToTable(String column, String table) {\n    mProjectionMap.put(column, table + \".\" + column);\n    return this;\n  }\n\n  public SelectionBuilder map(String fromColumn, String toClause) {\n    mProjectionMap.put(fromColumn, toClause + \" AS \" + fromColumn);\n    return this;\n  }\n\n  /**\n   * Return selection string for current internal state.\n   *\n   * @see #getSelectionArgs()\n   */\n  public String getSelection() {\n    return mSelection.toString();\n  }\n\n  /**\n   * Return selection arguments for current internal state.\n   *\n   * @see #getSelection()\n   */\n  public String[] getSelectionArgs() {\n    return mSelectionArgs.toArray(new String[mSelectionArgs.size()]);\n  }\n\n  private void mapColumns(String[] columns) {\n    for (int i = 0; i < columns.length; i++) {\n      final String target = mProjectionMap.get(columns[i]);\n      if (target != null) {\n        columns[i] = target;\n      }\n    }\n  }\n\n  @Override\n  public String toString() {\n    return \"SelectionBuilder[table=\" + mTable + \", selection=\" + getSelection()\n        + \", selectionArgs=\" + Arrays.toString(getSelectionArgs())\n        + \"projectionMap = \" + mProjectionMap + \" ]\";\n  }\n\n  /**\n   * Execute query using the current internal state as {@code WHERE} clause.\n   */\n  public Cursor query(SQLiteDatabase db, String[] columns, String orderBy) {\n    return query(db, false, columns, orderBy, null);\n  }\n\n  /**\n   * Execute query using the current internal state as {@code WHERE} clause.\n   */\n  public Cursor query(SQLiteDatabase db, boolean distinct, String[] columns, String orderBy,\n      String limit) {\n    assertTable();\n    if (columns != null) {\n      mapColumns(columns);\n    }\n    LogUtil.D(TAG, \"query(columns=\" + Arrays.toString(columns)\n        + \", distinct=\" + distinct + \") \" + this);\n    return db.query(distinct, mTable, columns, getSelection(), getSelectionArgs(), mGroupBy,\n        mHaving, orderBy, limit);\n  }\n\n  /**\n   * Execute update using the current internal state as {@code WHERE} clause.\n   */\n  public int update(SQLiteDatabase db, ContentValues values) {\n    assertTable();\n    LogUtil.D(TAG, \"update() \" + this);\n    return db.update(mTable, values, getSelection(), getSelectionArgs());\n  }\n\n  /**\n   * Execute delete using the current internal state as {@code WHERE} clause.\n   */\n  public int delete(SQLiteDatabase db) {\n    assertTable();\n    LogUtil.D(TAG, \"delete() \" + this);\n    return db.delete(mTable, getSelection(), getSelectionArgs());\n  }\n\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/utils/TimeUtil.java",
    "content": "package com.yalin.style.data.utils;\n\nimport android.content.Context;\n\n/**\n * YaLin 2017/1/3.\n */\n\npublic class TimeUtil {\n\n  public static long getCurrentTime(final Context context) {\n    return System.currentTimeMillis();\n  }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/utils/UriUtil.kt",
    "content": "package com.yalin.style.data.utils\n\nimport android.annotation.TargetApi\nimport android.content.Context\nimport android.content.Intent\nimport android.content.pm.PackageManager\nimport android.database.Cursor\nimport android.database.SQLException\nimport android.net.Uri\nimport android.os.Binder\nimport android.os.Build\nimport android.provider.DocumentsContract\nimport android.text.TextUtils\nimport com.yalin.style.data.log.LogUtil\nimport com.yalin.style.domain.GalleryWallpaper\nimport java.io.File\nimport java.io.FileOutputStream\nimport java.io.IOException\nimport java.io.UnsupportedEncodingException\nimport java.security.MessageDigest\nimport java.security.NoSuchAlgorithmException\nimport java.util.*\n\n/**\n * @author jinyalin\n * @since 2017/5/25.\n */\n\nprivate val TAG = \"UriUtil\"\n\nfun isTreeUri(possibleTreeUri: Uri): Boolean {\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n        return DocumentsContract.isTreeUri(possibleTreeUri)\n    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n        // Prior to N we can't directly check if the URI is a tree URI, so we have to just try it\n        try {\n            val treeDocumentId = DocumentsContract.getTreeDocumentId(possibleTreeUri)\n            return !TextUtils.isEmpty(treeDocumentId)\n        } catch (e: IllegalArgumentException) {\n            // Definitely not a tree URI\n            return false\n        }\n\n    }\n    // No tree URIs prior to Lollipop\n    return false\n}\n\n@TargetApi(Build.VERSION_CODES.LOLLIPOP)\nfun getImagesFromTreeUri(context: Context, treeUri: Uri, maxImages: Int): List<Uri> {\n    val images = ArrayList<Uri>()\n    val directories = LinkedList<String>()\n    directories.add(DocumentsContract.getTreeDocumentId(treeUri))\n    while (images.size < maxImages && !directories.isEmpty()) {\n        val parentDocumentId = directories.poll()\n        val childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(treeUri,\n                parentDocumentId)\n        var children: Cursor?\n        try {\n            children = context.contentResolver.query(childrenUri,\n                    arrayOf(DocumentsContract.Document.COLUMN_DOCUMENT_ID,\n                            DocumentsContract.Document.COLUMN_MIME_TYPE), null, null, null)\n        } catch (e: SecurityException) {\n            // No longer can read this URI, which means no images from this URI\n            // This a temporary state as the next onLoadFinished() will remove this item entirely\n            children = null\n        }\n\n        if (children == null) {\n            continue\n        }\n        while (children.moveToNext()) {\n            val documentId = children.getString(\n                    children.getColumnIndex(DocumentsContract.Document.COLUMN_DOCUMENT_ID))\n            val mimeType = children.getString(\n                    children.getColumnIndex(DocumentsContract.Document.COLUMN_MIME_TYPE))\n            if (DocumentsContract.Document.MIME_TYPE_DIR == mimeType) {\n                directories.add(documentId)\n            } else if (mimeType != null && mimeType.startsWith(\"image/\")) {\n                // Add images to the list\n                images.add(DocumentsContract.buildDocumentUriUsingTree(treeUri, documentId))\n            }\n            if (images.size == maxImages) {\n                break\n            }\n        }\n        children.close()\n    }\n    return images\n}\n\n@TargetApi(Build.VERSION_CODES.LOLLIPOP)\nfun getDisplayNameForTreeUri(context: Context, treeUri: Uri): String? {\n    val documentUri = DocumentsContract.buildDocumentUriUsingTree(treeUri,\n            DocumentsContract.getTreeDocumentId(treeUri))\n    var data: Cursor? = null\n    try {\n        data = context.contentResolver.query(documentUri,\n                arrayOf(DocumentsContract.Document.COLUMN_DISPLAY_NAME), null, null, null)\n    } catch (e: Throwable) {\n        LogUtil.E(TAG, \"getDisplayNameForTreeUri failed.\", e)\n    }\n    var displayName: String? = null\n    if (data != null && data.moveToNext()) {\n        displayName = data.getString(\n                data.getColumnIndex(DocumentsContract.Document.COLUMN_DISPLAY_NAME))\n    }\n    data?.close()\n    return displayName\n}\n\n\nfun processUriPermission(context: Context, galleryWallpaper: GalleryWallpaper) {\n    val uri = Uri.parse(galleryWallpaper.uri)\n    if (galleryWallpaper.isTreeUri) {\n        try {\n            context.contentResolver.takePersistableUriPermission(uri,\n                    Intent.FLAG_GRANT_READ_URI_PERMISSION)\n        } catch (e: SecurityException) {\n            // You can't persist URI permissions from your own app, so this fails.\n            // We'll still have access to it directly\n            LogUtil.E(TAG, \"processUriPermission exception \", e)\n        }\n    } else {\n        val haveUriPermission = context.checkUriPermission(uri,\n                Binder.getCallingPid(), Binder.getCallingUid(),\n                Intent.FLAG_GRANT_READ_URI_PERMISSION) == PackageManager.PERMISSION_GRANTED\n        // If we only have permission to this URI via URI permissions (rather than directly,\n        // such as if the URI is from our own app), it is from an external source and we need\n        // to make sure to gain persistent access to the URI's content\n        if (haveUriPermission) {\n            var persistedPermission = false\n            // Try to persist access to the URI, saving us from having to store a local copy\n            if (DocumentsContract.isDocumentUri(context, uri)) {\n                try {\n                    context.contentResolver.takePersistableUriPermission(uri,\n                            Intent.FLAG_GRANT_READ_URI_PERMISSION)\n                    persistedPermission = true\n                    // If we have a persisted URI permission, we don't need a local copy\n                    val cachedFile = getCacheFileForUri(context, galleryWallpaper.uri)\n                    if (cachedFile != null && cachedFile.exists()) {\n                        if (!cachedFile.delete()) {\n                            LogUtil.D(TAG, \"Unable to delete \" + cachedFile)\n                        }\n                    }\n                } catch (e: SecurityException) {\n                    // If we don't have FLAG_GRANT_PERSISTABLE_URI_PERMISSION (such as when using ACTION_GET_CONTENT),\n                    // this will fail. We'll need to make a local copy (handled below)\n                    LogUtil.E(TAG, \"processUriPermission exception \", e)\n                }\n\n            }\n            if (!persistedPermission) {\n                // We only need to make a local copy if we weren't able to persist the permission\n                try {\n                    writeUriToFile(context, galleryWallpaper.uri,\n                            getCacheFileForUri(context, galleryWallpaper.uri))\n                } catch (e: IOException) {\n                    LogUtil.E(TAG, \"Error downloading gallery image \"\n                            + galleryWallpaper.uri, e)\n                    throw SQLException(\"Error downloading gallery image \"\n                            + galleryWallpaper.uri, e)\n                }\n\n            }\n        } else {\n            // On API 25 and lower, we don't get URI permissions to URIs\n            // from our own package so we manage those URI permissions manually\n            val resolver = context.contentResolver\n            try {\n                resolver.call(uri, \"takePersistableUriPermission\",\n                        uri.toString(), null)\n            } catch (e: Exception) {\n                LogUtil.E(TAG, \"Unable to manually persist uri permissions to \" + uri, e)\n            }\n        }\n    }\n}\n\nprivate fun writeUriToFile(context: Context?, uri: String, destFile: File?) {\n    if (context == null) {\n        return\n    }\n    if (destFile == null) {\n        throw IOException(\"Invalid destination for \" + uri)\n    }\n    try {\n        val input = context.contentResolver.openInputStream(Uri.parse(uri)) ?: return\n        val fileOutput = FileOutputStream(destFile)\n\n        val buffer = ByteArray(1024)\n        var bytesRead = input.read(buffer)\n\n        while (bytesRead > 0) {\n            fileOutput.write(buffer, 0, bytesRead)\n            bytesRead = input.read(buffer)\n        }\n        fileOutput.flush()\n    } catch (e: SecurityException) {\n        throw IOException(\"Unable to read Uri: \" + uri, e)\n    }\n\n}\n\nfun getCacheFileForUri(context: Context, imageUri: String): File? {\n    val directory = File(context.getExternalFilesDir(null), \"gallery_images\")\n    if (!directory.exists() && !directory.mkdirs()) {\n        return null\n    }\n\n    // Create a unique filename based on the imageUri\n    val uri = Uri.parse(imageUri)\n    val filename = StringBuilder()\n    filename.append(uri.scheme).append(\"_\")\n            .append(uri.host).append(\"_\")\n    var encodedPath = uri.encodedPath\n    if (!TextUtils.isEmpty(encodedPath)) {\n        val length = encodedPath.length\n        if (length > 60) {\n            encodedPath = encodedPath.substring(length - 60)\n        }\n        encodedPath = encodedPath.replace('/', '_')\n        filename.append(encodedPath).append(\"_\")\n    }\n    try {\n        val md = MessageDigest.getInstance(\"MD5\")\n        md.update(uri.toString().toByteArray(charset(\"UTF-8\")))\n        val digest = md.digest()\n        for (b in digest) {\n            if (0xff and b.toInt() < 0x10) {\n                filename.append(\"0\").append(Integer.toHexString(0xFF and b.toInt()))\n            } else {\n                filename.append(Integer.toHexString(0xFF and b.toInt()))\n            }\n        }\n    } catch (e: NoSuchAlgorithmException) {\n        filename.append(uri.toString().hashCode())\n    } catch (e: UnsupportedEncodingException) {\n        filename.append(uri.toString().hashCode())\n    }\n\n    return File(directory, filename.toString())\n}"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/utils/WallpaperFileHelper.java",
    "content": "package com.yalin.style.data.utils;\n\nimport android.content.Context;\nimport android.net.Uri;\nimport android.os.ParcelFileDescriptor;\nimport android.text.TextUtils;\n\nimport com.yalin.style.data.log.LogUtil;\nimport com.yalin.style.data.repository.datasource.provider.StyleContract.Wallpaper;\n\nimport java.io.File;\nimport java.io.FileNotFoundException;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.HashSet;\nimport java.util.Set;\n\n/**\n * YaLin On 2017/1/2.\n */\n\npublic class WallpaperFileHelper {\n\n    private static final String TAG = \"WallpaperFileHelper\";\n\n    public static final String WALLPAPER_FOLDER = \"wallpaper\";\n    public static final String ADVANCE_WALLPAPER_FOLDER = \"component\";\n\n    public static ParcelFileDescriptor openReadFile(Context context, Uri uri, String mode)\n            throws FileNotFoundException {\n        LogUtil.D(TAG, \"Read file Uri=\" + (uri == null ? null : uri.toString()));\n\n        String wallpaperId = Wallpaper.getWallpaperId(uri);\n\n        File directory = new File(context.getFilesDir(), WALLPAPER_FOLDER);\n        if (!directory.exists()) {\n            throw new FileNotFoundException(\"Wallpaper file : \"\n                    + directory.toString() + \" cannot found.\");\n        }\n\n        File file = new File(directory, generateFileName(wallpaperId));\n\n        return ParcelFileDescriptor.open(file, parseMode(mode));\n    }\n\n    public static ParcelFileDescriptor openWriteFile(Context context, Uri uri, String mode)\n            throws FileNotFoundException {\n        String wallpaperId = Wallpaper.getWallpaperSaveId(uri);\n        File directory = new File(context.getFilesDir(), WALLPAPER_FOLDER);\n        if (!directory.exists() && !directory.mkdir()) {\n            throw new FileNotFoundException(\"Wallpaper save dir : \"\n                    + directory.toString() + \" cannot be create.\");\n        }\n        File file = new File(directory, generateFileName(wallpaperId));\n        return ParcelFileDescriptor.open(file, parseMode(mode));\n    }\n\n    private static String generateFileName(String wallpaperId) {\n        return wallpaperId;\n    }\n\n    private static int parseMode(String mode) {\n        final int modeBits;\n        if (\"r\".equals(mode)) {\n            modeBits = ParcelFileDescriptor.MODE_READ_ONLY;\n        } else if (\"w\".equals(mode) || \"wt\".equals(mode)) {\n            modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY\n                    | ParcelFileDescriptor.MODE_CREATE\n                    | ParcelFileDescriptor.MODE_TRUNCATE;\n        } else if (\"wa\".equals(mode)) {\n            modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY\n                    | ParcelFileDescriptor.MODE_CREATE\n                    | ParcelFileDescriptor.MODE_APPEND;\n        } else if (\"rw\".equals(mode)) {\n            modeBits = ParcelFileDescriptor.MODE_READ_WRITE\n                    | ParcelFileDescriptor.MODE_CREATE;\n        } else if (\"rwt\".equals(mode)) {\n            modeBits = ParcelFileDescriptor.MODE_READ_WRITE\n                    | ParcelFileDescriptor.MODE_CREATE\n                    | ParcelFileDescriptor.MODE_TRUNCATE;\n        } else {\n            throw new IllegalArgumentException(\"Bad mode '\" + mode + \"'\");\n        }\n        return modeBits;\n    }\n\n    public static boolean copyAssets(Context context, String name, File output) {\n        InputStream is = null;\n        FileOutputStream fos = null;\n        try {\n            is = context.getAssets().open(name);\n            fos = new FileOutputStream(output);\n            byte[] buffer = new byte[2048];\n            int len;\n            while ((len = is.read(buffer)) > 0) {\n                fos.write(buffer, 0, len);\n            }\n            fos.flush();\n            return true;\n        } catch (IOException e) {\n            return false;\n        } finally {\n            try {\n                if (is != null) {\n                    is.close();\n                }\n                if (fos != null) {\n                    fos.close();\n                }\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n\n    public static void deleteOldWallpapers(Context context, Set<String> excludeIds) {\n        File directory = new File(context.getFilesDir(), WALLPAPER_FOLDER);\n        if (!directory.exists()) {\n            return;\n        }\n        Set<String> namesSet = new HashSet<>();\n        for (String wallpaperId : excludeIds) {\n            namesSet.add(generateFileName(wallpaperId));\n        }\n        File[] files = directory.listFiles(fileName ->\n                !namesSet.contains(fileName.getName()));\n        for (File file : files) {\n            //noinspection ResultOfMethodCallIgnored\n            file.delete();\n        }\n    }\n\n    public static void deleteOldComponent(Context context, Set<String> excludeNames) {\n        File dir = getAdvanceWallpaperDir(context);\n        if (!dir.exists()) {\n            return;\n        }\n        File[] files = dir.listFiles(fileName ->\n                !excludeNames.contains(fileName.getName()));\n        for (File file : files) {\n            //noinspection ResultOfMethodCallIgnored\n            file.delete();\n            NativeFileHelperKt.clearNativeFiles(context, file.getAbsolutePath());\n        }\n    }\n\n\n    public static void deleteOldWallpapers(Context context, String... excludeIds) {\n        File directory = new File(context.getFilesDir(), WALLPAPER_FOLDER);\n        if (!directory.exists()) {\n            return;\n        }\n        Set<String> namesSet = new HashSet<>();\n        for (String wallpaperId : excludeIds) {\n            namesSet.add(generateFileName(wallpaperId));\n        }\n        File[] files = directory.listFiles(fileName ->\n                !namesSet.contains(fileName.getName()));\n        for (File file : files) {\n            //noinspection ResultOfMethodCallIgnored\n            file.delete();\n        }\n    }\n\n    public static boolean ensureWallpaperChecksumValid(Context context,\n                                                       String checksum, String wallpaperId) {\n        File directory = new File(context.getFilesDir(), WALLPAPER_FOLDER);\n        if (!directory.exists()) {\n            return false;\n        }\n\n        File file = new File(directory, generateFileName(wallpaperId));\n        String computedChecksum = ChecksumUtil.getChecksum(file);\n        if (TextUtils.equals(checksum, computedChecksum)) {\n            return true;\n        }\n        //noinspection ResultOfMethodCallIgnored\n        file.delete();\n        return false;\n    }\n\n    public static boolean ensureChecksumValid(Context context,\n                                              String checksum, String filePath) {\n        File file = new File(filePath);\n        if (!file.exists()) {\n            return false;\n        }\n        String computedChecksum = ChecksumUtil.getChecksum(file);\n        if (TextUtils.equals(checksum, computedChecksum)) {\n            return true;\n        }\n        //noinspection ResultOfMethodCallIgnored\n        file.delete();\n        return false;\n    }\n\n    public static File getAdvanceWallpaperDir(Context context) {\n        return new File(context.getFilesDir(), ADVANCE_WALLPAPER_FOLDER);\n    }\n\n    public static boolean isNeedDownloadAdvanceComponent(boolean lazy, String storePath) {\n        return lazy && !new File(storePath).exists();\n    }\n}\n"
  },
  {
    "path": "data/src/main/java/com/yalin/style/data/utils/WallpaperUtil.kt",
    "content": "package com.yalin.style.data.utils\n\nimport android.content.Context\nimport android.net.Uri\nimport com.yalin.style.data.repository.datasource.provider.StyleContractHelper\n\n/**\n * YaLin\n * On 2017/5/26.\n */\nfun notifyChange(context: Context, uri: Uri) {\n    if (!StyleContractHelper.isUriCalledFromSyncAdapter(uri)) {\n        context.contentResolver.notifyChange(uri, null)\n    }\n}"
  },
  {
    "path": "data/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">Style</string>\n\n    <string name=\"authority\">com.yalin.style</string>\n\n    <string name=\"advance_source_title\">Advance Wallpaper</string>\n    <string name=\"advance_source_description\">Advance animation component</string>\n\n    <string name=\"featuredart_source_title\">Featured art</string>\n    <string name=\"featuredart_source_description\">A new painting every day</string>\n\n    <string name=\"gallery_title\">My Photos</string>\n    <string name=\"gallery_description\">Random camera photos</string>\n\n    <string name=\"gallery_from_gallery\">From your gallery</string>\n    <string name=\"gallery_touch_to_view\">Touch to view more</string>\n</resources>\n"
  },
  {
    "path": "data/src/main/res/values-zh-rCN/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"app_name\">Style</string>\n\n    <string name=\"authority\">com.yalin.style</string>\n\n    <string name=\"advance_source_title\">特效动态壁纸</string>\n    <string name=\"advance_source_description\">富含各种炫酷效果</string>\n\n    <string name=\"featuredart_source_title\">Style</string>\n    <string name=\"featuredart_source_description\">每天自动更新艺术作品</string>\n\n    <string name=\"gallery_title\">我的照片</string>\n    <string name=\"gallery_description\">随机更新照片</string>\n\n    <string name=\"gallery_from_gallery\">来自你的相册</string>\n    <string name=\"gallery_touch_to_view\">点击查看更多</string>\n\n</resources>"
  },
  {
    "path": "data/src/main/res/xml/authenticator.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<account-authenticator xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:accountType=\"com.yalin.style\"\n    android:icon=\"@drawable/ic_launcher\"\n    android:label=\"@string/app_name\"\n    android:smallIcon=\"@drawable/ic_launcher\" />"
  },
  {
    "path": "data/src/main/res/xml/syncadapter.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<sync-adapter xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:accountType=\"com.yalin.style\"\n  android:contentAuthority=\"@string/authority\"\n  android:supportsUploading=\"false\"\n  android:userVisible=\"true\" />\n"
  },
  {
    "path": "data/src/test/java/com/yalin/data/ExampleUnitTest.java",
    "content": "package com.yalin.data;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\n/**\n * Example local unit test, which will execute on the development machine (host).\n *\n * @see <a href=\"http://d.android.com/tools/testing\">Testing documentation</a>\n */\npublic class ExampleUnitTest {\n    @Test\n    public void addition_isCorrect() throws Exception {\n        assertEquals(4, 2 + 2);\n    }\n}"
  },
  {
    "path": "domain/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "domain/build.gradle",
    "content": "apply plugin: 'java'\n\ndependencies {\n    compile fileTree(dir: 'libs', include: ['*.jar'])\n}\n\nsourceCompatibility = \"1.7\"\ntargetCompatibility = \"1.7\"\n\ndependencies {\n    compile \"io.reactivex.rxjava2:rxjava:${RXJAVA_VERSION}\"\n    compile \"com.fernandocejas:arrow:${ARROW_VERSION}\"\n    compile \"javax.inject:javax.inject:${JAVAX_INJECT_VERSION}\"\n}"
  },
  {
    "path": "domain/src/main/java/com/yalin/style/domain/AdvanceWallpaper.java",
    "content": "package com.yalin.style.domain;\n\n/**\n * @author jinyalin\n * @since 2017/7/28.\n */\n\npublic class AdvanceWallpaper {\n    public long id;\n    public String wallpaperId;\n    public String link;\n    public String name;\n    public String author;\n    public String iconUrl;\n    public String downloadUrl;\n\n    public boolean lazyDownload;\n    public boolean needAd;\n\n    public String providerName;\n\n    public String storePath;\n\n    public boolean isDefault;\n    public boolean isSelected;\n}\n"
  },
  {
    "path": "domain/src/main/java/com/yalin/style/domain/GalleryWallpaper.java",
    "content": "package com.yalin.style.domain;\n\n/**\n * @author jinyalin\n * @since 2017/5/24.\n */\n\npublic class GalleryWallpaper {\n    public long id;\n    public String uri;\n    public boolean isTreeUri;\n    public long dateTime;\n    public String location;\n    public boolean hasMetadata;\n}\n"
  },
  {
    "path": "domain/src/main/java/com/yalin/style/domain/Source.java",
    "content": "package com.yalin.style.domain;\n\n/**\n * @author jinyalin\n * @since 2017/5/22.\n */\n\npublic class Source {\n    public int id;\n    public String title;\n    public String description;\n    public int iconId;\n    public boolean selected;\n    public boolean hasSetting;\n    public int color;\n}\n"
  },
  {
    "path": "domain/src/main/java/com/yalin/style/domain/Wallpaper.java",
    "content": "package com.yalin.style.domain;\n\n/**\n * @author jinyalin\n * @since 2017/4/18.\n */\n\npublic class Wallpaper {\n\n    public String wallpaperId;\n    public String imageUri;\n    public String title;\n    public String byline;\n    public String attribution;\n\n    public boolean canLike;\n    public boolean liked;\n    public boolean isDefault;\n}\n"
  },
  {
    "path": "domain/src/main/java/com/yalin/style/domain/exception/DefaultErrorBundle.java",
    "content": "package com.yalin.style.domain.exception;\n\n/**\n * @author jinyalin\n * @since 2017/4/18.\n */\n\npublic class DefaultErrorBundle implements ErrorBundle {\n    private static final String DEFAULT_ERROR_MSG = \"Unknown error\";\n\n    private final Exception exception;\n\n    public DefaultErrorBundle(Exception exception) {\n        this.exception = exception;\n    }\n\n    @Override\n    public Exception getException() {\n        return exception;\n    }\n\n    @Override\n    public String getErrorMessage() {\n        return (exception != null) ? this.exception.getMessage() : DEFAULT_ERROR_MSG;\n    }\n}\n"
  },
  {
    "path": "domain/src/main/java/com/yalin/style/domain/exception/ErrorBundle.java",
    "content": "package com.yalin.style.domain.exception;\n\n/**\n * @author jinyalin\n * @since 2017/4/18.\n */\n\npublic interface ErrorBundle {\n    Exception getException();\n\n    String getErrorMessage();\n}\n"
  },
  {
    "path": "domain/src/main/java/com/yalin/style/domain/executor/PostExecutionThread.java",
    "content": "package com.yalin.style.domain.executor;\n\nimport io.reactivex.Scheduler;\n\n/**\n * @author jinyalin\n * @since 2017/4/18.\n */\n\npublic interface PostExecutionThread {\n    Scheduler getScheduler();\n}\n"
  },
  {
    "path": "domain/src/main/java/com/yalin/style/domain/executor/SerialThreadExecutor.java",
    "content": "package com.yalin.style.domain.executor;\n\nimport java.util.concurrent.Executor;\n\n/**\n * @author jinyalin\n * @since 2017/4/18.\n */\n\npublic interface SerialThreadExecutor extends Executor {\n}\n"
  },
  {
    "path": "domain/src/main/java/com/yalin/style/domain/executor/ThreadExecutor.java",
    "content": "package com.yalin.style.domain.executor;\n\nimport java.util.concurrent.Executor;\n\n/**\n * @author jinyalin\n * @since 2017/4/18.\n */\n\npublic interface ThreadExecutor extends Executor {\n}\n"
  },
  {
    "path": "domain/src/main/java/com/yalin/style/domain/interactor/AddGalleryWallpaper.java",
    "content": "package com.yalin.style.domain.interactor;\n\nimport com.yalin.style.domain.GalleryWallpaper;\nimport com.yalin.style.domain.executor.PostExecutionThread;\nimport com.yalin.style.domain.executor.SerialThreadExecutor;\nimport com.yalin.style.domain.executor.ThreadExecutor;\nimport com.yalin.style.domain.repository.SourcesRepository;\n\nimport java.util.List;\n\nimport javax.inject.Inject;\n\nimport io.reactivex.Observable;\n\n/**\n * @author jinyalin\n * @since 2017/5/24.\n */\n\npublic class AddGalleryWallpaper extends UseCase<Boolean, AddGalleryWallpaper.Params> {\n    private SourcesRepository sourcesRepository;\n\n    @Inject\n    public AddGalleryWallpaper(ThreadExecutor threadExecutor,\n                               SerialThreadExecutor serialThreadExecutor,\n                               PostExecutionThread postExecutionThread,\n                               SourcesRepository sourcesRepository) {\n        super(threadExecutor, serialThreadExecutor, postExecutionThread);\n        this.sourcesRepository = sourcesRepository;\n    }\n\n    @Override\n    Observable<Boolean> buildUseCaseObservable(Params params) {\n        return sourcesRepository.getWallpaperRepository()\n                .addGalleryWallpaperUris(params.galleryWallpaperUris);\n    }\n\n    public static final class Params {\n\n        private final List<GalleryWallpaper> galleryWallpaperUris;\n\n        private Params(List<GalleryWallpaper> galleryWallpapers) {\n            this.galleryWallpaperUris = galleryWallpapers;\n        }\n\n        public static Params addGalleryWallpaperUris(List<GalleryWallpaper> galleryWallpapers) {\n            return new Params(galleryWallpapers);\n        }\n    }\n}\n"
  },
  {
    "path": "domain/src/main/java/com/yalin/style/domain/interactor/DefaultObserver.java",
    "content": "package com.yalin.style.domain.interactor;\n\nimport io.reactivex.observers.DisposableObserver;\n\n/**\n * @author jinyalin\n * @since 2017/4/18.\n */\n\npublic class DefaultObserver<T> extends DisposableObserver<T> {\n    @Override\n    public void onNext(T needDownload) {\n        // no-op by default.\n    }\n\n    @Override\n    public void onComplete() {\n        // no-op by default.\n    }\n\n    @Override\n    public void onError(Throwable exception) {\n        // no-op by default.\n    }\n}\n"
  },
  {
    "path": "domain/src/main/java/com/yalin/style/domain/interactor/DownloadAdvanceWallpaper.java",
    "content": "package com.yalin.style.domain.interactor;\n\nimport com.yalin.style.domain.executor.PostExecutionThread;\nimport com.yalin.style.domain.executor.SerialThreadExecutor;\nimport com.yalin.style.domain.executor.ThreadExecutor;\nimport com.yalin.style.domain.repository.SourcesRepository;\n\nimport javax.inject.Inject;\n\nimport io.reactivex.Observable;\n\n/**\n * @author jinyalin\n * @since 2017/8/11.\n */\n\npublic class DownloadAdvanceWallpaper extends UseCase<Long, DownloadAdvanceWallpaper.Params> {\n    private SourcesRepository sourcesRepository;\n\n    @Inject\n    public DownloadAdvanceWallpaper(ThreadExecutor threadExecutor,\n                                    SerialThreadExecutor serialThreadExecutor,\n                                    PostExecutionThread postExecutionThread,\n                                    SourcesRepository sourcesRepository) {\n        super(threadExecutor, serialThreadExecutor, postExecutionThread);\n        this.sourcesRepository = sourcesRepository;\n    }\n\n    @Override\n    Observable<Long> buildUseCaseObservable(Params params) {\n        return sourcesRepository.getWallpaperRepository()\n                .downloadAdvanceWallpaper(params.wallpaperId);\n    }\n\n    public static final class Params {\n\n        private final String wallpaperId;\n\n        private Params(String wallpaperId) {\n            this.wallpaperId = wallpaperId;\n        }\n\n        public static Params download(String wallpaperId) {\n            return new Params(wallpaperId);\n        }\n    }\n}"
  },
  {
    "path": "domain/src/main/java/com/yalin/style/domain/interactor/ForceNow.java",
    "content": "package com.yalin.style.domain.interactor;\n\nimport com.yalin.style.domain.executor.PostExecutionThread;\nimport com.yalin.style.domain.executor.SerialThreadExecutor;\nimport com.yalin.style.domain.executor.ThreadExecutor;\nimport com.yalin.style.domain.repository.SourcesRepository;\n\nimport javax.inject.Inject;\n\nimport io.reactivex.Observable;\n\n/**\n * @author jinyalin\n * @since 2017/6/9.\n */\n\npublic class ForceNow extends UseCase<Boolean, ForceNow.Params> {\n    private SourcesRepository sourcesRepository;\n\n    @Inject\n    public ForceNow(ThreadExecutor threadExecutor,\n                    SerialThreadExecutor serialThreadExecutor,\n                    PostExecutionThread postExecutionThread,\n                    SourcesRepository sourcesRepository) {\n        super(threadExecutor, serialThreadExecutor, postExecutionThread);\n        this.sourcesRepository = sourcesRepository;\n    }\n\n    @Override\n    Observable<Boolean> buildUseCaseObservable(Params params) {\n        return sourcesRepository.getWallpaperRepository().foreNow(params.galleryWallpaperUri);\n    }\n\n    public static final class Params {\n        private final String galleryWallpaperUri;\n\n        private Params(String galleryWallpaperUri) {\n            this.galleryWallpaperUri = galleryWallpaperUri;\n        }\n\n        public static Params fromUri(String galleryWallpaperUri) {\n            return new Params(galleryWallpaperUri);\n        }\n    }\n}\n"
  },
  {
    "path": "domain/src/main/java/com/yalin/style/domain/interactor/GetAdvanceWallpapers.java",
    "content": "package com.yalin.style.domain.interactor;\n\nimport com.yalin.style.domain.AdvanceWallpaper;\nimport com.yalin.style.domain.executor.PostExecutionThread;\nimport com.yalin.style.domain.executor.SerialThreadExecutor;\nimport com.yalin.style.domain.executor.ThreadExecutor;\nimport com.yalin.style.domain.repository.SourcesRepository;\n\nimport java.util.List;\n\nimport javax.inject.Inject;\n\nimport io.reactivex.Observable;\n\n/**\n * @author jinyalin\n * @since 2017/7/28.\n */\n\npublic class GetAdvanceWallpapers extends UseCase<List<AdvanceWallpaper>, Void> {\n    private SourcesRepository sourcesRepository;\n\n    @Inject\n    public GetAdvanceWallpapers(ThreadExecutor threadExecutor,\n                                SerialThreadExecutor serialThreadExecutor,\n                                PostExecutionThread postExecutionThread,\n                                SourcesRepository sourcesRepository) {\n        super(threadExecutor, serialThreadExecutor, postExecutionThread);\n        this.sourcesRepository = sourcesRepository;\n    }\n\n    @Override\n    Observable<List<AdvanceWallpaper>> buildUseCaseObservable(Void aVoid) {\n        return sourcesRepository.getWallpaperRepository().getAdvanceWallpapers();\n    }\n}\n"
  },
  {
    "path": "domain/src/main/java/com/yalin/style/domain/interactor/GetGalleryUpdateInterval.java",
    "content": "package com.yalin.style.domain.interactor;\n\nimport com.yalin.style.domain.executor.PostExecutionThread;\nimport com.yalin.style.domain.executor.SerialThreadExecutor;\nimport com.yalin.style.domain.executor.ThreadExecutor;\nimport com.yalin.style.domain.repository.SourcesRepository;\n\nimport javax.inject.Inject;\n\nimport io.reactivex.Observable;\n\n/**\n * @author jinyalin\n * @since 2017/6/9.\n */\n\npublic class GetGalleryUpdateInterval extends UseCase<Integer, Void> {\n\n    private SourcesRepository sourcesRepository;\n\n    @Inject\n    public GetGalleryUpdateInterval(ThreadExecutor threadExecutor,\n                                    SerialThreadExecutor serialThreadExecutor,\n                                    PostExecutionThread postExecutionThread,\n                                    SourcesRepository sourcesRepository) {\n        super(threadExecutor, serialThreadExecutor, postExecutionThread);\n        this.sourcesRepository = sourcesRepository;\n    }\n\n    @Override\n    Observable<Integer> buildUseCaseObservable(Void aVoid) {\n        return sourcesRepository.getWallpaperRepository().getGalleryUpdateInterval();\n    }\n}\n"
  },
  {
    "path": "domain/src/main/java/com/yalin/style/domain/interactor/GetGalleryWallpaper.java",
    "content": "package com.yalin.style.domain.interactor;\n\nimport com.yalin.style.domain.GalleryWallpaper;\nimport com.yalin.style.domain.executor.PostExecutionThread;\nimport com.yalin.style.domain.executor.SerialThreadExecutor;\nimport com.yalin.style.domain.executor.ThreadExecutor;\nimport com.yalin.style.domain.repository.SourcesRepository;\n\nimport java.util.List;\n\nimport io.reactivex.Observable;\n\nimport javax.inject.Inject;\n\n/**\n * @author jinyalin\n * @since 2017/5/24.\n */\npublic class GetGalleryWallpaper extends UseCase<List<GalleryWallpaper>, Void> {\n    private SourcesRepository sourcesRepository;\n\n    @Inject\n    public GetGalleryWallpaper(ThreadExecutor threadExecutor,\n                               SerialThreadExecutor serialThreadExecutor,\n                               PostExecutionThread postExecutionThread,\n                               SourcesRepository sourcesRepository) {\n        super(threadExecutor, serialThreadExecutor, postExecutionThread);\n        this.sourcesRepository = sourcesRepository;\n    }\n\n    @Override\n    Observable<List<GalleryWallpaper>> buildUseCaseObservable(Void aVoid) {\n        return sourcesRepository.getWallpaperRepository().getGalleryWallpapers();\n    }\n}\n"
  },
  {
    "path": "domain/src/main/java/com/yalin/style/domain/interactor/GetSelectedAdvanceWallpaper.java",
    "content": "package com.yalin.style.domain.interactor;\n\nimport com.yalin.style.domain.AdvanceWallpaper;\nimport com.yalin.style.domain.repository.SourcesRepository;\n\nimport javax.inject.Inject;\n\n/**\n * @author jinyalin\n * @since 2017/7/28.\n */\n\npublic class GetSelectedAdvanceWallpaper {\n    private SourcesRepository sourcesRepository;\n\n    @Inject\n    public GetSelectedAdvanceWallpaper(SourcesRepository sourcesRepository) {\n        this.sourcesRepository = sourcesRepository;\n    }\n\n    public AdvanceWallpaper getSelected() {\n        return sourcesRepository.getWallpaperRepository().getAdvanceWallpaper();\n    }\n}\n"
  },
  {
    "path": "domain/src/main/java/com/yalin/style/domain/interactor/GetSelectedSource.java",
    "content": "package com.yalin.style.domain.interactor;\n\nimport com.yalin.style.domain.repository.SourcesRepository;\n\nimport javax.inject.Inject;\n\n/**\n * @author jinyalin\n * @since 2017/7/27.\n */\n\npublic class GetSelectedSource {\n    private SourcesRepository sourcesRepository;\n\n    @Inject\n    public GetSelectedSource(SourcesRepository sourcesRepository) {\n        this.sourcesRepository = sourcesRepository;\n    }\n\n    public int getSelectedSourceId() {\n        return sourcesRepository.getSelectedSource();\n    }\n}\n"
  },
  {
    "path": "domain/src/main/java/com/yalin/style/domain/interactor/GetSources.java",
    "content": "package com.yalin.style.domain.interactor;\n\nimport com.yalin.style.domain.Source;\nimport com.yalin.style.domain.executor.PostExecutionThread;\nimport com.yalin.style.domain.executor.SerialThreadExecutor;\nimport com.yalin.style.domain.executor.ThreadExecutor;\nimport com.yalin.style.domain.repository.SourcesRepository;\n\nimport java.util.List;\n\nimport javax.inject.Inject;\n\nimport io.reactivex.Observable;\n\n/**\n * @author jinyalin\n * @since 2017/5/22.\n */\n\npublic class GetSources extends UseCase<List<Source>, Void> {\n    private SourcesRepository sourcesRepository;\n\n    @Inject\n    public GetSources(ThreadExecutor threadExecutor,\n                      SerialThreadExecutor serialThreadExecutor,\n                      PostExecutionThread postExecutionThread,\n                      SourcesRepository sourcesRepository) {\n        super(threadExecutor, serialThreadExecutor, postExecutionThread);\n        this.sourcesRepository = sourcesRepository;\n    }\n\n    @Override\n    Observable<List<Source>> buildUseCaseObservable(Void a) {\n        return sourcesRepository.getSources();\n    }\n}\n"
  },
  {
    "path": "domain/src/main/java/com/yalin/style/domain/interactor/GetWallpaper.java",
    "content": "package com.yalin.style.domain.interactor;\n\nimport com.yalin.style.domain.Wallpaper;\nimport com.yalin.style.domain.executor.PostExecutionThread;\nimport com.yalin.style.domain.executor.SerialThreadExecutor;\nimport com.yalin.style.domain.executor.ThreadExecutor;\nimport com.yalin.style.domain.repository.SourcesRepository;\n\nimport javax.inject.Inject;\n\nimport io.reactivex.Observable;\n\n/**\n * @author jinyalin\n * @since 2017/4/18.\n */\n\npublic class GetWallpaper extends UseCase<Wallpaper, Void> {\n    private SourcesRepository sourcesRepository;\n\n    @Inject\n    public GetWallpaper(ThreadExecutor threadExecutor,\n                        SerialThreadExecutor serialThreadExecutor,\n                        PostExecutionThread postExecutionThread,\n                        SourcesRepository sourcesRepository) {\n        super(threadExecutor, serialThreadExecutor, postExecutionThread);\n        this.sourcesRepository = sourcesRepository;\n    }\n\n    @Override\n    Observable<Wallpaper> buildUseCaseObservable(Void a) {\n        return sourcesRepository.getWallpaperRepository().getWallpaper();\n    }\n}\n"
  },
  {
    "path": "domain/src/main/java/com/yalin/style/domain/interactor/GetWallpaperCount.java",
    "content": "package com.yalin.style.domain.interactor;\n\n\nimport com.yalin.style.domain.executor.PostExecutionThread;\nimport com.yalin.style.domain.executor.SerialThreadExecutor;\nimport com.yalin.style.domain.executor.ThreadExecutor;\nimport com.yalin.style.domain.repository.SourcesRepository;\n\nimport javax.inject.Inject;\n\nimport io.reactivex.Observable;\n\n/**\n * @author jinyalin\n * @since 2017/4/28.\n */\n\npublic class GetWallpaperCount extends UseCase<Integer, Void> {\n    private SourcesRepository sourcesRepository;\n\n    @Inject\n    public GetWallpaperCount(ThreadExecutor threadExecutor,\n                             SerialThreadExecutor serialThreadExecutor,\n                             PostExecutionThread postExecutionThread,\n                             SourcesRepository sourcesRepository) {\n        super(threadExecutor, serialThreadExecutor, postExecutionThread);\n        this.sourcesRepository = sourcesRepository;\n    }\n\n    @Override\n    Observable<Integer> buildUseCaseObservable(Void aVoid) {\n        return sourcesRepository.getWallpaperRepository().getWallpaperCount();\n    }\n}\n"
  },
  {
    "path": "domain/src/main/java/com/yalin/style/domain/interactor/LikeWallpaper.java",
    "content": "package com.yalin.style.domain.interactor;\n\nimport com.fernandocejas.arrow.checks.Preconditions;\nimport com.yalin.style.domain.executor.PostExecutionThread;\nimport com.yalin.style.domain.executor.SerialThreadExecutor;\nimport com.yalin.style.domain.executor.ThreadExecutor;\nimport com.yalin.style.domain.interactor.LikeWallpaper.Params;\nimport com.yalin.style.domain.repository.SourcesRepository;\n\nimport io.reactivex.Observable;\n\nimport javax.inject.Inject;\n\n/**\n * YaLin\n * On 2017/4/30.\n */\n\npublic class LikeWallpaper extends UseCase<Boolean, Params> {\n\n    private SourcesRepository sourcesRepository;\n\n    @Inject\n    public LikeWallpaper(ThreadExecutor threadExecutor,\n                         SerialThreadExecutor serialThreadExecutor,\n                         PostExecutionThread postExecutionThread,\n                         SourcesRepository sourcesRepository) {\n        super(threadExecutor, serialThreadExecutor, postExecutionThread);\n        this.sourcesRepository = sourcesRepository;\n    }\n\n\n    @Override\n    Observable<Boolean> buildUseCaseObservable(LikeWallpaper.Params params) {\n        Preconditions.checkNotNull(params);\n        return sourcesRepository.getWallpaperRepository().likeWallpaper(params.wallpaperId);\n    }\n\n    public static final class Params {\n\n        private final String wallpaperId;\n\n        private Params(String wallpaperId) {\n            this.wallpaperId = wallpaperId;\n        }\n\n        public static LikeWallpaper.Params likeWallpaper(String wallpaperId) {\n            return new LikeWallpaper.Params(wallpaperId);\n        }\n    }\n}\n"
  },
  {
    "path": "domain/src/main/java/com/yalin/style/domain/interactor/LoadAdvanceWallpaper.java",
    "content": "package com.yalin.style.domain.interactor;\n\nimport com.yalin.style.domain.AdvanceWallpaper;\nimport com.yalin.style.domain.executor.PostExecutionThread;\nimport com.yalin.style.domain.executor.SerialThreadExecutor;\nimport com.yalin.style.domain.executor.ThreadExecutor;\nimport com.yalin.style.domain.repository.SourcesRepository;\n\nimport java.util.List;\n\nimport javax.inject.Inject;\n\nimport io.reactivex.Observable;\n\n/**\n * @author jinyalin\n * @since 2017/7/31.\n */\n\npublic class LoadAdvanceWallpaper extends UseCase<List<AdvanceWallpaper>, Void> {\n    private SourcesRepository sourcesRepository;\n\n    @Inject\n    public LoadAdvanceWallpaper(ThreadExecutor threadExecutor,\n                                SerialThreadExecutor serialThreadExecutor,\n                                PostExecutionThread postExecutionThread,\n                                SourcesRepository sourcesRepository) {\n        super(threadExecutor, serialThreadExecutor, postExecutionThread);\n        this.sourcesRepository = sourcesRepository;\n    }\n\n    @Override\n    Observable<List<AdvanceWallpaper>> buildUseCaseObservable(Void aVoid) {\n        return sourcesRepository.getWallpaperRepository().loadAdvanceWallpapers();\n    }\n}\n"
  },
  {
    "path": "domain/src/main/java/com/yalin/style/domain/interactor/ObserverSources.java",
    "content": "package com.yalin.style.domain.interactor;\n\nimport com.yalin.style.domain.observable.SourcesObservable;\n\nimport javax.inject.Inject;\n\n/**\n * @author jinyalin\n * @since 2017/5/23.\n */\n\npublic class ObserverSources {\n\n    private SourcesObservable sourceObservable;\n\n    @Inject\n    public ObserverSources(SourcesObservable sourceObservable) {\n        this.sourceObservable = sourceObservable;\n    }\n\n    public void registerObserver(DefaultObserver<Void> observer) {\n        sourceObservable.registerObserver(observer);\n    }\n\n    public void unregisterObserver(DefaultObserver<Void> observer) {\n        sourceObservable.unregisterObserver(observer);\n    }\n\n}\n"
  },
  {
    "path": "domain/src/main/java/com/yalin/style/domain/interactor/ObserverWallpaper.java",
    "content": "package com.yalin.style.domain.interactor;\n\nimport com.yalin.style.domain.observable.WallpaperObservable;\n\nimport javax.inject.Inject;\n\n/**\n * @author jinyalin\n * @since 2017/5/23.\n */\n\npublic class ObserverWallpaper {\n\n    private WallpaperObservable wallpaperObservable;\n\n    @Inject\n    public ObserverWallpaper(WallpaperObservable wallpaperObservable) {\n        this.wallpaperObservable = wallpaperObservable;\n    }\n\n    public void registerObserver(DefaultObserver<Void> observer) {\n        wallpaperObservable.registerObserver(observer);\n    }\n\n    public void unregisterObserver(DefaultObserver<Void> observer) {\n        wallpaperObservable.unregisterObserver(observer);\n    }\n\n}\n"
  },
  {
    "path": "domain/src/main/java/com/yalin/style/domain/interactor/OpenWallpaperInputStream.java",
    "content": "package com.yalin.style.domain.interactor;\n\nimport com.fernandocejas.arrow.checks.Preconditions;\nimport com.yalin.style.domain.executor.PostExecutionThread;\nimport com.yalin.style.domain.executor.SerialThreadExecutor;\nimport com.yalin.style.domain.executor.ThreadExecutor;\nimport com.yalin.style.domain.repository.SourcesRepository;\n\nimport java.io.InputStream;\n\nimport javax.inject.Inject;\n\nimport io.reactivex.Observable;\n\n/**\n * @author jinyalin\n * @since 2017/4/21.\n */\n\npublic class OpenWallpaperInputStream\n        extends UseCase<InputStream, OpenWallpaperInputStream.Params> {\n    private SourcesRepository sourcesRepository;\n\n    @Inject\n    public OpenWallpaperInputStream(ThreadExecutor threadExecutor,\n                                    SerialThreadExecutor serialThreadExecutor,\n                                    PostExecutionThread postExecutionThread,\n                                    SourcesRepository sourcesRepository) {\n        super(threadExecutor, serialThreadExecutor, postExecutionThread);\n        this.sourcesRepository = sourcesRepository;\n    }\n\n    @Override\n    Observable<InputStream> buildUseCaseObservable(Params params) {\n        Preconditions.checkNotNull(params);\n        return sourcesRepository.getWallpaperRepository().openInputStream(params.wallpaperId);\n    }\n\n    public static final class Params {\n        private final String wallpaperId;\n\n        private Params(String wallpaperId) {\n            this.wallpaperId = wallpaperId;\n        }\n\n        public static Params openInputStream(String wallpaperId) {\n            return new Params(wallpaperId);\n        }\n    }\n}\n"
  },
  {
    "path": "domain/src/main/java/com/yalin/style/domain/interactor/ReadAdvanceAd.java",
    "content": "package com.yalin.style.domain.interactor;\n\nimport com.yalin.style.domain.executor.PostExecutionThread;\nimport com.yalin.style.domain.executor.SerialThreadExecutor;\nimport com.yalin.style.domain.executor.ThreadExecutor;\nimport com.yalin.style.domain.repository.SourcesRepository;\n\nimport javax.inject.Inject;\n\nimport io.reactivex.Observable;\n\n/**\n * @author jinyalin\n * @since 2017/8/16.\n */\n\npublic class ReadAdvanceAd extends UseCase<Boolean, ReadAdvanceAd.Params> {\n\n    private SourcesRepository sourcesRepository;\n\n    @Inject\n    public ReadAdvanceAd(ThreadExecutor threadExecutor,\n                         SerialThreadExecutor serialThreadExecutor,\n                         PostExecutionThread postExecutionThread,\n                         SourcesRepository sourcesRepository) {\n        super(threadExecutor, serialThreadExecutor, postExecutionThread);\n        this.sourcesRepository = sourcesRepository;\n    }\n\n    @Override\n    Observable<Boolean> buildUseCaseObservable(Params params) {\n        return sourcesRepository.getWallpaperRepository()\n                .readAdvanceAd(params.wallpaperId);\n    }\n\n    public static final class Params {\n\n        private final String wallpaperId;\n\n        private Params(String wallpaperId) {\n            this.wallpaperId = wallpaperId;\n        }\n\n        public static Params read(String wallpaperId) {\n            return new Params(wallpaperId);\n        }\n    }\n}\n"
  },
  {
    "path": "domain/src/main/java/com/yalin/style/domain/interactor/RemoveGalleryWallpaper.java",
    "content": "package com.yalin.style.domain.interactor;\n\nimport com.yalin.style.domain.GalleryWallpaper;\nimport com.yalin.style.domain.executor.PostExecutionThread;\nimport com.yalin.style.domain.executor.SerialThreadExecutor;\nimport com.yalin.style.domain.executor.ThreadExecutor;\nimport com.yalin.style.domain.interactor.RemoveGalleryWallpaper.Params;\nimport com.yalin.style.domain.repository.SourcesRepository;\n\nimport io.reactivex.Observable;\n\nimport java.util.List;\n\nimport javax.inject.Inject;\n\n/**\n * YaLin\n * On 2017/5/26.\n */\n\npublic class RemoveGalleryWallpaper extends UseCase<Boolean, Params> {\n\n    private SourcesRepository sourcesRepository;\n\n    @Inject\n    public RemoveGalleryWallpaper(ThreadExecutor threadExecutor,\n                                  SerialThreadExecutor serialThreadExecutor,\n                                  PostExecutionThread postExecutionThread,\n                                  SourcesRepository sourcesRepository) {\n        super(threadExecutor, serialThreadExecutor, postExecutionThread);\n        this.sourcesRepository = sourcesRepository;\n    }\n\n    @Override\n    Observable<Boolean> buildUseCaseObservable(Params params) {\n        return sourcesRepository.getWallpaperRepository()\n                .removeGalleryWallpaperUris(params.galleryWallpaperUris);\n    }\n\n    public static final class Params {\n\n        private final List<GalleryWallpaper> galleryWallpaperUris;\n\n        private Params(List<GalleryWallpaper> galleryWallpaperUris) {\n            this.galleryWallpaperUris = galleryWallpaperUris;\n        }\n\n        public static RemoveGalleryWallpaper.Params removeGalleryWallpaperUris(\n                List<GalleryWallpaper> customWallpapers) {\n            return new RemoveGalleryWallpaper.Params(customWallpapers);\n        }\n    }\n}\n"
  },
  {
    "path": "domain/src/main/java/com/yalin/style/domain/interactor/SelectAdvanceWallpaper.java",
    "content": "package com.yalin.style.domain.interactor;\n\nimport com.yalin.style.domain.executor.PostExecutionThread;\nimport com.yalin.style.domain.executor.SerialThreadExecutor;\nimport com.yalin.style.domain.executor.ThreadExecutor;\nimport com.yalin.style.domain.repository.SourcesRepository;\n\nimport javax.inject.Inject;\n\nimport io.reactivex.Observable;\n\n/**\n * @author jinyalin\n * @since 2017/7/31.\n */\n\npublic class SelectAdvanceWallpaper extends UseCase<Boolean, SelectAdvanceWallpaper.Params> {\n    private SourcesRepository sourcesRepository;\n\n    @Inject\n    public SelectAdvanceWallpaper(ThreadExecutor threadExecutor,\n                                  SerialThreadExecutor serialThreadExecutor,\n                                  PostExecutionThread postExecutionThread,\n                                  SourcesRepository sourcesRepository) {\n        super(threadExecutor, serialThreadExecutor, postExecutionThread);\n        this.sourcesRepository = sourcesRepository;\n    }\n\n    @Override\n    Observable<Boolean> buildUseCaseObservable(SelectAdvanceWallpaper.Params params) {\n        return sourcesRepository.getWallpaperRepository()\n                .selectAdvanceWallpaper(params.wallpaperId, params.tempSelect);\n    }\n\n    public static final class Params {\n        private final String wallpaperId;\n        private final boolean tempSelect;\n\n        private Params(String wallpaperId, boolean tempSelect) {\n            this.wallpaperId = wallpaperId;\n            this.tempSelect = tempSelect;\n        }\n\n        public static Params selectWallpaper(String wallpaperId, boolean tempSelect) {\n            return new Params(wallpaperId, tempSelect);\n        }\n    }\n}"
  },
  {
    "path": "domain/src/main/java/com/yalin/style/domain/interactor/SelectSource.java",
    "content": "package com.yalin.style.domain.interactor;\n\nimport com.yalin.style.domain.executor.PostExecutionThread;\nimport com.yalin.style.domain.executor.SerialThreadExecutor;\nimport com.yalin.style.domain.executor.ThreadExecutor;\nimport com.yalin.style.domain.repository.SourcesRepository;\n\nimport javax.inject.Inject;\n\nimport io.reactivex.Observable;\n\n/**\n * @author jinyalin\n * @since 2017/5/23.\n */\n\npublic class SelectSource extends UseCase<Boolean, SelectSource.Params> {\n    private SourcesRepository sourcesRepository;\n\n    @Inject\n    public SelectSource(ThreadExecutor threadExecutor,\n                        SerialThreadExecutor serialThreadExecutor,\n                        PostExecutionThread postExecutionThread,\n                        SourcesRepository sourcesRepository) {\n        super(threadExecutor, serialThreadExecutor, postExecutionThread);\n        this.sourcesRepository = sourcesRepository;\n    }\n\n    @Override\n    Observable<Boolean> buildUseCaseObservable(Params params) {\n        return sourcesRepository.selectSource(params.sourceId, params.tempSelect);\n    }\n\n    public static final class Params {\n\n        private final int sourceId;\n        private final boolean tempSelect;\n\n        public Params(int sourceId, boolean tempSelect) {\n            this.sourceId = sourceId;\n            this.tempSelect = tempSelect;\n        }\n\n        public static Params selectSource(int sourceId, boolean tempSelect) {\n            return new Params(sourceId, tempSelect);\n        }\n    }\n\n}\n"
  },
  {
    "path": "domain/src/main/java/com/yalin/style/domain/interactor/SetGalleryUpdateInterval.java",
    "content": "package com.yalin.style.domain.interactor;\n\nimport com.yalin.style.domain.executor.PostExecutionThread;\nimport com.yalin.style.domain.executor.SerialThreadExecutor;\nimport com.yalin.style.domain.executor.ThreadExecutor;\nimport com.yalin.style.domain.repository.SourcesRepository;\n\nimport javax.inject.Inject;\n\nimport io.reactivex.Observable;\n\n/**\n * @author jinyalin\n * @since 2017/6/9.\n */\n\npublic class SetGalleryUpdateInterval extends UseCase<Boolean, SetGalleryUpdateInterval.Params> {\n    private SourcesRepository sourcesRepository;\n\n    @Inject\n    public SetGalleryUpdateInterval(ThreadExecutor threadExecutor,\n                                    SerialThreadExecutor serialThreadExecutor,\n                                    PostExecutionThread postExecutionThread,\n                                    SourcesRepository sourcesRepository) {\n        super(threadExecutor, serialThreadExecutor, postExecutionThread);\n        this.sourcesRepository = sourcesRepository;\n    }\n\n    @Override\n    Observable<Boolean> buildUseCaseObservable(Params params) {\n        return sourcesRepository.getWallpaperRepository()\n                .setGalleryUpdateInterval(params.intervalMin);\n    }\n\n    public static final class Params {\n        private final int intervalMin;\n\n        private Params(int intervalMin) {\n            this.intervalMin = intervalMin;\n        }\n\n        public static Params interval(int intervalMin) {\n            return new Params(intervalMin);\n        }\n    }\n}\n"
  },
  {
    "path": "domain/src/main/java/com/yalin/style/domain/interactor/SwitchWallpaper.java",
    "content": "package com.yalin.style.domain.interactor;\n\nimport com.yalin.style.domain.Wallpaper;\nimport com.yalin.style.domain.executor.PostExecutionThread;\nimport com.yalin.style.domain.executor.SerialThreadExecutor;\nimport com.yalin.style.domain.executor.ThreadExecutor;\nimport com.yalin.style.domain.repository.SourcesRepository;\n\nimport javax.inject.Inject;\n\nimport io.reactivex.Observable;\n\n/**\n * @author jinyalin\n * @since 2017/4/28.\n */\n\npublic class SwitchWallpaper extends UseCase<Wallpaper, Void> {\n    private SourcesRepository sourcesRepository;\n\n    @Inject\n    public SwitchWallpaper(ThreadExecutor threadExecutor,\n                           SerialThreadExecutor serialThreadExecutor,\n                           PostExecutionThread postExecutionThread,\n                           SourcesRepository sourcesRepository) {\n        super(threadExecutor, serialThreadExecutor, postExecutionThread);\n        this.sourcesRepository = sourcesRepository;\n    }\n\n    @Override\n    Observable<Wallpaper> buildUseCaseObservable(Void aVoid) {\n        return sourcesRepository.getWallpaperRepository().switchWallpaper();\n    }\n}\n"
  },
  {
    "path": "domain/src/main/java/com/yalin/style/domain/interactor/UseCase.java",
    "content": "package com.yalin.style.domain.interactor;\n\nimport com.fernandocejas.arrow.checks.Preconditions;\nimport com.yalin.style.domain.executor.PostExecutionThread;\nimport com.yalin.style.domain.executor.SerialThreadExecutor;\nimport com.yalin.style.domain.executor.ThreadExecutor;\n\nimport io.reactivex.Observable;\nimport io.reactivex.disposables.CompositeDisposable;\nimport io.reactivex.disposables.Disposable;\nimport io.reactivex.observers.DisposableObserver;\nimport io.reactivex.schedulers.Schedulers;\n\n/**\n * @author jinyalin\n * @since 2017/4/18.\n */\n\npublic abstract class UseCase<T, Params> {\n    private final ThreadExecutor threadExecutor;\n    private final SerialThreadExecutor serialThreadExecutor;\n    private final PostExecutionThread postExecutionThread;\n    private final CompositeDisposable disposables;\n\n    public UseCase(ThreadExecutor threadExecutor, SerialThreadExecutor serialThreadExecutor,\n                   PostExecutionThread postExecutionThread) {\n        this.threadExecutor = threadExecutor;\n        this.serialThreadExecutor = serialThreadExecutor;\n        this.postExecutionThread = postExecutionThread;\n        this.disposables = new CompositeDisposable();\n    }\n\n    abstract Observable<T> buildUseCaseObservable(Params params);\n\n    public void execute(DisposableObserver<T> observer, Params params) {\n        Preconditions.checkNotNull(observer);\n        final Observable<T> observable = this.buildUseCaseObservable(params)\n                .subscribeOn(Schedulers.from(threadExecutor))\n                .observeOn(postExecutionThread.getScheduler());\n        addDisposable(observable.subscribeWith(observer));\n    }\n\n    public void executeSerial(DisposableObserver<T> observer, Params params) {\n        Preconditions.checkNotNull(observer);\n        final Observable<T> observable = this.buildUseCaseObservable(params)\n                .subscribeOn(Schedulers.from(serialThreadExecutor))\n                .observeOn(postExecutionThread.getScheduler());\n        addDisposable(observable.subscribeWith(observer));\n    }\n\n    public void dispose() {\n        if (!disposables.isDisposed()) {\n            disposables.dispose();\n        }\n    }\n\n    private void addDisposable(Disposable disposable) {\n        Preconditions.checkNotNull(disposable);\n        Preconditions.checkNotNull(disposables);\n        disposables.add(disposable);\n    }\n}\n"
  },
  {
    "path": "domain/src/main/java/com/yalin/style/domain/observable/SourcesObservable.java",
    "content": "package com.yalin.style.domain.observable;\n\nimport com.yalin.style.domain.interactor.DefaultObserver;\n\n/**\n * @author jinyalin\n * @since 2017/5/23.\n */\n\npublic interface SourcesObservable {\n    void registerObserver(DefaultObserver<Void> observer);\n\n    void unregisterObserver(DefaultObserver<Void> observer);\n}\n"
  },
  {
    "path": "domain/src/main/java/com/yalin/style/domain/observable/WallpaperObservable.java",
    "content": "package com.yalin.style.domain.observable;\n\nimport com.yalin.style.domain.interactor.DefaultObserver;\n\n/**\n * @author jinyalin\n * @since 2017/5/23.\n */\n\npublic interface WallpaperObservable {\n    void registerObserver(DefaultObserver<Void> observer);\n\n    void unregisterObserver(DefaultObserver<Void> observer);\n}\n"
  },
  {
    "path": "domain/src/main/java/com/yalin/style/domain/repository/SourcesRepository.java",
    "content": "package com.yalin.style.domain.repository;\n\nimport com.yalin.style.domain.Source;\n\nimport java.util.List;\n\nimport io.reactivex.Observable;\n\n/**\n * @author jinyalin\n * @since 2017/5/23.\n */\n\npublic interface SourcesRepository {\n    int SOURCE_ID_STYLE = 0;\n    int SOURCE_ID_CUSTOM = 1;\n    int SOURCE_ID_ADVANCE = 2;\n\n    Observable<List<Source>> getSources();\n\n    Observable<Boolean> selectSource(int sourceId, boolean tempSelect);\n\n    WallpaperRepository getWallpaperRepository();\n\n    int getSelectedSource();\n\n}\n"
  },
  {
    "path": "domain/src/main/java/com/yalin/style/domain/repository/WallpaperRepository.java",
    "content": "package com.yalin.style.domain.repository;\n\nimport com.yalin.style.domain.AdvanceWallpaper;\nimport com.yalin.style.domain.GalleryWallpaper;\nimport com.yalin.style.domain.Wallpaper;\n\nimport java.io.InputStream;\nimport java.util.List;\n\nimport io.reactivex.Observable;\n\n/**\n * @author jinyalin\n * @since 2017/4/18.\n */\n\npublic interface WallpaperRepository {\n\n    Observable<Wallpaper> getWallpaper();\n\n    Observable<Wallpaper> switchWallpaper();\n\n    Observable<InputStream> openInputStream(String wallpaperId);\n\n    Observable<Integer> getWallpaperCount();\n\n    Observable<Boolean> likeWallpaper(String wallpaperId);\n\n    Observable<Boolean> addGalleryWallpaperUris(List<GalleryWallpaper> uris);\n\n    Observable<Boolean> removeGalleryWallpaperUris(List<GalleryWallpaper> uris);\n\n    Observable<List<GalleryWallpaper>> getGalleryWallpapers();\n\n    Observable<List<AdvanceWallpaper>> getAdvanceWallpapers();\n\n    Observable<List<AdvanceWallpaper>> loadAdvanceWallpapers();\n\n    Observable<Long> downloadAdvanceWallpaper(String wallpaperId);\n\n    Observable<Boolean> selectAdvanceWallpaper(String wallpaperId, boolean tempSelect);\n\n    AdvanceWallpaper getAdvanceWallpaper();\n\n    Observable<Boolean> readAdvanceAd(String wallpaperId);\n\n    Observable<Boolean> foreNow(String wallpaperUri);\n\n    Observable<Boolean> setGalleryUpdateInterval(int intervalMin);\n\n    Observable<Integer> getGalleryUpdateInterval();\n}\n"
  },
  {
    "path": "engine/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "engine/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\n\nandroid {\n    compileSdkVersion COMPILE_SDK_VERSION as int\n    buildToolsVersion BUILD_TOOLS_VERSION as String\n\n    defaultConfig {\n        minSdkVersion MIN_SDK_VERSION as int\n        targetSdkVersion TARGET_SDK_VERSION as int\n        versionCode APP_VERSION_CODE as int\n        versionName APP_VERSION_NAME as String\n\n        testInstrumentationRunner \"android.support.test.runner.AndroidJUnitRunner\"\n\n    }\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n    publishNonDefault true\n    productFlavors {\n        demo {\n        }\n        production {\n        }\n    }\n    sourceSets {\n        main {\n            jniLibs.srcDirs = ['libs']\n        }\n    }\n}\n\ndependencies {\n    compile fileTree(dir: 'libs', include: ['*.jar'])\n    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {\n        exclude group: 'com.android.support', module: 'support-annotations'\n    })\n    compile \"com.android.support:appcompat-v7:${SUPPORT_LIBRARY_VERSION}\"\n    compile \"org.jetbrains.kotlin:kotlin-stdlib:${KOTLIN_VERSION}\"\n    compile \"com.badlogicgames.gdx:gdx-backend-android:$GDX_VERSION\"\n\n    compile project(':domain')\n    demoProvided project(path: ':data', configuration: 'demoDebug')\n    productionProvided project(path: ':data', configuration: 'productionRelease')\n\n    testCompile 'junit:junit:4.12'\n}\n"
  },
  {
    "path": "engine/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /Users/jinyalin/Library/Android/sdk/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n"
  },
  {
    "path": "engine/src/androidTest/java/com/yalin/style/engine/ExampleInstrumentedTest.java",
    "content": "package com.yalin.style.engine;\n\nimport android.content.Context;\nimport android.support.test.InstrumentationRegistry;\nimport android.support.test.runner.AndroidJUnit4;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.junit.Assert.*;\n\n/**\n * Instrumentation test, which will execute on an Android device.\n *\n * @see <a href=\"http://d.android.com/tools/testing\">Testing documentation</a>\n */\n@RunWith(AndroidJUnit4.class)\npublic class ExampleInstrumentedTest {\n    @Test\n    public void useAppContext() throws Exception {\n        // Context of the app under test.\n        Context appContext = InstrumentationRegistry.getTargetContext();\n\n        assertEquals(\"com.yalin.style.engine.test\", appContext.getPackageName());\n    }\n}\n"
  },
  {
    "path": "engine/src/main/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\n    package=\"com.yalin.style.engine\">\n\n    <application\n        android:allowBackup=\"true\"\n        android:supportsRtl=\"true\">\n\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "engine/src/main/java/com/yalin/style/engine/GDXWallpaperServiceProxy.kt",
    "content": "package com.yalin.style.engine\n\n\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport android.content.Intent\nimport android.content.IntentFilter\nimport android.os.Build\nimport android.service.wallpaper.WallpaperService\nimport android.support.v4.os.UserManagerCompat\nimport android.view.SurfaceHolder\n\nimport com.badlogic.gdx.backends.android.AndroidLiveWallpaperService\n\n/**\n * @author jinyalin\n * *\n * @since 2017/7/28.\n */\n\nopen class GDXWallpaperServiceProxy(host: Context) : AndroidLiveWallpaperService() {\n    private val activateCallback: WallpaperActiveCallback?\n\n    init {\n        @Suppress(\"LeakingThis\")\n        attachBaseContext(host)\n\n        activateCallback = if (host is WallpaperActiveCallback) host else null\n    }\n\n    override fun onCreateEngine(): WallpaperService.Engine {\n        return GDXActiveEngine()\n    }\n\n    inner class GDXActiveEngine : AndroidWallpaperEngine() {\n        private var mWallpaperActivate = false\n\n        private var mEngineUnlockReceiver: BroadcastReceiver? = null\n\n        override fun onCreate(surfaceHolder: SurfaceHolder?) {\n            if (!isPreview) {\n                if (UserManagerCompat.isUserUnlocked(this@GDXWallpaperServiceProxy)) {\n                    activateWallpaper()\n                } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n                    mEngineUnlockReceiver = object : BroadcastReceiver() {\n                        override fun onReceive(context: Context, intent: Intent) {\n                            activateWallpaper()\n                            context.unregisterReceiver(this)\n                        }\n                    }\n                    val filter = IntentFilter(Intent.ACTION_USER_UNLOCKED)\n                    this@GDXWallpaperServiceProxy.registerReceiver(mEngineUnlockReceiver, filter)\n                }\n            }\n        }\n\n        override fun onDestroy() {\n            super.onDestroy()\n            deactivateWallpaper()\n        }\n\n        private fun activateWallpaper() {\n            mWallpaperActivate = true\n            activateCallback?.onWallpaperActivate()\n        }\n\n        private fun deactivateWallpaper() {\n            if (mWallpaperActivate) {\n                activateCallback?.onWallpaperDeactivate()\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "engine/src/main/java/com/yalin/style/engine/GLWallpaperServiceProxy.kt",
    "content": "package com.yalin.style.engine\n\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport android.content.Intent\nimport android.content.IntentFilter\nimport android.os.Build\nimport android.support.v4.os.UserManagerCompat\nimport android.view.SurfaceHolder\nimport net.rbgrn.android.glwallpaperservice.GLWallpaperService\n\n/**\n * @author jinyalin\n * *\n * @since 2017/7/27.\n */\n\nopen class GLWallpaperServiceProxy(var host: Context) : GLWallpaperService() {\n    private val activateCallback: WallpaperActiveCallback?\n\n    init {\n        @Suppress(\"LeakingThis\")\n        attachBaseContext(host)\n\n        activateCallback = if (host is WallpaperActiveCallback)\n            host as WallpaperActiveCallback else null\n    }\n\n    override fun onCreate() {\n    }\n\n    override fun onDestroy() {\n    }\n\n    open inner class GLActiveEngine : GLEngine() {\n        private var mWallpaperActivate = false\n\n        private var mEngineUnlockReceiver: BroadcastReceiver? = null\n\n        override fun onCreate(surfaceHolder: SurfaceHolder?) {\n            if (!isPreview) {\n                if (UserManagerCompat.isUserUnlocked(this@GLWallpaperServiceProxy)) {\n                    activateWallpaper()\n                } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n                    mEngineUnlockReceiver = object : BroadcastReceiver() {\n                        override fun onReceive(context: Context, intent: Intent) {\n                            activateWallpaper()\n                            context.unregisterReceiver(this)\n                        }\n                    }\n                    val filter = IntentFilter(Intent.ACTION_USER_UNLOCKED)\n                    this@GLWallpaperServiceProxy.registerReceiver(mEngineUnlockReceiver, filter)\n                }\n            }\n        }\n\n        override fun onDestroy() {\n            super.onDestroy()\n            deactivateWallpaper()\n        }\n\n        private fun activateWallpaper() {\n            mWallpaperActivate = true\n            activateCallback?.onWallpaperActivate()\n        }\n\n        private fun deactivateWallpaper() {\n            if (mWallpaperActivate) {\n                activateCallback?.onWallpaperDeactivate()\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "engine/src/main/java/com/yalin/style/engine/IProvider.java",
    "content": "package com.yalin.style.engine;\n\nimport android.content.Context;\nimport android.service.wallpaper.WallpaperService;\n\n/**\n * YaLin\n * On 2017/7/27.\n */\n\npublic interface IProvider {\n\n    WallpaperService provideProxy(Context host);\n}\n"
  },
  {
    "path": "engine/src/main/java/com/yalin/style/engine/ProxyApi.java",
    "content": "package com.yalin.style.engine;\n\nimport android.content.Context;\nimport android.service.wallpaper.WallpaperService;\n\nimport com.yalin.style.engine.component.ComponentContext;\n\n/**\n * YaLin\n * On 2017/7/27.\n */\n\npublic class ProxyApi {\n\n    private static IProvider getProvider(ComponentContext context, String providerName)\n            throws Exception {\n        synchronized (ProxyApi.class) {\n            IProvider provider;\n            Class providerClazz = context.getClassLoader().loadClass(providerName);\n            if (providerClazz != null) {\n                provider = (IProvider) providerClazz.newInstance();\n                return provider;\n            } else {\n                throw new IllegalStateException(\"Load Provider error.\");\n            }\n        }\n    }\n\n    public static WallpaperService getProxy(Context context,\n                                            String componentPath, String providerName) {\n        try {\n            ComponentContext componentContext = new ComponentContext(context, componentPath);\n            IProvider provider = getProvider(componentContext, providerName);\n            return provider.provideProxy(componentContext);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "engine/src/main/java/com/yalin/style/engine/ProxyProvider.kt",
    "content": "package com.yalin.style.engine\n\nimport android.content.Context\nimport android.service.wallpaper.WallpaperService\nimport com.yalin.style.domain.interactor.GetSelectedAdvanceWallpaper\nimport com.yalin.style.domain.interactor.GetSelectedSource\nimport com.yalin.style.domain.repository.SourcesRepository\nimport com.yalin.style.engine.advance.BokehRainbowWallpaper\nimport javax.inject.Inject\n\n/**\n * @author jinyalin\n * *\n * @since 2017/7/27.\n */\n\nclass ProxyProvider @Inject constructor(val getSelectedSourceUseCase: GetSelectedSource,\n                                        val getAdvanceWallpaper: GetSelectedAdvanceWallpaper) {\n    val NORMAL_PROXY_CLASS = \"com.yalin.style.engine.StyleWallpaperProxy\"\n\n    fun provideProxy(host: Context): WallpaperService {\n        if (getSelectedSourceUseCase.selectedSourceId == SourcesRepository.SOURCE_ID_ADVANCE) {\n            val selected = getAdvanceWallpaper.selected\n            if (selected.isDefault) {\n                return BokehRainbowWallpaper(host)\n            } else {\n                val proxy = ProxyApi.getProxy(host, selected.storePath, selected.providerName)\n                if (proxy != null) {\n                    return proxy\n                }\n            }\n        }\n\n        val constructor = Class.forName(NORMAL_PROXY_CLASS).getConstructor(Context::class.java)\n        return constructor.newInstance(host) as WallpaperService\n    }\n}\n"
  },
  {
    "path": "engine/src/main/java/com/yalin/style/engine/WallpaperActiveCallback.kt",
    "content": "package com.yalin.style.engine\n\n/**\n * @author jinyalin\n * @since 2017/8/1.\n */\ninterface WallpaperActiveCallback {\n    fun onWallpaperActivate()\n    fun onWallpaperDeactivate()\n}"
  },
  {
    "path": "engine/src/main/java/com/yalin/style/engine/WallpaperServiceProxy.kt",
    "content": "package com.yalin.style.engine\n\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport android.content.Intent\nimport android.content.IntentFilter\nimport android.os.Build\nimport android.service.wallpaper.WallpaperService\nimport android.support.v4.os.UserManagerCompat\nimport android.view.SurfaceHolder\n\n/**\n * @author jinyalin\n * *\n * @since 2017/7/27.\n */\nopen class WallpaperServiceProxy(var host: Context) : WallpaperService() {\n\n    private val activateCallback: WallpaperActiveCallback?\n\n    init {\n        @Suppress(\"LeakingThis\")\n        attachBaseContext(host)\n\n        activateCallback = if (host is WallpaperActiveCallback)\n            host as WallpaperActiveCallback else null\n    }\n\n    override fun onCreateEngine(): WallpaperService.Engine? {\n        return null\n    }\n\n    override fun onCreate() {\n    }\n\n    override fun onDestroy() {\n    }\n\n    open inner class ActiveEngine : Engine() {\n        private var mWallpaperActivate = false\n\n        private var mEngineUnlockReceiver: BroadcastReceiver? = null\n\n        override fun onCreate(surfaceHolder: SurfaceHolder?) {\n            if (!isPreview) {\n                if (UserManagerCompat.isUserUnlocked(this@WallpaperServiceProxy)) {\n                    activateWallpaper()\n                } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n                    mEngineUnlockReceiver = object : BroadcastReceiver() {\n                        override fun onReceive(context: Context, intent: Intent) {\n                            activateWallpaper()\n                            context.unregisterReceiver(this)\n                        }\n                    }\n                    val filter = IntentFilter(Intent.ACTION_USER_UNLOCKED)\n                    this@WallpaperServiceProxy.registerReceiver(mEngineUnlockReceiver, filter)\n                }\n            }\n        }\n\n        override fun onDestroy() {\n            super.onDestroy()\n            deactivateWallpaper()\n        }\n\n        private fun activateWallpaper() {\n            mWallpaperActivate = true\n            activateCallback?.onWallpaperActivate()\n        }\n\n        private fun deactivateWallpaper() {\n            if (mWallpaperActivate) {\n                activateCallback?.onWallpaperDeactivate()\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "engine/src/main/java/com/yalin/style/engine/advance/AnimationWallpaper.java",
    "content": "/*\n * Copyright (C) 2007 Google Inc.\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 */\npackage com.yalin.style.engine.advance;\n\nimport android.content.Context;\nimport android.os.Handler;\nimport android.view.SurfaceHolder;\n\nimport com.yalin.style.engine.WallpaperServiceProxy;\n\npublic abstract class AnimationWallpaper extends WallpaperServiceProxy {\n\n\tpublic AnimationWallpaper(Context host) {\n\t\tsuper(host);\n\t}\n\n\tprotected abstract class AnimationEngine extends ActiveEngine {\n\t\tprivate Handler mHandler = new Handler();\n\n\t\tprivate Runnable mIteration = new Runnable() {\n\t\t\tpublic void run() {\n\t\t\t\titeration();\n\t\t\t\tdrawFrame();\n\t\t\t}\n\t\t};\n\n\t\tprivate boolean mVisible;\n\n\t\t@Override\n\t\tpublic void onDestroy() {\n\t\t\tsuper.onDestroy();\n\t\t\t// stop the animation\n\t\t\tmHandler.removeCallbacks(mIteration);\n\t\t}\n\n\t\t@Override\n\t\tpublic void onVisibilityChanged(boolean visible) {\n\t\t\tmVisible = visible;\n\t\t\tif (visible) {\n\t\t\t\titeration();\n\t\t\t\tdrawFrame();\n\t\t\t} else {\n\t\t\t\t// stop the animation\n\t\t\t\tmHandler.removeCallbacks(mIteration);\n\t\t\t}\n\t\t}\n\n\t\t@Override\n\t\tpublic void onSurfaceChanged(SurfaceHolder holder, int format,\n\t\t\t\tint width, int height) {\n\t\t\titeration();\n\t\t\tdrawFrame();\n\t\t}\n\n\t\t@Override\n\t\tpublic void onSurfaceDestroyed(SurfaceHolder holder) {\n\t\t\tsuper.onSurfaceDestroyed(holder);\n\t\t\tmVisible = false;\n\t\t\t// stop the animation\n\t\t\tmHandler.removeCallbacks(mIteration);\n\t\t}\n\n\t\t@Override\n\t\tpublic void onOffsetsChanged(float xOffset, float yOffset,\n\t\t\t\tfloat xOffsetStep, float yOffsetStep, int xPixelOffset,\n\t\t\t\tint yPixelOffset) {\n\t\t\titeration();\n\t\t\tdrawFrame();\n\t\t}\n\n\t\tprotected abstract void drawFrame();\n\n\t\tprotected void iteration() {\n\t\t\t// Reschedule the next redraw in 40ms\n\t\t\tmHandler.removeCallbacks(mIteration);\n\t\t\tif (mVisible) {\n\t\t\t\tmHandler.postDelayed(mIteration, 1000 / 25);\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "engine/src/main/java/com/yalin/style/engine/advance/BokehRainbowCircle.java",
    "content": "/*\n * Copyright (C) 2007 Google Inc.\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 */\npackage com.yalin.style.engine.advance;\n\nimport android.graphics.Bitmap;\n\npublic class BokehRainbowCircle {\n\tfloat origRadius;\n\n\tfloat deltaRadius;\n\n\tfloat radius;\n\n\tfloat origX;\n\n\tfloat deltaX;\n\n\tfloat x;\n\n\tfloat origY;\n\n\tfloat deltaY;\n\n\tfloat y;\n\n\tint color;\n\n\tint alpha;\n\n\tint steps;\n\n\tint currentStep;\n\n\tBitmap bitmap;\n\n\tpublic BokehRainbowCircle(float xCenter, float yCenter, float radius,\n\t\t\tint color, int steps) {\n\t\tthis.x = xCenter;\n\t\tthis.origX = xCenter;\n\t\tthis.deltaX = (float) (40.0 * Math.random() - 20.0);\n\n\t\tthis.y = yCenter;\n\t\tthis.origY = yCenter;\n\t\tthis.deltaY = (float) (40.0 * Math.random() - 20.0);\n\n\t\tthis.origRadius = radius;\n\t\tthis.radius = radius;\n\t\tthis.deltaRadius = 0.5f * radius;\n\n\t\tthis.color = color;\n\t\tthis.alpha = 0;\n\n\t\tthis.steps = steps;\n\t}\n\n\tvoid tick() {\n\t\tthis.currentStep++;\n\n\t\tfloat fraction = (float) this.currentStep / (float) this.steps;\n\n\t\tthis.radius = this.origRadius + fraction * this.deltaRadius;\n\t\tthis.x = this.origX + fraction * this.deltaX;\n\t\tthis.y = this.origY + fraction * this.deltaY;\n\n\t\tif (fraction <= 0.25f) {\n\t\t\tthis.alpha = (int) (128 * 4.0f * fraction);\n\t\t} else {\n\t\t\tthis.alpha = (int) (-128 * (fraction - 1) / 0.75f);\n\t\t}\n\t}\n\n\tboolean isDone() {\n\t\treturn this.currentStep > this.steps;\n\t}\n}\n"
  },
  {
    "path": "engine/src/main/java/com/yalin/style/engine/advance/BokehRainbowWallpaper.java",
    "content": "/*\n * Copyright (C) 2007 Google Inc.\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 */\npackage com.yalin.style.engine.advance;\n\nimport android.content.Context;\nimport android.graphics.Canvas;\nimport android.graphics.Color;\nimport android.graphics.Paint;\nimport android.os.Bundle;\nimport android.view.SurfaceHolder;\n\nimport java.util.HashSet;\nimport java.util.Iterator;\nimport java.util.Set;\n\npublic class BokehRainbowWallpaper extends AnimationWallpaper {\n\n\tpublic BokehRainbowWallpaper(Context host) {\n\t\tsuper(host);\n\t}\n\n\t@Override\n\tpublic Engine onCreateEngine() {\n\t\treturn new BokehEngine();\n\t}\n\n\tclass BokehEngine extends AnimationEngine {\n\t\tint offsetX;\n\t\tint offsetY;\n\t\tint height;\n\t\tint width;\n\t\tint visibleWidth;\n\n\t\tSet<BokehRainbowCircle> circles = new HashSet<BokehRainbowCircle>();\n\n\t\tint iterationCount = 0;\n\n\t\tPaint paint = new Paint();\n\n\t\t@Override\n\t\tpublic void onCreate(SurfaceHolder surfaceHolder) {\n\t\t\tsuper.onCreate(surfaceHolder);\n\n\t\t\t// By default we don't get touch events, so enable them.\n\t\t\tsetTouchEventsEnabled(true);\n\t\t}\n\n\t\t@Override\n\t\tpublic void onSurfaceChanged(SurfaceHolder holder, int format,\n\t\t\t\tint width, int height) {\n\n\t\t\tthis.height = height;\n\t\t\tif (this.isPreview()) {\n\t\t\t\tthis.width = width;\n\t\t\t} else {\n\t\t\t\tthis.width = 2 * width;\n\t\t\t}\n\t\t\tthis.visibleWidth = width;\n\n\t\t\tfor (int i = 0; i < 20; i++) {\n\t\t\t\tthis.createRandomCircle();\n\t\t\t}\n\n\t\t\tsuper.onSurfaceChanged(holder, format, width, height);\n\t\t}\n\n\t\t@Override\n\t\tpublic void onOffsetsChanged(float xOffset, float yOffset,\n\t\t\t\tfloat xOffsetStep, float yOffsetStep, int xPixelOffset,\n\t\t\t\tint yPixelOffset) {\n\t\t\t// store the offsets\n\t\t\tthis.offsetX = xPixelOffset;\n\t\t\tthis.offsetY = yPixelOffset;\n\n\t\t\tsuper.onOffsetsChanged(xOffset, yOffset, xOffsetStep, yOffsetStep,\n\t\t\t\t\txPixelOffset, yPixelOffset);\n\t\t}\n\n\t\t@Override\n\t\tpublic Bundle onCommand(String action, int x, int y, int z,\n\t\t\t\tBundle extras, boolean resultRequested) {\n\t\t\tif (\"android.wallpaper.tap\".equals(action)) {\n\t\t\t\tcreateCircle(x - this.offsetX, y - this.offsetY);\n\t\t\t}\n\t\t\treturn super.onCommand(action, x, y, z, extras, resultRequested);\n\t\t}\n\n\t\t@Override\n\t\tprotected void drawFrame() {\n\t\t\tSurfaceHolder holder = getSurfaceHolder();\n\n\t\t\tCanvas c = null;\n\t\t\ttry {\n\t\t\t\tc = holder.lockCanvas();\n\t\t\t\tif (c != null) {\n\t\t\t\t\tdraw(c);\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tif (c != null)\n\t\t\t\t\tholder.unlockCanvasAndPost(c);\n\t\t\t}\n\t\t}\n\n\t\tvoid draw(Canvas c) {\n\t\t\tc.save();\n\t\t\tc.drawColor(0xff000000);\n\n\t\t\tsynchronized (circles) {\n\t\t\t\tfor (BokehRainbowCircle circle : circles) {\n\t\t\t\t\tif (circle.alpha == 0)\n\t\t\t\t\t\tcontinue;\n\n\t\t\t\t\t// intersects with the screen?\n\t\t\t\t\tfloat minX = circle.x - circle.radius;\n\t\t\t\t\tif (minX > (-this.offsetX + this.visibleWidth)) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tfloat maxX = circle.x + circle.radius;\n\t\t\t\t\tif (maxX < -this.offsetX) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tpaint.setAntiAlias(true);\n\n\t\t\t\t\t// paint the fill\n\t\t\t\t\tpaint.setColor(Color.argb(circle.alpha, Color\n\t\t\t\t\t\t\t.red(circle.color), Color.green(circle.color),\n\t\t\t\t\t\t\tColor.blue(circle.color)));\n\t\t\t\t\tpaint.setStyle(Paint.Style.FILL_AND_STROKE);\n\t\t\t\t\tc.drawCircle(circle.x + this.offsetX, circle.y\n\t\t\t\t\t\t\t+ this.offsetY, circle.radius, paint);\n\n\t\t\t\t\t// paint the contour\n\t\t\t\t\tpaint.setColor(Color.argb(circle.alpha, 63 + 3 * Color\n\t\t\t\t\t\t\t.red(circle.color) / 4, 63 + 3 * Color\n\t\t\t\t\t\t\t.green(circle.color) / 4, 63 + 3 * Color\n\t\t\t\t\t\t\t.blue(circle.color) / 4));\n\t\t\t\t\tpaint.setStyle(Paint.Style.STROKE);\n\t\t\t\t\tpaint.setStrokeWidth(3.0f);\n\t\t\t\t\tc.drawCircle(circle.x + this.offsetX, circle.y\n\t\t\t\t\t\t\t+ this.offsetY, circle.radius, paint);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tc.restore();\n\t\t}\n\n\t\t@Override\n\t\tprotected void iteration() {\n\t\t\tsynchronized (circles) {\n\t\t\t\tfor (Iterator<BokehRainbowCircle> it = circles.iterator(); it\n\t\t\t\t\t\t.hasNext();) {\n\t\t\t\t\tBokehRainbowCircle circle = it.next();\n\t\t\t\t\tcircle.tick();\n\t\t\t\t\tif (circle.isDone())\n\t\t\t\t\t\tit.remove();\n\t\t\t\t}\n\t\t\t\titerationCount++;\n\t\t\t\tif (isPreview() || iterationCount % 2 == 0)\n\t\t\t\t\tcreateRandomCircle();\n\t\t\t}\n\n\t\t\tsuper.iteration();\n\t\t}\n\n\t\tvoid createRandomCircle() {\n\t\t\tint x = (int) (width * Math.random());\n\t\t\tint y = (int) (height * Math.random());\n\t\t\tcreateCircle(x, y);\n\t\t}\n\n\t\tint getColor(float yFraction) {\n\t\t\treturn Color.HSVToColor(new float[] { 360.0f * yFraction, 1.0f,\n\t\t\t\t\t1.0f });\n\t\t}\n\n\t\tvoid createCircle(int x, int y) {\n\t\t\tfloat radius = (float) (40 + 20 * Math.random());\n\n\t\t\tfloat yFraction = (float) y / (float) height;\n\t\t\tyFraction = yFraction + 0.05f - (float) (0.1f * (Math.random()));\n\t\t\tif (yFraction < 0.0f)\n\t\t\t\tyFraction += 1.0f;\n\t\t\tif (yFraction > 1.0f)\n\t\t\t\tyFraction -= 1.0f;\n\t\t\tint color = getColor(yFraction);\n\n\t\t\tint steps = 40 + (int) (20 * Math.random());\n\t\t\tBokehRainbowCircle circle = new BokehRainbowCircle(x, y, radius,\n\t\t\t\t\tcolor, steps);\n\t\t\tsynchronized (this.circles) {\n\t\t\t\tthis.circles.add(circle);\n\t\t\t}\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "engine/src/main/java/com/yalin/style/engine/component/ComponentContext.java",
    "content": "package com.yalin.style.engine.component;\n\nimport android.content.Context;\nimport android.content.ContextWrapper;\nimport android.content.res.AssetManager;\nimport android.content.res.Resources;\n\nimport com.yalin.style.engine.WallpaperActiveCallback;\nimport com.yalin.style.engine.resource.ResourcesManager;\n\nimport dalvik.system.DexClassLoader;\n\n\n/**\n * @author jinyalin\n * @since 2017/7/3.\n */\n\npublic class ComponentContext extends ContextWrapper implements WallpaperActiveCallback {\n    private String componentPath;\n    private WallpaperActiveCallback origin;\n\n    private Resources mResources;\n    private ClassLoader mClassLoader;\n\n    public ComponentContext(Context base, String componentPath) {\n        super(base.getApplicationContext());\n        this.componentPath = componentPath;\n        if (base instanceof WallpaperActiveCallback) {\n            origin = (WallpaperActiveCallback) base;\n        }\n    }\n\n    @Override\n    public Resources getResources() {\n        if (mResources == null) {\n            mResources = ResourcesManager.createResources(getBaseContext(), componentPath);\n        }\n        return mResources;\n    }\n\n    @Override\n    public ClassLoader getClassLoader() {\n        return getClassLoader(componentPath);\n    }\n\n\n    private ClassLoader getClassLoader(String componentFilePath) {\n        if (mClassLoader == null) {\n            mClassLoader = new DexClassLoader(componentFilePath,\n                    getCacheDir().getAbsolutePath(), null, getBaseContext().getClassLoader());\n        }\n        return mClassLoader;\n    }\n\n    @Override\n    public AssetManager getAssets() {\n        return getResources().getAssets();\n    }\n\n    @Override\n    public Context getApplicationContext() {\n        return this;\n    }\n\n    @Override\n    public void onWallpaperActivate() {\n        if (origin != null) {\n            origin.onWallpaperActivate();\n        }\n    }\n\n    @Override\n    public void onWallpaperDeactivate() {\n        if (origin != null) {\n            origin.onWallpaperDeactivate();\n        }\n    }\n}\n\n"
  },
  {
    "path": "engine/src/main/java/com/yalin/style/engine/component/StyleClassLoader.kt",
    "content": "package com.yalin.style.engine.component\n\nimport android.content.Context\nimport android.os.Build\nimport android.util.Log\nimport com.yalin.style.data.utils.getNativeDir\nimport com.yalin.style.data.utils.getNativeFileName\n\nimport java.io.BufferedInputStream\nimport java.io.BufferedOutputStream\nimport java.io.File\nimport java.io.FileOutputStream\nimport java.io.IOException\nimport java.io.InputStream\nimport java.io.OutputStream\nimport java.util.Enumeration\nimport java.util.zip.ZipEntry\nimport java.util.zip.ZipFile\n\nimport dalvik.system.DexClassLoader\n\n/**\n * @author jinyalin\n * *\n * @since 2017/8/9.\n */\n\nclass StyleClassLoader(private val context: Context,\n                       private val dexPath: String,\n                       optimizedDirectory: String?,\n                       librarySearchPath: String?, parent: ClassLoader?) : DexClassLoader(dexPath, optimizedDirectory, librarySearchPath, parent) {\n\n    override fun findLibrary(name: String): String? {\n        val soName = getNativeFileName(dexPath, name)\n        maybeCopyNativeLib(soName)\n        val targetFile = File(getNativeDir(context), soName)\n        return if (targetFile.exists()) targetFile.absolutePath else null\n    }\n\n    private fun maybeCopyNativeLib(libName: String) {\n        try {\n            val soFile = File(getNativeDir(context), libName)\n            if (soFile.exists()) {\n                return\n            }\n\n            val cpuArch: String\n            if (Build.VERSION.SDK_INT >= 21) {\n                cpuArch = Build.SUPPORTED_ABIS[0]\n            } else {\n                cpuArch = Build.CPU_ABI\n            }\n            var findSo = false\n\n            val zipfile = ZipFile(dexPath)\n            var entry: ZipEntry\n            var e: Enumeration<*> = zipfile.entries()\n            while (e.hasMoreElements()) {\n                entry = e.nextElement() as ZipEntry\n                if (entry.isDirectory)\n                    continue\n                if (entry.name.endsWith(\".so\") && entry.name.contains(\"lib/\" + cpuArch)) {\n                    findSo = true\n                    break\n                }\n            }\n            e = zipfile.entries()\n            while (e.hasMoreElements()) {\n                entry = e.nextElement()\n                if (entry.isDirectory || !entry.name.endsWith(\".so\"))\n                    continue\n                if (findSo && entry.name.contains(\"lib/\" + cpuArch)\n                        || !findSo && entry.name.contains(\"lib/armeabi/\")) {\n                    val libFile = File(getNativeDir(context).absolutePath\n                            + File.separator + libName)\n                    if (libFile.exists()) {\n                        // check version\n                    }\n                    val fos = FileOutputStream(libFile)\n                    Log.d(TAG, \"copy so \" + entry.name + \" of \" + cpuArch)\n                    copySo(zipfile.getInputStream(entry), fos)\n                    break\n                }\n\n            }\n\n            zipfile.close()\n        } catch (e: IOException) {\n            e.printStackTrace()\n        }\n\n    }\n\n    @Throws(IOException::class)\n    private fun copySo(input: InputStream, output: OutputStream) {\n        val bufferedInput = BufferedInputStream(input)\n        val bufferedOutput = BufferedOutputStream(output)\n        var count: Int\n        val data = ByteArray(8192)\n        count = bufferedInput.read(data, 0, 8192)\n        while (count != -1) {\n            bufferedOutput.write(data, 0, count)\n            count = bufferedInput.read(data, 0, 8192)\n        }\n        bufferedOutput.flush()\n        bufferedOutput.close()\n        output.close()\n        bufferedInput.close()\n        input.close()\n    }\n\n    companion object {\n        private val TAG = \"StyleClassLoader\"\n    }\n}\n"
  },
  {
    "path": "engine/src/main/java/com/yalin/style/engine/resource/BrandUtil.java",
    "content": "package com.yalin.style.engine.resource;\n\nimport android.content.res.Resources;\n\n/**\n * @author jinyalin\n * @since 2017/7/1.\n */\n\npublic class BrandUtil {\n    public static boolean isMiUi(Resources resources) {\n        return resources.getClass().getName().equals(\"android.content.res.MiuiResources\");\n    }\n\n    public static boolean isVivo(Resources resources) {\n        return resources.getClass().getName().equals(\"android.content.res.VivoResources\");\n    }\n\n    public static boolean isNubia(Resources resources) {\n        return resources.getClass().getName().equals(\"android.content.res.NubiaResources\");\n    }\n\n    public static boolean isNotRawResources(Resources resources) {\n        return !resources.getClass().getName().equals(\"android.content.res.Resources\");\n    }\n}\n"
  },
  {
    "path": "engine/src/main/java/com/yalin/style/engine/resource/CompatResources.java",
    "content": "package com.yalin.style.engine.resource;\n\nimport android.content.res.AssetFileDescriptor;\nimport android.content.res.AssetManager;\nimport android.content.res.ColorStateList;\nimport android.content.res.Configuration;\nimport android.content.res.Resources;\nimport android.content.res.TypedArray;\nimport android.content.res.XmlResourceParser;\nimport android.graphics.Movie;\nimport android.graphics.drawable.Drawable;\nimport android.os.Bundle;\nimport android.support.annotation.AnimRes;\nimport android.support.annotation.AnyRes;\nimport android.support.annotation.ArrayRes;\nimport android.support.annotation.BoolRes;\nimport android.support.annotation.ColorRes;\nimport android.support.annotation.DimenRes;\nimport android.support.annotation.DrawableRes;\nimport android.support.annotation.FractionRes;\nimport android.support.annotation.IntegerRes;\nimport android.support.annotation.LayoutRes;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\nimport android.support.annotation.PluralsRes;\nimport android.support.annotation.RawRes;\nimport android.support.annotation.RequiresApi;\nimport android.support.annotation.StringRes;\nimport android.support.annotation.XmlRes;\nimport android.util.AttributeSet;\nimport android.util.DisplayMetrics;\nimport android.util.TypedValue;\n\nimport org.xmlpull.v1.XmlPullParserException;\n\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * @author jinyalin\n * @since 2017/7/17.\n */\n\npublic class CompatResources extends Resources {\n    private Resources mHostResources;\n\n\n    public CompatResources(Resources host, AssetManager assets,\n                           DisplayMetrics metrics, Configuration config) {\n        super(assets, metrics, config);\n        mHostResources = host;\n    }\n\n    @NonNull\n    @Override\n    public CharSequence getText(@StringRes int id) throws NotFoundException {\n        try {\n            return super.getText(id);\n        } catch (Exception e) {\n            return mHostResources.getText(id);\n        }\n    }\n\n    @NonNull\n    @Override\n    public CharSequence getQuantityText(@PluralsRes int id, int quantity) throws NotFoundException {\n        try {\n            return super.getQuantityText(id, quantity);\n        } catch (Exception e) {\n            return mHostResources.getQuantityText(id, quantity);\n        }\n    }\n\n    @NonNull\n    @Override\n    public String getString(@StringRes int id) throws NotFoundException {\n        try {\n            return super.getString(id);\n        } catch (Exception e) {\n            return mHostResources.getString(id);\n        }\n    }\n\n    @NonNull\n    @Override\n    public String getString(@StringRes int id, Object... formatArgs) throws NotFoundException {\n        try {\n            return super.getString(id, formatArgs);\n        } catch (Exception e) {\n            return mHostResources.getString(id, formatArgs);\n        }\n    }\n\n    @NonNull\n    @Override\n    public String getQuantityString(@PluralsRes int id, int quantity, Object... formatArgs) throws NotFoundException {\n        try {\n            return super.getQuantityString(id, quantity, formatArgs);\n        } catch (Exception e) {\n            return mHostResources.getQuantityString(id, quantity, formatArgs);\n        }\n    }\n\n    @NonNull\n    @Override\n    public String getQuantityString(@PluralsRes int id, int quantity) throws NotFoundException {\n        try {\n            return super.getQuantityString(id, quantity);\n        } catch (Exception e) {\n            return mHostResources.getQuantityString(id, quantity);\n        }\n    }\n\n    @Override\n    public CharSequence getText(@StringRes int id, CharSequence def) {\n        try {\n            return super.getText(id, def);\n        } catch (Exception e) {\n            return mHostResources.getText(id, def);\n        }\n    }\n\n    @NonNull\n    @Override\n    public CharSequence[] getTextArray(@ArrayRes int id) throws NotFoundException {\n        try {\n            return super.getTextArray(id);\n        } catch (Exception e) {\n            return mHostResources.getTextArray(id);\n        }\n    }\n\n    @NonNull\n    @Override\n    public String[] getStringArray(@ArrayRes int id) throws NotFoundException {\n        try {\n            return super.getStringArray(id);\n        } catch (Exception e) {\n            return mHostResources.getStringArray(id);\n        }\n    }\n\n    @NonNull\n    @Override\n    public int[] getIntArray(@ArrayRes int id) throws NotFoundException {\n        try {\n            return super.getIntArray(id);\n        } catch (Exception e) {\n            return mHostResources.getIntArray(id);\n        }\n    }\n\n    @NonNull\n    @Override\n    public TypedArray obtainTypedArray(@ArrayRes int id) throws NotFoundException {\n        try {\n            return super.obtainTypedArray(id);\n        } catch (Exception e) {\n            return mHostResources.obtainTypedArray(id);\n        }\n    }\n\n    @Override\n    public float getDimension(@DimenRes int id) throws NotFoundException {\n        try {\n            return super.getDimension(id);\n        } catch (Exception e) {\n            return mHostResources.getDimension(id);\n        }\n    }\n\n    @Override\n    public int getDimensionPixelOffset(@DimenRes int id) throws NotFoundException {\n        try {\n            return super.getDimensionPixelOffset(id);\n        } catch (Exception e) {\n            return mHostResources.getDimensionPixelOffset(id);\n        }\n    }\n\n    @Override\n    public int getDimensionPixelSize(@DimenRes int id) throws NotFoundException {\n        try {\n            return super.getDimensionPixelSize(id);\n        } catch (Exception e) {\n            return mHostResources.getDimensionPixelSize(id);\n        }\n    }\n\n    @Override\n    public float getFraction(@FractionRes int id, int base, int pbase) {\n        try {\n            return super.getFraction(id, base, pbase);\n        } catch (Exception e) {\n            return mHostResources.getFraction(id, base, pbase);\n        }\n    }\n\n    @Override\n    public Drawable getDrawable(@DrawableRes int id) throws NotFoundException {\n        try {\n            return super.getDrawable(id);\n        } catch (Exception e) {\n            return mHostResources.getDrawable(id);\n        }\n    }\n\n    @RequiresApi(api = 21)\n    @Override\n    public Drawable getDrawable(@DrawableRes int id, @Nullable Theme theme)\n            throws NotFoundException {\n        try {\n            return super.getDrawable(id, theme);\n        } catch (Exception e) {\n            return mHostResources.getDrawable(id, theme);\n        }\n    }\n\n    @RequiresApi(api = 15)\n    @Override\n    public Drawable getDrawableForDensity(@DrawableRes int id, int density)\n            throws NotFoundException {\n        try {\n            return super.getDrawableForDensity(id, density);\n        } catch (Exception e) {\n            return mHostResources.getDrawableForDensity(id, density);\n        }\n    }\n\n    @RequiresApi(api = 21)\n    @Override\n    public Drawable getDrawableForDensity(@DrawableRes int id, int density, @Nullable Theme theme) {\n        try {\n            return super.getDrawableForDensity(id, density, theme);\n        } catch (Exception e) {\n            return mHostResources.getDrawableForDensity(id, density, theme);\n        }\n    }\n\n    @Override\n    public Movie getMovie(@RawRes int id) throws NotFoundException {\n        try {\n            return super.getMovie(id);\n        } catch (Exception e) {\n            return mHostResources.getMovie(id);\n        }\n    }\n\n    @Override\n    public int getColor(@ColorRes int id) throws NotFoundException {\n        try {\n            return super.getColor(id);\n        } catch (Exception e) {\n            return mHostResources.getColor(id);\n        }\n    }\n\n    @RequiresApi(api = 23)\n    @Override\n    public int getColor(@ColorRes int id, @Nullable Theme theme) throws NotFoundException {\n        try {\n            return super.getColor(id, theme);\n        } catch (Exception e) {\n            return mHostResources.getColor(id, theme);\n        }\n    }\n\n    @Nullable\n    @Override\n    public ColorStateList getColorStateList(@ColorRes int id) throws NotFoundException {\n        try {\n            return super.getColorStateList(id);\n        } catch (Exception e) {\n            return mHostResources.getColorStateList(id);\n        }\n    }\n\n    @RequiresApi(api = 23)\n    @Nullable\n    @Override\n    public ColorStateList getColorStateList(@ColorRes int id, @Nullable Theme theme)\n            throws NotFoundException {\n        try {\n            return super.getColorStateList(id, theme);\n        } catch (Exception e) {\n            return mHostResources.getColorStateList(id, theme);\n        }\n    }\n\n    @Override\n    public boolean getBoolean(@BoolRes int id) throws NotFoundException {\n        try {\n            return super.getBoolean(id);\n        } catch (Exception e) {\n            return mHostResources.getBoolean(id);\n        }\n    }\n\n    @Override\n    public int getInteger(@IntegerRes int id) throws NotFoundException {\n        try {\n            return super.getInteger(id);\n        } catch (Exception e) {\n            return mHostResources.getInteger(id);\n        }\n    }\n\n    @Override\n    public XmlResourceParser getLayout(@LayoutRes int id) throws NotFoundException {\n        try {\n            return super.getLayout(id);\n        } catch (Exception e) {\n            return mHostResources.getLayout(id);\n        }\n    }\n\n    @Override\n    public XmlResourceParser getAnimation(@AnimRes int id) throws NotFoundException {\n        try {\n            return super.getAnimation(id);\n        } catch (Exception e) {\n            return mHostResources.getAnimation(id);\n        }\n    }\n\n    @Override\n    public XmlResourceParser getXml(@XmlRes int id) throws NotFoundException {\n        try {\n            return super.getXml(id);\n        } catch (Exception e) {\n            return mHostResources.getXml(id);\n        }\n    }\n\n    @Override\n    public InputStream openRawResource(@RawRes int id) throws NotFoundException {\n        try {\n            return super.openRawResource(id);\n        } catch (Exception e) {\n            return mHostResources.openRawResource(id);\n        }\n    }\n\n    @Override\n    public InputStream openRawResource(@RawRes int id, TypedValue value) throws NotFoundException {\n        try {\n            return super.openRawResource(id, value);\n        } catch (Exception e) {\n            return mHostResources.openRawResource(id, value);\n        }\n    }\n\n    @Override\n    public AssetFileDescriptor openRawResourceFd(@RawRes int id) throws NotFoundException {\n        try {\n            return super.openRawResourceFd(id);\n        } catch (Exception e) {\n            return mHostResources.openRawResourceFd(id);\n        }\n    }\n\n    @Override\n    public void getValue(@AnyRes int id, TypedValue outValue, boolean resolveRefs)\n            throws NotFoundException {\n        try {\n            super.getValue(id, outValue, resolveRefs);\n        } catch (Exception e) {\n            mHostResources.getValue(id, outValue, resolveRefs);\n        }\n    }\n\n    @RequiresApi(api = 15)\n    @Override\n    public void getValueForDensity(@AnyRes int id, int density, TypedValue outValue,\n                                   boolean resolveRefs) throws NotFoundException {\n        try {\n            super.getValueForDensity(id, density, outValue, resolveRefs);\n        } catch (Exception e) {\n            mHostResources.getValueForDensity(id, density, outValue, resolveRefs);\n        }\n    }\n\n    @Override\n    public void getValue(String name, TypedValue outValue, boolean resolveRefs)\n            throws NotFoundException {\n        try {\n            super.getValue(name, outValue, resolveRefs);\n        } catch (Exception e) {\n            mHostResources.getValue(name, outValue, resolveRefs);\n        }\n    }\n\n    @Override\n    public TypedArray obtainAttributes(AttributeSet set, int[] attrs) {\n        try {\n            return super.obtainAttributes(set, attrs);\n        } catch (Exception e) {\n            return mHostResources.obtainAttributes(set, attrs);\n        }\n    }\n\n    @Override\n    public void updateConfiguration(Configuration config, DisplayMetrics metrics) {\n        try {\n            super.updateConfiguration(config, metrics);\n        } catch (Exception e) {\n//            mHostResources.updateConfiguration(config, metrics);\n        }\n    }\n\n    @Override\n    public DisplayMetrics getDisplayMetrics() {\n        try {\n            return super.getDisplayMetrics();\n        } catch (Exception e) {\n            return mHostResources.getDisplayMetrics();\n        }\n    }\n\n    @Override\n    public Configuration getConfiguration() {\n        try {\n            return super.getConfiguration();\n        } catch (Exception e) {\n            return mHostResources.getConfiguration();\n        }\n    }\n\n    @Override\n    public int getIdentifier(String name, String defType, String defPackage) {\n        try {\n            return super.getIdentifier(name, defType, defPackage);\n        } catch (Exception e) {\n            return mHostResources.getIdentifier(name, defType, defPackage);\n        }\n    }\n\n    @Override\n    public String getResourceName(@AnyRes int resid) throws NotFoundException {\n        try {\n            return super.getResourceName(resid);\n        } catch (Exception e) {\n            return mHostResources.getResourceName(resid);\n        }\n    }\n\n    @Override\n    public String getResourcePackageName(@AnyRes int resid) throws NotFoundException {\n        try {\n            return super.getResourcePackageName(resid);\n        } catch (Exception e) {\n            return mHostResources.getResourcePackageName(resid);\n        }\n    }\n\n    @Override\n    public String getResourceTypeName(@AnyRes int resid) throws NotFoundException {\n        try {\n            return super.getResourceTypeName(resid);\n        } catch (Exception e) {\n            return mHostResources.getResourceTypeName(resid);\n        }\n    }\n\n    @Override\n    public String getResourceEntryName(@AnyRes int resid) throws NotFoundException {\n        try {\n            return super.getResourceEntryName(resid);\n        } catch (Exception e) {\n            return mHostResources.getResourceEntryName(resid);\n        }\n    }\n\n    @Override\n    public void parseBundleExtras(XmlResourceParser parser, Bundle outBundle)\n            throws XmlPullParserException, IOException {\n        try {\n            super.parseBundleExtras(parser, outBundle);\n        } catch (Exception e) {\n//            mHostResources.parseBundleExtras(parser, outBundle);\n        }\n    }\n\n    @Override\n    public void parseBundleExtra(String tagName, AttributeSet attrs, Bundle outBundle)\n            throws XmlPullParserException {\n        try {\n            super.parseBundleExtra(tagName, attrs, outBundle);\n        } catch (Exception e) {\n//            mHostResources.parseBundleExtra(tagName, attrs, outBundle);\n        }\n    }\n\n    public static TypedArray obtainAttributes(\n            Resources res, Theme theme, AttributeSet set, int[] attrs) {\n        try {\n            if (theme == null) {\n                return res.obtainAttributes(set, attrs);\n            }\n            return theme.obtainStyledAttributes(set, attrs, 0, 0);\n        } catch (Exception e) {\n            return (TypedArray) ReflectUtil.invokeNoException(\n                    Resources.class, null, \"obtainAttributes\",\n                    new Class[]{Resources.class, Theme.class, AttributeSet.class, int[].class},\n                    res, theme, set, attrs);\n        }\n    }\n}\n"
  },
  {
    "path": "engine/src/main/java/com/yalin/style/engine/resource/ReflectUtil.java",
    "content": "package com.yalin.style.engine.resource;\n\nimport java.lang.reflect.Constructor;\nimport java.lang.reflect.Field;\nimport java.lang.reflect.Method;\n\n/**\n * @author jinyalin\n * @since 2017/7/1.\n */\n\npublic class ReflectUtil {\n\n    public static Object getField(Class clazz, Object target, String name)\n            throws Exception {\n        Field field = clazz.getDeclaredField(name);\n        field.setAccessible(true);\n        return field.get(target);\n    }\n\n    public static Object getFieldNoException(Class clazz, Object target, String name) {\n        try {\n            return ReflectUtil.getField(clazz, target, name);\n        } catch (Exception e) {\n            //ignored.\n        }\n\n        return null;\n    }\n\n    public static void setField(Class clazz, Object target, String name, Object value)\n            throws Exception {\n        Field field = clazz.getDeclaredField(name);\n        field.setAccessible(true);\n        field.set(target, value);\n    }\n\n    public static void setFieldNoException(Class clazz, Object target, String name, Object value) {\n        try {\n            ReflectUtil.setField(clazz, target, name, value);\n        } catch (Exception e) {\n            //ignored.\n        }\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public static Object invoke(Class clazz, Object target, String name, Object... args)\n            throws Exception {\n        Class[] parameterTypes = null;\n        if (args != null) {\n            parameterTypes = new Class[args.length];\n            for (int i = 0; i < args.length; i++) {\n                parameterTypes[i] = args[i].getClass();\n            }\n        }\n\n        Method method = clazz.getDeclaredMethod(name, parameterTypes);\n        method.setAccessible(true);\n        return method.invoke(target, args);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public static Object invoke(Class clazz, Object target, String name,\n                                Class[] parameterTypes, Object... args)\n            throws Exception {\n        Method method = clazz.getDeclaredMethod(name, parameterTypes);\n        method.setAccessible(true);\n        return method.invoke(target, args);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public static Object invokeNoException(Class clazz, Object target, String name,\n                                           Class[] parameterTypes, Object... args) {\n        try {\n            return invoke(clazz, target, name, parameterTypes, args);\n        } catch (Exception ignore) {\n        }\n\n        return null;\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public static Object invokeConstructor(Class clazz, Class[] parameterTypes, Object... args)\n            throws Exception {\n        Constructor constructor = clazz.getDeclaredConstructor(parameterTypes);\n        constructor.setAccessible(true);\n        return constructor.newInstance(args);\n    }\n}\n"
  },
  {
    "path": "engine/src/main/java/com/yalin/style/engine/resource/ResourcesManager.java",
    "content": "package com.yalin.style.engine.resource;\n\nimport android.content.Context;\nimport android.content.ContextWrapper;\nimport android.content.res.AssetManager;\nimport android.content.res.Configuration;\nimport android.content.res.Resources;\nimport android.os.Build;\nimport android.util.DisplayMetrics;\n\nimport java.lang.ref.WeakReference;\nimport java.util.Map;\n\n/**\n * @author jinyalin\n * @since 2017/7/1.\n */\n\npublic class ResourcesManager {\n    public static synchronized Resources createCompatResources(Context context, String apkFile) {\n        Resources hostResources = context.getResources();\n        AssetManager assetManager = createAssetManager(context, apkFile);\n        return new CompatResources(hostResources, assetManager, hostResources.getDisplayMetrics(),\n                hostResources.getConfiguration());\n    }\n\n    public static synchronized Resources createResources(Context context, String apkFile) {\n        Resources hostResources = context.getResources();\n        AssetManager assetManager = createAssetManager(context, apkFile);\n        return new Resources(assetManager, hostResources.getDisplayMetrics(),\n                hostResources.getConfiguration());\n    }\n\n    private static AssetManager createAssetManager(Context context, String apkFile) {\n        try {\n            AssetManager am = AssetManager.class.newInstance();\n            ReflectUtil.invoke(AssetManager.class, am, \"addAssetPath\", apkFile);\n            return am;\n        } catch (Exception e) {\n            e.printStackTrace();\n            return null;\n        }\n    }\n\n    private static ResourcesCompat createResourcesCompat(Resources hostResources) {\n        if (BrandUtil.isMiUi(hostResources)) {\n            return new MiUiResourcesCompat();\n        } else if (BrandUtil.isVivo(hostResources)) {\n            return new VivoResourcesCompat();\n        } else if (BrandUtil.isNubia(hostResources)) {\n            return new NubiaResourcesCompat();\n        } else if (BrandUtil.isNotRawResources(hostResources)) {\n            return new AdaptationResourcesCompat();\n        } else {\n            // is raw android resources\n            return new AndroidResourcesCompat();\n        }\n    }\n\n    private static void hookResources(Context context, Resources resources) {\n        if (Build.VERSION.SDK_INT >= 24) {\n            return;\n        }\n\n        try {\n            context = getContextImpl(context);\n            ReflectUtil.setField(context.getClass(), context, \"mResources\", resources);\n            Object loadedApk = ReflectUtil.getField(context.getClass(), context, \"mPackageInfo\");\n            ReflectUtil.setField(loadedApk.getClass(), loadedApk, \"mResources\", resources);\n\n            Object activityThread = getActivityThread(context);\n            Object resManager = ReflectUtil.getField(activityThread.getClass(),\n                    activityThread, \"mResourcesManager\");\n            //noinspection unchecked\n            Map<Object, WeakReference<Resources>> map =\n                    (Map<Object, WeakReference<Resources>>)\n                            ReflectUtil.getField(resManager.getClass(),\n                                    resManager, \"mActiveResources\");\n            Object key = map.keySet().iterator().next();\n            map.put(key, new WeakReference<>(resources));\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n\n    private static Context getContextImpl(Context base) throws Exception {\n        Context impl;\n        if (base instanceof ContextWrapper) {\n            impl = (Context) ReflectUtil.getField(ContextWrapper.class, base, \"mBase\");\n        } else {\n            impl = base;\n        }\n        return impl;\n    }\n\n    private static Object getActivityThread(Context base) throws Exception {\n        Class<?> activityThreadClazz = Class.forName(\"android.app.ActivityThread\");\n        Object activityThread = null;\n        try {\n            activityThread = ReflectUtil.getField(activityThreadClazz,\n                    null, \"sCurrentActivityThread\");\n        } catch (Exception e) {\n            // ignored\n        }\n        if (activityThread == null) {\n            activityThread = ((ThreadLocal<?>) ReflectUtil.getField(activityThreadClazz,\n                    null, \"sThreadLocal\")).get();\n        }\n        return activityThread;\n\n    }\n\n    private static abstract class ResourcesCompat {\n        abstract Resources createResources(Context context,\n                                           Resources hostResources,\n                                           AssetManager assetManager) throws Exception;\n    }\n\n    private static final class MiUiResourcesCompat extends ResourcesCompat {\n        @Override\n        Resources createResources(Context context,\n                                  Resources hostResources,\n                                  AssetManager assetManager) throws Exception {\n            Class resourcesClazz = Class.forName(\"android.content.res.MiuiResources\");\n            return (Resources) ReflectUtil.invokeConstructor(resourcesClazz,\n                    new Class[]{AssetManager.class, DisplayMetrics.class, Configuration.class},\n                    assetManager, hostResources.getDisplayMetrics(),\n                    hostResources.getConfiguration());\n        }\n    }\n\n    private static final class VivoResourcesCompat extends ResourcesCompat {\n        @Override\n        Resources createResources(Context hostContext,\n                                  Resources hostResources,\n                                  AssetManager assetManager) throws Exception {\n            Class resourcesClazz = Class.forName(\"android.content.res.VivoResources\");\n            Resources newResources = (Resources) ReflectUtil.invokeConstructor(resourcesClazz,\n                    new Class[]{AssetManager.class, DisplayMetrics.class, Configuration.class},\n                    assetManager, hostResources.getDisplayMetrics(),\n                    hostResources.getConfiguration());\n            ReflectUtil.invokeNoException(resourcesClazz, newResources, \"init\",\n                    new Class[]{String.class}, hostContext.getPackageName());\n            Object themeValues = ReflectUtil.getFieldNoException(resourcesClazz,\n                    hostResources, \"mThemeValues\");\n            ReflectUtil.setFieldNoException(resourcesClazz, newResources,\n                    \"mThemeValues\", themeValues);\n            return newResources;\n        }\n    }\n\n    private static final class NubiaResourcesCompat extends ResourcesCompat {\n        @Override\n        Resources createResources(Context context,\n                                  Resources hostResources,\n                                  AssetManager assetManager) throws Exception {\n            Class resourcesClazz = Class.forName(\"android.content.res.NubiaResources\");\n            return (Resources) ReflectUtil.invokeConstructor(resourcesClazz,\n                    new Class[]{AssetManager.class, DisplayMetrics.class, Configuration.class},\n                    assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());\n        }\n    }\n\n    private static final class AdaptationResourcesCompat extends ResourcesCompat {\n        @Override\n        Resources createResources(Context context,\n                                  Resources hostResources,\n                                  AssetManager assetManager) throws Exception {\n            Resources newResources;\n            try {\n                Class resourcesClazz = hostResources.getClass();\n                newResources = (Resources) ReflectUtil.invokeConstructor(resourcesClazz,\n                        new Class[]{AssetManager.class, DisplayMetrics.class, Configuration.class},\n                        assetManager, hostResources.getDisplayMetrics(),\n                        hostResources.getConfiguration());\n            } catch (Exception e) {\n                newResources = new Resources(assetManager,\n                        hostResources.getDisplayMetrics(),\n                        hostResources.getConfiguration());\n            }\n\n            return newResources;\n        }\n    }\n\n    private static final class AndroidResourcesCompat extends ResourcesCompat {\n\n        @Override\n        Resources createResources(Context context,\n                                  Resources hostResources,\n                                  AssetManager assetManager) throws Exception {\n            return new Resources(assetManager,\n                    hostResources.getDisplayMetrics(),\n                    hostResources.getConfiguration());\n        }\n    }\n}\n"
  },
  {
    "path": "engine/src/main/java/net/rbgrn/android/glwallpaperservice/BaseConfigChooser.java",
    "content": "/*\n * Copyright (C) 2008 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\npackage net.rbgrn.android.glwallpaperservice;\n\nimport android.opengl.GLSurfaceView;\n\nimport javax.microedition.khronos.egl.EGL10;\nimport javax.microedition.khronos.egl.EGLConfig;\nimport javax.microedition.khronos.egl.EGLDisplay;\n\n/**\n * Created by romannurik on 11/6/13.\n */\nabstract class BaseConfigChooser implements GLSurfaceView.EGLConfigChooser {\n        private int eglContextClientVersion;\n\n        public BaseConfigChooser(int[] configSpec, int eglContextClientVersion) {\n                this.eglContextClientVersion = eglContextClientVersion;\n        mConfigSpec = filterConfigSpec(configSpec);\n    }\n\n    public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {\n        int[] num_config = new int[1];\n        if (!egl.eglChooseConfig(display, mConfigSpec, null, 0,\n                num_config)) {\n            throw new IllegalArgumentException(\"eglChooseConfig failed\");\n        }\n\n        int numConfigs = num_config[0];\n\n        if (numConfigs <= 0) {\n            throw new IllegalArgumentException(\n                    \"No configs match configSpec\");\n        }\n\n        EGLConfig[] configs = new EGLConfig[numConfigs];\n        if (!egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs,\n                num_config)) {\n            throw new IllegalArgumentException(\"eglChooseConfig#2 failed\");\n        }\n        EGLConfig config = chooseConfig(egl, display, configs);\n        if (config == null) {\n            throw new IllegalArgumentException(\"No config chosen\");\n        }\n        return config;\n    }\n\n    abstract EGLConfig chooseConfig(EGL10 egl, EGLDisplay display,\n            EGLConfig[] configs);\n\n    protected int[] mConfigSpec;\n\n    private int[] filterConfigSpec(int[] configSpec) {\n        if (eglContextClientVersion != 2) {\n            return configSpec;\n        }\n        /* We know none of the subclasses define EGL_RENDERABLE_TYPE.\n         * And we know the configSpec is well formed.\n         */\n        int len = configSpec.length;\n        int[] newConfigSpec = new int[len + 2];\n        System.arraycopy(configSpec, 0, newConfigSpec, 0, len-1);\n        newConfigSpec[len-1] = EGL10.EGL_RENDERABLE_TYPE;\n        newConfigSpec[len] = 4; /* EGL_OPENGL_ES2_BIT */\n        newConfigSpec[len+1] = EGL10.EGL_NONE;\n        return newConfigSpec;\n    }\n\n        public static class ComponentSizeChooser extends BaseConfigChooser {\n                public ComponentSizeChooser(int redSize, int greenSize, int blueSize, int alphaSize, int depthSize,\n                                int stencilSize, int eglContextClientVersion) {\n                        super(new int[] { EGL10.EGL_RED_SIZE, redSize, EGL10.EGL_GREEN_SIZE, greenSize, EGL10.EGL_BLUE_SIZE,\n                                        blueSize, EGL10.EGL_ALPHA_SIZE, alphaSize, EGL10.EGL_DEPTH_SIZE, depthSize, EGL10.EGL_STENCIL_SIZE,\n                                        stencilSize, EGL10.EGL_NONE }, eglContextClientVersion);\n                        mValue = new int[1];\n                        mRedSize = redSize;\n                        mGreenSize = greenSize;\n                        mBlueSize = blueSize;\n                        mAlphaSize = alphaSize;\n                        mDepthSize = depthSize;\n                        mStencilSize = stencilSize;\n                }\n\n                @Override\n                public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs) {\n                        EGLConfig closestConfig = null;\n                        int closestDistance = 1000;\n                        for (EGLConfig config : configs) {\n                                int d = findConfigAttrib(egl, display, config, EGL10.EGL_DEPTH_SIZE);\n                                int s = findConfigAttrib(egl, display, config, EGL10.EGL_STENCIL_SIZE);\n                                if (d >= mDepthSize && s >= mStencilSize) {\n                                        int r = findConfigAttrib(egl, display, config, EGL10.EGL_RED_SIZE);\n                                        int g = findConfigAttrib(egl, display, config, EGL10.EGL_GREEN_SIZE);\n                                        int b = findConfigAttrib(egl, display, config, EGL10.EGL_BLUE_SIZE);\n                                        int a = findConfigAttrib(egl, display, config, EGL10.EGL_ALPHA_SIZE);\n                                        int distance = Math.abs(r - mRedSize) + Math.abs(g - mGreenSize) + Math.abs(b - mBlueSize)\n                                        + Math.abs(a - mAlphaSize);\n                                        if (distance < closestDistance) {\n                                                closestDistance = distance;\n                                                closestConfig = config;\n                                        }\n                                }\n                        }\n                        return closestConfig;\n                }\n\n                private int findConfigAttrib(EGL10 egl, EGLDisplay display, EGLConfig config, int attribute) {\n\n                        if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) {\n                                return mValue[0];\n                        }\n                        return 0;\n                }\n\n                private int[] mValue;\n                // Subclasses can adjust these values:\n                protected int mRedSize;\n                protected int mGreenSize;\n                protected int mBlueSize;\n                protected int mAlphaSize;\n                protected int mDepthSize;\n                protected int mStencilSize;\n        }\n\n        /**\n         * This class will choose a supported surface as close to RGB565 as possible, with or without a depth buffer.\n         *\n         */\n        public static class SimpleEGLConfigChooser extends ComponentSizeChooser {\n                public SimpleEGLConfigChooser(boolean withDepthBuffer, int eglContextClientVersion) {\n                        super(4, 4, 4, 0, withDepthBuffer ? 16 : 0, 0, eglContextClientVersion);\n                        // Adjust target values. This way we'll accept a 4444 or\n                        // 555 buffer if there's no 565 buffer available.\n                        mRedSize = 5;\n                        mGreenSize = 6;\n                        mBlueSize = 5;\n                }\n        }\n}\n"
  },
  {
    "path": "engine/src/main/java/net/rbgrn/android/glwallpaperservice/GLWallpaperService.java",
    "content": "/*\n * Copyright (C) 2008 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\npackage net.rbgrn.android.glwallpaperservice;\n\nimport android.opengl.GLSurfaceView;\nimport android.service.wallpaper.WallpaperService;\nimport android.util.Log;\nimport android.view.SurfaceHolder;\n\nimport net.rbgrn.android.glwallpaperservice.BaseConfigChooser.ComponentSizeChooser;\nimport net.rbgrn.android.glwallpaperservice.BaseConfigChooser.SimpleEGLConfigChooser;\n\nimport java.io.Writer;\nimport java.util.ArrayList;\n\nimport javax.microedition.khronos.egl.EGL10;\nimport javax.microedition.khronos.egl.EGL11;\nimport javax.microedition.khronos.egl.EGLConfig;\nimport javax.microedition.khronos.egl.EGLContext;\nimport javax.microedition.khronos.egl.EGLDisplay;\nimport javax.microedition.khronos.egl.EGLSurface;\nimport javax.microedition.khronos.opengles.GL;\nimport javax.microedition.khronos.opengles.GL10;\n\n// Original code provided by Robert Green\n// http://www.rbgrn.net/content/354-glsurfaceview-adapted-3d-live-wallpapers\npublic class GLWallpaperService extends WallpaperService {\n        private static final String TAG = \"GLWallpaperService\";\n\n        @Override\n        public Engine onCreateEngine() {\n                return new GLEngine();\n        }\n\n        public class GLEngine extends Engine {\n                public final static int RENDERMODE_WHEN_DIRTY = 0;\n                public final static int RENDERMODE_CONTINUOUSLY = 1;\n\n                private GLThread mGLThread;\n                private GLSurfaceView.EGLConfigChooser mEGLConfigChooser;\n                private GLSurfaceView.EGLContextFactory mEGLContextFactory;\n                private GLSurfaceView.EGLWindowSurfaceFactory mEGLWindowSurfaceFactory;\n                private GLSurfaceView.GLWrapper mGLWrapper;\n                private int mDebugFlags;\n                private int mEGLContextClientVersion;\n\n                public GLEngine() {\n                        super();\n                }\n\n                @Override\n                public void onVisibilityChanged(boolean visible) {\n                        if (visible) {\n                                onResume();\n                        } else {\n                                onPause();\n                        }\n                        super.onVisibilityChanged(visible);\n                }\n\n                @Override\n                public void onCreate(SurfaceHolder surfaceHolder) {\n                        super.onCreate(surfaceHolder);\n                        // Log.D(TAG, \"GLEngine.onCreate()\");\n                }\n\n                @Override\n                public void onDestroy() {\n                        super.onDestroy();\n                        // Log.D(TAG, \"GLEngine.onDestroy()\");\n                        mGLThread.requestExitAndWait();\n                }\n\n                @Override\n                public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {\n                        // Log.D(TAG, \"onSurfaceChanged()\");\n                        mGLThread.onWindowResize(width, height);\n                        super.onSurfaceChanged(holder, format, width, height);\n                }\n\n                @Override\n                public void onSurfaceCreated(SurfaceHolder holder) {\n                        Log.d(TAG, \"onSurfaceCreated()\");\n                        mGLThread.surfaceCreated(holder);\n                        super.onSurfaceCreated(holder);\n                }\n\n                @Override\n                public void onSurfaceDestroyed(SurfaceHolder holder) {\n                        Log.d(TAG, \"onSurfaceDestroyed()\");\n                        mGLThread.surfaceDestroyed();\n                        super.onSurfaceDestroyed(holder);\n                }\n\n                /**\n                 * An EGL helper class.\n                 */\n                public void setGLWrapper(GLSurfaceView.GLWrapper glWrapper) {\n                        mGLWrapper = glWrapper;\n                }\n\n                public void setDebugFlags(int debugFlags) {\n                        mDebugFlags = debugFlags;\n                }\n\n                public int getDebugFlags() {\n                        return mDebugFlags;\n                }\n\n                public void setRenderer(GLSurfaceView.Renderer renderer) {\n                        checkRenderThreadState();\n                        if (mEGLConfigChooser == null) {\n                                mEGLConfigChooser = new SimpleEGLConfigChooser(true, mEGLContextClientVersion);\n                        }\n                        if (mEGLContextFactory == null) {\n                                mEGLContextFactory = new DefaultContextFactory(mEGLContextClientVersion);\n                        }\n                        if (mEGLWindowSurfaceFactory == null) {\n                                mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory();\n                        }\n                        mGLThread = new GLThread(renderer, mEGLConfigChooser, mEGLContextFactory, mEGLWindowSurfaceFactory, mGLWrapper);\n                        mGLThread.start();\n                }\n\n                public void setEGLContextFactory(GLSurfaceView.EGLContextFactory factory) {\n                        checkRenderThreadState();\n                        mEGLContextFactory = factory;\n                }\n\n                public void setEGLWindowSurfaceFactory(GLSurfaceView.EGLWindowSurfaceFactory factory) {\n                        checkRenderThreadState();\n                        mEGLWindowSurfaceFactory = factory;\n                }\n\n                public void setEGLConfigChooser(GLSurfaceView.EGLConfigChooser configChooser) {\n                        checkRenderThreadState();\n                        mEGLConfigChooser = configChooser;\n                }\n\n                public void setEGLConfigChooser(boolean needDepth) {\n                        setEGLConfigChooser(new SimpleEGLConfigChooser(needDepth, mEGLContextClientVersion));\n                }\n\n                public void setEGLConfigChooser(int redSize, int greenSize, int blueSize, int alphaSize, int depthSize,\n                                int stencilSize) {\n                        setEGLConfigChooser(\n                                new ComponentSizeChooser(redSize, greenSize, blueSize, alphaSize,\n                                        depthSize,\n                                        stencilSize, mEGLContextClientVersion));\n                }\n\n                public void setEGLContextClientVersion(int version) {\n                    checkRenderThreadState();\n                    mEGLContextClientVersion = version;\n                }\n\n                public void setRenderMode(int renderMode) {\n                        mGLThread.setRenderMode(renderMode);\n                }\n\n                public int getRenderMode() {\n                        return mGLThread.getRenderMode();\n                }\n\n                public void requestRender() {\n                        mGLThread.requestRender();\n                }\n\n                public void onPause() {\n                        mGLThread.onPause();\n                }\n\n                public void onResume() {\n                        mGLThread.onResume();\n                }\n\n                public void queueEvent(Runnable r) {\n                        mGLThread.queueEvent(r);\n                }\n\n\n                private void checkRenderThreadState() {\n                        if (mGLThread != null) {\n                                throw new IllegalStateException(\"setRenderer has already been called for this instance.\");\n                        }\n                }\n        }\n\n        /**\n         * Empty wrapper for {@link GLSurfaceView.Renderer}.\n         *\n         * @deprecated Use {@link GLSurfaceView.Renderer} instead.\n         */\n        @Deprecated\n        public interface Renderer extends GLSurfaceView.Renderer {\n        }\n}\n\nclass LogWriter extends Writer {\n        private StringBuilder mBuilder = new StringBuilder();\n\n        @Override\n        public void close() {\n                flushBuilder();\n        }\n\n        @Override\n        public void flush() {\n                flushBuilder();\n        }\n\n        @Override\n        public void write(char[] buf, int offset, int count) {\n                for (int i = 0; i < count; i++) {\n                        char c = buf[offset + i];\n                        if (c == '\\n') {\n                                flushBuilder();\n                        } else {\n                                mBuilder.append(c);\n                        }\n                }\n        }\n\n        private void flushBuilder() {\n                if (mBuilder.length() > 0) {\n                        Log.v(\"GLSurfaceView\", mBuilder.toString());\n                        mBuilder.delete(0, mBuilder.length());\n                }\n        }\n}\n\n// ----------------------------------------------------------------------\n\n/**\n * Empty wrapper for {@link GLSurfaceView.EGLContextFactory}.\n *\n * @deprecated Use {@link GLSurfaceView.EGLContextFactory} instead.\n */\n@Deprecated\ninterface EGLContextFactory extends GLSurfaceView.EGLContextFactory {\n}\n\nclass DefaultContextFactory implements GLSurfaceView.EGLContextFactory {\n        private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;\n        private int eglContextClientVersion;\n\n        DefaultContextFactory(int eglContextClientVersion) {\n                this.eglContextClientVersion = eglContextClientVersion;\n        }\n\n        public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig config) {\n                int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, eglContextClientVersion,\n                EGL10.EGL_NONE };\n                return egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT,\n                                eglContextClientVersion != 0 ? attrib_list : null);\n        }\n\n        public void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context) {\n                egl.eglDestroyContext(display, context);\n        }\n}\n\n/**\n * Empty wrapper for {@link GLSurfaceView.EGLWindowSurfaceFactory}.\n *\n * @deprecated Use {@link GLSurfaceView.EGLWindowSurfaceFactory} instead.\n */\n@Deprecated\ninterface EGLWindowSurfaceFactory extends GLSurfaceView.EGLWindowSurfaceFactory {\n}\n\nclass DefaultWindowSurfaceFactory implements GLSurfaceView.EGLWindowSurfaceFactory {\n\n        public EGLSurface createWindowSurface(EGL10 egl, EGLDisplay\n                        display, EGLConfig config, Object nativeWindow) {\n                // this is a bit of a hack to work around Droid init problems - if you don't have this, it'll get hung up on orientation changes\n                EGLSurface eglSurface = null;\n                while (eglSurface == null) {\n                        try {\n                                eglSurface = egl.eglCreateWindowSurface(display,\n                                                config, nativeWindow, null);\n                        } catch (Throwable t) {\n                        } finally {\n                                if (eglSurface == null) {\n                                        try {\n                                                Thread.sleep(10);\n                                        } catch (InterruptedException t) {\n                                        }\n                                }\n                        }\n                }\n                return eglSurface;\n        }\n\n        public void destroySurface(EGL10 egl, EGLDisplay display, EGLSurface surface) {\n                egl.eglDestroySurface(display, surface);\n        }\n}\n\n/**\n * Empty wrapper for {@link GLSurfaceView.GLWrapper}.\n *\n * @deprecated Use {@link GLSurfaceView.GLWrapper} instead.\n */\n@Deprecated\ninterface GLWrapper extends GLSurfaceView.GLWrapper {\n}\n\nclass EglHelper {\n\n        private EGL10 mEgl;\n        private EGLDisplay mEglDisplay;\n        private EGLSurface mEglSurface;\n        private EGLContext mEglContext;\n        EGLConfig mEglConfig;\n\n        private GLSurfaceView.EGLConfigChooser mEGLConfigChooser;\n        private GLSurfaceView.EGLContextFactory mEGLContextFactory;\n        private GLSurfaceView.EGLWindowSurfaceFactory mEGLWindowSurfaceFactory;\n        private GLSurfaceView.GLWrapper mGLWrapper;\n\n        public EglHelper(GLSurfaceView.EGLConfigChooser chooser, GLSurfaceView.EGLContextFactory contextFactory,\n                        GLSurfaceView.EGLWindowSurfaceFactory surfaceFactory, GLSurfaceView.GLWrapper wrapper) {\n                this.mEGLConfigChooser = chooser;\n                this.mEGLContextFactory = contextFactory;\n                this.mEGLWindowSurfaceFactory = surfaceFactory;\n                this.mGLWrapper = wrapper;\n        }\n\n        /**\n         * Initialize EGL for a given configuration spec.\n         *\n         * @param configSpec\n         */\n        public void start() {\n                // Log.D(\"EglHelper\" + instanceId, \"start()\");\n                if (mEgl == null) {\n                        // Log.D(\"EglHelper\" + instanceId, \"getting new EGL\");\n                        /*\n                         * Get an EGL instance\n                         */\n                        mEgl = (EGL10) EGLContext.getEGL();\n                } else {\n                        // Log.D(\"EglHelper\" + instanceId, \"reusing EGL\");\n                }\n\n                if (mEglDisplay == null) {\n                        // Log.D(\"EglHelper\" + instanceId, \"getting new display\");\n                        /*\n                         * Get to the default display.\n                         */\n                        mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);\n                } else {\n                        // Log.D(\"EglHelper\" + instanceId, \"reusing display\");\n                }\n\n                if (mEglConfig == null) {\n                        // Log.D(\"EglHelper\" + instanceId, \"getting new config\");\n                        /*\n                         * We can now initialize EGL for that display\n                         */\n                        int[] version = new int[2];\n                        mEgl.eglInitialize(mEglDisplay, version);\n                        mEglConfig = mEGLConfigChooser.chooseConfig(mEgl, mEglDisplay);\n                } else {\n                        // Log.D(\"EglHelper\" + instanceId, \"reusing config\");\n                }\n\n                if (mEglContext == null) {\n                        // Log.D(\"EglHelper\" + instanceId, \"creating new context\");\n                        /*\n                         * Create an OpenGL ES context. This must be done only once, an OpenGL context is a somewhat heavy object.\n                         */\n                        mEglContext = mEGLContextFactory.createContext(mEgl, mEglDisplay, mEglConfig);\n                        if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) {\n                                throw new RuntimeException(\"createContext failed\");\n                        }\n                } else {\n                        // Log.D(\"EglHelper\" + instanceId, \"reusing context\");\n                }\n\n                mEglSurface = null;\n        }\n\n        /*\n         * React to the creation of a new surface by creating and returning an OpenGL interface that renders to that\n         * surface.\n         */\n        public GL createSurface(SurfaceHolder holder) {\n                /*\n                 * The window size has changed, so we need to create a new surface.\n                 */\n                if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) {\n\n                        /*\n                         * Unbind and destroy the old EGL surface, if there is one.\n                         */\n                        mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);\n                        mEGLWindowSurfaceFactory.destroySurface(mEgl, mEglDisplay, mEglSurface);\n                }\n\n                /*\n                 * Create an EGL surface we can render into.\n                 */\n                mEglSurface = mEGLWindowSurfaceFactory.createWindowSurface(mEgl, mEglDisplay, mEglConfig, holder);\n\n                if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {\n                        throw new RuntimeException(\"createWindowSurface failed\");\n                }\n\n                /*\n                 * Before we can issue GL commands, we need to make sure the context is current and bound to a surface.\n                 */\n                if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {\n                        throw new RuntimeException(\"eglMakeCurrent failed.\");\n                }\n\n                GL gl = mEglContext.getGL();\n                if (mGLWrapper != null) {\n                        gl = mGLWrapper.wrap(gl);\n                }\n\n                /*\n                 * if ((mDebugFlags & (DEBUG_CHECK_GL_ERROR | DEBUG_LOG_GL_CALLS))!= 0) { int configFlags = 0; Writer log =\n                 * null; if ((mDebugFlags & DEBUG_CHECK_GL_ERROR) != 0) { configFlags |= GLDebugHelper.CONFIG_CHECK_GL_ERROR; }\n                 * if ((mDebugFlags & DEBUG_LOG_GL_CALLS) != 0) { log = new LogWriter(); } gl = GLDebugHelper.wrap(gl,\n                 * configFlags, log); }\n                 */\n                return gl;\n        }\n\n        /**\n         * Display the current render surface.\n         *\n         * @return false if the context has been lost.\n         */\n        public boolean swap() {\n                mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);\n\n                /*\n                 * Always check for EGL_CONTEXT_LOST, which means the context and all associated data were lost (For instance\n                 * because the device went to sleep). We need to sleep until we get a new surface.\n                 */\n                return mEgl.eglGetError() != EGL11.EGL_CONTEXT_LOST;\n        }\n\n        public void destroySurface() {\n                if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) {\n                        mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);\n                        mEGLWindowSurfaceFactory.destroySurface(mEgl, mEglDisplay, mEglSurface);\n                        mEglSurface = null;\n                }\n        }\n\n        public void finish() {\n                if (mEglContext != null) {\n                        mEGLContextFactory.destroyContext(mEgl, mEglDisplay, mEglContext);\n                        mEglContext = null;\n                }\n                if (mEglDisplay != null) {\n                        mEgl.eglTerminate(mEglDisplay);\n                        mEglDisplay = null;\n                }\n        }\n}\n\nclass GLThread extends Thread {\n        private final static boolean LOG_THREADS = false;\n        public final static int DEBUG_CHECK_GL_ERROR = 1;\n        public final static int DEBUG_LOG_GL_CALLS = 2;\n\n        private final GLThreadManager sGLThreadManager = new GLThreadManager();\n        private GLThread mEglOwner;\n\n        private GLSurfaceView.EGLConfigChooser mEGLConfigChooser;\n        private GLSurfaceView.EGLContextFactory mEGLContextFactory;\n        private GLSurfaceView.EGLWindowSurfaceFactory mEGLWindowSurfaceFactory;\n        private GLSurfaceView.GLWrapper mGLWrapper;\n\n        public SurfaceHolder mHolder;\n        private boolean mSizeChanged = true;\n\n        // Once the thread is started, all accesses to the following member\n        // variables are protected by the sGLThreadManager monitor\n        public boolean mDone;\n        private boolean mPaused;\n        private boolean mHasSurface;\n        private boolean mWaitingForSurface;\n        private boolean mHaveEgl;\n        private int mWidth;\n        private int mHeight;\n        private int mRenderMode;\n        private boolean mRequestRender;\n        private boolean mEventsWaiting;\n        // End of member variables protected by the sGLThreadManager monitor.\n\n        private GLSurfaceView.Renderer mRenderer;\n        private ArrayList<Runnable> mEventQueue = new ArrayList<Runnable>();\n        private EglHelper mEglHelper;\n\n        GLThread(GLSurfaceView.Renderer renderer, GLSurfaceView.EGLConfigChooser chooser, GLSurfaceView.EGLContextFactory contextFactory,\n                        GLSurfaceView.EGLWindowSurfaceFactory surfaceFactory, GLSurfaceView.GLWrapper wrapper) {\n                super();\n                mDone = false;\n                mWidth = 0;\n                mHeight = 0;\n                mRequestRender = true;\n                mRenderMode = GLWallpaperService.GLEngine.RENDERMODE_CONTINUOUSLY;\n                mRenderer = renderer;\n                this.mEGLConfigChooser = chooser;\n                this.mEGLContextFactory = contextFactory;\n                this.mEGLWindowSurfaceFactory = surfaceFactory;\n                this.mGLWrapper = wrapper;\n        }\n\n        @Override\n        public void run() {\n                setName(\"GLThread \" + getId());\n                if (LOG_THREADS) {\n                        Log.i(\"GLThread\", \"starting tid=\" + getId());\n                }\n\n                try {\n                        guardedRun();\n                } catch (InterruptedException e) {\n                        // fall thru and exit normally\n                } finally {\n                        sGLThreadManager.threadExiting(this);\n                }\n        }\n\n        /*\n         * This private method should only be called inside a synchronized(sGLThreadManager) block.\n         */\n        private void stopEglLocked() {\n                if (mHaveEgl) {\n                        mHaveEgl = false;\n                        mEglHelper.destroySurface();\n                        sGLThreadManager.releaseEglSurface(this);\n                }\n        }\n\n        private void guardedRun() throws InterruptedException {\n                mEglHelper = new EglHelper(mEGLConfigChooser, mEGLContextFactory, mEGLWindowSurfaceFactory, mGLWrapper);\n                try {\n                        GL10 gl = null;\n                        boolean tellRendererSurfaceCreated = true;\n                        boolean tellRendererSurfaceChanged = true;\n\n                        /*\n                         * This is our main activity thread's loop, we go until asked to quit.\n                         */\n                        while (!isDone()) {\n                                /*\n                                 * Update the asynchronous state (window size)\n                                 */\n                                int w = 0;\n                                int h = 0;\n                                boolean changed = false;\n                                boolean needStart = false;\n                                boolean eventsWaiting = false;\n\n                                synchronized (sGLThreadManager) {\n                                        while (true) {\n                                                // Manage acquiring and releasing the SurfaceView\n                                                // surface and the EGL surface.\n                                                if (mPaused) {\n                                                        stopEglLocked();\n                                                }\n                                                if (!mHasSurface) {\n                                                        if (!mWaitingForSurface) {\n                                                                stopEglLocked();\n                                                                mWaitingForSurface = true;\n                                                                sGLThreadManager.notifyAll();\n                                                        }\n                                                } else {\n                                                        if (!mHaveEgl) {\n                                                                if (sGLThreadManager.tryAcquireEglSurface(this)) {\n                                                                        mHaveEgl = true;\n                                                                        mEglHelper.start();\n                                                                        mRequestRender = true;\n                                                                        needStart = true;\n                                                                }\n                                                        }\n                                                }\n\n                                                // Check if we need to wait. If not, update any state\n                                                // that needs to be updated, copy any state that\n                                                // needs to be copied, and use \"break\" to exit the\n                                                // wait loop.\n\n                                                if (mDone) {\n                                                        return;\n                                                }\n\n                                                if (mEventsWaiting) {\n                                                        eventsWaiting = true;\n                                                        mEventsWaiting = false;\n                                                        break;\n                                                }\n\n                                                if ((!mPaused) && mHasSurface && mHaveEgl && (mWidth > 0) && (mHeight > 0)\n                                                                && (mRequestRender || (mRenderMode == GLWallpaperService.GLEngine.RENDERMODE_CONTINUOUSLY))) {\n                                                        changed = mSizeChanged;\n                                                        w = mWidth;\n                                                        h = mHeight;\n                                                        mSizeChanged = false;\n                                                        mRequestRender = false;\n                                                        if (mHasSurface && mWaitingForSurface) {\n                                                                changed = true;\n                                                                mWaitingForSurface = false;\n                                                                sGLThreadManager.notifyAll();\n                                                        }\n                                                        break;\n                                                }\n\n                                                // By design, this is the only place where we wait().\n\n                                                if (LOG_THREADS) {\n                                                        Log.i(\"GLThread\", \"waiting tid=\" + getId());\n                                                }\n                                                sGLThreadManager.wait();\n                                        }\n                                } // end of synchronized(sGLThreadManager)\n\n                                /*\n                                 * Handle queued events\n                                 */\n                                if (eventsWaiting) {\n                                        Runnable r;\n                                        while ((r = getEvent()) != null) {\n                                                r.run();\n                                                if (isDone()) {\n                                                        return;\n                                                }\n                                        }\n                                        // Go back and see if we need to wait to render.\n                                        continue;\n                                }\n\n                                if (needStart) {\n                                        tellRendererSurfaceCreated = true;\n                                        changed = true;\n                                }\n                                if (changed) {\n                                        gl = (GL10) mEglHelper.createSurface(mHolder);\n                                        tellRendererSurfaceChanged = true;\n                                }\n                                if (tellRendererSurfaceCreated) {\n                                        mRenderer.onSurfaceCreated(gl, mEglHelper.mEglConfig);\n                                        tellRendererSurfaceCreated = false;\n                                }\n                                if (tellRendererSurfaceChanged) {\n                                        mRenderer.onSurfaceChanged(gl, w, h);\n                                        tellRendererSurfaceChanged = false;\n                                }\n                                if ((w > 0) && (h > 0)) {\n                                        /* draw a frame here */\n                                        mRenderer.onDrawFrame(gl);\n\n                                        /*\n                                         * Once we're done with GL, we need to call swapBuffers() to instruct the system to display the\n                                         * rendered frame\n                                         */\n                                        mEglHelper.swap();\n                                        Thread.sleep(10);\n                                }\n                        }\n                } finally {\n                        /*\n                         * clean-up everything...\n                         */\n                        synchronized (sGLThreadManager) {\n                                stopEglLocked();\n                                mEglHelper.finish();\n                        }\n                }\n        }\n\n        private boolean isDone() {\n                synchronized (sGLThreadManager) {\n                        return mDone;\n                }\n        }\n\n        public void setRenderMode(int renderMode) {\n                if (!((GLWallpaperService.GLEngine.RENDERMODE_WHEN_DIRTY <= renderMode) && (renderMode <= GLWallpaperService.GLEngine.RENDERMODE_CONTINUOUSLY))) {\n                        throw new IllegalArgumentException(\"renderMode\");\n                }\n                synchronized (sGLThreadManager) {\n                        mRenderMode = renderMode;\n                        if (renderMode == GLWallpaperService.GLEngine.RENDERMODE_CONTINUOUSLY) {\n                                sGLThreadManager.notifyAll();\n                        }\n                }\n        }\n\n        public int getRenderMode() {\n                synchronized (sGLThreadManager) {\n                        return mRenderMode;\n                }\n        }\n\n        public void requestRender() {\n                synchronized (sGLThreadManager) {\n                        mRequestRender = true;\n                        sGLThreadManager.notifyAll();\n                }\n        }\n\n        public void surfaceCreated(SurfaceHolder holder) {\n                mHolder = holder;\n                synchronized (sGLThreadManager) {\n                        if (LOG_THREADS) {\n                                Log.i(\"GLThread\", \"surfaceCreated tid=\" + getId());\n                        }\n                        mHasSurface = true;\n                        sGLThreadManager.notifyAll();\n                }\n        }\n\n        public void surfaceDestroyed() {\n                synchronized (sGLThreadManager) {\n                        if (LOG_THREADS) {\n                                Log.i(\"GLThread\", \"surfaceDestroyed tid=\" + getId());\n                        }\n                        mHasSurface = false;\n                        sGLThreadManager.notifyAll();\n                        while (!mWaitingForSurface && isAlive() && !mDone) {\n                                try {\n                                        sGLThreadManager.wait();\n                                } catch (InterruptedException e) {\n                                        Thread.currentThread().interrupt();\n                                }\n                        }\n                }\n        }\n\n        public void onPause() {\n                synchronized (sGLThreadManager) {\n                        mPaused = true;\n                        sGLThreadManager.notifyAll();\n                }\n        }\n\n        public void onResume() {\n                synchronized (sGLThreadManager) {\n                        mPaused = false;\n                        mRequestRender = true;\n                        sGLThreadManager.notifyAll();\n                }\n        }\n\n        public void onWindowResize(int w, int h) {\n                synchronized (sGLThreadManager) {\n                        mWidth = w;\n                        mHeight = h;\n                        mSizeChanged = true;\n                        sGLThreadManager.notifyAll();\n                }\n        }\n\n        public void requestExitAndWait() {\n                // don't call this from GLThread thread or it is a guaranteed\n                // deadlock!\n                synchronized (sGLThreadManager) {\n                        mDone = true;\n                        sGLThreadManager.notifyAll();\n                }\n                try {\n                        join();\n                } catch (InterruptedException ex) {\n                        Thread.currentThread().interrupt();\n                }\n        }\n\n        /**\n         * Queue an \"event\" to be run on the GL rendering thread.\n         *\n         * @param r\n         * the runnable to be run on the GL rendering thread.\n         */\n        public void queueEvent(Runnable r) {\n                synchronized (this) {\n                        mEventQueue.add(r);\n                        synchronized (sGLThreadManager) {\n                                mEventsWaiting = true;\n                                sGLThreadManager.notifyAll();\n                        }\n                }\n        }\n\n        private Runnable getEvent() {\n                synchronized (this) {\n                        if (mEventQueue.size() > 0) {\n                                return mEventQueue.remove(0);\n                        }\n\n                }\n                return null;\n        }\n\n        private class GLThreadManager {\n\n                public synchronized void threadExiting(GLThread thread) {\n                        if (LOG_THREADS) {\n                                Log.i(\"GLThread\", \"exiting tid=\" + thread.getId());\n                        }\n                        thread.mDone = true;\n                        if (mEglOwner == thread) {\n                                mEglOwner = null;\n                        }\n                        notifyAll();\n                }\n\n                /*\n                 * Tries once to acquire the right to use an EGL surface. Does not block.\n                 *\n                 * @return true if the right to use an EGL surface was acquired.\n                 */\n                public synchronized boolean tryAcquireEglSurface(GLThread thread) {\n                        if (mEglOwner == thread || mEglOwner == null) {\n                                mEglOwner = thread;\n                                notifyAll();\n                                return true;\n                        }\n                        return false;\n                }\n\n                public synchronized void releaseEglSurface(GLThread thread) {\n                        if (mEglOwner == thread) {\n                                mEglOwner = null;\n                        }\n                        notifyAll();\n                }\n        }\n}\n\n/**\n * Empty wrapper for {@link GLSurfaceView.EGLConfigChooser}.\n *\n * @deprecated Use {@link GLSurfaceView.EGLConfigChooser} instead.\n */\n@Deprecated\ninterface EGLConfigChooser extends GLSurfaceView.EGLConfigChooser {\n}\n\n"
  },
  {
    "path": "engine/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">engine</string>\n</resources>\n"
  },
  {
    "path": "engine/src/test/java/com/yalin/style/engine/ExampleUnitTest.java",
    "content": "package com.yalin.style.engine;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\n/**\n * Example local unit test, which will execute on the development machine (host).\n *\n * @see <a href=\"http://d.android.com/tools/testing\">Testing documentation</a>\n */\npublic class ExampleUnitTest {\n    @Test\n    public void addition_isCorrect() throws Exception {\n        assertEquals(4, 2 + 2);\n    }\n}"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "#Tue Apr 18 15:48:48 CST 2017\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-3.3-all.zip\n"
  },
  {
    "path": "gradle.properties",
    "content": "COMPILE_SDK_VERSION=25\nBUILD_TOOLS_VERSION=25.0.2\nMIN_SDK_VERSION=19\nTARGET_SDK_VERSION=25\n\nAPP_VERSION_NAME=2.7.1\nAPP_VERSION_CODE=25\n\nKOTLIN_VERSION = 1.1.3-2\nANKO_VESION = 0.8.2\nSUPPORT_LIBRARY_VERSION=25.3.1\n\nOK_HTTP_VERSION=3.5.0\nGSON_VERSION=2.8.0\nRXJAVA_VERSION=2.0.8\nRXANDROID_VERSION=2.0.1\nARROW_VERSION=1.0.0\nDAGGER_VERSION=2.10\nGLIDE_VERSION=3.7.0\nEVENT_BUS_VERSION=3.0.0\nBUGLY_VERSION=2.5.0\nFLURRY_VERSION=7.0.0@aar\nGDX_VERSION = 1.7.1\n\nFIRE_BASE_VERSION=10.2.1\n\nJAVAX_INJECT_VERSION=1\n\nCONF_VERSION = 20170102\n\nDEMO_API_WALLPAPER_ENDPOINT = http://api.kinglloy.com:6060/style\nPRODUCTION_API_WALLPAPER_ENDPOINT = http://api.kinglloy.com:6060/style"
  },
  {
    "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# 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\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\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    JAVACMD=`cygpath --unix \"$JAVACMD\"`\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": "presentation/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "presentation/build.gradle",
    "content": "apply plugin: 'com.android.application'\napply plugin: 'kotlin-android'\napply plugin: 'kotlin-kapt'\napply plugin: 'kotlin-android-extensions'\n\nProperties properties = new Properties()\nproperties.load(project.rootProject.file('local.properties').newDataInputStream())\n\ndef alias = properties.get('alias')\ndef _keyPassword = properties.get('keyPassword')\ndef _storePassword = properties.get('storePassword')\ndef keystorePath = properties.get('keystorePath')\ndef buglyAppId = properties.get('buglyAppId')\ndef flurryApiKey = properties.get('flurryApiKey')\n\ndef CHANNEL = System.getProperty(\"channel\", \"default\")\ndef APP_NAME = System.getProperty(\"app_name\", \"Style\")\n\nandroid {\n    compileSdkVersion COMPILE_SDK_VERSION as int\n    buildToolsVersion BUILD_TOOLS_VERSION as String\n    defaultConfig {\n        applicationId \"com.yalin.style\"\n        minSdkVersion MIN_SDK_VERSION as int\n        targetSdkVersion TARGET_SDK_VERSION as int\n        versionCode APP_VERSION_CODE as int\n        versionName APP_VERSION_NAME as String\n\n        testInstrumentationRunner \"android.support.test.runner.AndroidJUnitRunner\"\n\n        multiDexEnabled true\n\n        buildConfigField(\"String\", \"BUGLY_APP_ID\", \"\\\"${buglyAppId}\\\"\")\n        buildConfigField(\"String\", \"FLURRY_API_KEY\", \"\\\"${flurryApiKey}\\\"\")\n\n        resValue(\"string\", \"app_name_ext\", \"${APP_NAME}\")\n\n        ndk {\n            abiFilters 'x86', 'armeabi', 'armeabi-v7a'\n        }\n    }\n    signingConfigs {\n        release {\n            keyAlias alias\n            keyPassword _keyPassword\n            storeFile file(keystorePath)\n            storePassword _storePassword\n        }\n    }\n    buildTypes {\n        debug {\n            minifyEnabled false\n            signingConfig signingConfigs.release\n        }\n        release {\n            minifyEnabled true\n            proguardFiles getDefaultProguardFile('proguard-android.txt'),\n                    'proguard-rules.pro', 'component-proguard.pro'\n\n            signingConfig signingConfigs.release\n        }\n    }\n    productFlavors {\n        demo {\n            buildConfigField(\"boolean\", \"DEMO_MODE\", \"true\")\n        }\n        production {\n            buildConfigField(\"boolean\", \"DEMO_MODE\", \"false\")\n        }\n    }\n    dexOptions {\n        javaMaxHeapSize \"4g\"\n    }\n}\n\ndependencies {\n    compile \"com.android.support:appcompat-v7:${SUPPORT_LIBRARY_VERSION}\"\n    compile \"com.android.support:exifinterface:${SUPPORT_LIBRARY_VERSION}\"\n    compile \"com.android.support:design:${SUPPORT_LIBRARY_VERSION}\"\n    compile \"com.android.support:recyclerview-v7:${SUPPORT_LIBRARY_VERSION}\"\n    compile \"com.android.support:percent:${SUPPORT_LIBRARY_VERSION}\"\n\n    compile \"com.google.code.gson:gson:${GSON_VERSION}\"\n    compile \"com.squareup.okhttp3:okhttp:${OK_HTTP_VERSION}\"\n    compile \"com.google.dagger:dagger:${DAGGER_VERSION}\"\n    compile \"io.reactivex.rxjava2:rxjava:${RXJAVA_VERSION}\"\n    compile \"io.reactivex.rxjava2:rxandroid:${RXANDROID_VERSION}\"\n    compile \"com.github.bumptech.glide:glide:${GLIDE_VERSION}\"\n    compile \"org.greenrobot:eventbus:${EVENT_BUS_VERSION}\"\n    compile \"com.tencent.bugly:crashreport:${BUGLY_VERSION}\"\n    compile \"com.flurry.android:analytics:${FLURRY_VERSION}\"\n\n    compile \"com.google.firebase:firebase-core:${FIRE_BASE_VERSION}\"\n    compile \"com.google.firebase:firebase-crash:${FIRE_BASE_VERSION}\"\n    compile \"com.google.firebase:firebase-ads:${FIRE_BASE_VERSION}\"\n\n//    annotationProcessor \"com.google.dagger:dagger-compiler:${DAGGER_VERSION}\"\n    kapt \"com.google.dagger:dagger-compiler:${DAGGER_VERSION}\"\n\n    compile 'com.afollestad.material-dialogs:core:0.9.4.5'\n\n    compile project(':domain')\n    demoCompile project(path: ':engine', configuration: 'demoDebug')\n    productionCompile project(path: ':engine', configuration: 'productionRelease')\n    demoCompile project(path: ':data', configuration: 'demoDebug')\n    productionCompile project(path: ':data', configuration: 'productionRelease')\n\n    demoCompile 'com.facebook.stetho:stetho:1.5.0'\n\n    compile 'com.android.support:multidex:1.0.1'\n\n    testCompile 'junit:junit:4.12'\n    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {\n        exclude group: 'com.android.support', module: 'support-annotations'\n    })\n}\n\nproject.afterEvaluate {\n    android.applicationVariants.all { variant ->\n        variant.outputs.each { output ->\n            def oldFile = output.outputFile\n\n            output.outputFile = new File(oldFile.parent,\n                    \"${APP_NAME}-${CHANNEL}.apk\")\n        }\n    }\n}\n\nbuildscript {\n    repositories {\n        jcenter()\n    }\n    dependencies {\n        classpath \"org.jetbrains.kotlin:kotlin-android-extensions:${KOTLIN_VERSION}\"\n    }\n}\napply plugin: 'com.google.gms.google-services'"
  },
  {
    "path": "presentation/component-proguard.pro",
    "content": "# for component\n-keep public class com.yalin.style.engine.StyleWallpaperProxy{\n    *;\n}\n-keep public class com.yalin.style.engine.GLWallpaperServiceProxy{\n    *;\n}\n-keep public class com.yalin.style.engine.GLWallpaperServiceProxy$GLActiveEngine{\n    *;\n}\n-keep public class com.yalin.style.engine.WallpaperServiceProxy{\n    *;\n}\n-keep public class com.yalin.style.engine.WallpaperServiceProxy$ActiveEngine{\n    *;\n}\n-keep public class com.yalin.style.engine.GDXWallpaperServiceProxy{\n    *;\n}\n-keep public class com.yalin.style.engine.GDXWallpaperServiceProxy$GDXActiveEngine{\n    *;\n}\n-keep interface com.yalin.style.engine.IProvider{\n    *;\n}\n-keep class android.support.v4.content.ContextCompat{\n    *;\n}\n-keep class android.support.v4.content.res.ResourcesCompat{\n    *;\n}\n-keep class android.support.v4.graphics.drawable.DrawableCompat{\n    *;\n}\n-keep class android.support.graphics.drawable.VectorDrawableCompat{\n    *;\n}\n\n-dontwarn com.badlogic.gdx.backends.android.AndroidFragmentApplication\n-dontwarn com.badlogic.gdx.utils.GdxBuild\n-dontwarn com.badlogic.gdx.physics.box2d.utils.Box2DBuild\n-dontwarn com.badlogic.gdx.jnigen.BuildTarget*\n-keep interface com.badlogic.gdx.**{\n    *;\n}\n-keep class com.badlogic.gdx.**{\n    *;\n}\n"
  },
  {
    "path": "presentation/google-services.json",
    "content": "{\n  \"project_info\": {\n    \"project_number\": \"938703193302\",\n    \"firebase_url\": \"https://style-997fc.firebaseio.com\",\n    \"project_id\": \"style-997fc\",\n    \"storage_bucket\": \"style-997fc.appspot.com\"\n  },\n  \"client\": [\n    {\n      \"client_info\": {\n        \"mobilesdk_app_id\": \"1:938703193302:android:08f0f10e5a95f84f\",\n        \"android_client_info\": {\n          \"package_name\": \"com.yalin.style\"\n        }\n      },\n      \"oauth_client\": [\n        {\n          \"client_id\": \"938703193302-vutiiimvcf6kl6p8ea7klbapa0c4kkq1.apps.googleusercontent.com\",\n          \"client_type\": 3\n        }\n      ],\n      \"api_key\": [\n        {\n          \"current_key\": \"AIzaSyAXgpHrzEXNQSROL4OHn9ZYU41ijhUd5vc\"\n        }\n      ],\n      \"services\": {\n        \"analytics_service\": {\n          \"status\": 1\n        },\n        \"appinvite_service\": {\n          \"status\": 1,\n          \"other_platform_oauth_client\": []\n        },\n        \"ads_service\": {\n          \"status\": 2\n        }\n      }\n    }\n  ],\n  \"configuration_version\": \"1\"\n}"
  },
  {
    "path": "presentation/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 D:\\DevTools\\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 liked 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-dontpreverify\n-repackageclasses ''\n-allowaccessmodification\n-optimizations !code/simplification/arithmetic\n-keepattributes *Annotation*\n\n-keep public class * extends android.app.Activity\n-keep public class * extends android.app.Application\n-keep public class * extends android.app.Service\n-keep public class * extends android.content.BroadcastReceiver\n-keep public class * extends android.content.ContentProvider\n\n-keep public class * extends android.view.View {\n      public <init>(android.content.Context);\n      public <init>(android.content.Context, android.util.AttributeSet);\n      public <init>(android.content.Context, android.util.AttributeSet, int);\n      public void set*(...);\n}\n\n-keepclasseswithmembers class * {\n    public <init>(android.content.Context, android.util.AttributeSet);\n}\n\n-keepclasseswithmembers class * {\n    public <init>(android.content.Context, android.util.AttributeSet, int);\n}\n\n-keepclassmembers class * extends android.content.Context {\n    public void *(android.view.View);\n    public void *(android.view.MenuItem);\n}\n\n-keepclassmembers class * implements android.os.Parcelable {\n    static ** CREATOR;\n}\n\n-keepclassmembers class **.R$* {\n    public static <fields>;\n}\n\n-keepclassmembers class * {\n    @android.webkit.JavascriptInterface <methods>;\n}\n\n#Gson\n-keepattributes Signature\n# For using GSON @Expose annotation\n-keepattributes *Annotation*\n# Gson specific classes\n-keep class sun.misc.Unsafe { *; }\n# Application classes that will be serialized/deserialized over Gson\n-keep class com.yalin.style.data.entity.** { *; }\n# Prevent proguard from stripping interface information from TypeAdapterFactory,\n# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)\n-keep class * implements com.google.gson.TypeAdapterFactory\n-keep class * implements com.google.gson.JsonSerializer\n-keep class * implements com.google.gson.JsonDeserializer\n\n-dontwarn okio.**\n\n#Glide\n-keep public class * implements com.bumptech.glide.module.GlideModule\n-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {\n  **[] $VALUES;\n  public *;\n}\n# for DexGuard only\n#-keepresourcexmlelements manifest/application/meta-data@value=GlideModule\n\n\n#EventBus\n-keepattributes *Annotation*\n-keepclassmembers class ** {\n    @org.greenrobot.eventbus.Subscribe <methods>;\n}\n-keep enum org.greenrobot.eventbus.ThreadMode { *; }\n\n# Only required if you use AsyncExecutor\n-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {\n    <init>(java.lang.Throwable);\n}\n\n#Bugly\n-dontwarn com.tencent.bugly.**\n-keep public class com.tencent.bugly.**{*;}\n\n#Retrolambda\n-dontwarn java.lang.invoke**\n\n# Required to preserve the Flurry SDK\n-keep class com.flurry.** { *; }\n-dontwarn com.flurry.**\n-keepattributes *Annotation*,EnclosingMethod,Signature\n-keepclasseswithmembers class * {\n\tpublic <init>(android.content.Context, android.util.AttributeSet, int);\n}\n\n-dontwarn kotlin.**\n"
  },
  {
    "path": "presentation/src/androidTest/java/com/yalin/style/ExampleInstrumentedTest.java",
    "content": "package com.yalin.style;\n\nimport android.content.Context;\nimport android.support.test.InstrumentationRegistry;\nimport android.support.test.runner.AndroidJUnit4;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.junit.Assert.*;\n\n/**\n * Instrumentation test, which will execute on an Android device.\n *\n * @see <a href=\"http://d.android.com/tools/testing\">Testing documentation</a>\n */\n@RunWith(AndroidJUnit4.class)\npublic class ExampleInstrumentedTest {\n\n  @Test\n  public void useAppContext() throws Exception {\n    // Context of the app under test.\n    Context appContext = InstrumentationRegistry.getTargetContext();\n\n    assertEquals(\"com.yalin.style\", appContext.getPackageName());\n  }\n\n}\n"
  },
  {
    "path": "presentation/src/androidTest/java/com/yalin/style/provider/DatabaseTest.java",
    "content": "package com.yalin.style.provider;\n\nimport android.content.ContentValues;\nimport android.content.Context;\nimport android.database.Cursor;\nimport android.net.Uri;\nimport android.support.test.InstrumentationRegistry;\nimport android.support.test.runner.AndroidJUnit4;\nimport com.yalin.style.data.repository.datasource.provider.StyleContract.Wallpaper;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport junit.framework.Assert;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n/**\n * YaLin 2016/12/30.\n */\n@RunWith(AndroidJUnit4.class)\npublic class DatabaseTest {\n\n  @Test\n  public void testInsertWallpaper() {\n\n    Context context = InstrumentationRegistry.getTargetContext();\n\n    ContentValues contentValues = new ContentValues();\n    contentValues.put(Wallpaper.COLUMN_NAME_WALLPAPER_ID, \"wallpaperId\");\n    contentValues.put(Wallpaper.COLUMN_NAME_TITLE, \"title111\");\n    contentValues.put(Wallpaper.COLUMN_NAME_ATTRIBUTION, \"yalin...\");\n    contentValues.put(Wallpaper.COLUMN_NAME_BYLINE, \"byline111\");\n    contentValues.put(Wallpaper.COLUMN_NAME_IMAGE_URI, \"xxx\");\n    contentValues.put(Wallpaper.COLUMN_NAME_ADD_DATE, System.currentTimeMillis());\n    Uri uri = context.getContentResolver().insert(Wallpaper.CONTENT_URI, contentValues);\n\n    try {\n      Cursor cursor = context.getContentResolver()\n          .query(Wallpaper.CONTENT_URI, null, null, null, null);\n\n      Assert.assertNotNull(uri);\n      Assert.assertNotNull(cursor);\n      Assert.assertTrue(cursor.getCount() == 1);\n\n      if (cursor.moveToFirst()) {\n        String title = cursor.getString(cursor.getColumnIndex(Wallpaper.COLUMN_NAME_TITLE));\n\n        Assert.assertEquals(title, \"title111\");\n      }\n      cursor.close();\n    } finally {\n      if (uri != null) {\n        int retVal = context.getContentResolver().delete(uri, null, null);\n        Assert.assertEquals(retVal, 1);\n      }\n    }\n  }\n\n  @Test\n  public void testOpenFile() {\n    Context context = InstrumentationRegistry.getTargetContext();\n    try {\n      InputStream is = context.getContentResolver().openInputStream(Wallpaper.CONTENT_URI);\n      Assert.assertNotNull(is);\n      is.close();\n    } catch (IOException e) {\n      e.printStackTrace();\n    }\n  }\n}\n"
  },
  {
    "path": "presentation/src/androidTest/java/com/yalin/style/sync/SyncAdapterTest.java",
    "content": "package com.yalin.style.sync;\n\nimport android.accounts.Account;\nimport android.content.ContentResolver;\nimport android.os.Bundle;\nimport android.support.test.runner.AndroidJUnit4;\nimport com.yalin.style.data.repository.datasource.provider.StyleContract;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n/**\n * YaLin 2017/1/3.\n */\n@RunWith(AndroidJUnit4.class)\npublic class SyncAdapterTest {\n\n  @Test\n  public void syncAdapterTest() {\n//    ContentResolver\n//        .setSyncAutomatically(null, StyleContract.AUTHORITY, true);\n//    ContentResolver.setIsSyncable(account, ScheduleContract.CONTENT_AUTHORITY, 1);\n    Account account = com.yalin.style.data.repository.datasource.sync.account.Account.getAccount();\n//    ContentResolver.setIsSyncable(account, StyleContract.AUTHORITY, 1);\n    int syncable = ContentResolver.getIsSyncable(account, StyleContract.AUTHORITY);\n\n    Bundle b = new Bundle();\n    b.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);\n    b.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);\n    ContentResolver.requestSync(account, StyleContract.AUTHORITY, b);\n  }\n}\n"
  },
  {
    "path": "presentation/src/demo/java/com/yalin/style/util/AdUtil.kt",
    "content": "package com.yalin.style.util\n\nimport android.app.Activity\nimport com.google.android.gms.ads.*\nimport com.yalin.style.R\nimport com.yalin.style.view.activity.AdvanceSettingActivity\n\n/**\n * @author jinyalin\n * @since 2017/8/10.\n */\n\nfun maybeAttachAd(activity: Activity) {\n    MobileAds.initialize(activity.applicationContext,\n            activity.getString(R.string.app_ad_id))\n    val adView = activity.findViewById(R.id.adView) as AdView\n    val adRequest = AdRequest.Builder().build()\n    adView.loadAd(adRequest)\n}\n\nprivate var mInterstitialAd: InterstitialAd? = null\n\nfun maybeAttachInterstitialAd(activity: AdvanceSettingActivity, listener: AdListener) {\n    mInterstitialAd = InterstitialAd(activity)\n    mInterstitialAd!!.adUnitId = activity.getString(R.string.advance_insert_ad_unit_id)\n    mInterstitialAd!!.adListener = object : AdListener() {\n        override fun onAdLeftApplication() {\n            listener.onAdLeftApplication()\n        }\n\n        override fun onAdFailedToLoad(p0: Int) {\n            listener.onAdFailedToLoad(p0)\n        }\n\n        override fun onAdClosed() {\n            mInterstitialAd!!.loadAd(AdRequest.Builder().build())\n            listener.onAdClosed()\n        }\n\n        override fun onAdOpened() {\n            listener.onAdOpened()\n        }\n\n        override fun onAdLoaded() {\n            listener.onAdLoaded()\n        }\n    }\n    mInterstitialAd!!.loadAd(AdRequest.Builder().build())\n}\n\nfun maybeShowInterstitialAd(): Boolean {\n    if (mInterstitialAd != null) {\n        if (mInterstitialAd!!.isLoaded) {\n            mInterstitialAd!!.show()\n            return true\n        }\n    }\n    return false\n}"
  },
  {
    "path": "presentation/src/demo/res/values/ad_strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"test_id\" translatable=\"false\">ca-app-pub-3940256099942544/6300978111</string>\n    <string name=\"app_ad_id\" translatable=\"false\">@string/test_id</string>\n    <string name=\"advance_banner_ad_unit_id\" translatable=\"false\">@string/test_id</string>\n    <string name=\"gallery_banner_ad_unit_id\" translatable=\"false\">@string/test_id</string>\n    <string name=\"advance_insert_ad_unit_id\" translatable=\"false\">@string/test_id</string>\n</resources>"
  },
  {
    "path": "presentation/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.yalin.style\">\n\n    <uses-feature\n        android:name=\"android.software.live_wallpaper\"\n        android:required=\"true\" />\n    <uses-feature\n        android:glEsVersion=\"0x00020000\"\n        android:required=\"true\" />\n\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n    <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />\n    <uses-permission android:name=\"android.permission.WRITE_SYNC_SETTINGS\" />\n    <uses-permission android:name=\"android.permission.READ_SYNC_SETTINGS\" />\n    <uses-permission android:name=\"android.permission.READ_SYNC_STATS\" />\n    <uses-permission android:name=\"android.permission.AUTHENTICATE_ACCOUNTS\" />\n\n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />\n    <uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\" />\n\n    <uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\" />\n\n    <!--Bugly-->\n    <uses-permission android:name=\"android.permission.READ_LOGS\" />\n\n    <application\n        android:name=\".StyleApplication\"\n        android:allowBackup=\"true\"\n        android:icon=\"@drawable/ic_launcher_round\"\n        android:label=\"@string/app_name_ext\"\n        android:supportsRtl=\"true\">\n        <activity\n            android:name=\".view.activity.StyleActivity\"\n            android:theme=\"@style/Theme.StyleActivity\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n\n            <meta-data\n                android:name=\"android.app.shortcuts\"\n                android:resource=\"@xml/shortcuts\" />\n        </activity>\n\n        <activity\n            android:name=\".view.activity.SettingsActivity\"\n            android:exported=\"true\"\n            android:label=\"@string/settings_title\"\n            android:parentActivityName=\".view.activity.StyleActivity\"\n            android:theme=\"@style/Theme.Style.Settings\" />\n\n        <activity\n            android:name=\".view.activity.AboutActivity\"\n            android:label=\"@string/about_title\"\n            android:parentActivityName=\".view.activity.SettingsActivity\"\n            android:theme=\"@style/Theme.Style.About\" />\n\n        <activity\n            android:name=\".view.activity.GallerySettingActivity\"\n            android:label=\"@string/gallery_title\"\n            android:theme=\"@style/Theme.Style.Gallery\" />\n\n        <activity\n            android:name=\".view.activity.AdvanceSettingActivity\"\n            android:label=\"@string/advance_source_title\"\n            android:theme=\"@style/Theme.Style.Advance\" />\n\n        <service\n            android:name=\".StyleWallpaperService\"\n            android:permission=\"android.permission.BIND_WALLPAPER\">\n            <intent-filter>\n                <action android:name=\"android.service.wallpaper.WallpaperService\" />\n            </intent-filter>\n            <meta-data\n                android:name=\"android.service.wallpaper\"\n                android:resource=\"@xml/wallpaper\" />\n        </service>\n\n        <service\n            android:name=\".StyleWallpaperServiceMirror\"\n            android:permission=\"android.permission.BIND_WALLPAPER\">\n            <intent-filter>\n                <action android:name=\"android.service.wallpaper.WallpaperService\" />\n            </intent-filter>\n            <meta-data\n                android:name=\"android.service.wallpaper\"\n                android:resource=\"@xml/wallpaper\" />\n        </service>\n\n    </application>\n\n</manifest>"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/LockScreenVisibleReceiver.kt",
    "content": "package com.yalin.style\n\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport android.content.Intent\n\n/**\n * YaLin 2016/12/30.\n */\n\nclass LockScreenVisibleReceiver : BroadcastReceiver() {\n\n    override fun onReceive(context: Context, intent: Intent) {\n\n    }\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/StyleApplication.kt",
    "content": "package com.yalin.style\n\n//import com.facebook.stetho.Stetho\n\nimport android.content.Context\nimport android.support.multidex.MultiDex\nimport android.support.multidex.MultiDexApplication\nimport com.tencent.bugly.crashreport.CrashReport\nimport com.yalin.style.analytics.Analytics\nimport com.yalin.style.data.log.LogUtil\nimport com.yalin.style.data.repository.AdvanceWallpaperDataRepository\nimport com.yalin.style.injection.component.ApplicationComponent\nimport com.yalin.style.injection.component.DaggerApplicationComponent\nimport com.yalin.style.injection.modules.ApplicationModule\nimport com.yalin.style.extensions.DelegatesExt\nimport javax.inject.Inject\n\n/**\n * @author jinyalin\n * *\n * @since 2017/4/18.\n */\nclass StyleApplication : MultiDexApplication() {\n\n    companion object {\n        private val TAG = \"StyleApplication\"\n\n        var instance: StyleApplication by DelegatesExt.notNullSingleValue()\n    }\n\n    val applicationComponent: ApplicationComponent by lazy { initializeInjector() }\n\n    @Inject\n    lateinit var advanceWallpaperRepository: AdvanceWallpaperDataRepository\n\n    override fun attachBaseContext(base: Context) {\n        super.attachBaseContext(base)\n        MultiDex.install(this)\n    }\n\n    override fun onCreate() {\n        super.onCreate()\n        instance = this\n\n        applicationComponent.inject(this)\n\n        resetExceptionHandler()\n\n        Analytics.init(this)\n        CrashReport.initCrashReport(applicationContext,\n                BuildConfig.BUGLY_APP_ID, LogUtil.LOG_ENABLE)\n        CrashReport.setAppChannel(applicationContext, com.yalin.style.data.BuildConfig.CHANNEL)\n\n//        if (BuildConfig.DEMO_MODE) {\n//            Stetho.initialize(\n//                    Stetho.newInitializerBuilder(this)\n//                            .enableDumpapp(\n//                                    Stetho.defaultDumperPluginsProvider(this))\n//                            .enableWebKitInspector(\n//                                    Stetho.defaultInspectorModulesProvider(this))\n//                            .build())\n//        }\n    }\n\n    private fun initializeInjector() = DaggerApplicationComponent.builder()\n            .applicationModule(ApplicationModule(this))\n            .build()\n\n\n    private fun resetExceptionHandler() {\n        val exceptionHandler = Thread.getDefaultUncaughtExceptionHandler()\n        Thread.setDefaultUncaughtExceptionHandler { t, e ->\n            advanceWallpaperRepository.markRollback()\n            LogUtil.F(TAG, \"exception\", e)\n            exceptionHandler.uncaughtException(t, e)\n        }\n    }\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/StyleWallpaperService.kt",
    "content": "package com.yalin.style\n\nimport android.app.WallpaperManager\nimport android.content.ActivityNotFoundException\nimport android.content.ComponentName\nimport android.content.Intent\nimport android.service.wallpaper.WallpaperService\nimport com.yalin.style.analytics.Analytics\nimport com.yalin.style.analytics.Event\nimport com.yalin.style.domain.interactor.GetSelectedSource\nimport com.yalin.style.domain.interactor.ObserverSources\nimport com.yalin.style.engine.ProxyProvider\nimport com.yalin.style.engine.WallpaperActiveCallback\nimport com.yalin.style.event.SwitchWallpaperServiceEvent\nimport com.yalin.style.event.WallpaperActivateEvent\n\nimport org.greenrobot.eventbus.EventBus\nimport org.greenrobot.eventbus.Subscribe\nimport org.jetbrains.anko.toast\nimport javax.inject.Inject\n\n\n/**\n * YaLin 2016/12/30.\n */\nopen class StyleWallpaperService : WallpaperService(), WallpaperActiveCallback {\n    @Inject lateinit var proxyProvider: ProxyProvider\n    @Inject lateinit var sourcesObserverUseCase: ObserverSources\n    @Inject lateinit var getSelectedSourceUseCase: GetSelectedSource\n\n    private var proxy: WallpaperService? = null\n\n    private var currentSelectedSource: Int\n\n    init {\n        StyleApplication.instance.applicationComponent.inject(this)\n        currentSelectedSource = getSelectedSourceUseCase.selectedSourceId\n    }\n\n    override fun onCreateEngine(): WallpaperService.Engine {\n        return proxy!!.onCreateEngine()\n    }\n\n    override fun onCreate() {\n        super.onCreate()\n        proxy = proxyProvider.provideProxy(this)\n        proxy?.onCreate()\n\n        EventBus.getDefault().register(this)\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        proxy?.onDestroy()\n\n        EventBus.getDefault().unregister(this)\n    }\n\n\n    override fun onWallpaperActivate() {\n        Analytics.logEvent(this, Event.WALLPAPER_CREATED)\n        EventBus.getDefault().postSticky(WallpaperActivateEvent(true))\n    }\n\n    override fun onWallpaperDeactivate() {\n        Analytics.logEvent(this, Event.WALLPAPER_DESTROYED)\n        EventBus.getDefault().postSticky(WallpaperActivateEvent(false))\n    }\n\n    open fun getWallpaperTargetClass(): Class<*> {\n        return StyleWallpaperServiceMirror::class.java\n    }\n\n    private fun pickWallpaper() {\n        try {\n            startActivity(Intent(WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER)\n                    .putExtra(WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT,\n                            ComponentName(this, getWallpaperTargetClass()))\n                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))\n        } catch (e: ActivityNotFoundException) {\n            try {\n                startActivity(Intent(WallpaperManager.ACTION_LIVE_WALLPAPER_CHOOSER)\n                        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))\n            } catch (e2: ActivityNotFoundException) {\n                toast(R.string.exception_message_device_unsupported)\n                Analytics.logEvent(this, Event.DEVICE_UNSUPPORTED)\n            }\n        }\n    }\n\n    @Subscribe\n    fun onEventMainThread(e: SwitchWallpaperServiceEvent) {\n        pickWallpaper()\n    }\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/StyleWallpaperServiceMirror.kt",
    "content": "package com.yalin.style\n\n/**\n * YaLin 2016/12/30.\n */\nclass StyleWallpaperServiceMirror : StyleWallpaperService() {\n\n    override fun getWallpaperTargetClass(): Class<*> {\n        return StyleWallpaperService::class.java\n    }\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/UIThread.kt",
    "content": "package com.yalin.style\n\nimport com.yalin.style.domain.executor.PostExecutionThread\n\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\nimport io.reactivex.Scheduler\nimport io.reactivex.android.schedulers.AndroidSchedulers\n\n/**\n * @author jinyalin\n * *\n * @since 2017/4/18.\n */\n@Singleton\nclass UIThread @Inject\nconstructor() : PostExecutionThread {\n\n    override fun getScheduler(): Scheduler {\n        return AndroidSchedulers.mainThread()\n    }\n}"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/WallpaperDetailViewport.kt",
    "content": "/*\n * Copyright 2014 Google Inc.\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\npackage com.yalin.style\n\nimport android.graphics.RectF\n\nimport org.greenrobot.eventbus.EventBus\n\n// Singleton that also behaves as an event\nclass WallpaperDetailViewport private constructor() {\n    companion object {\n        val instance = WallpaperDetailViewport()\n    }\n\n    private val mViewport0 = RectF()\n    private val mViewport1 = RectF()\n    var isFromUser: Boolean = false\n        private set\n\n    fun getViewport(id: Int): RectF {\n        return if (id == 0) mViewport0 else mViewport1\n    }\n\n    fun setViewport(id: Int, viewport: RectF, fromUser: Boolean) {\n        setViewport(id, viewport.left, viewport.top, viewport.right, viewport.bottom,\n                fromUser)\n    }\n\n    fun setViewport(id: Int, left: Float, top: Float, right: Float, bottom: Float,\n                    fromUser: Boolean) {\n        isFromUser = fromUser\n        getViewport(id).set(left, top, right, bottom)\n        EventBus.getDefault().post(this)\n    }\n\n    fun setDefaultViewport(id: Int, bitmapAspectRatio: Float,\n                           screenAspectRatio: Float): WallpaperDetailViewport {\n        isFromUser = false\n        if (bitmapAspectRatio > screenAspectRatio) {\n            getViewport(id).set(\n                    0.5f - screenAspectRatio / bitmapAspectRatio / 2f,\n                    0f,\n                    0.5f + screenAspectRatio / bitmapAspectRatio / 2f,\n                    1f)\n        } else {\n            getViewport(id).set(\n                    0f,\n                    0.5f - bitmapAspectRatio / screenAspectRatio / 2f,\n                    1f,\n                    0.5f + bitmapAspectRatio / screenAspectRatio / 2f)\n        }\n        EventBus.getDefault().post(this)\n        return this\n    }\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/analytics/Analytics.kt",
    "content": "package com.yalin.style.analytics\n\nimport android.content.Context\nimport android.os.Bundle\n\nimport com.flurry.android.FlurryAgent\nimport com.google.firebase.analytics.FirebaseAnalytics\nimport com.yalin.style.BuildConfig\nimport com.yalin.style.data.log.LogUtil\n\nimport java.util.HashMap\n\n/**\n * @author jinyalin\n * *\n * @since 2017/5/3.\n */\n\nobject Analytics : IAnalytics {\n    override fun init(context: Context) {\n        FlurryAgent.Builder().withLogEnabled(LogUtil.LOG_ENABLE)\n                .build(context, BuildConfig.FLURRY_API_KEY)\n    }\n\n\n    override fun setUserProperty(context: Context, key: String, value: String) {\n        FirebaseAnalytics.getInstance(context)\n                .setUserProperty(key, value)\n    }\n\n    override fun onStartSession(context: Context) {\n        FlurryAgent.onStartSession(context)\n    }\n\n    override fun onEndSession(context: Context) {\n        FlurryAgent.onEndSession(context)\n    }\n\n    override fun logEvent(context: Context, event: String) {\n        FlurryAgent.logEvent(event)\n\n        FirebaseAnalytics.getInstance(context).logEvent(event, null)\n    }\n\n    override fun logEvent(context: Context, event: String, vararg params: String) {\n        val paramsMap = HashMap<String, String>()\n        paramsMap.put(event, params[0])\n        FlurryAgent.logEvent(event, paramsMap)\n\n        val bundle = Bundle()\n        bundle.putString(FirebaseAnalytics.Param.ITEM_NAME, params[0])\n        FirebaseAnalytics.getInstance(context).logEvent(event, bundle)\n    }\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/analytics/Event.kt",
    "content": "package com.yalin.style.analytics\n\n\n/**\n * @author jinyalin\n * *\n * @since 2017/5/3.\n */\n\nobject Event {\n    val SETTINGS_OPEN = \"settings_open\"\n    val WALLPAPER_CREATED = \"wallpaper_created\"\n    val WALLPAPER_DESTROYED = \"wallpaper_destroyed\"\n    val ACTIVATE = \"activate\"\n    val ABOUT_OPEN = \"about_open\"\n    val LIKE = \"like\"\n    val UN_LIKE = \"unlike\"\n    val SHARE = \"share\"\n    val SWITCH = \"switch\"\n    val TUTORIAL_BEGIN = \"tutorial_begin\"\n    val TUTORIAL_COMPLETE = \"tutorial_complete\"\n    val PRESENT_WALLPAPER = \"present_wallpaper\"\n    val DEVICE_UNSUPPORTED = \"device_unsupported\"\n    val VIEW_WALLPAPER_DETAIL = \"wallpaper_detail\"\n    val VIEW_WALLPAPER_DETAIL_FAILED = \"wallpaper_detail_failed\"\n\n    // custom settings\n    val SELECT_WALLPAPER_SOURCE = \"select_wallpaper_source\"\n    val SELECT_ADVANCE_WALLPAPER_SOURCE = \"select_advance_wallpaper_source\"\n    val CUSTOM_WALLPAPER_SETTINGS = \"custom_wallpaper_settings\"\n    val SETUP_UPDATE_INTERVAL = \"setup_update_interval\"\n    val IMPORT_FROM_GALLERY = \"import_from_gallery\"\n    val ADD_PHOTO_CLICK = \"add_photo_click\"\n    val CLEAR_WALLPAPER = \"clear_wallpaper\"\n\n    // shortcuts\n    val SHORTCUTS_SETTINGS = \"shortcuts_settings\"\n    val SHORTCUTS_ADVANCE_SETTINGS = \"shortcuts_advance_settings\"\n    val SHORTCUTS_ABOUT = \"shortcuts_about\"\n\n    val LOAD_ADVANCES = \"load_advance_wallpapers\"\n    val DOWNLOAD_COMPONENT = \"download_component\"\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/analytics/IAnalytics.kt",
    "content": "package com.yalin.style.analytics\n\nimport android.content.Context\n\n/**\n * @author jinyalin\n * *\n * @since 2017/8/4.\n */\n\ninterface IAnalytics {\n    fun init(context: Context)\n\n    fun setUserProperty(context: Context, key: String, value: String)\n\n    fun onStartSession(context: Context)\n\n    fun onEndSession(context: Context)\n\n    fun logEvent(context: Context, event: String)\n\n    fun logEvent(context: Context, event: String, vararg params: String)\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/engine/StyleWallpaperProxy.kt",
    "content": "package com.yalin.style.engine\n\nimport android.app.KeyguardManager\nimport android.app.WallpaperManager\nimport android.content.*\nimport android.os.Build\nimport android.os.Bundle\nimport android.os.Handler\nimport android.support.v4.os.UserManagerCompat\nimport android.support.v4.view.GestureDetectorCompat\nimport android.view.GestureDetector\nimport android.view.MotionEvent\nimport android.view.SurfaceHolder\nimport android.view.ViewConfiguration\nimport com.yalin.style.StyleApplication\nimport com.yalin.style.WallpaperDetailViewport\nimport com.yalin.style.event.SystemWallpaperSizeChangedEvent\nimport com.yalin.style.event.WallpaperDetailOpenedEvent\nimport com.yalin.style.event.WallpaperSwitchEvent\nimport com.yalin.style.render.RenderController\nimport com.yalin.style.render.StyleBlurRenderer\nimport com.yalin.style.settings.Prefs\nimport org.greenrobot.eventbus.EventBus\nimport org.greenrobot.eventbus.Subscribe\nimport javax.inject.Inject\n\n/**\n * @author jinyalin\n * @since 2017/7/27.\n */\nclass StyleWallpaperProxy(host: Context) : GLWallpaperServiceProxy(host) {\n\n    companion object {\n        private val TEMPORARY_FOCUS_DURATION_MILLIS: Long = 3000\n    }\n\n    private var mInitialized = false\n    private var mUnlockReceiver: BroadcastReceiver? = null\n\n    override fun onCreateEngine(): Engine {\n        return StyleWallpaperEngine()\n    }\n\n    override fun onCreate() {\n        super.onCreate()\n\n        if (UserManagerCompat.isUserUnlocked(this)) {\n            initialize()\n        } else if (Build.VERSION.SDK_INT >= 24) {\n            mUnlockReceiver = object : BroadcastReceiver() {\n                override fun onReceive(context: Context, intent: Intent) {\n                    initialize()\n                    unregisterReceiver(this)\n                }\n            }\n            val filter = IntentFilter(Intent.ACTION_USER_UNLOCKED)\n            registerReceiver(mUnlockReceiver, filter)\n        }\n    }\n\n    private fun initialize() {\n        mInitialized = true\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        if (mInitialized) {\n            //todo\n        } else {\n            unregisterReceiver(mUnlockReceiver)\n        }\n    }\n\n    inner class StyleWallpaperEngine : GLActiveEngine(),\n            StyleBlurRenderer.Callbacks, RenderController.Callbacks {\n\n\n        private var mRenderer: StyleBlurRenderer? = null\n\n        @Inject\n        lateinit var mRenderController: RenderController\n\n        internal val mGestureDetector: GestureDetectorCompat =\n                GestureDetectorCompat(host,\n                        object : GestureDetector.SimpleOnGestureListener() {\n                            override fun onDown(e: MotionEvent): Boolean {\n                                return true\n                            }\n\n                            override fun onDoubleTap(e: MotionEvent): Boolean {\n                                if (mWallpaperDetailMode) {\n                                    return true\n                                }\n\n                                mValidDoubleTap = true\n                                mMainThreadHandler.removeCallbacks(mDoubleTapTimeoutRunnable)\n                                val timeout = ViewConfiguration.getTapTimeout()\n                                mMainThreadHandler.postDelayed(mDoubleTapTimeoutRunnable,\n                                        timeout.toLong())\n                                return true\n                            }\n                        })\n\n        private val mMainThreadHandler = Handler()\n\n        private var mVisible = true\n\n        // is MainActivity visible\n        private var mWallpaperDetailMode = false\n\n        // is last double tab valid\n        private var mValidDoubleTap = false\n\n        private var mIsLockScreenVisibleReceiverRegistered = false\n        private val mLockScreenPreferenceChangeListener =\n                SharedPreferences.OnSharedPreferenceChangeListener { sp, key ->\n                    if (Prefs.PREF_DISABLE_BLUR_WHEN_LOCKED == key) {\n                        if (sp.getBoolean(Prefs.PREF_DISABLE_BLUR_WHEN_LOCKED, false)) {\n                            val intentFilter = IntentFilter()\n                            intentFilter.addAction(Intent.ACTION_USER_PRESENT)\n                            intentFilter.addAction(Intent.ACTION_SCREEN_OFF)\n                            intentFilter.addAction(Intent.ACTION_SCREEN_ON)\n                            host.registerReceiver(mLockScreenVisibleReceiver, intentFilter)\n                            mIsLockScreenVisibleReceiverRegistered = true\n                            // If the user is not yet unlocked (i.e., using Direct Boot), we should\n                            // immediately send the lock screen visible callback\n                            if (!UserManagerCompat.isUserUnlocked(host)) {\n                                lockScreenVisibleChanged(true)\n                            }\n                        } else if (mIsLockScreenVisibleReceiverRegistered) {\n                            host.unregisterReceiver(mLockScreenVisibleReceiver)\n                            mIsLockScreenVisibleReceiverRegistered = false\n                        }\n                    }\n                }\n        private val mLockScreenVisibleReceiver = object : BroadcastReceiver() {\n            override fun onReceive(context: Context, intent: Intent?) {\n                if (intent != null) {\n                    if (Intent.ACTION_USER_PRESENT == intent.action) {\n                        lockScreenVisibleChanged(false)\n                    } else if (Intent.ACTION_SCREEN_OFF == intent.action) {\n                        lockScreenVisibleChanged(true)\n                    } else if (Intent.ACTION_SCREEN_ON == intent.action) {\n                        val kgm = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager\n                        if (!kgm.inKeyguardRestrictedInputMode()) {\n                            lockScreenVisibleChanged(false)\n                        }\n                    }\n                }\n            }\n        }\n\n        override fun onCreate(surfaceHolder: SurfaceHolder?) {\n            super.onCreate(surfaceHolder)\n\n            mRenderer = StyleBlurRenderer(host, this).apply {\n                setIsPreview(isPreview)\n            }\n\n            setEGLContextClientVersion(2)\n            setEGLConfigChooser(8, 8, 8, 0, 0, 0)\n            setRenderer(mRenderer)\n            renderMode = GLEngine.RENDERMODE_WHEN_DIRTY\n            requestRender()\n\n            StyleApplication.instance.applicationComponent.inject(this)\n\n            mRenderController.setComponent(mRenderer!!, this)\n\n            val sp = Prefs.getSharedPreferences(host)\n            sp.registerOnSharedPreferenceChangeListener(mLockScreenPreferenceChangeListener)\n            // Trigger the initial registration if needed\n            mLockScreenPreferenceChangeListener.onSharedPreferenceChanged(sp,\n                    Prefs.PREF_DISABLE_BLUR_WHEN_LOCKED)\n\n            setTouchEventsEnabled(true)\n            setOffsetNotificationsEnabled(true)\n            EventBus.getDefault().register(this)\n        }\n\n        override fun onDestroy() {\n            EventBus.getDefault().unregister(this)\n            if (mIsLockScreenVisibleReceiverRegistered) {\n                host.unregisterReceiver(mLockScreenVisibleReceiver)\n            }\n            Prefs.getSharedPreferences(host)\n                    .unregisterOnSharedPreferenceChangeListener(\n                            mLockScreenPreferenceChangeListener)\n            queueEvent {\n                mRenderer?.destroy()\n            }\n            mRenderController.destroy()\n            super.onDestroy()\n        }\n\n\n        @Subscribe\n        fun onEventMainThread(e: WallpaperDetailOpenedEvent) {\n            if (e.isWallpaperDetailOpened == mWallpaperDetailMode) {\n                return\n            }\n\n            mWallpaperDetailMode = e.isWallpaperDetailOpened\n            cancelDelayedBlur()\n            queueEvent { mRenderer?.setIsBlurred(!e.isWallpaperDetailOpened, true) }\n        }\n\n        @Subscribe\n        fun onEventMainThread(e: WallpaperDetailViewport) {\n            requestRender()\n        }\n\n        @Subscribe\n        fun onEventMainThread(e: WallpaperSwitchEvent) {\n            mRenderController.reloadCurrentWallpaper()\n        }\n\n        override fun onSurfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {\n            super.onSurfaceChanged(holder, format, width, height)\n            if (!isPreview) {\n                EventBus.getDefault().postSticky(\n                        SystemWallpaperSizeChangedEvent(width, height))\n            }\n            mRenderController.reloadCurrentWallpaper()\n        }\n\n        override fun onVisibilityChanged(visible: Boolean) {\n            mVisible = visible\n            mRenderController.setVisible(visible)\n        }\n\n        override fun onOffsetsChanged(xOffset: Float, yOffset: Float, xOffsetStep: Float,\n                                      yOffsetStep: Float, xPixelOffset: Int, yPixelOffset: Int) {\n            super.onOffsetsChanged(xOffset, yOffset, xOffsetStep,\n                    yOffsetStep, xPixelOffset, yPixelOffset)\n            mRenderer?.setNormalOffsetX(xOffset)\n        }\n\n        override fun onTouchEvent(event: MotionEvent) {\n            super.onTouchEvent(event)\n            mGestureDetector.onTouchEvent(event)\n            delayBlur()\n        }\n\n        override fun onCommand(action: String, x: Int, y: Int, z: Int, extras: Bundle?,\n                               resultRequested: Boolean): Bundle? {\n            if (WallpaperManager.COMMAND_TAP == action && mValidDoubleTap) {\n                queueEvent {\n                    mRenderer?.setIsBlurred(!mRenderer!!.isBlurred, false)\n                    delayBlur()\n                }\n                mValidDoubleTap = false\n            }\n            return super.onCommand(action, x, y, z, extras, resultRequested)\n        }\n\n        override fun queueEventOnGlThread(runnable: Runnable) {\n            queueEvent(runnable)\n        }\n\n        override fun requestRender() {\n            if (mVisible) {\n                super.requestRender()\n            }\n        }\n\n        private fun lockScreenVisibleChanged(isLockScreenVisible: Boolean) {\n            cancelDelayedBlur()\n            queueEvent { mRenderer?.setIsBlurred(!isLockScreenVisible, false) }\n        }\n\n        private fun cancelDelayedBlur() {\n            mMainThreadHandler.removeCallbacks(mBlurRunnable)\n        }\n\n        private fun delayBlur() {\n            if (mWallpaperDetailMode || mRenderer!!.isBlurred) {\n                return\n            }\n            cancelDelayedBlur()\n            mMainThreadHandler.postDelayed(mBlurRunnable, TEMPORARY_FOCUS_DURATION_MILLIS)\n        }\n\n        private val mBlurRunnable = Runnable {\n            queueEvent {\n                mRenderer?.setIsBlurred(true, false)\n            }\n        }\n\n        private val mDoubleTapTimeoutRunnable = Runnable { mValidDoubleTap = false }\n    }\n}"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/event/MainContainerInsetsChangedEvent.java",
    "content": "package com.yalin.style.event;\n\nimport android.graphics.Rect;\n\n/**\n * @author jinyalin\n * @since 2017/4/25.\n */\n\npublic class MainContainerInsetsChangedEvent {\n    private Rect insets;\n\n    public MainContainerInsetsChangedEvent(Rect insets) {\n        this.insets = insets;\n    }\n\n    public Rect getInsets() {\n        return insets;\n    }\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/event/SeenTutorialEvent.java",
    "content": "package com.yalin.style.event;\n\n/**\n * @author jinyalin\n * @since 2017/5/2.\n */\n\npublic class SeenTutorialEvent {\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/event/StyleWallpaperSizeChangedEvent.java",
    "content": "package com.yalin.style.event;\n\n/**\n * @author jinyalin\n * @since 2017/4/25.\n */\n\npublic class StyleWallpaperSizeChangedEvent {\n    private int mWidth;\n    private int mHeight;\n\n    public StyleWallpaperSizeChangedEvent(int width, int height) {\n        mWidth = width;\n        mHeight = height;\n    }\n\n    public int getWidth() {\n        return mWidth;\n    }\n\n    public int getHeight() {\n        return mHeight;\n    }\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/event/SwitchWallpaperServiceEvent.java",
    "content": "package com.yalin.style.event;\n\n/**\n * @author jinyalin\n * @since 2017/7/31.\n */\n\npublic class SwitchWallpaperServiceEvent {\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/event/SwitchingPhotosStateChangedEvent.java",
    "content": "package com.yalin.style.event;\n\n/**\n * @author jinyalin\n * @since 2017/4/25.\n */\n\npublic class SwitchingPhotosStateChangedEvent {\n    private boolean mSwitchingPhotos;\n    private int mId;\n\n    public SwitchingPhotosStateChangedEvent(int id, boolean switchingPhotos) {\n        mId = id;\n        mSwitchingPhotos = switchingPhotos;\n    }\n\n    public int getCurrentId() {\n        return mId;\n    }\n\n    public boolean isSwitchingPhotos() {\n        return mSwitchingPhotos;\n    }\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/event/SystemWallpaperSizeChangedEvent.java",
    "content": "package com.yalin.style.event;\n\n/**\n * @author jinyalin\n * @since 2017/4/25.\n */\n\npublic class SystemWallpaperSizeChangedEvent {\n    private int mWidth;\n    private int mHeight;\n\n    public SystemWallpaperSizeChangedEvent(int width, int height) {\n        mWidth = width;\n        mHeight = height;\n    }\n\n    public int getWidth() {\n        return mWidth;\n    }\n\n    public int getHeight() {\n        return mHeight;\n    }\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/event/WallpaperActivateEvent.java",
    "content": "package com.yalin.style.event;\n\n/**\n * @author jinyalin\n * @since 2017/4/21.\n */\n\npublic class WallpaperActivateEvent {\n    private final boolean wallpaperActivate;\n\n    public WallpaperActivateEvent(boolean wallpaperActivate) {\n        this.wallpaperActivate = wallpaperActivate;\n    }\n\n    public boolean isWallpaperActivate() {\n        return wallpaperActivate;\n    }\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/event/WallpaperDetailOpenedEvent.java",
    "content": "package com.yalin.style.event;\n\n/**\n * @author jinyalin\n * @since 2017/4/21.\n */\n\npublic class WallpaperDetailOpenedEvent {\n    private final boolean wallpaperDetailOpened;\n\n    public WallpaperDetailOpenedEvent(boolean wallpaperDetailOpened) {\n        this.wallpaperDetailOpened = wallpaperDetailOpened;\n    }\n\n    public boolean isWallpaperDetailOpened() {\n        return wallpaperDetailOpened;\n    }\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/event/WallpaperSwitchEvent.java",
    "content": "package com.yalin.style.event;\n\n/**\n * @author jinyalin\n * @since 2017/4/28.\n */\n\npublic class WallpaperSwitchEvent {\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/exception/ErrorMessageFactory.java",
    "content": "package com.yalin.style.exception;\n\nimport android.content.Context;\n\nimport com.yalin.style.R;\nimport com.yalin.style.data.exception.NetworkConnectionException;\nimport com.yalin.style.data.exception.RemoteServerException;\nimport com.yalin.style.data.exception.ReswitchException;\n\nimport java.net.SocketTimeoutException;\n\n/**\n * @author jinyalin\n * @since 2017/4/29.\n */\n\npublic class ErrorMessageFactory {\n    private ErrorMessageFactory() {\n        //empty\n    }\n\n    /**\n     * Creates a String representing an error message.\n     *\n     * @param context   Context needed to retrieve string resources.\n     * @param exception An exception used as a condition to retrieve the correct error message.\n     * @return {@link String} an error message.\n     */\n    public static String create(Context context, Exception exception) {\n        String message = context.getString(R.string.exception_message_generic);\n\n        if (exception instanceof NetworkConnectionException) {\n            message = context.getString(R.string.exception_message_no_connection);\n        } else if (exception instanceof ReswitchException) {\n            message = context.getString(R.string.exception_message_resync);\n        } else if (exception instanceof SocketTimeoutException) {\n            message = context.getString(R.string.exception_message_remote_service);\n        } else if (exception instanceof RemoteServerException) {\n            message = context.getString(R.string.exception_message_remote_service);\n        }\n        return message;\n    }\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/extensions/DelegatesExtensions.kt",
    "content": "package com.yalin.style.extensions\n\nimport kotlin.properties.ReadWriteProperty\nimport kotlin.reflect.KProperty\n\n/**\n * @author jinyalin\n * @since 2017/5/8.\n */\nobject DelegatesExt {\n    fun <T> notNullSingleValue():\n            ReadWriteProperty<Any?, T> = NotNullSingleValueVar()\n}\n\nprivate class NotNullSingleValueVar<T> : ReadWriteProperty<Any?, T> {\n    private var value: T? = null\n    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {\n        this.value = if (this.value == null) value\n        else throw IllegalStateException(\"${property.name} already initialized\")\n    }\n\n    override fun getValue(thisRef: Any?, property: KProperty<*>): T {\n        return value ?: throw IllegalStateException(\"${property.name} \" +\n                \"not initialized\")\n    }\n}"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/injection/HasComponent.kt",
    "content": "package com.yalin.style.injection\n\n/**\n * @author jinyalin\n * *\n * @since 2017/4/20.\n */\n\ninterface HasComponent<C> {\n    val component: C\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/injection/PerActivity.kt",
    "content": "package com.yalin.style.injection\n\n\nimport javax.inject.Scope\n\n/**\n * @author jinyalin\n * *\n * @since 2017/4/20.\n */\n@Scope\n@Retention(AnnotationRetention.RUNTIME)\nannotation class PerActivity\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/injection/component/ApplicationComponent.kt",
    "content": "package com.yalin.style.injection.component\n\nimport android.content.Context\nimport com.yalin.style.StyleApplication\nimport com.yalin.style.StyleWallpaperService\nimport com.yalin.style.StyleWallpaperServiceMirror\n\nimport com.yalin.style.domain.executor.PostExecutionThread\nimport com.yalin.style.domain.executor.SerialThreadExecutor\nimport com.yalin.style.domain.executor.ThreadExecutor\nimport com.yalin.style.domain.observable.SourcesObservable\nimport com.yalin.style.domain.repository.SourcesRepository\nimport com.yalin.style.domain.observable.WallpaperObservable\nimport com.yalin.style.domain.repository.WallpaperRepository\nimport com.yalin.style.engine.StyleWallpaperProxy\nimport com.yalin.style.injection.modules.ApplicationModule\nimport com.yalin.style.view.activity.AdvanceSettingActivity\nimport com.yalin.style.view.activity.GallerySettingActivity\nimport com.yalin.style.view.activity.StyleActivity\nimport com.yalin.style.view.fragment.StyleRenderFragment\n\nimport javax.inject.Singleton\n\nimport dagger.Component\n\n/**\n * @author jinyalin\n * *\n * @since 2017/4/18.\n */\n@Singleton\n@Component(modules = arrayOf(ApplicationModule::class))\ninterface ApplicationComponent {\n    fun inject(styleApplication: StyleApplication)\n    fun inject(styleActivity: StyleActivity)\n\n    fun inject(styleStyleWallpaperEngine: StyleWallpaperProxy.StyleWallpaperEngine)\n\n    fun inject(styleView: StyleRenderFragment.StyleView)\n\n    fun inject(gallerySettingActivity: GallerySettingActivity)\n    fun inject(advanceSettingActivity: AdvanceSettingActivity)\n\n    fun inject(styleWallpaperService: StyleWallpaperService)\n    fun inject(styleWallpaperService: StyleWallpaperServiceMirror)\n\n    //Exposed to sub-graphs.\n    fun context(): Context\n\n    fun threadExecutor(): ThreadExecutor\n    fun serialThreadExecutor(): SerialThreadExecutor\n    fun postExecutionThread(): PostExecutionThread\n    fun wallpaperRepository(): WallpaperRepository\n    fun sourcesRepository(): SourcesRepository\n    fun wallpaperObservable(): WallpaperObservable\n    fun sourceObservable(): SourcesObservable\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/injection/component/SourceComponent.kt",
    "content": "package com.yalin.style.injection.component\n\nimport com.yalin.style.injection.PerActivity\nimport com.yalin.style.injection.modules.SourceModule\nimport com.yalin.style.view.fragment.SettingsChooseSourceFragment\n\nimport dagger.Component\n\n/**\n * @author jinyalin\n * *\n * @since 2017/4/18.\n */\n@PerActivity\n@Component(dependencies = arrayOf(ApplicationComponent::class),\n        modules = arrayOf(SourceModule::class))\ninterface SourceComponent {\n    fun inject(settingsChooseSourceFragment: SettingsChooseSourceFragment)\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/injection/component/WallpaperComponent.kt",
    "content": "package com.yalin.style.injection.component\n\nimport com.yalin.style.injection.PerActivity\nimport com.yalin.style.injection.modules.WallpaperModule\nimport com.yalin.style.view.fragment.WallpaperDetailFragment\n\nimport javax.inject.Singleton\n\nimport dagger.Component\n\n/**\n * @author jinyalin\n * *\n * @since 2017/4/18.\n */\n@PerActivity\n@Component(dependencies = arrayOf(ApplicationComponent::class),\n        modules = arrayOf(WallpaperModule::class))\ninterface WallpaperComponent {\n    fun inject(wallpaperDetailFragment: WallpaperDetailFragment)\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/injection/modules/ApplicationModule.kt",
    "content": "package com.yalin.style.injection.modules\n\nimport android.content.Context\n\nimport com.yalin.style.UIThread\nimport com.yalin.style.data.cache.*\nimport com.yalin.style.data.executor.JobExecutor\nimport com.yalin.style.data.executor.SerialJobExecutor\nimport com.yalin.style.data.observable.SourcesObservableImpl\nimport com.yalin.style.data.repository.SourcesDataRepository\nimport com.yalin.style.data.repository.StyleWallpaperDataRepository\nimport com.yalin.style.data.observable.WallpaperObservableImpl\nimport com.yalin.style.domain.executor.PostExecutionThread\nimport com.yalin.style.domain.executor.SerialThreadExecutor\nimport com.yalin.style.domain.executor.ThreadExecutor\nimport com.yalin.style.domain.observable.SourcesObservable\nimport com.yalin.style.domain.repository.SourcesRepository\nimport com.yalin.style.domain.observable.WallpaperObservable\nimport com.yalin.style.domain.repository.WallpaperRepository\n\nimport javax.inject.Singleton\n\nimport dagger.Module\nimport dagger.Provides\n\n/**\n * @author jinyalin\n * *\n * @since 2017/4/18.\n */\n@Module\nclass ApplicationModule(context: Context) {\n    private val applicationContext: Context = context.applicationContext\n\n    @Provides\n    @Singleton\n    internal fun provideApplicationContext(): Context {\n        return applicationContext\n    }\n\n    @Provides\n    @Singleton\n    internal fun provideThreadExecutor(jobExecutor: JobExecutor): ThreadExecutor {\n        return jobExecutor\n    }\n\n    @Provides\n    @Singleton\n    internal fun provideSerialThreadExecutor(jobExecutor: SerialJobExecutor): SerialThreadExecutor {\n        return jobExecutor\n    }\n\n    @Provides\n    @Singleton\n    internal fun providePostExecutionThread(uiThread: UIThread): PostExecutionThread {\n        return uiThread\n    }\n\n    @Provides\n    @Singleton\n    internal fun provideWallpaperRepository(styleWallpaperDataRepository: StyleWallpaperDataRepository):\n            WallpaperRepository {\n        return styleWallpaperDataRepository\n    }\n\n    @Provides\n    @Singleton\n    internal fun provideSourcesRepository(sourcesDataRepository: SourcesDataRepository):\n            SourcesRepository {\n        return sourcesDataRepository\n    }\n\n    @Provides\n    @Singleton\n    internal fun provideWallpaperCache(wallpaperCache: WallpaperCacheImpl): WallpaperCache {\n        return wallpaperCache\n    }\n\n    @Provides\n    @Singleton\n    internal fun provideSourceCache(sourceCache: SourcesCacheImpl): SourcesCache {\n        return sourceCache\n    }\n\n    @Provides\n    @Singleton\n    internal fun provideAdvanceWallpaperCache(cache: AdvanceWallpaperCacheImpl):\n            AdvanceWallpaperCache {\n        return cache\n    }\n\n    @Provides\n    @Singleton\n    internal fun provideWallpaperObservable(observable: WallpaperObservableImpl):\n            WallpaperObservable {\n        return observable\n    }\n\n    @Provides\n    @Singleton\n    internal fun provideSourceObservable(observable: SourcesObservableImpl):\n            SourcesObservable {\n        return observable\n    }\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/injection/modules/SourceModule.kt",
    "content": "package com.yalin.style.injection.modules\n\nimport dagger.Module\n\n/**\n * @author jinyalin\n * *\n * @since 2017/4/18.\n */\n@Module\nclass SourceModule\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/injection/modules/WallpaperModule.kt",
    "content": "package com.yalin.style.injection.modules\n\n\nimport dagger.Module\n\n/**\n * @author jinyalin\n * *\n * @since 2017/4/18.\n */\n@Module\nclass WallpaperModule\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/mapper/AdvanceWallpaperItemMapper.kt",
    "content": "package com.yalin.style.mapper\n\nimport com.fernandocejas.arrow.checks.Preconditions\nimport com.yalin.style.domain.AdvanceWallpaper\nimport com.yalin.style.model.AdvanceWallpaperItem\nimport java.util.ArrayList\nimport javax.inject.Inject\n\n/**\n * @author jinyalin\n * @since 2017/7/28.\n */\nclass AdvanceWallpaperItemMapper @Inject constructor() {\n\n    fun transform(wallpaper: AdvanceWallpaper): AdvanceWallpaperItem {\n        Preconditions.checkNotNull(wallpaper, \"Wallpaper can not be null.\")\n        val wallpaperItem = AdvanceWallpaperItem()\n        wallpaperItem.id = wallpaper.id\n        wallpaperItem.wallpaperId = wallpaper.wallpaperId\n        wallpaperItem.link = wallpaper.link\n        wallpaperItem.name = wallpaper.name\n        wallpaperItem.author = wallpaper.author\n        wallpaperItem.iconUrl = wallpaper.iconUrl\n        wallpaperItem.downloadUrl = wallpaper.downloadUrl\n        wallpaperItem.providerName = wallpaper.providerName\n        wallpaperItem.storePath = wallpaper.storePath\n        wallpaperItem.isSelected = wallpaper.isSelected\n        wallpaperItem.lazyDownload = wallpaper.lazyDownload\n        wallpaperItem.needAd = wallpaper.needAd\n        return wallpaperItem\n    }\n\n    fun transformList(wallpaperEntities: List<AdvanceWallpaper>): List<AdvanceWallpaperItem> {\n        Preconditions.checkNotNull(wallpaperEntities, \"SourceEntity can not be null.\")\n        val sources = ArrayList<AdvanceWallpaperItem>()\n        for (entity in wallpaperEntities) {\n            sources.add(transform(entity))\n        }\n        return sources\n    }\n}"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/mapper/WallpaperItemMapper.kt",
    "content": "package com.yalin.style.mapper\n\nimport com.fernandocejas.arrow.checks.Preconditions\nimport com.yalin.style.domain.GalleryWallpaper\nimport com.yalin.style.domain.Source\nimport com.yalin.style.domain.Wallpaper\nimport com.yalin.style.model.GalleryWallpaperItem\nimport com.yalin.style.model.SourceItem\nimport com.yalin.style.model.WallpaperItem\n\nimport javax.inject.Inject\nimport kotlin.collections.ArrayList\n\n/**\n * @author jinyalin\n * *\n * @since 2017/4/18.\n */\nclass WallpaperItemMapper @Inject\nconstructor() {\n\n    fun transform(wallpaper: Wallpaper): WallpaperItem {\n        Preconditions.checkNotNull(wallpaper, \"Wallpaper can not be null.\")\n        val wallpaperItem = WallpaperItem()\n        wallpaperItem.title = wallpaper.title\n        wallpaperItem.attribution = wallpaper.attribution\n        wallpaperItem.byline = wallpaper.byline\n        wallpaperItem.imageUri = wallpaper.imageUri\n        wallpaperItem.wallpaperId = wallpaper.wallpaperId\n        wallpaperItem.liked = wallpaper.liked\n        wallpaperItem.isDefault = wallpaper.isDefault\n        wallpaperItem.canLike = wallpaper.canLike\n        return wallpaperItem\n    }\n\n    fun transformSources(sourceEntities: List<Source>): List<SourceItem> {\n        Preconditions.checkNotNull(sourceEntities, \"SourceEntity can not be null.\")\n        val sources = ArrayList<SourceItem>()\n        for (entity in sourceEntities) {\n            sources.add(transformSource(entity))\n        }\n        return sources\n    }\n\n    fun transformSource(source: Source): SourceItem {\n        Preconditions.checkNotNull(source, \"SourceEntity can not be null.\")\n        val sourceItem = SourceItem()\n        sourceItem.id = source.id\n        sourceItem.title = source.title\n        sourceItem.description = source.description\n        sourceItem.iconId = source.iconId\n        sourceItem.selected = source.selected\n        sourceItem.hasSetting = source.hasSetting\n        sourceItem.color = source.color\n        return sourceItem\n    }\n\n    fun transformGalleryWallpaper(\n            galleryWallpapers: List<GalleryWallpaper>): List<GalleryWallpaperItem> {\n        Preconditions.checkNotNull(galleryWallpapers,\n                \"GalleryWallpaperEntity can not be null.\")\n        val items = ArrayList<GalleryWallpaperItem>()\n        for (wallpaper in galleryWallpapers) {\n            items.add(transformGalleryWallpaper(wallpaper))\n        }\n        return items\n    }\n\n    fun transformGalleryWallpaper(\n            galleryWallpaper: GalleryWallpaper): GalleryWallpaperItem {\n        Preconditions.checkNotNull(galleryWallpaper,\n                \"GalleryWallpaperEntity can not be null.\")\n        val galleryWallpaperItem = GalleryWallpaperItem()\n        galleryWallpaperItem.id = galleryWallpaper.id\n        galleryWallpaperItem.isTreeUri = galleryWallpaper.isTreeUri\n        galleryWallpaperItem.uri = galleryWallpaper.uri\n        galleryWallpaperItem.dateTime = galleryWallpaper.dateTime\n        galleryWallpaperItem.location = galleryWallpaper.location\n        galleryWallpaperItem.hasMetadata = galleryWallpaper.hasMetadata\n        return galleryWallpaperItem\n    }\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/model/AdvanceWallpaperItem.java",
    "content": "package com.yalin.style.model;\n\nimport android.os.Parcel;\nimport android.os.Parcelable;\n\n/**\n * @author jinyalin\n * @since 2017/7/28.\n */\n\npublic class AdvanceWallpaperItem implements Parcelable {\n    public long id;\n    public String wallpaperId;\n    public String link;\n    public String name;\n    public String author;\n    public String iconUrl;\n    public String downloadUrl;\n\n    public boolean lazyDownload;\n    public boolean needAd;\n\n    public String providerName;\n\n    public String storePath;\n\n    public boolean isSelected;\n\n    public AdvanceWallpaperItem() {\n    }\n\n    protected AdvanceWallpaperItem(Parcel in) {\n        id = in.readLong();\n        wallpaperId = in.readString();\n        link = in.readString();\n        name = in.readString();\n        author = in.readString();\n        iconUrl = in.readString();\n        downloadUrl = in.readString();\n        lazyDownload = in.readByte() != 0;\n        needAd = in.readByte() != 0;\n        providerName = in.readString();\n        storePath = in.readString();\n        isSelected = in.readByte() != 0;\n    }\n\n    public static final Creator<AdvanceWallpaperItem> CREATOR = new Creator<AdvanceWallpaperItem>() {\n        @Override\n        public AdvanceWallpaperItem createFromParcel(Parcel in) {\n            return new AdvanceWallpaperItem(in);\n        }\n\n        @Override\n        public AdvanceWallpaperItem[] newArray(int size) {\n            return new AdvanceWallpaperItem[size];\n        }\n    };\n\n    @Override\n    public int describeContents() {\n        return 0;\n    }\n\n    @Override\n    public void writeToParcel(Parcel dest, int flags) {\n        dest.writeLong(id);\n        dest.writeString(wallpaperId);\n        dest.writeString(link);\n        dest.writeString(name);\n        dest.writeString(author);\n        dest.writeString(iconUrl);\n        dest.writeString(downloadUrl);\n        dest.writeByte((byte) (lazyDownload ? 1 : 0));\n        dest.writeByte((byte) (needAd ? 1 : 0));\n        dest.writeString(providerName);\n        dest.writeString(storePath);\n        dest.writeByte((byte) (isSelected ? 1 : 0));\n    }\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/model/GalleryWallpaperItem.java",
    "content": "package com.yalin.style.model;\n\nimport android.os.Parcel;\nimport android.os.Parcelable;\n\n/**\n * @author jinyalin\n * @since 2017/5/25.\n */\n\npublic class GalleryWallpaperItem implements Parcelable {\n    public long id;\n    public String uri;\n    public boolean isTreeUri;\n    public long dateTime;\n    public String location;\n    public boolean hasMetadata;\n\n    public GalleryWallpaperItem() {\n\n    }\n\n    protected GalleryWallpaperItem(Parcel in) {\n        id = in.readLong();\n        uri = in.readString();\n        isTreeUri = in.readByte() != 0;\n        dateTime = in.readLong();\n        location = in.readString();\n        hasMetadata = in.readByte() != 0;\n    }\n\n    @Override\n    public void writeToParcel(Parcel dest, int flags) {\n        dest.writeLong(id);\n        dest.writeString(uri);\n        dest.writeByte((byte) (isTreeUri ? 1 : 0));\n        dest.writeLong(dateTime);\n        dest.writeString(location);\n        dest.writeByte((byte) (hasMetadata ? 1 : 0));\n    }\n\n    @Override\n    public int describeContents() {\n        return 0;\n    }\n\n    public static final Creator<GalleryWallpaperItem> CREATOR = new Creator<GalleryWallpaperItem>() {\n        @Override\n        public GalleryWallpaperItem createFromParcel(Parcel in) {\n            return new GalleryWallpaperItem(in);\n        }\n\n        @Override\n        public GalleryWallpaperItem[] newArray(int size) {\n            return new GalleryWallpaperItem[size];\n        }\n    };\n\n    @Override\n    public boolean equals(Object obj) {\n        if (obj instanceof GalleryWallpaperItem) {\n            GalleryWallpaperItem objItem = (GalleryWallpaperItem) obj;\n            return objItem.id == id;\n        }\n        return false;\n    }\n\n    @Override\n    public int hashCode() {\n        int result = (int) id;\n        result = 31 * result + uri.hashCode();\n        return result;\n    }\n\n\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/model/SourceItem.kt",
    "content": "package com.yalin.style.model\n\nimport com.yalin.style.domain.repository.SourcesRepository\n\n/**\n * @author jinyalin\n * @since 2017/5/22.\n */\nclass SourceItem {\n    var id: Int = 0\n    var title: String? = null\n    var description: String? = null\n    var iconId: Int = 0\n    var selected: Boolean = false\n    var hasSetting: Boolean = false\n    var color: Int = 0\n\n    val needPermission: Boolean\n        get() = id == SourcesRepository.SOURCE_ID_CUSTOM\n}"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/model/WallpaperItem.java",
    "content": "package com.yalin.style.model;\n\nimport android.os.Parcel;\nimport android.os.Parcelable;\n\n/**\n * YaLin 2017/1/3.\n */\n\npublic class WallpaperItem implements Parcelable {\n\n    public String wallpaperId;\n    public String imageUri;\n    public String title;\n    public String byline;\n    public String attribution;\n\n    public boolean canLike;\n    public boolean liked;\n    public boolean isDefault;\n\n    public WallpaperItem() {\n    }\n\n    protected WallpaperItem(Parcel in) {\n        wallpaperId = in.readString();\n        imageUri = in.readString();\n        title = in.readString();\n        byline = in.readString();\n        attribution = in.readString();\n        liked = in.readByte() != 0;\n        isDefault = in.readByte() != 0;\n    }\n\n    public static final Creator<WallpaperItem> CREATOR = new Creator<WallpaperItem>() {\n        @Override\n        public WallpaperItem createFromParcel(Parcel in) {\n            return new WallpaperItem(in);\n        }\n\n        @Override\n        public WallpaperItem[] newArray(int size) {\n            return new WallpaperItem[size];\n        }\n    };\n\n    @Override\n    public int describeContents() {\n        return 0;\n    }\n\n    @Override\n    public void writeToParcel(Parcel dest, int flags) {\n        dest.writeString(wallpaperId);\n        dest.writeString(imageUri);\n        dest.writeString(title);\n        dest.writeString(byline);\n        dest.writeString(attribution);\n        dest.writeByte((byte) (liked ? 1 : 0));\n        dest.writeByte((byte) (isDefault ? 1 : 0));\n    }\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/presenter/AdvanceSettingPresenter.kt",
    "content": "package com.yalin.style.presenter\n\nimport android.os.Bundle\nimport android.text.TextUtils\nimport com.yalin.style.data.log.LogUtil\nimport com.yalin.style.data.utils.WallpaperFileHelper\nimport com.yalin.style.domain.AdvanceWallpaper\nimport com.yalin.style.domain.interactor.*\nimport com.yalin.style.event.SwitchWallpaperServiceEvent\nimport com.yalin.style.event.WallpaperActivateEvent\nimport com.yalin.style.exception.ErrorMessageFactory\nimport com.yalin.style.mapper.AdvanceWallpaperItemMapper\nimport com.yalin.style.model.AdvanceWallpaperItem\nimport com.yalin.style.view.AdvanceSettingView\nimport org.greenrobot.eventbus.EventBus\nimport org.greenrobot.eventbus.Subscribe\nimport javax.inject.Inject\n\n/**\n * @author jinyalin\n * @since 2017/7/28.\n */\nclass AdvanceSettingPresenter\n@Inject constructor(val getAdvanceWallpapers: GetAdvanceWallpapers,\n                    val loadAdvanceWallpaper: LoadAdvanceWallpaper,\n                    val selectAdvanceWallpaper: SelectAdvanceWallpaper,\n                    val getSelectedAdvanceWallpaper: GetSelectedAdvanceWallpaper,\n                    val advanceWallpaperItemMapper: AdvanceWallpaperItemMapper,\n                    val downloadAdvanceWallpaper: DownloadAdvanceWallpaper,\n                    val readAdvanceAd: ReadAdvanceAd)\n    : Presenter {\n\n    companion object {\n        val DOWNLOAD_STATE = \"download_state\"\n        val DOWNLOADING_ITEM = \"download_item\"\n\n        val DOWNLOAD_NONE = 0\n        val DOWNLOADING = 1\n        val DOWNLOAD_ERROR = 2\n    }\n\n    private val wallpaperObserver = WallpapersObserver()\n    private var view: AdvanceSettingView? = null\n\n    private var mLastSelectedItemId: String? = null\n\n    private var selecting = false\n\n    private var downloadingWallpaper: AdvanceWallpaperItem? = null\n\n    private var downloadState = DOWNLOAD_NONE\n\n    fun setView(view: AdvanceSettingView) {\n        this.view = view\n    }\n\n    fun initialize() {\n        view?.showLoading()\n        getAdvanceWallpapers.execute(wallpaperObserver, null)\n        mLastSelectedItemId = getSelectedAdvanceWallpaper.selected.wallpaperId\n        EventBus.getDefault().register(this)\n    }\n\n    fun loadAdvanceWallpaper() {\n        view?.showLoading()\n        loadAdvanceWallpaper.execute(object : DefaultObserver<List<AdvanceWallpaper>>() {\n            override fun onNext(needDownload: List<AdvanceWallpaper>) {\n                view?.renderWallpapers(advanceWallpaperItemMapper.transformList(needDownload))\n            }\n\n            override fun onComplete() {\n\n            }\n\n            override fun onError(exception: Throwable) {\n                view?.showError(\n                        ErrorMessageFactory.create(view!!.context(), exception as Exception))\n                view?.showRetry()\n            }\n        }, null)\n    }\n\n    fun selectAdvanceWallpaper(item: AdvanceWallpaperItem) {\n        if (WallpaperFileHelper.isNeedDownloadAdvanceComponent(item.lazyDownload,\n                item.storePath) || (downloadingWallpaper != null\n                && TextUtils.equals(downloadingWallpaper!!.wallpaperId, item.wallpaperId))) {\n            view?.showDownloadHintDialog(item)\n        } else if (item.needAd) {\n            view?.showAd(item)\n        } else {\n            selectAdvanceWallpaper(item.wallpaperId, false)\n        }\n    }\n\n    fun requestDownload(item: AdvanceWallpaperItem) {\n        view?.showDownloadingDialog(item)\n        downloadingWallpaper = item\n        downloadState = DOWNLOADING\n        downloadAdvanceWallpaper.execute(object : DefaultObserver<Long>() {\n            override fun onNext(progress: Long) {\n                view?.updateDownloadingProgress(progress)\n            }\n\n            override fun onComplete() {\n                view?.downloadComplete(item)\n                downloadingWallpaper = null\n                downloadState = DOWNLOAD_NONE\n            }\n\n            override fun onError(exception: Throwable) {\n                view?.showDownloadError(item, exception as Exception)\n                downloadingWallpaper = null\n                downloadState = DOWNLOAD_ERROR\n            }\n        }, DownloadAdvanceWallpaper.Params.download(item.wallpaperId))\n    }\n\n    fun adViewed(item: AdvanceWallpaperItem) {\n        readAdvanceAd.execute(object : DefaultObserver<Boolean>() {\n            override fun onNext(success: Boolean) {\n                if (success) {\n                    view?.adViewed(item)\n                }\n            }\n        }, ReadAdvanceAd.Params.read(item.wallpaperId))\n    }\n\n\n    fun onSaveInstanceState(outState: Bundle) {\n        outState.putInt(DOWNLOAD_STATE, downloadState)\n        if (downloadingWallpaper != null) {\n            outState.putParcelable(DOWNLOADING_ITEM, downloadingWallpaper!!)\n        }\n    }\n\n    fun onRestoreInstanceState(savedInstanceState: Bundle) {\n        downloadState = savedInstanceState.getInt(DOWNLOAD_STATE)\n        downloadingWallpaper = savedInstanceState.getParcelable(DOWNLOADING_ITEM)\n\n        if (downloadingWallpaper != null) {\n            if (downloadState == DOWNLOADING) {\n                requestDownload(downloadingWallpaper!!)\n            } else if (downloadState == DOWNLOAD_ERROR) {\n\n            }\n        }\n    }\n\n    fun getDownloadingItem(): AdvanceWallpaperItem? {\n        return downloadingWallpaper\n    }\n\n\n    private fun selectAdvanceWallpaper(wallpaperId: String, rollback: Boolean) {\n        if (!rollback && TextUtils.equals(wallpaperId, mLastSelectedItemId)) {\n            view?.complete()\n            return\n        }\n        selecting = false\n        var tempSelect = false\n        if (!rollback) {\n            selecting = true\n            tempSelect = true\n        }\n        selectAdvanceWallpaper.execute(object : DefaultObserver<Boolean>() {\n            override fun onNext(success: Boolean) {\n                if (!rollback && success) {\n                    EventBus.getDefault().post(SwitchWallpaperServiceEvent())\n                }\n            }\n\n            override fun onComplete() {\n                view?.wallpaperSelected(wallpaperId)\n            }\n        }, SelectAdvanceWallpaper.Params.selectWallpaper(wallpaperId, tempSelect))\n    }\n\n    override fun resume() {\n        maybeResetWallpaper()\n    }\n\n    private fun maybeResetWallpaper() {\n        if (selecting && !TextUtils.isEmpty(mLastSelectedItemId)) {\n            LogUtil.D(SettingsChooseSourcePresenter.TAG,\n                    \"restore wallpaper to $mLastSelectedItemId\")\n            selectAdvanceWallpaper(mLastSelectedItemId!!, true)\n        }\n    }\n\n    override fun pause() {\n\n    }\n\n    override fun destroy() {\n        EventBus.getDefault().unregister(this)\n        getAdvanceWallpapers.dispose()\n        loadAdvanceWallpaper.dispose()\n        selectAdvanceWallpaper.dispose()\n        downloadAdvanceWallpaper.dispose()\n        downloadingWallpaper = null\n        view = null\n    }\n\n    private inner class WallpapersObserver : DefaultObserver<List<AdvanceWallpaper>>() {\n        override fun onNext(needDownload: List<AdvanceWallpaper>) {\n            if (needDownload.isEmpty()) {\n                view?.showEmpty()\n            } else {\n                view?.renderWallpapers(advanceWallpaperItemMapper.transformList(needDownload))\n            }\n        }\n\n        override fun onComplete() {\n        }\n\n        override fun onError(exception: Throwable?) {\n        }\n    }\n\n    @Subscribe\n    fun onEventMainThread(e: WallpaperActivateEvent) {\n        // we cannot known if the user cancel the wallpaper set\n        // so we need listen wallpaper be reactivated\n        if (!e.isWallpaperActivate) {\n            selecting = false\n        } else {\n            mLastSelectedItemId = getSelectedAdvanceWallpaper.selected.wallpaperId\n        }\n    }\n}"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/presenter/GallerySettingPresenter.kt",
    "content": "package com.yalin.style.presenter\n\nimport android.net.Uri\nimport com.yalin.style.domain.GalleryWallpaper\nimport com.yalin.style.domain.interactor.*\nimport com.yalin.style.mapper.WallpaperItemMapper\nimport com.yalin.style.model.GalleryWallpaperItem\nimport com.yalin.style.view.GallerySettingView\nimport javax.inject.Inject\n\n/**\n * @author jinyalin\n * @since 2017/5/25.\n */\nclass GallerySettingPresenter @Inject\nconstructor(val wallpaperItemMapper: WallpaperItemMapper,\n            val addGalleryWallpaperUseCase: AddGalleryWallpaper,\n            val removeGalleryWallpaperUseCase: RemoveGalleryWallpaper,\n            val getGalleryWallpaperUseCase: GetGalleryWallpaper,\n            val forceNowUseCase: ForceNow,\n            val setIntervalUseCase: SetGalleryUpdateInterval,\n            val getIntervalUseCase: GetGalleryUpdateInterval) : Presenter {\n\n    var gallerySettingView: GallerySettingView? = null\n\n    val mWallpapers = ArrayList<GalleryWallpaperItem>()\n\n    fun setView(gallerySettingView: GallerySettingView) {\n        this.gallerySettingView = gallerySettingView\n    }\n\n    fun initialize() {\n        refreshGalleryWallpaper()\n        getUpdateInterval()\n    }\n\n    fun addGalleryWallpaper(uris: Set<Uri>) {\n        val galleryWallpapers = ArrayList<GalleryWallpaper>()\n        for (uri in uris) {\n            val wallpaper = GalleryWallpaper()\n            wallpaper.uri = uri.toString()\n            galleryWallpapers.add(wallpaper)\n        }\n\n        addGalleryWallpaperUseCase.execute(\n                GalleryWallpaperChangedObserver(),\n                AddGalleryWallpaper.Params.addGalleryWallpaperUris(galleryWallpapers))\n    }\n\n    fun removeGalleryWallpaper(items: List<GalleryWallpaperItem>) {\n        val galleryWallpapers = ArrayList<GalleryWallpaper>()\n        for (item in items) {\n            val wallpaper = GalleryWallpaper()\n            wallpaper.uri = item.uri\n            galleryWallpapers.add(wallpaper)\n        }\n\n        removeGalleryWallpaperUseCase.execute(\n                GalleryWallpaperChangedObserver(),\n                RemoveGalleryWallpaper.Params.removeGalleryWallpaperUris(galleryWallpapers))\n    }\n\n    fun forceNow(wallpaperUri: String) {\n        forceNowUseCase.execute(DefaultObserver<Boolean>(), ForceNow.Params.fromUri(wallpaperUri))\n    }\n\n    fun setUpdateInterval(intervalMin: Int) {\n        setIntervalUseCase.execute(DefaultObserver<Boolean>(),\n                SetGalleryUpdateInterval.Params.interval(intervalMin))\n    }\n\n    override fun resume() {\n    }\n\n    override fun pause() {\n    }\n\n    override fun destroy() {\n        addGalleryWallpaperUseCase.dispose()\n        getGalleryWallpaperUseCase.dispose()\n        removeGalleryWallpaperUseCase.dispose()\n        getGalleryWallpaperUseCase.dispose()\n        forceNowUseCase.dispose()\n        setIntervalUseCase.dispose()\n        getIntervalUseCase.dispose()\n        gallerySettingView = null\n    }\n\n\n    private fun refreshGalleryWallpaper() {\n        getGalleryWallpaperUseCase.execute(object : DefaultObserver<List<GalleryWallpaper>>() {\n            override fun onNext(sources: List<GalleryWallpaper>) {\n                val itemSet = wallpaperItemMapper.transformGalleryWallpaper(sources)\n\n                mWallpapers.clear()\n                mWallpapers.addAll(itemSet)\n\n                gallerySettingView?.renderGalleryWallpapers(mWallpapers)\n            }\n        }, null)\n    }\n\n    private fun getUpdateInterval() {\n        getIntervalUseCase.execute(object : DefaultObserver<Int>() {\n            override fun onNext(intervalMin: Int) {\n                gallerySettingView?.renderUpdateInterval(intervalMin)\n            }\n        }, null)\n    }\n\n\n    private inner class GalleryWallpaperChangedObserver : DefaultObserver<Boolean>() {\n        override fun onNext(success: Boolean) {\n            super.onNext(success)\n            if (success) {\n                refreshGalleryWallpaper()\n            }\n        }\n    }\n}"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/presenter/Presenter.kt",
    "content": "package com.yalin.style.presenter\n\n/**\n * Interface representing a Presenter in a model view presenter (MVP) pattern.\n\n * @author jinyalin\n * *\n * @since 2017/4/20.\n */\ninterface Presenter {\n    /**\n     * Method that control the lifecycle of the view. It should be called in the view's\n     * (Activity or Fragment) onResume() method.\n     */\n    fun resume()\n\n    /**\n     * Method that control the lifecycle of the view. It should be called in the view's\n     * (Activity or Fragment) onPause() method.\n     */\n    fun pause()\n\n    /**\n     * Method that control the lifecycle of the view. It should be called in the view's\n     * (Activity or Fragment) onDestroy() method.\n     */\n    fun destroy()\n}"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/presenter/SettingsChooseSourcePresenter.kt",
    "content": "package com.yalin.style.presenter\n\nimport android.os.Bundle\nimport com.yalin.style.data.log.LogUtil\nimport com.yalin.style.domain.Source\nimport com.yalin.style.domain.interactor.DefaultObserver\nimport com.yalin.style.domain.interactor.GetSources\nimport com.yalin.style.domain.interactor.SelectSource\nimport com.yalin.style.domain.repository.SourcesRepository\nimport com.yalin.style.event.SwitchWallpaperServiceEvent\nimport com.yalin.style.event.WallpaperActivateEvent\nimport com.yalin.style.mapper.WallpaperItemMapper\nimport com.yalin.style.model.SourceItem\nimport com.yalin.style.view.SourceChooseView\nimport org.greenrobot.eventbus.EventBus\nimport org.greenrobot.eventbus.Subscribe\nimport java.util.ArrayList\nimport javax.inject.Inject\n\n/**\n * @author jinyalin\n * @since 2017/5/23.\n */\nclass SettingsChooseSourcePresenter @Inject\nconstructor(val getSourcesUseCase: GetSources,\n            val selectSourceUseCae: SelectSource,\n            val wallpaperMapper: WallpaperItemMapper) : Presenter {\n    companion object {\n        val TAG = \"SettingsChooseSource\"\n\n        var LAST_SELECTED_ID = \"last_selected_id\"\n        var SELECTING = \"selecting\"\n    }\n\n    private val mSources = ArrayList<SourceItem>()\n\n    private var mSourceChooseView: SourceChooseView? = null\n\n    private var mSelectedSource: SourceItem? = null\n\n    private var mLastSelectedItemId = -1\n\n    private var selecting = false\n\n    fun setView(sourceChooseView: SourceChooseView) {\n        mSourceChooseView = sourceChooseView\n    }\n\n    fun initialize() {\n        updateSources()\n\n        EventBus.getDefault().register(this)\n    }\n\n    fun selectSource(sourceId: Int) {\n        selectSource(sourceId, false)\n    }\n\n    private fun selectSource(sourceId: Int, force: Boolean) {\n        selecting = false\n        mLastSelectedItemId = mSelectedSource!!.id\n\n        var tempSelect = false\n        if (!force && needSwitchWallpaper(sourceId)) {\n            selecting = true\n            tempSelect = true\n            LogUtil.D(TAG, \"Select source Last Selected $mLastSelectedItemId\")\n        }\n        selectSourceUseCae.executeSerial(object : DefaultObserver<Boolean>() {\n            override fun onNext(success: Boolean) {\n                LogUtil.D(TAG, \"onNext \" + success)\n                if (success) {\n                    for (source in mSources) {\n                        source.selected = source.id == sourceId\n                        if (source.selected) {\n                            mSelectedSource = source\n                        }\n                    }\n                    LogUtil.D(TAG, \"selected wallpaper id ${mSelectedSource!!.id}\")\n                    mSourceChooseView?.sourceSelected(mSources, mSelectedSource!!)\n                    if (!force && needSwitchWallpaper(sourceId)) {\n                        EventBus.getDefault().post(SwitchWallpaperServiceEvent())\n                    }\n                }\n            }\n        }, SelectSource.Params.selectSource(sourceId, tempSelect))\n    }\n\n    private fun needSwitchWallpaper(sourceId: Int): Boolean {\n        return mLastSelectedItemId == SourcesRepository.SOURCE_ID_ADVANCE ||\n                sourceId == SourcesRepository.SOURCE_ID_ADVANCE\n    }\n\n    override fun resume() {\n        maybeResetSource()\n    }\n\n    private fun maybeResetSource() {\n        if (selecting) {\n            LogUtil.D(TAG, \"restore wallpaper to $mLastSelectedItemId\")\n            selectSource(mLastSelectedItemId, true)\n        }\n    }\n\n    override fun pause() {\n    }\n\n    override fun destroy() {\n        EventBus.getDefault().unregister(this)\n        getSourcesUseCase.dispose()\n        selectSourceUseCae.dispose()\n        mSourceChooseView = null\n    }\n\n    fun restoreInstanceState(instanceState: Bundle?) {\n        if (instanceState != null && instanceState.containsKey(LAST_SELECTED_ID)) {\n            mLastSelectedItemId = instanceState.getInt(LAST_SELECTED_ID)\n            selecting = instanceState.getBoolean(SELECTING)\n        }\n    }\n\n    fun saveInstanceState(outState: Bundle) {\n        outState.putInt(LAST_SELECTED_ID, mLastSelectedItemId)\n        outState.putBoolean(SELECTING, selecting)\n    }\n\n    fun updateSources() {\n        getSourcesUseCase.executeSerial(object : DefaultObserver<List<Source>>() {\n            override fun onNext(sources: List<Source>) {\n                super.onNext(sources)\n                mSources.clear()\n                mSources.addAll(wallpaperMapper.transformSources(sources))\n                for (source in mSources) {\n                    if (source.selected) {\n                        mSelectedSource = source\n                        break\n                    }\n                }\n                mSourceChooseView?.renderSources(mSources)\n                mSourceChooseView?.sourceSelected(mSources, mSelectedSource!!)\n            }\n        }, null)\n    }\n\n    @Subscribe\n    fun onEventMainThread(e: WallpaperActivateEvent) {\n        // we cannot known if the user cancel the wallpaper set\n        // so we need listen wallpaper be reactivated\n        if (!e.isWallpaperActivate) {\n            selecting = false\n        }\n    }\n\n}"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/presenter/WallpaperDetailPresenter.kt",
    "content": "package com.yalin.style.presenter\n\nimport android.os.Bundle\n\nimport com.yalin.style.analytics.Analytics\nimport com.yalin.style.analytics.Event\nimport com.yalin.style.data.exception.ReswitchException\nimport com.yalin.style.domain.Wallpaper\nimport com.yalin.style.domain.interactor.*\nimport com.yalin.style.event.WallpaperSwitchEvent\nimport com.yalin.style.exception.ErrorMessageFactory\nimport com.yalin.style.injection.PerActivity\nimport com.yalin.style.mapper.WallpaperItemMapper\nimport com.yalin.style.model.WallpaperItem\nimport com.yalin.style.util.ShareUtil\nimport com.yalin.style.view.WallpaperDetailView\n\nimport org.greenrobot.eventbus.EventBus\n\nimport javax.inject.Inject\n\n/**\n * @author jinyalin\n * *\n * @since 2017/4/20.\n */\n@PerActivity\nclass WallpaperDetailPresenter @Inject\nconstructor(private val getWallpaperUseCase: GetWallpaper,\n            private val observerWallpaper: ObserverWallpaper,\n            private val getWallpaperCountUseCase: GetWallpaperCount,\n            private val switchWallpaperUseCase: SwitchWallpaper,\n            private val likeWallpaperUseCase: LikeWallpaper,\n            private val wallpaperItemMapper: WallpaperItemMapper) : Presenter {\n\n    companion object {\n        private val CURRENT_ITEM = \"current_item\"\n        private val WALLPAPER_COUNT = \"wallpaper_count\"\n    }\n\n    private var currentShowItem: WallpaperItem? = null\n    private var wallpaperCount: Int = 0\n\n    private var wallpaperDetailView: WallpaperDetailView? = null\n\n    private val wallpaperRefreshObserver: WallpaperRefreshObserver\n\n    init {\n        wallpaperRefreshObserver = WallpaperRefreshObserver()\n        observerWallpaper.registerObserver(wallpaperRefreshObserver)\n    }\n\n    fun setView(wallpaperDetailView: WallpaperDetailView) {\n        this.wallpaperDetailView = wallpaperDetailView\n    }\n\n    fun initialize() {\n        getWallpaperUseCase.execute(WallpaperObserver(), null)\n        getWallpaperCountUseCase.execute(WallpaperCountObserver(), null)\n    }\n\n    fun getNextWallpaper() {\n        switchWallpaperUseCase.execute(WallpaperObserver(true), null)\n    }\n\n    fun likeWallpaper() {\n        currentShowItem?.apply {\n            likeWallpaperUseCase.execute(WallpaperLikeObserver(),\n                    LikeWallpaper.Params.likeWallpaper(wallpaperId))\n\n            Analytics.logEvent(wallpaperDetailView!!.context(),\n                    if (!liked) Event.LIKE\n                    else Event.UN_LIKE, title)\n        }\n    }\n\n    fun shareWallpaper() {\n        currentShowItem?.apply {\n            wallpaperDetailView!!.shareWallpaper(\n                    ShareUtil.createShareIntent(wallpaperDetailView!!.context(), this))\n        }\n    }\n\n    fun restoreInstanceState(instanceState: Bundle?) {\n        if (instanceState != null && instanceState.containsKey(CURRENT_ITEM)) {\n            currentShowItem = instanceState.getParcelable<WallpaperItem>(CURRENT_ITEM)\n            wallpaperCount = instanceState.getInt(WALLPAPER_COUNT)\n            showWallpaperDetailInView(currentShowItem!!)\n            showOrHideNextView(wallpaperCount)\n        } else {\n            initialize()\n        }\n    }\n\n    fun saveInstanceState(outState: Bundle) {\n        if (currentShowItem != null) {\n            outState.putParcelable(CURRENT_ITEM, currentShowItem)\n            outState.putInt(WALLPAPER_COUNT, wallpaperCount)\n        }\n    }\n\n    override fun resume() {\n\n    }\n\n    override fun pause() {\n\n    }\n\n    override fun destroy() {\n        getWallpaperUseCase.dispose()\n        getWallpaperCountUseCase.dispose()\n        observerWallpaper.unregisterObserver(wallpaperRefreshObserver)\n        wallpaperDetailView = null\n    }\n\n    private fun showWallpaperDetailInView(wallpaperItem: WallpaperItem) {\n        with(wallpaperDetailView!!) {\n            renderWallpaper(wallpaperItem)\n            validLikeAction(wallpaperItem.canLike)\n            if (wallpaperItem.canLike) {\n                updateLikeState(wallpaperItem, wallpaperItem.liked)\n            }\n        }\n    }\n\n    private fun showOrHideNextView(count: Int) {\n        wallpaperDetailView?.showNextButton(count > 1)\n    }\n\n    private inner class WallpaperObserver @JvmOverloads\n    constructor(private val isSwitch: Boolean = false) : DefaultObserver<Wallpaper>() {\n\n        override fun onNext(wallpaper: Wallpaper) {\n            val wallpaperItem = wallpaperItemMapper.transform(wallpaper)\n            currentShowItem = wallpaperItem\n            showWallpaperDetailInView(wallpaperItem)\n        }\n\n        override fun onComplete() {\n            if (isSwitch) {\n                EventBus.getDefault().post(WallpaperSwitchEvent())\n            }\n        }\n\n        override fun onError(exception: Throwable) {\n            if (exception is ReswitchException) {\n                return\n            }\n            wallpaperDetailView?.\n                    showError(ErrorMessageFactory.create(wallpaperDetailView!!.context(),\n                            exception as Exception))\n        }\n    }\n\n    private inner class WallpaperCountObserver : DefaultObserver<Int>() {\n\n        override fun onNext(count: Int) {\n            wallpaperCount = count\n            showOrHideNextView(count)\n        }\n\n        override fun onComplete() {}\n\n        override fun onError(exception: Throwable) {\n            showOrHideNextView(0)\n        }\n    }\n\n    private inner class WallpaperLikeObserver : DefaultObserver<Boolean>() {\n\n        override fun onNext(liked: Boolean) {\n            wallpaperDetailView!!.updateLikeState(currentShowItem!!, liked)\n        }\n    }\n\n    private inner class WallpaperRefreshObserver : DefaultObserver<Void>() {\n        override fun onComplete() {\n            initialize()\n        }\n    }\n\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/render/BitmapRegionLoader.java",
    "content": "/*\n * Copyright 2014 Google Inc.\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\npackage com.yalin.style.render;\n\nimport static android.graphics.BitmapFactory.Options;\n\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapRegionDecoder;\nimport android.graphics.Matrix;\nimport android.graphics.Rect;\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * Wrapper for {@link BitmapRegionDecoder} with some extra functionality.\n */\npublic class BitmapRegionLoader {\n    private boolean mValid = false;\n    private int mRotation = 0;\n    private int mOriginalWidth;\n    private int mOriginalHeight;\n    private Rect mTempRect = new Rect();\n    private InputStream mInputStream;\n    private volatile BitmapRegionDecoder mBitmapRegionDecoder;\n    private Matrix mRotateMatrix;\n\n    public static BitmapRegionLoader newInstance(InputStream in) throws IOException {\n        return newInstance(in, 0);\n    }\n\n    public static BitmapRegionLoader newInstance(InputStream in, int rotation) throws IOException {\n        if (in == null) {\n            return null;\n        }\n\n        BitmapRegionLoader loader = new BitmapRegionLoader(in);\n        if (loader.mValid) {\n            loader.mRotation = rotation;\n            if (loader.mRotation != 0) {\n                loader.mRotateMatrix = new Matrix();\n                loader.mRotateMatrix.postRotate(rotation);\n            }\n            return loader;\n        }\n\n        return null;\n    }\n\n    private BitmapRegionLoader(InputStream in) throws IOException {\n        mInputStream = in;\n        mBitmapRegionDecoder = BitmapRegionDecoder.newInstance(in, false);\n        if (mBitmapRegionDecoder != null) {\n            mOriginalWidth = mBitmapRegionDecoder.getWidth();\n            mOriginalHeight = mBitmapRegionDecoder.getHeight();\n            if (mOriginalWidth > 0 && mOriginalHeight > 0) {\n                mValid = true;\n            }\n        }\n    }\n\n    /**\n     * Key difference, aside from support for rotation, from\n     * {@link BitmapRegionDecoder#decodeRegion(Rect, Options)} in this implementation is that even\n     * if <code>inBitmap</code> is given, a sub-bitmap might be returned.\n     */\n    public synchronized Bitmap decodeRegion(Rect rect, Options options) {\n        int unsampledInBitmapWidth = -1;\n        int unsampledInBitmapHeight = -1;\n        int sampleSize = Math.max(1, options != null ? options.inSampleSize : 1);\n        if (options != null && options.inBitmap != null) {\n            unsampledInBitmapWidth = options.inBitmap.getWidth() * sampleSize;\n            unsampledInBitmapHeight = options.inBitmap.getHeight() * sampleSize;\n        }\n\n        // Decode with rotation\n        switch (mRotation) {\n            case 90:\n                mTempRect.set(\n                        rect.top, mOriginalHeight - rect.right,\n                        rect.bottom, mOriginalHeight - rect.left);\n                break;\n\n            case 180:\n                mTempRect.set(\n                        mOriginalWidth - rect.right, mOriginalHeight - rect.bottom,\n                        mOriginalWidth - rect.left, mOriginalHeight - rect.top);\n                break;\n\n            case 270:\n                mTempRect.set(\n                        mOriginalWidth - rect.bottom, rect.left,\n                        mOriginalWidth - rect.top, rect.right);\n                break;\n\n            default:\n                mTempRect.set(rect);\n        }\n\n        Bitmap bitmap = mBitmapRegionDecoder.decodeRegion(mTempRect, options);\n        if (bitmap == null) {\n            return null;\n        }\n\n        if (options != null && options.inBitmap != null &&\n                ((mTempRect.width() != unsampledInBitmapWidth\n                        || mTempRect.height() != unsampledInBitmapHeight))) {\n            // Need to extract the sub-bitmap\n            Bitmap subBitmap = Bitmap.createBitmap(\n                    bitmap, 0, 0,\n                    mTempRect.width() / sampleSize,\n                    mTempRect.height() / sampleSize);\n            if (bitmap != options.inBitmap && bitmap != subBitmap) {\n                bitmap.recycle();\n            }\n            bitmap = subBitmap;\n        }\n\n        if (mRotateMatrix != null) {\n            // Rotate decoded bitmap\n            Bitmap rotatedBitmap = Bitmap.createBitmap(\n                    bitmap, 0, 0,\n                    bitmap.getWidth(), bitmap.getHeight(),\n                    mRotateMatrix, true);\n            if ((options == null || bitmap != options.inBitmap) && bitmap != rotatedBitmap) {\n                bitmap.recycle();\n            }\n            bitmap = rotatedBitmap;\n        }\n\n        return bitmap;\n    }\n\n    public synchronized int getWidth() {\n        return (mRotation == 90 || mRotation == 270) ? mOriginalHeight : mOriginalWidth;\n    }\n\n    public synchronized int getHeight() {\n        return (mRotation == 90 || mRotation == 270) ? mOriginalWidth : mOriginalHeight;\n    }\n\n    public synchronized void destroy() {\n        mBitmapRegionDecoder.recycle();\n        mBitmapRegionDecoder = null;\n        try {\n            mInputStream.close();\n        } catch (IOException ignored) {\n        }\n    }\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/render/DemoRenderController.kt",
    "content": "package com.yalin.style.render\n\nimport android.animation.Animator\nimport android.animation.AnimatorListenerAdapter\nimport android.animation.ObjectAnimator\nimport android.content.Context\nimport android.os.Handler\n\nimport com.yalin.style.domain.interactor.GetWallpaper\nimport com.yalin.style.domain.interactor.ObserverWallpaper\nimport com.yalin.style.domain.interactor.OpenWallpaperInputStream\nimport com.yalin.style.mapper.WallpaperItemMapper\n\nimport javax.inject.Inject\n\n/**\n * @author jinyalin\n * *\n * @since 2017/4/19.\n */\nclass DemoRenderController @Inject\nconstructor(context: Context, getWallpaperUseCase: GetWallpaper,\n            observerWallpaper: ObserverWallpaper,\n            openWallpaperInputStream: OpenWallpaperInputStream,\n            wallpaperItemMapper: WallpaperItemMapper)\n    : RenderController(context, getWallpaperUseCase, observerWallpaper,\n        openWallpaperInputStream, wallpaperItemMapper) {\n\n    companion object {\n\n        private val ANIMATION_CYCLE_TIME_MILLIS: Long = 35000\n        private val FOCUS_DELAY_TIME_MILLIS: Long = 2000\n        private val FOCUS_TIME_MILLIS: Long = 6000\n\n        private val mHandler = Handler()\n    }\n\n    private var mCurrentScrollAnimator: Animator? = null\n    private var mReverseDirection = false\n    private var mAllowFocus = false\n\n    fun start(allowFocus: Boolean) {\n        mAllowFocus = allowFocus\n        runAnimation()\n    }\n\n    private fun runAnimation() {\n        if (mCurrentScrollAnimator != null) {\n            mCurrentScrollAnimator!!.cancel()\n        }\n\n        mCurrentScrollAnimator = ObjectAnimator\n                .ofFloat(mRenderer, \"normalOffsetX\",\n                        if (mReverseDirection) 1f else 0f, if (mReverseDirection) 0f else 1f)\n                .setDuration(ANIMATION_CYCLE_TIME_MILLIS)\n        mCurrentScrollAnimator!!.start()\n        mCurrentScrollAnimator!!.addListener(object : AnimatorListenerAdapter() {\n            override fun onAnimationEnd(animation: Animator) {\n                super.onAnimationEnd(animation)\n                mReverseDirection = !mReverseDirection\n                runAnimation()\n            }\n        })\n        if (mAllowFocus) {\n            mHandler.postDelayed({\n                mRenderer!!.setIsBlurred(false, false)\n                mHandler.postDelayed({ mRenderer!!.setIsBlurred(true, false) }, FOCUS_TIME_MILLIS)\n            }, FOCUS_DELAY_TIME_MILLIS)\n        }\n    }\n\n    override fun destroy() {\n        super.destroy()\n        if (mCurrentScrollAnimator != null) {\n            mCurrentScrollAnimator!!.cancel()\n            mCurrentScrollAnimator!!.removeAllListeners()\n        }\n        mHandler.removeCallbacksAndMessages(null)\n    }\n\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/render/GLColorOverlay.java",
    "content": "/*\n * Copyright 2014 Google Inc.\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\npackage com.yalin.style.render;\n\nimport android.graphics.Color;\nimport android.opengl.GLES20;\nimport java.nio.FloatBuffer;\n\nclass GLColorOverlay {\n    private static final String VERTEX_SHADER_CODE = \"\" +\n            // This matrix member variable provides a hook to manipulate\n            // the coordinates of the objects that use this vertex shader\n            \"uniform mat4 uMVPMatrix;\" +\n            \"attribute vec4 aPosition;\" +\n            \"void main(){\" +\n            \"  gl_Position = uMVPMatrix * aPosition;\" +\n            \"}\";\n\n    private static final String FRAGMENT_SHADER_CODE = \"\" +\n            \"precision mediump float;\" +\n            \"uniform sampler2D uTexture;\" +\n            \"uniform vec4 uColor;\" +\n            \"void main(){\" +\n            \"  gl_FragColor = uColor;\" +\n            \"}\";\n\n    // number of coordinates per vertex in this array\n    private static final int COORDS_PER_VERTEX = 3;\n    private static final int VERTEX_STRIDE_BYTES = COORDS_PER_VERTEX * GLUtil.BYTES_PER_FLOAT;\n\n    private float mVertices[] = {\n            -1,  1, 0,   // top left\n            -1, -1, 0,   // bottom left\n             1, -1, 0,   // bottom right\n\n            -1,  1, 0,   // top left\n             1, -1, 0,   // bottom right\n             1,  1, 0,   // top right\n    };\n\n    private int mColor = 0;\n\n    private FloatBuffer mVertexBuffer;\n\n    private static int sProgramHandle;\n    private static int sAttribPositionHandle;\n    private static int sUniformColorHandle;\n    private static int sUniformMVPMatrixHandle;\n\n    public GLColorOverlay() {\n        mVertexBuffer = GLUtil.asFloatBuffer(mVertices);\n    }\n\n    public static void initGl() {\n        // Initialize shaders and create/link program\n        int vertexShaderHandle = GLUtil.loadShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER_CODE);\n        int fragShaderHandle = GLUtil.loadShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER_CODE);\n\n        sProgramHandle = GLUtil.createAndLinkProgram(vertexShaderHandle, fragShaderHandle, null);\n        sAttribPositionHandle = GLES20.glGetAttribLocation(sProgramHandle, \"aPosition\");\n        sUniformMVPMatrixHandle = GLES20.glGetUniformLocation(sProgramHandle, \"uMVPMatrix\");\n        sUniformColorHandle = GLES20.glGetUniformLocation(sProgramHandle, \"uColor\");\n    }\n\n    public void draw(float[] mvpMatrix) {\n        // Add program to OpenGL ES environment\n        GLES20.glUseProgram(sProgramHandle);\n\n        // Pass in the vertex information\n        GLES20.glEnableVertexAttribArray(sAttribPositionHandle);\n        GLES20.glVertexAttribPointer(sAttribPositionHandle,\n                COORDS_PER_VERTEX, GLES20.GL_FLOAT, false,\n                VERTEX_STRIDE_BYTES, mVertexBuffer);\n\n        // Apply the projection and view transformation\n        GLES20.glUniformMatrix4fv(sUniformMVPMatrixHandle, 1, false, mvpMatrix, 0);\n        GLUtil.checkGlError(\"glUniformMatrix4fv\");\n\n        // Set the alpha\n        float r = Color.red(mColor) * 1f / 255;\n        float g = Color.green(mColor) * 1f / 255;\n        float b = Color.blue(mColor) * 1f / 255;\n        float a = Color.alpha(mColor) * 1f / 255;\n        GLES20.glUniform4f(sUniformColorHandle, r, g, b, a);\n\n        // Draw the triangle\n        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, mVertices.length / COORDS_PER_VERTEX);\n\n        GLES20.glDisableVertexAttribArray(sAttribPositionHandle);\n    }\n\n    public void setColor(int color) {\n        mColor = color;\n    }\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/render/GLPicture.java",
    "content": "/*\n * Copyright 2014 Google Inc.\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\npackage com.yalin.style.render;\n\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.graphics.Rect;\nimport android.opengl.GLES20;\nimport com.yalin.style.util.MathUtil;\nimport java.nio.FloatBuffer;\n\nclass GLPicture {\n\n  private static final String VERTEX_SHADER_CODE = \"\" +\n      // This matrix member variable provides a hook to manipulate\n      // the coordinates of the objects that use this vertex shader\n      \"uniform mat4 uMVPMatrix;\" +\n      \"attribute vec4 aPosition;\" +\n      \"attribute vec2 aTexCoords;\" +\n      \"varying vec2 vTexCoords;\" +\n      \"void main(){\" +\n      \"  vTexCoords = aTexCoords;\" +\n      \"  gl_Position = uMVPMatrix * aPosition;\" +\n      \"}\";\n\n  private static final String FRAGMENT_SHADER_CODE = \"\" +\n      \"precision mediump float;\" +\n      \"uniform sampler2D uTexture;\" +\n      \"uniform float uAlpha;\" +\n      \"varying vec2 vTexCoords;\" +\n      \"void main(){\" +\n      \"  gl_FragColor = texture2D(uTexture, vTexCoords);\" +\n      \"  gl_FragColor.a = uAlpha;\" +\n      \"}\";\n\n  // number of coordinates per vertex in this array\n  private static final int COORDS_PER_VERTEX = 3;\n  private static final int VERTEX_STRIDE_BYTES = COORDS_PER_VERTEX * GLUtil.BYTES_PER_FLOAT;\n  private static final int VERTICES = 6; // TL, BL, BR, TL, BR, TR\n\n  // S, T (or X, Y)\n  private static final int COORDS_PER_TEXTURE_VERTEX = 2;\n  private static final int TEXTURE_VERTEX_STRIDE_BYTES = COORDS_PER_TEXTURE_VERTEX\n      * GLUtil.BYTES_PER_FLOAT;\n\n  private static final float[] SQUARE_TEXTURE_VERTICES = {\n      0, 0, // top left\n      0, 1, // bottom left\n      1, 1, // bottom right\n\n      0, 0, // top left\n      1, 1, // bottom right\n      1, 0, // top right\n  };\n\n  private boolean mHasContent = false;\n\n  private float[] mVertices = new float[COORDS_PER_VERTEX * VERTICES];\n  private FloatBuffer mVertexBuffer;\n  private FloatBuffer mTextureCoordsBuffer;\n\n  private static int sMaxTextureSize;\n\n  private static int sProgramHandle;\n  private static int sAttribPositionHandle;\n  private static int sAttribTextureCoordsHandle;\n  private static int sUniformAlphaHandle;\n  private static int sUniformTextureHandle;\n  private static int sUniformMVPMatrixHandle;\n\n  private int mCols = 1;\n  private int mRows = 1;\n  private int mWidth = 0;\n  private int mHeight = 0;\n  private int mTileSize = sMaxTextureSize;\n  private int[] mTextureHandles;\n\n  public static void initGl() {\n    // Initialize shaders and create/link program\n    int vertexShaderHandle = GLUtil.loadShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER_CODE);\n    int fragShaderHandle = GLUtil.loadShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER_CODE);\n\n    sProgramHandle = GLUtil.createAndLinkProgram(vertexShaderHandle, fragShaderHandle, null);\n    sAttribPositionHandle = GLES20.glGetAttribLocation(sProgramHandle, \"aPosition\");\n    sAttribTextureCoordsHandle = GLES20.glGetAttribLocation(sProgramHandle, \"aTexCoords\");\n    sUniformMVPMatrixHandle = GLES20.glGetUniformLocation(sProgramHandle, \"uMVPMatrix\");\n    sUniformTextureHandle = GLES20.glGetUniformLocation(sProgramHandle, \"uTexture\");\n    sUniformAlphaHandle = GLES20.glGetUniformLocation(sProgramHandle, \"uAlpha\");\n\n    // Compute max texture size\n    int[] maxTextureSize = new int[1];\n    GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE, maxTextureSize, 0);\n    sMaxTextureSize = maxTextureSize[0];\n  }\n\n  public GLPicture(BitmapRegionLoader bitmapRegionLoader, int maxHeight) {\n    if (bitmapRegionLoader == null || maxHeight == 0) {\n      return;\n    }\n\n    mHasContent = true;\n    mVertexBuffer = GLUtil.newFloatBuffer(mVertices.length);\n    mTextureCoordsBuffer = GLUtil.asFloatBuffer(SQUARE_TEXTURE_VERTICES);\n\n    int originalWidth = bitmapRegionLoader.getWidth();\n    int originalHeight = bitmapRegionLoader.getHeight();\n    int sampleSize = 1;\n    while (originalHeight / (sampleSize << 1) > maxHeight) {\n      sampleSize <<= 1;\n    }\n\n    mWidth = originalWidth / sampleSize;\n    mHeight = originalHeight / sampleSize;\n\n    mTileSize = Math.min(512, sMaxTextureSize);\n    int unsampledTileSize = mTileSize * sampleSize;\n    int leftoverHeight = originalHeight % unsampledTileSize;\n\n    // Load m x n textures\n    mCols = MathUtil.intDivideRoundUp(mWidth, mTileSize);\n    mRows = MathUtil.intDivideRoundUp(mHeight, mTileSize);\n\n    mTextureHandles = new int[mCols * mRows];\n\n    Bitmap tileBitmap = Bitmap.createBitmap(mTileSize, mTileSize, Bitmap.Config.ARGB_8888);\n    Rect rect = new Rect();\n    BitmapFactory.Options options = new BitmapFactory.Options();\n    options.inSampleSize = sampleSize;\n    options.inBitmap = tileBitmap;\n    for (int y = 0; y < mRows; y++) {\n      for (int x = 0; x < mCols; x++) {\n        rect.set(x * unsampledTileSize,\n            (mRows - y - 1) * unsampledTileSize,\n            (x + 1) * unsampledTileSize,\n            (mRows - y) * unsampledTileSize);\n        // The bottom tiles must be full tiles for drawing, so only allow edge tiles\n        // at the top\n        if (leftoverHeight > 0) {\n          rect.offset(0, -unsampledTileSize + leftoverHeight);\n        }\n        rect.intersect(0, 0, originalWidth, originalHeight);\n        Bitmap useBitmap = bitmapRegionLoader.decodeRegion(rect, options);\n        if (useBitmap != null) {\n          mTextureHandles[y * mCols + x] = GLUtil.loadTexture(useBitmap);\n          if (useBitmap != tileBitmap) {\n            useBitmap.recycle();\n          }\n        }\n      }\n    }\n  }\n\n  public GLPicture(Bitmap bitmap) {\n    if (bitmap == null) {\n      return;\n    }\n\n    mTileSize = Math.min(512, sMaxTextureSize);\n    mHasContent = true;\n    mVertexBuffer = GLUtil.newFloatBuffer(mVertices.length);\n    mTextureCoordsBuffer = GLUtil.asFloatBuffer(SQUARE_TEXTURE_VERTICES);\n\n    mWidth = bitmap.getWidth();\n    mHeight = bitmap.getHeight();\n    int leftoverHeight = mHeight % mTileSize;\n\n    // Load m x n textures\n    mCols = MathUtil.intDivideRoundUp(mWidth, mTileSize);\n    mRows = MathUtil.intDivideRoundUp(mHeight, mTileSize);\n\n    mTextureHandles = new int[mCols * mRows];\n    if (mCols == 1 && mRows == 1) {\n      mTextureHandles[0] = GLUtil.loadTexture(bitmap);\n    } else {\n      Rect rect = new Rect();\n      for (int y = 0; y < mRows; y++) {\n        for (int x = 0; x < mCols; x++) {\n          rect.set(x * mTileSize,\n              (mRows - y - 1) * mTileSize,\n              (x + 1) * mTileSize,\n              (mRows - y) * mTileSize);\n          // The bottom tiles must be full tiles for drawing, so only allow edge tiles\n          // at the top\n          if (leftoverHeight > 0) {\n            rect.offset(0, -mTileSize + leftoverHeight);\n          }\n          rect.intersect(0, 0, mWidth, mHeight);\n          Bitmap subBitmap = Bitmap.createBitmap(bitmap,\n              rect.left, rect.top, rect.width(), rect.height());\n          mTextureHandles[y * mCols + x] = GLUtil.loadTexture(subBitmap);\n          subBitmap.recycle();\n        }\n      }\n    }\n  }\n\n  public void draw(float[] mvpMatrix, float alpha) {\n    if (!mHasContent) {\n      return;\n    }\n\n    // Add program to OpenGL ES environment\n    GLES20.glUseProgram(sProgramHandle);\n\n    // Apply the projection and view transformation\n    GLES20.glUniformMatrix4fv(sUniformMVPMatrixHandle, 1, false, mvpMatrix, 0);\n    GLUtil.checkGlError(\"glUniformMatrix4fv\");\n\n    // Set up vertex buffer\n    GLES20.glEnableVertexAttribArray(sAttribPositionHandle);\n    GLES20.glVertexAttribPointer(sAttribPositionHandle,\n        COORDS_PER_VERTEX, GLES20.GL_FLOAT, false,\n        VERTEX_STRIDE_BYTES, mVertexBuffer);\n\n    // Set up texture stuff\n    GLES20.glActiveTexture(GLES20.GL_TEXTURE0);\n    GLES20.glUniform1i(sUniformTextureHandle, 0);\n    GLES20.glVertexAttribPointer(sAttribTextureCoordsHandle,\n        COORDS_PER_TEXTURE_VERTEX, GLES20.GL_FLOAT, false,\n        TEXTURE_VERTEX_STRIDE_BYTES, mTextureCoordsBuffer);\n    GLES20.glEnableVertexAttribArray(sAttribTextureCoordsHandle);\n\n    // Set the alpha\n    GLES20.glUniform1f(sUniformAlphaHandle, alpha);\n\n    // Draw tiles\n    for (int y = 0; y < mRows; y++) {\n      for (int x = 0; x < mCols; x++) {\n        // Pass in the vertex information\n        mVertices[0] = mVertices[3] = mVertices[9]\n            = Math.min(-1 + 2f * x * mTileSize / mWidth, 1); // left\n        mVertices[1] = mVertices[10] = mVertices[16]\n            = Math.min(-1 + 2f * (y + 1) * mTileSize / mHeight, 1); // top\n        mVertices[6] = mVertices[12] = mVertices[15]\n            = Math.min(-1 + 2f * (x + 1) * mTileSize / mWidth, 1); // right\n        mVertices[4] = mVertices[7] = mVertices[13]\n            = Math.min(-1 + 2f * y * mTileSize / mHeight, 1); // bottom\n        mVertexBuffer.put(mVertices);\n        mVertexBuffer.position(0);\n\n        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,\n            mTextureHandles[y * mCols + x]);\n        GLUtil.checkGlError(\"glBindTexture\");\n\n        // Draw the two triangles\n        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, mVertices.length / COORDS_PER_VERTEX);\n      }\n    }\n\n    GLES20.glDisableVertexAttribArray(sAttribPositionHandle);\n    GLES20.glDisableVertexAttribArray(sAttribTextureCoordsHandle);\n  }\n\n  public void destroy() {\n    if (mTextureHandles != null) {\n      GLES20.glDeleteTextures(mTextureHandles.length, mTextureHandles, 0);\n      GLUtil.checkGlError(\"Destroy picture\");\n      mTextureHandles = null;\n    }\n  }\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/render/GLTextureView.java",
    "content": "/*\n * Copyright 2014 Google Inc.\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\npackage com.yalin.style.render;\n\nimport android.content.Context;\nimport android.graphics.SurfaceTexture;\nimport android.opengl.GLDebugHelper;\nimport android.opengl.GLSurfaceView;\nimport android.util.AttributeSet;\nimport android.util.Log;\nimport android.view.SurfaceHolder;\nimport android.view.TextureView;\n\nimport java.io.Writer;\nimport java.lang.ref.WeakReference;\nimport java.util.ArrayList;\n\nimport javax.microedition.khronos.egl.EGL10;\nimport javax.microedition.khronos.egl.EGL11;\nimport javax.microedition.khronos.egl.EGLConfig;\nimport javax.microedition.khronos.egl.EGLContext;\nimport javax.microedition.khronos.egl.EGLDisplay;\nimport javax.microedition.khronos.egl.EGLSurface;\nimport javax.microedition.khronos.opengles.GL;\nimport javax.microedition.khronos.opengles.GL10;\n\n/**\n * See http://stackoverflow.com/questions/12061419/converting-from-glsurfaceview-to-textureview-via-gltextureview\n */\npublic class GLTextureView extends TextureView implements TextureView.SurfaceTextureListener {\n    private final static String TAG = \"GLTextureView\";\n    private final static boolean LOG_ATTACH_DETACH = false;\n    private final static boolean LOG_THREADS = false;\n    private final static boolean LOG_PAUSE_RESUME = false;\n    private final static boolean LOG_SURFACE = false;\n    private final static boolean LOG_RENDERER = false;\n    private final static boolean LOG_RENDERER_DRAW_FRAME = false;\n    private final static boolean LOG_EGL = false;\n    /**\n     * The renderer only renders\n     * when the surface is created, or when {@link #requestRender} is called.\n     *\n     * @see #getRenderMode()\n     * @see #setRenderMode(int)\n     * @see #requestRender()\n     */\n    public final static int RENDERMODE_WHEN_DIRTY = 0;\n    /**\n     * The renderer is called\n     * continuously to re-render the scene.\n     *\n     * @see #getRenderMode()\n     * @see #setRenderMode(int)\n     */\n    public final static int RENDERMODE_CONTINUOUSLY = 1;\n\n    /**\n     * Check glError() after every GL call and throw an exception if glError indicates\n     * that an error has occurred. This can be used to help track down which OpenGL ES call\n     * is causing an error.\n     *\n     * @see #getDebugFlags\n     * @see #setDebugFlags\n     */\n    public final static int DEBUG_CHECK_GL_ERROR = 1;\n\n    /**\n     * Log GL calls to the system log at \"verbose\" level with tag \"GLSurfaceView\".\n     *\n     * @see #getDebugFlags\n     * @see #setDebugFlags\n     */\n    public final static int DEBUG_LOG_GL_CALLS = 2;\n\n    /**\n     * Standard View constructor. In order to render something, you\n     * must call {@link #setRenderer} to register a renderer.\n     */\n    public GLTextureView(Context context) {\n        super(context);\n        init();\n    }\n\n    /**\n     * Standard View constructor. In order to render something, you\n     * must call {@link #setRenderer} to register a renderer.\n     */\n    public GLTextureView(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        init();\n    }\n\n    @Override\n    protected void finalize() throws Throwable {\n        try {\n            if (mGLThread != null) {\n                // GLThread may still be running if this view was never\n                // attached to a window.\n                mGLThread.requestExitAndWait();\n            }\n        } finally {\n            super.finalize();\n        }\n    }\n\n    private void init() {\n        setSurfaceTextureListener(this);\n    }\n\n    /**\n     * Set the glWrapper. If the glWrapper is not null, its\n     * {@link GLWrapper#wrap(GL)} method is called\n     * whenever a surface is created. A GLWrapper can be used to wrap\n     * the GL object that's passed to the renderer. Wrapping a GL\n     * object enables examining and modifying the behavior of the\n     * GL calls made by the renderer.\n     * <p>\n     * Wrapping is typically used for debugging purposes.\n     * <p>\n     * The default value is null.\n     *\n     * @param glWrapper the new GLWrapper\n     */\n    public void setGLWrapper(GLWrapper glWrapper) {\n        mGLWrapper = glWrapper;\n    }\n\n    /**\n     * Set the debug flags to a new value. The value is\n     * constructed by OR-together zero or more\n     * of the DEBUG_CHECK_* constants. The debug flags take effect\n     * whenever a surface is created. The default value is zero.\n     *\n     * @param debugFlags the new debug flags\n     * @see #DEBUG_CHECK_GL_ERROR\n     * @see #DEBUG_LOG_GL_CALLS\n     */\n    public void setDebugFlags(int debugFlags) {\n        mDebugFlags = debugFlags;\n    }\n\n    /**\n     * Get the current value of the debug flags.\n     *\n     * @return the current value of the debug flags.\n     */\n    public int getDebugFlags() {\n        return mDebugFlags;\n    }\n\n    /**\n     * Control whether the EGL context is preserved when the GLSurfaceView is paused and\n     * resumed.\n     * <p>\n     * If set to true, then the EGL context may be preserved when the GLSurfaceView is paused.\n     * Whether the EGL context is actually preserved or not depends upon whether the\n     * Android device that the program is running on can support an arbitrary number of EGL\n     * contexts or not. Devices that can only support a limited number of EGL contexts must\n     * release the  EGL context in order to allow multiple applications to share the GPU.\n     * <p>\n     * If set to false, the EGL context will be released when the GLSurfaceView is paused,\n     * and recreated when the GLSurfaceView is resumed.\n     * <p>\n     * <p>\n     * The default is false.\n     *\n     * @param preserveOnPause preserve the EGL context when paused\n     */\n    public void setPreserveEGLContextOnPause(boolean preserveOnPause) {\n        mPreserveEGLContextOnPause = preserveOnPause;\n    }\n\n    /**\n     * @return true if the EGL context will be preserved when paused\n     */\n    public boolean getPreserveEGLContextOnPause() {\n        return mPreserveEGLContextOnPause;\n    }\n\n    /**\n     * Set the renderer associated with this view. Also starts the thread that\n     * will call the renderer, which in turn causes the rendering to start.\n     * <p>This method should be called once and only once in the life-cycle of\n     * a GLSurfaceView.\n     * <p>The following GLSurfaceView methods can only be called <em>before</em>\n     * setRenderer is called:\n     * <ul>\n     * <li>{@link #setEGLConfigChooser(boolean)}\n     * <li>{@link #setEGLConfigChooser(EGLConfigChooser)}\n     * <li>{@link #setEGLConfigChooser(int, int, int, int, int, int)}\n     * </ul>\n     * <p>\n     * The following GLSurfaceView methods can only be called <em>after</em>\n     * setRenderer is called:\n     * <ul>\n     * <li>{@link #getRenderMode()}\n     * <li>{@link #onPause()}\n     * <li>{@link #onResume()}\n     * <li>{@link #queueEvent(Runnable)}\n     * <li>{@link #requestRender()}\n     * <li>{@link #setRenderMode(int)}\n     * </ul>\n     *\n     * @param renderer the renderer to use to perform OpenGL drawing.\n     */\n    public void setRenderer(GLSurfaceView.Renderer renderer) {\n        checkRenderThreadState();\n        if (mEGLConfigChooser == null) {\n            mEGLConfigChooser = new SimpleEGLConfigChooser(true);\n        }\n        if (mEGLContextFactory == null) {\n            mEGLContextFactory = new DefaultContextFactory();\n        }\n        if (mEGLWindowSurfaceFactory == null) {\n            mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory();\n        }\n        mRenderer = renderer;\n        mGLThread = new GLThread(mThisWeakRef);\n        mGLThread.start();\n    }\n\n    /**\n     * Install a custom EGLContextFactory.\n     * <p>If this method is\n     * called, it must be called before {@link #setRenderer(Renderer)}\n     * is called.\n     * <p>\n     * If this method is not called, then by default\n     * a context will be created with no shared context and\n     * with a null attribute list.\n     */\n    public void setEGLContextFactory(EGLContextFactory factory) {\n        checkRenderThreadState();\n        mEGLContextFactory = factory;\n    }\n\n    /**\n     * Install a custom EGLWindowSurfaceFactory.\n     * <p>If this method is\n     * called, it must be called before {@link #setRenderer(Renderer)}\n     * is called.\n     * <p>\n     * If this method is not called, then by default\n     * a window surface will be created with a null attribute list.\n     */\n    public void setEGLWindowSurfaceFactory(EGLWindowSurfaceFactory factory) {\n        checkRenderThreadState();\n        mEGLWindowSurfaceFactory = factory;\n    }\n\n    /**\n     * Install a custom EGLConfigChooser.\n     * <p>If this method is\n     * called, it must be called before {@link #setRenderer(Renderer)}\n     * is called.\n     * <p>\n     * If no setEGLConfigChooser method is called, then by default the\n     * view will choose an EGLConfig that is compatible with the current\n     * android.view.Surface, with a depth buffer depth of\n     * at least 16 bits.\n     *\n     * @param configChooser\n     */\n    public void setEGLConfigChooser(EGLConfigChooser configChooser) {\n        checkRenderThreadState();\n        mEGLConfigChooser = configChooser;\n    }\n\n    /**\n     * Install a config chooser which will choose a config\n     * as close to 16-bit RGB as possible, with or without an optional depth\n     * buffer as close to 16-bits as possible.\n     * <p>If this method is\n     * called, it must be called before {@link #setRenderer(Renderer)}\n     * is called.\n     * <p>\n     * If no setEGLConfigChooser method is called, then by default the\n     * view will choose an RGB_888 surface with a depth buffer depth of\n     * at least 16 bits.\n     *\n     * @param needDepth\n     */\n    public void setEGLConfigChooser(boolean needDepth) {\n        setEGLConfigChooser(new SimpleEGLConfigChooser(needDepth));\n    }\n\n    /**\n     * Install a config chooser which will choose a config\n     * with at least the specified depthSize and stencilSize,\n     * and exactly the specified redSize, greenSize, blueSize and alphaSize.\n     * <p>If this method is\n     * called, it must be called before {@link #setRenderer(Renderer)}\n     * is called.\n     * <p>\n     * If no setEGLConfigChooser method is called, then by default the\n     * view will choose an RGB_888 surface with a depth buffer depth of\n     * at least 16 bits.\n     */\n    public void setEGLConfigChooser(int redSize, int greenSize, int blueSize,\n                                    int alphaSize, int depthSize, int stencilSize) {\n        setEGLConfigChooser(new ComponentSizeChooser(redSize, greenSize,\n                blueSize, alphaSize, depthSize, stencilSize));\n    }\n\n    /**\n     * Inform the default EGLContextFactory and default EGLConfigChooser\n     * which EGLContext client version to pick.\n     * <p>Use this method to create an OpenGL ES 2.0-compatible context.\n     * Example:\n     * <pre class=\"prettyprint\">\n     * public MyView(Context context) {\n     * super(context);\n     * setEGLContextClientVersion(2); // Pick an OpenGL ES 2.0 context.\n     * setRenderer(new MyRenderer());\n     * }\n     * </pre>\n     * <p>Note: Activities which require OpenGL ES 2.0 should indicate this by\n     * setting @lt;uses-feature android:glEsVersion=\"0x00020000\" /> in the activity's\n     * AndroidManifest.xml file.\n     * <p>If this method is called, it must be called before {@link #setRenderer(Renderer)}\n     * is called.\n     * <p>This method only affects the behavior of the default EGLContexFactory and the\n     * default EGLConfigChooser. If\n     * {@link #setEGLContextFactory(EGLContextFactory)} has been called, then the supplied\n     * EGLContextFactory is responsible for creating an OpenGL ES 2.0-compatible context.\n     * If\n     * {@link #setEGLConfigChooser(EGLConfigChooser)} has been called, then the supplied\n     * EGLConfigChooser is responsible for choosing an OpenGL ES 2.0-compatible config.\n     *\n     * @param version The EGLContext client version to choose. Use 2 for OpenGL ES 2.0\n     */\n    public void setEGLContextClientVersion(int version) {\n        checkRenderThreadState();\n        mEGLContextClientVersion = version;\n    }\n\n    /**\n     * Set the rendering mode. When renderMode is\n     * RENDERMODE_CONTINUOUSLY, the renderer is called\n     * repeatedly to re-render the scene. When renderMode\n     * is RENDERMODE_WHEN_DIRTY, the renderer only rendered when the surface\n     * is created, or when {@link #requestRender} is called. Defaults to RENDERMODE_CONTINUOUSLY.\n     * <p>\n     * Using RENDERMODE_WHEN_DIRTY can improve battery life and overall system performance\n     * by allowing the GPU and CPU to idle when the view does not need to be updated.\n     * <p>\n     * This method can only be called after {@link #setRenderer(Renderer)}\n     *\n     * @param renderMode one of the RENDERMODE_X constants\n     * @see #RENDERMODE_CONTINUOUSLY\n     * @see #RENDERMODE_WHEN_DIRTY\n     */\n    public void setRenderMode(int renderMode) {\n        mGLThread.setRenderMode(renderMode);\n    }\n\n    /**\n     * Get the current rendering mode. May be called\n     * from any thread. Must not be called before a renderer has been set.\n     *\n     * @return the current rendering mode.\n     * @see #RENDERMODE_CONTINUOUSLY\n     * @see #RENDERMODE_WHEN_DIRTY\n     */\n    public int getRenderMode() {\n        return mGLThread.getRenderMode();\n    }\n\n    /**\n     * Request that the renderer render a frame.\n     * This method is typically used when the render mode has been set to\n     * {@link #RENDERMODE_WHEN_DIRTY}, so that frames are only rendered on demand.\n     * May be called\n     * from any thread. Must not be called before a renderer has been set.\n     */\n    public void requestRender() {\n        mGLThread.requestRender();\n    }\n\n    @Override\n    public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {\n        mGLThread.surfaceCreated();\n    }\n\n    @Override\n    public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) {\n        mGLThread.onWindowResize(width, height);\n    }\n\n    @Override\n    protected void onSizeChanged(int w, int h, int oldw, int oldh) {\n        super.onSizeChanged(w, h, oldw, oldh);\n        mGLThread.onWindowResize(w, h);\n    }\n\n    @Override\n    public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {\n        mGLThread.surfaceDestroyed();\n        return true;\n    }\n\n    @Override\n    public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {\n        requestRender();\n    }\n\n    /**\n     * This method is part of the SurfaceHolder.Callback interface, and is\n     * not normally called or subclassed by clients of GLSurfaceView.\n     */\n    public void on(SurfaceHolder holder) {\n        mGLThread.surfaceCreated();\n    }\n\n    /**\n     * Inform the view that the activity is paused. The owner of this view must\n     * call this method when the activity is paused. Calling this method will\n     * pause the rendering thread.\n     * Must not be called before a renderer has been set.\n     */\n    public void onPause() {\n        mGLThread.onPause();\n    }\n\n    /**\n     * Inform the view that the activity is resumed. The owner of this view must\n     * call this method when the activity is resumed. Calling this method will\n     * recreate the OpenGL display and resume the rendering\n     * thread.\n     * Must not be called before a renderer has been set.\n     */\n    public void onResume() {\n        mGLThread.onResume();\n    }\n\n    /**\n     * Queue a runnable to be run on the GL rendering thread. This can be used\n     * to communicate with the Renderer on the rendering thread.\n     * Must not be called before a renderer has been set.\n     *\n     * @param r the runnable to be run on the GL rendering thread.\n     */\n    public void queueEvent(Runnable r) {\n        mGLThread.queueEvent(r);\n    }\n\n    /**\n     * This method is used as part of the View class and is not normally\n     * called or subclassed by clients of GLSurfaceView.\n     */\n    @Override\n    protected void onAttachedToWindow() {\n        super.onAttachedToWindow();\n        if (LOG_ATTACH_DETACH) {\n            Log.d(TAG, \"onAttachedToWindow reattach =\" + mDetached);\n        }\n        if (mDetached && (mRenderer != null)) {\n            int renderMode = RENDERMODE_CONTINUOUSLY;\n            if (mGLThread != null) {\n                renderMode = mGLThread.getRenderMode();\n            }\n            mGLThread = new GLThread(mThisWeakRef);\n            if (renderMode != RENDERMODE_CONTINUOUSLY) {\n                mGLThread.setRenderMode(renderMode);\n            }\n            mGLThread.start();\n        }\n        mDetached = false;\n    }\n\n    /**\n     * This method is used as part of the View class and is not normally\n     * called or subclassed by clients of GLSurfaceView.\n     * Must not be called before a renderer has been set.\n     */\n    @Override\n    protected void onDetachedFromWindow() {\n        if (LOG_ATTACH_DETACH) {\n            Log.d(TAG, \"onDetachedFromWindow\");\n        }\n        if (mGLThread != null) {\n            mGLThread.requestExitAndWait();\n        }\n        mDetached = true;\n        super.onDetachedFromWindow();\n    }\n\n    // ----------------------------------------------------------------------\n\n    /**\n     * An interface used to wrap a GL interface.\n     * <p>Typically\n     * used for implementing debugging and tracing on top of the default\n     * GL interface. You would typically use this by creating your own class\n     * that implemented all the GL methods by delegating to another GL instance.\n     * Then you could add your own behavior before or after calling the\n     * delegate. All the GLWrapper would do was instantiate and return the\n     * wrapper GL instance:\n     * <pre class=\"prettyprint\">\n     * class MyGLWrapper implements GLWrapper {\n     * GL wrap(GL gl) {\n     * return new MyGLImplementation(gl);\n     * }\n     * static class MyGLImplementation implements GL,GL10,GL11,... {\n     * ...\n     * }\n     * }\n     * </pre>\n     *\n     * @see #setGLWrapper(GLWrapper)\n     */\n    public interface GLWrapper {\n        /**\n         * Wraps a gl interface in another gl interface.\n         *\n         * @param gl a GL interface that is to be wrapped.\n         * @return either the input argument or another GL object that wraps the input argument.\n         */\n        GL wrap(GL gl);\n    }\n\n    /**\n     * An interface for customizing the eglCreateContext and eglDestroyContext calls.\n     * <p>\n     * This interface must be implemented by clients wishing to call\n     * {@link GLSurfaceView#setEGLContextFactory(EGLContextFactory)}\n     */\n    public interface EGLContextFactory {\n        EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig);\n\n        void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context);\n    }\n\n    private class DefaultContextFactory implements EGLContextFactory {\n        private int EGL_CONTEXT_CLIENT_VERSION = 0x3098;\n\n        public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig config) {\n            int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, mEGLContextClientVersion,\n                    EGL10.EGL_NONE};\n\n            return egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT,\n                    mEGLContextClientVersion != 0 ? attrib_list : null);\n        }\n\n        public void destroyContext(EGL10 egl, EGLDisplay display,\n                                   EGLContext context) {\n            if (!egl.eglDestroyContext(display, context)) {\n                Log.e(\"DefaultContextFactory\", \"display:\" + display + \" context: \" + context);\n                if (LOG_THREADS) {\n                    Log.i(\"DefaultContextFactory\", \"tid=\" + Thread.currentThread().getId());\n                }\n                EglHelper.throwEglException(\"eglDestroyContex\", egl.eglGetError());\n            }\n        }\n    }\n\n    /**\n     * An interface for customizing the eglCreateWindowSurface and eglDestroySurface calls.\n     * <p>\n     * This interface must be implemented by clients wishing to call\n     * {@link GLSurfaceView#setEGLWindowSurfaceFactory(EGLWindowSurfaceFactory)}\n     */\n    public interface EGLWindowSurfaceFactory {\n        /**\n         * @return null if the surface cannot be constructed.\n         */\n        EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display, EGLConfig config,\n                                       Object nativeWindow);\n\n        void destroySurface(EGL10 egl, EGLDisplay display, EGLSurface surface);\n    }\n\n    private static class DefaultWindowSurfaceFactory implements EGLWindowSurfaceFactory {\n\n        public EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display,\n                                              EGLConfig config, Object nativeWindow) {\n            EGLSurface result = null;\n            try {\n                result = egl.eglCreateWindowSurface(display, config, nativeWindow, null);\n            } catch (IllegalArgumentException e) {\n                // This exception indicates that the surface flinger surface\n                // is not valid. This can happen if the surface flinger surface has\n                // been torn down, but the application has not yet been\n                // notified via SurfaceHolder.Callback.surfaceDestroyed.\n                // In theory the application should be notified first,\n                // but in practice sometimes it is not. See b/4588890\n                Log.e(TAG, \"eglCreateWindowSurface\", e);\n            }\n            return result;\n        }\n\n        public void destroySurface(EGL10 egl, EGLDisplay display,\n                                   EGLSurface surface) {\n            egl.eglDestroySurface(display, surface);\n        }\n    }\n\n    /**\n     * An interface for choosing an EGLConfig configuration from a list of\n     * potential configurations.\n     * <p>\n     * This interface must be implemented by clients wishing to call\n     * {@link GLSurfaceView#setEGLConfigChooser(EGLConfigChooser)}\n     */\n    public interface EGLConfigChooser {\n        /**\n         * Choose a configuration from the list. Implementors typically\n         * implement this method by calling\n         * {@link EGL10#eglChooseConfig} and iterating through the results. Please consult the\n         * EGL specification available from The Khronos Group to learn how to call eglChooseConfig.\n         *\n         * @param egl     the EGL10 for the current display.\n         * @param display the current display.\n         * @return the chosen configuration.\n         */\n        EGLConfig chooseConfig(EGL10 egl, EGLDisplay display);\n    }\n\n    private abstract class BaseConfigChooser\n            implements EGLConfigChooser {\n        public BaseConfigChooser(int[] configSpec) {\n            mConfigSpec = filterConfigSpec(configSpec);\n        }\n\n        public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {\n            int[] num_config = new int[1];\n            if (!egl.eglChooseConfig(display, mConfigSpec, null, 0,\n                    num_config)) {\n                throw new IllegalArgumentException(\"eglChooseConfig failed\");\n            }\n\n            int numConfigs = num_config[0];\n\n            if (numConfigs <= 0) {\n                throw new IllegalArgumentException(\n                        \"No configs match configSpec\");\n            }\n\n            EGLConfig[] configs = new EGLConfig[numConfigs];\n            if (!egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs,\n                    num_config)) {\n                throw new IllegalArgumentException(\"eglChooseConfig#2 failed\");\n            }\n            EGLConfig config = chooseConfig(egl, display, configs);\n            if (config == null) {\n                throw new IllegalArgumentException(\"No config chosen\");\n            }\n            return config;\n        }\n\n        abstract EGLConfig chooseConfig(EGL10 egl, EGLDisplay display,\n                                        EGLConfig[] configs);\n\n        protected int[] mConfigSpec;\n\n        private int[] filterConfigSpec(int[] configSpec) {\n            if (mEGLContextClientVersion != 2) {\n                return configSpec;\n            }\n            /* We know none of the subclasses define EGL_RENDERABLE_TYPE.\n             * And we know the configSpec is well formed.\n             */\n            int len = configSpec.length;\n            int[] newConfigSpec = new int[len + 2];\n            System.arraycopy(configSpec, 0, newConfigSpec, 0, len - 1);\n            newConfigSpec[len - 1] = EGL10.EGL_RENDERABLE_TYPE;\n            newConfigSpec[len] = 4; /* EGL_OPENGL_ES2_BIT */\n            newConfigSpec[len + 1] = EGL10.EGL_NONE;\n            return newConfigSpec;\n        }\n    }\n\n    /**\n     * Choose a configuration with exactly the specified r,g,b,a sizes,\n     * and at least the specified depth and stencil sizes.\n     */\n    private class ComponentSizeChooser extends BaseConfigChooser {\n        public ComponentSizeChooser(int redSize, int greenSize, int blueSize,\n                                    int alphaSize, int depthSize, int stencilSize) {\n            super(new int[]{\n                    EGL10.EGL_RED_SIZE, redSize,\n                    EGL10.EGL_GREEN_SIZE, greenSize,\n                    EGL10.EGL_BLUE_SIZE, blueSize,\n                    EGL10.EGL_ALPHA_SIZE, alphaSize,\n                    EGL10.EGL_DEPTH_SIZE, depthSize,\n                    EGL10.EGL_STENCIL_SIZE, stencilSize,\n                    EGL10.EGL_NONE});\n            mValue = new int[1];\n            mRedSize = redSize;\n            mGreenSize = greenSize;\n            mBlueSize = blueSize;\n            mAlphaSize = alphaSize;\n            mDepthSize = depthSize;\n            mStencilSize = stencilSize;\n        }\n\n        @Override\n        public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display,\n                                      EGLConfig[] configs) {\n            for (EGLConfig config : configs) {\n                int d = findConfigAttrib(egl, display, config,\n                        EGL10.EGL_DEPTH_SIZE, 0);\n                int s = findConfigAttrib(egl, display, config,\n                        EGL10.EGL_STENCIL_SIZE, 0);\n                if ((d >= mDepthSize) && (s >= mStencilSize)) {\n                    int r = findConfigAttrib(egl, display, config,\n                            EGL10.EGL_RED_SIZE, 0);\n                    int g = findConfigAttrib(egl, display, config,\n                            EGL10.EGL_GREEN_SIZE, 0);\n                    int b = findConfigAttrib(egl, display, config,\n                            EGL10.EGL_BLUE_SIZE, 0);\n                    int a = findConfigAttrib(egl, display, config,\n                            EGL10.EGL_ALPHA_SIZE, 0);\n                    if ((r == mRedSize) && (g == mGreenSize)\n                            && (b == mBlueSize) && (a == mAlphaSize)) {\n                        return config;\n                    }\n                }\n            }\n            return null;\n        }\n\n        private int findConfigAttrib(EGL10 egl, EGLDisplay display,\n                                     EGLConfig config, int attribute, int defaultValue) {\n\n            if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) {\n                return mValue[0];\n            }\n            return defaultValue;\n        }\n\n        private int[] mValue;\n        // Subclasses can adjust these values:\n        protected int mRedSize;\n        protected int mGreenSize;\n        protected int mBlueSize;\n        protected int mAlphaSize;\n        protected int mDepthSize;\n        protected int mStencilSize;\n    }\n\n    /**\n     * This class will choose a RGB_888 surface with\n     * or without a depth buffer.\n     */\n    private class SimpleEGLConfigChooser extends ComponentSizeChooser {\n        public SimpleEGLConfigChooser(boolean withDepthBuffer) {\n            super(8, 8, 8, 0, withDepthBuffer ? 16 : 0, 0);\n        }\n    }\n\n    /**\n     * An EGL helper class.\n     */\n\n    private static class EglHelper {\n        public EglHelper(WeakReference<GLTextureView> glTextureViewWeakRef) {\n            mGLTextureViewWeakRef = glTextureViewWeakRef;\n        }\n\n        /**\n         * Initialize EGL for a given configuration spec.\n         *\n         * @param configSpec\n         */\n        public void start() {\n            if (LOG_EGL) {\n                Log.w(\"EglHelper\", \"start() tid=\" + Thread.currentThread().getId());\n            }\n            /*\n             * Get an EGL instance\n             */\n            mEgl = (EGL10) EGLContext.getEGL();\n\n            /*\n             * Get to the default display.\n             */\n            mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);\n\n            if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {\n                throw new RuntimeException(\"eglGetDisplay failed\");\n            }\n\n            /*\n             * We can now initialize EGL for that display\n             */\n            int[] version = new int[2];\n            if (!mEgl.eglInitialize(mEglDisplay, version)) {\n                throw new RuntimeException(\"eglInitialize failed\");\n            }\n            GLTextureView view = mGLTextureViewWeakRef.get();\n            if (view == null) {\n                mEglConfig = null;\n                mEglContext = null;\n            } else {\n                mEglConfig = view.mEGLConfigChooser.chooseConfig(mEgl, mEglDisplay);\n\n                /*\n                * Create an EGL context. We want to do this as rarely as we can, because an\n                * EGL context is a somewhat heavy object.\n                */\n                mEglContext = view.mEGLContextFactory.createContext(mEgl, mEglDisplay, mEglConfig);\n            }\n            if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) {\n                mEglContext = null;\n                throwEglException(\"createContext\");\n            }\n            if (LOG_EGL) {\n                Log.w(\"EglHelper\", \"createContext \" + mEglContext + \" tid=\" + Thread.currentThread().getId());\n            }\n\n            mEglSurface = null;\n        }\n\n        /**\n         * Create an egl surface for the current SurfaceHolder surface. If a surface\n         * already exists, destroy it before creating the new surface.\n         *\n         * @return true if the surface was created successfully.\n         */\n        public boolean createSurface() {\n            if (LOG_EGL) {\n                Log.w(\"EglHelper\", \"createSurface()  tid=\" + Thread.currentThread().getId());\n            }\n            /*\n             * Check preconditions.\n             */\n            if (mEgl == null) {\n                throw new RuntimeException(\"egl not initialized\");\n            }\n            if (mEglDisplay == null) {\n                throw new RuntimeException(\"eglDisplay not initialized\");\n            }\n            if (mEglConfig == null) {\n                throw new RuntimeException(\"mEglConfig not initialized\");\n            }\n\n            /*\n             *  The window size has changed, so we need to create a new\n             *  surface.\n             */\n            destroySurfaceImp();\n\n            /*\n             * Create an EGL surface we can render into.\n             */\n            GLTextureView view = mGLTextureViewWeakRef.get();\n            if (view != null) {\n                mEglSurface = view.mEGLWindowSurfaceFactory.createWindowSurface(mEgl,\n                        mEglDisplay, mEglConfig, view.getSurfaceTexture());\n            } else {\n                mEglSurface = null;\n            }\n\n            if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {\n                int error = mEgl.eglGetError();\n                if (error == EGL10.EGL_BAD_NATIVE_WINDOW) {\n                    Log.e(\"EglHelper\", \"createWindowSurface returned EGL_BAD_NATIVE_WINDOW.\");\n                }\n                return false;\n            }\n\n            /*\n             * Before we can issue GL commands, we need to make sure\n             * the context is current and bound to a surface.\n             */\n            if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {\n                /*\n                 * Could not make the context current, probably because the underlying\n                 * SurfaceView surface has been destroyed.\n                 */\n                logEglErrorAsWarning(\"EGLHelper\", \"eglMakeCurrent\", mEgl.eglGetError());\n                return false;\n            }\n\n            return true;\n        }\n\n        /**\n         * Create a GL object for the current EGL context.\n         *\n         * @return\n         */\n        GL createGL() {\n\n            GL gl = mEglContext.getGL();\n            GLTextureView view = mGLTextureViewWeakRef.get();\n            if (view != null) {\n                if (view.mGLWrapper != null) {\n                    gl = view.mGLWrapper.wrap(gl);\n                }\n\n                if ((view.mDebugFlags & (DEBUG_CHECK_GL_ERROR | DEBUG_LOG_GL_CALLS)) != 0) {\n                    int configFlags = 0;\n                    Writer log = null;\n                    if ((view.mDebugFlags & DEBUG_CHECK_GL_ERROR) != 0) {\n                        configFlags |= GLDebugHelper.CONFIG_CHECK_GL_ERROR;\n                    }\n                    if ((view.mDebugFlags & DEBUG_LOG_GL_CALLS) != 0) {\n                        log = new LogWriter();\n                    }\n                    gl = GLDebugHelper.wrap(gl, configFlags, log);\n                }\n            }\n            return gl;\n        }\n\n        /**\n         * Display the current render surface.\n         *\n         * @return the EGL error code from eglSwapBuffers.\n         */\n        public int swap() {\n            if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {\n                return mEgl.eglGetError();\n            }\n            return EGL10.EGL_SUCCESS;\n        }\n\n        public void destroySurface() {\n            if (LOG_EGL) {\n                Log.w(\"EglHelper\", \"destroySurface()  tid=\" + Thread.currentThread().getId());\n            }\n            destroySurfaceImp();\n        }\n\n        private void destroySurfaceImp() {\n            if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) {\n                mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE,\n                        EGL10.EGL_NO_SURFACE,\n                        EGL10.EGL_NO_CONTEXT);\n                GLTextureView view = mGLTextureViewWeakRef.get();\n                if (view != null) {\n                    view.mEGLWindowSurfaceFactory.destroySurface(mEgl, mEglDisplay, mEglSurface);\n                }\n                mEglSurface = null;\n            }\n        }\n\n        public void finish() {\n            if (LOG_EGL) {\n                Log.w(\"EglHelper\", \"finish() tid=\" + Thread.currentThread().getId());\n            }\n            if (mEglContext != null) {\n                GLTextureView view = mGLTextureViewWeakRef.get();\n                if (view != null) {\n                    view.mEGLContextFactory.destroyContext(mEgl, mEglDisplay, mEglContext);\n                }\n                mEglContext = null;\n            }\n            if (mEglDisplay != null) {\n                mEgl.eglTerminate(mEglDisplay);\n                mEglDisplay = null;\n            }\n        }\n\n        private void throwEglException(String function) {\n            throwEglException(function, mEgl.eglGetError());\n        }\n\n        public static void throwEglException(String function, int error) {\n            String message = formatEglError(function, error);\n            if (LOG_THREADS) {\n                Log.e(\"EglHelper\", \"throwEglException tid=\" + Thread.currentThread().getId() + \" \"\n                        + message);\n            }\n            throw new RuntimeException(message);\n        }\n\n        public static void logEglErrorAsWarning(String tag, String function, int error) {\n            Log.w(tag, formatEglError(function, error));\n        }\n\n        public static String formatEglError(String function, int error) {\n            return function + \" failed\"; // + EGLLogWrapper.getErrorString(error);\n        }\n\n        private WeakReference<GLTextureView> mGLTextureViewWeakRef;\n        EGL10 mEgl;\n        EGLDisplay mEglDisplay;\n        EGLSurface mEglSurface;\n        EGLConfig mEglConfig;\n        EGLContext mEglContext;\n\n    }\n\n    /**\n     * A generic GL Thread. Takes care of initializing EGL and GL. Delegates\n     * to a Renderer instance to do the actual drawing. Can be configured to\n     * render continuously or on request.\n     * <p>\n     * All potentially blocking synchronization is done through the\n     * sGLThreadManager object. This avoids multiple-lock ordering issues.\n     */\n    static class GLThread extends Thread {\n        GLThread(WeakReference<GLTextureView> glTextureViewWeakRef) {\n            super();\n            mWidth = 0;\n            mHeight = 0;\n            mRequestRender = true;\n            mRenderMode = RENDERMODE_CONTINUOUSLY;\n            mGLTextureViewWeakRef = glTextureViewWeakRef;\n        }\n\n        @Override\n        public void run() {\n            setName(\"GLThread \" + getId());\n            if (LOG_THREADS) {\n                Log.i(\"GLThread\", \"starting tid=\" + getId());\n            }\n\n            try {\n                guardedRun();\n            } catch (InterruptedException e) {\n                // fall thru and exit normally\n            } finally {\n                sGLThreadManager.threadExiting(this);\n            }\n        }\n\n        /*\n         * This private method should only be called inside a\n         * synchronized(sGLThreadManager) block.\n         */\n        private void stopEglSurfaceLocked() {\n            if (mHaveEglSurface) {\n                mHaveEglSurface = false;\n                mEglHelper.destroySurface();\n            }\n        }\n\n        /*\n         * This private method should only be called inside a\n         * synchronized(sGLThreadManager) block.\n         */\n        private void stopEglContextLocked() {\n            if (mHaveEglContext) {\n                mEglHelper.finish();\n                mHaveEglContext = false;\n                sGLThreadManager.releaseEglContextLocked(this);\n            }\n        }\n\n        private void guardedRun() throws InterruptedException {\n            mEglHelper = new EglHelper(mGLTextureViewWeakRef);\n            mHaveEglContext = false;\n            mHaveEglSurface = false;\n            try {\n                GL10 gl = null;\n                boolean createEglContext = false;\n                boolean createEglSurface = false;\n                boolean createGlInterface = false;\n                boolean lostEglContext = false;\n                boolean sizeChanged = false;\n                boolean wantRenderNotification = false;\n                boolean doRenderNotification = false;\n                boolean askedToReleaseEglContext = false;\n                int w = 0;\n                int h = 0;\n                Runnable event = null;\n\n                while (true) {\n                    synchronized (sGLThreadManager) {\n                        while (true) {\n                            if (mShouldExit) {\n                                return;\n                            }\n\n                            if (!mEventQueue.isEmpty()) {\n                                event = mEventQueue.remove(0);\n                                break;\n                            }\n\n                            // Update the pause state.\n                            boolean pausing = false;\n                            if (mPaused != mRequestPaused) {\n                                pausing = mRequestPaused;\n                                mPaused = mRequestPaused;\n                                sGLThreadManager.notifyAll();\n                                if (LOG_PAUSE_RESUME) {\n                                    Log.i(\"GLThread\", \"mPaused is now \" + mPaused + \" tid=\" + getId());\n                                }\n                            }\n\n                            // Do we need to give up the EGL context?\n                            if (mShouldReleaseEglContext) {\n                                if (LOG_SURFACE) {\n                                    Log.i(\"GLThread\", \"releasing EGL context because asked to tid=\" + getId());\n                                }\n                                stopEglSurfaceLocked();\n                                stopEglContextLocked();\n                                mShouldReleaseEglContext = false;\n                                askedToReleaseEglContext = true;\n                            }\n\n                            // Have we lost the EGL context?\n                            if (lostEglContext) {\n                                stopEglSurfaceLocked();\n                                stopEglContextLocked();\n                                lostEglContext = false;\n                            }\n\n                            // When pausing, release the EGL surface:\n                            if (pausing && mHaveEglSurface) {\n                                if (LOG_SURFACE) {\n                                    Log.i(\"GLThread\", \"releasing EGL surface because paused tid=\" + getId());\n                                }\n                                stopEglSurfaceLocked();\n                            }\n\n                            // When pausing, optionally release the EGL Context:\n                            if (pausing && mHaveEglContext) {\n                                GLTextureView view = mGLTextureViewWeakRef.get();\n                                boolean preserveEglContextOnPause = view == null ?\n                                        false : view.mPreserveEGLContextOnPause;\n                                if (!preserveEglContextOnPause || sGLThreadManager.shouldReleaseEGLContextWhenPausing()) {\n                                    stopEglContextLocked();\n                                    if (LOG_SURFACE) {\n                                        Log.i(\"GLThread\", \"releasing EGL context because paused tid=\" + getId());\n                                    }\n                                }\n                            }\n\n                            // When pausing, optionally terminate EGL:\n                            if (pausing) {\n                                if (sGLThreadManager.shouldTerminateEGLWhenPausing()) {\n                                    mEglHelper.finish();\n                                    if (LOG_SURFACE) {\n                                        Log.i(\"GLThread\", \"terminating EGL because paused tid=\" + getId());\n                                    }\n                                }\n                            }\n\n                            // Have we lost the SurfaceView surface?\n                            if ((!mHasSurface) && (!mWaitingForSurface)) {\n                                if (LOG_SURFACE) {\n                                    Log.i(\"GLThread\", \"noticed surfaceView surface lost tid=\" + getId());\n                                }\n                                if (mHaveEglSurface) {\n                                    stopEglSurfaceLocked();\n                                }\n                                mWaitingForSurface = true;\n                                mSurfaceIsBad = false;\n                                sGLThreadManager.notifyAll();\n                            }\n\n                            // Have we acquired the surface view surface?\n                            if (mHasSurface && mWaitingForSurface) {\n                                if (LOG_SURFACE) {\n                                    Log.i(\"GLThread\", \"noticed surfaceView surface acquired tid=\" + getId());\n                                }\n                                mWaitingForSurface = false;\n                                sGLThreadManager.notifyAll();\n                            }\n\n                            if (doRenderNotification) {\n                                if (LOG_SURFACE) {\n                                    Log.i(\"GLThread\", \"sending render notification tid=\" + getId());\n                                }\n                                wantRenderNotification = false;\n                                doRenderNotification = false;\n                                mRenderComplete = true;\n                                sGLThreadManager.notifyAll();\n                            }\n\n                            // Ready to draw?\n                            if (readyToDraw()) {\n\n                                // If we don't have an EGL context, try to acquire one.\n                                if (!mHaveEglContext) {\n                                    if (askedToReleaseEglContext) {\n                                        askedToReleaseEglContext = false;\n                                    } else if (sGLThreadManager.tryAcquireEglContextLocked(this)) {\n                                        try {\n                                            mEglHelper.start();\n                                        } catch (RuntimeException t) {\n                                            sGLThreadManager.releaseEglContextLocked(this);\n                                            throw t;\n                                        }\n                                        mHaveEglContext = true;\n                                        createEglContext = true;\n\n                                        sGLThreadManager.notifyAll();\n                                    }\n                                }\n\n                                if (mHaveEglContext && !mHaveEglSurface) {\n                                    mHaveEglSurface = true;\n                                    createEglSurface = true;\n                                    createGlInterface = true;\n                                    sizeChanged = true;\n                                }\n\n                                if (mHaveEglSurface) {\n                                    if (mSizeChanged) {\n                                        sizeChanged = true;\n                                        w = mWidth;\n                                        h = mHeight;\n                                        wantRenderNotification = true;\n                                        if (LOG_SURFACE) {\n                                            Log.i(\"GLThread\",\n                                                    \"noticing that we want render notification tid=\"\n                                                            + getId());\n                                        }\n\n                                        // Destroy and recreate the EGL surface.\n                                        createEglSurface = true;\n\n                                        mSizeChanged = false;\n                                    }\n                                    mRequestRender = false;\n                                    sGLThreadManager.notifyAll();\n                                    break;\n                                }\n                            }\n\n                            // By design, this is the only place in a GLThread thread where we wait().\n                            if (LOG_THREADS) {\n                                Log.i(\"GLThread\", \"waiting tid=\" + getId()\n                                        + \" mHaveEglContext: \" + mHaveEglContext\n                                        + \" mHaveEglSurface: \" + mHaveEglSurface\n                                        + \" mFinishedCreatingEglSurface: \" + mFinishedCreatingEglSurface\n                                        + \" mPaused: \" + mPaused\n                                        + \" mHasSurface: \" + mHasSurface\n                                        + \" mSurfaceIsBad: \" + mSurfaceIsBad\n                                        + \" mWaitingForSurface: \" + mWaitingForSurface\n                                        + \" mWidth: \" + mWidth\n                                        + \" mHeight: \" + mHeight\n                                        + \" mRequestRender: \" + mRequestRender\n                                        + \" mRenderMode: \" + mRenderMode);\n                            }\n                            sGLThreadManager.wait();\n                        }\n                    } // end of synchronized(sGLThreadManager)\n\n                    if (event != null) {\n                        event.run();\n                        event = null;\n                        continue;\n                    }\n\n                    if (createEglSurface) {\n                        if (LOG_SURFACE) {\n                            Log.w(\"GLThread\", \"egl createSurface\");\n                        }\n                        if (mEglHelper.createSurface()) {\n                            synchronized (sGLThreadManager) {\n                                mFinishedCreatingEglSurface = true;\n                                sGLThreadManager.notifyAll();\n                            }\n                        } else {\n                            synchronized (sGLThreadManager) {\n                                mFinishedCreatingEglSurface = true;\n                                mSurfaceIsBad = true;\n                                sGLThreadManager.notifyAll();\n                            }\n                            continue;\n                        }\n                        createEglSurface = false;\n                    }\n\n                    if (createGlInterface) {\n                        gl = (GL10) mEglHelper.createGL();\n\n                        sGLThreadManager.checkGLDriver(gl);\n                        createGlInterface = false;\n                    }\n\n                    if (createEglContext) {\n                        if (LOG_RENDERER) {\n                            Log.w(\"GLThread\", \"onSurfaceCreated\");\n                        }\n                        GLTextureView view = mGLTextureViewWeakRef.get();\n                        if (view != null) {\n                            view.mRenderer.onSurfaceCreated(gl, mEglHelper.mEglConfig);\n                        }\n                        createEglContext = false;\n                    }\n\n                    if (sizeChanged) {\n                        if (LOG_RENDERER) {\n                            Log.w(\"GLThread\", \"onSurfaceChanged(\" + w + \", \" + h + \")\");\n                        }\n                        GLTextureView view = mGLTextureViewWeakRef.get();\n                        if (view != null) {\n                            view.mRenderer.onSurfaceChanged(gl, w, h);\n                        }\n                        sizeChanged = false;\n                    }\n\n                    if (LOG_RENDERER_DRAW_FRAME) {\n                        Log.w(\"GLThread\", \"onDrawFrame tid=\" + getId());\n                    }\n                    {\n                        GLTextureView view = mGLTextureViewWeakRef.get();\n                        if (view != null) {\n                            view.mRenderer.onDrawFrame(gl);\n                        }\n                    }\n                    int swapError = mEglHelper.swap();\n                    switch (swapError) {\n                        case EGL10.EGL_SUCCESS:\n                            break;\n                        case EGL11.EGL_CONTEXT_LOST:\n                            if (LOG_SURFACE) {\n                                Log.i(\"GLThread\", \"egl context lost tid=\" + getId());\n                            }\n                            lostEglContext = true;\n                            break;\n                        default:\n                            // Other errors typically mean that the current surface is bad,\n                            // probably because the SurfaceView surface has been destroyed,\n                            // but we haven't been notified yet.\n                            // Log the error to help developers understand why rendering stopped.\n                            EglHelper.logEglErrorAsWarning(\"GLThread\", \"eglSwapBuffers\", swapError);\n\n                            synchronized (sGLThreadManager) {\n                                mSurfaceIsBad = true;\n                                sGLThreadManager.notifyAll();\n                            }\n                            break;\n                    }\n\n                    if (wantRenderNotification) {\n                        doRenderNotification = true;\n                    }\n                }\n\n            } finally {\n                /*\n                 * clean-up everything...\n                 */\n                synchronized (sGLThreadManager) {\n                    stopEglSurfaceLocked();\n                    stopEglContextLocked();\n                }\n            }\n        }\n\n        public boolean ableToDraw() {\n            return mHaveEglContext && mHaveEglSurface && readyToDraw();\n        }\n\n        private boolean readyToDraw() {\n            return (!mPaused) && mHasSurface && (!mSurfaceIsBad)\n                    && (mWidth > 0) && (mHeight > 0)\n                    && (mRequestRender || (mRenderMode == RENDERMODE_CONTINUOUSLY));\n        }\n\n        public void setRenderMode(int renderMode) {\n            if (!((RENDERMODE_WHEN_DIRTY <= renderMode) && (renderMode <= RENDERMODE_CONTINUOUSLY))) {\n                throw new IllegalArgumentException(\"renderMode\");\n            }\n            synchronized (sGLThreadManager) {\n                mRenderMode = renderMode;\n                sGLThreadManager.notifyAll();\n            }\n        }\n\n        public int getRenderMode() {\n            synchronized (sGLThreadManager) {\n                return mRenderMode;\n            }\n        }\n\n        public void requestRender() {\n            synchronized (sGLThreadManager) {\n                mRequestRender = true;\n                sGLThreadManager.notifyAll();\n            }\n        }\n\n        public void surfaceCreated() {\n            synchronized (sGLThreadManager) {\n                if (LOG_THREADS) {\n                    Log.i(\"GLThread\", \"surfaceCreated tid=\" + getId());\n                }\n                mHasSurface = true;\n                mFinishedCreatingEglSurface = false;\n                sGLThreadManager.notifyAll();\n                while (mWaitingForSurface\n                        && !mFinishedCreatingEglSurface\n                        && !mExited) {\n                    try {\n                        sGLThreadManager.wait();\n                    } catch (InterruptedException e) {\n                        Thread.currentThread().interrupt();\n                    }\n                }\n            }\n        }\n\n        public void surfaceDestroyed() {\n            synchronized (sGLThreadManager) {\n                if (LOG_THREADS) {\n                    Log.i(\"GLThread\", \"surfaceDestroyed tid=\" + getId());\n                }\n                mHasSurface = false;\n                sGLThreadManager.notifyAll();\n                while ((!mWaitingForSurface) && (!mExited)) {\n                    try {\n                        sGLThreadManager.wait();\n                    } catch (InterruptedException e) {\n                        Thread.currentThread().interrupt();\n                    }\n                }\n            }\n        }\n\n        public void onPause() {\n            synchronized (sGLThreadManager) {\n                if (LOG_PAUSE_RESUME) {\n                    Log.i(\"GLThread\", \"onPause tid=\" + getId());\n                }\n                mRequestPaused = true;\n                sGLThreadManager.notifyAll();\n                while ((!mExited) && (!mPaused)) {\n                    if (LOG_PAUSE_RESUME) {\n                        Log.i(\"Main thread\", \"onPause waiting for mPaused.\");\n                    }\n                    try {\n                        sGLThreadManager.wait();\n                    } catch (InterruptedException ex) {\n                        Thread.currentThread().interrupt();\n                    }\n                }\n            }\n        }\n\n        public void onResume() {\n            synchronized (sGLThreadManager) {\n                if (LOG_PAUSE_RESUME) {\n                    Log.i(\"GLThread\", \"onResume tid=\" + getId());\n                }\n                mRequestPaused = false;\n                mRequestRender = true;\n                mRenderComplete = false;\n                sGLThreadManager.notifyAll();\n                while ((!mExited) && mPaused && (!mRenderComplete)) {\n                    if (LOG_PAUSE_RESUME) {\n                        Log.i(\"Main thread\", \"onResume waiting for !mPaused.\");\n                    }\n                    try {\n                        sGLThreadManager.wait();\n                    } catch (InterruptedException ex) {\n                        Thread.currentThread().interrupt();\n                    }\n                }\n            }\n        }\n\n        public void onWindowResize(int w, int h) {\n            synchronized (sGLThreadManager) {\n                mWidth = w;\n                mHeight = h;\n                mSizeChanged = true;\n                mRequestRender = true;\n                mRenderComplete = false;\n                sGLThreadManager.notifyAll();\n\n                // Wait for thread to react to resize and render a frame\n                while (!mExited && !mPaused && !mRenderComplete\n                        && ableToDraw()) {\n                    if (LOG_SURFACE) {\n                        Log.i(\"Main thread\", \"onWindowResize waiting for render complete from tid=\" + getId());\n                    }\n                    try {\n                        sGLThreadManager.wait();\n                    } catch (InterruptedException ex) {\n                        Thread.currentThread().interrupt();\n                    }\n                }\n            }\n        }\n\n        public void requestExitAndWait() {\n            // don't call this from GLThread thread or it is a guaranteed\n            // deadlock!\n            synchronized (sGLThreadManager) {\n                mShouldExit = true;\n                sGLThreadManager.notifyAll();\n                while (!mExited) {\n                    try {\n                        sGLThreadManager.wait();\n                    } catch (InterruptedException ex) {\n                        Thread.currentThread().interrupt();\n                    }\n                }\n            }\n        }\n\n        public void requestReleaseEglContextLocked() {\n            mShouldReleaseEglContext = true;\n            sGLThreadManager.notifyAll();\n        }\n\n        /**\n         * Queue an \"event\" to be run on the GL rendering thread.\n         *\n         * @param r the runnable to be run on the GL rendering thread.\n         */\n        public void queueEvent(Runnable r) {\n            if (r == null) {\n                throw new IllegalArgumentException(\"r must not be null\");\n            }\n            synchronized (sGLThreadManager) {\n                mEventQueue.add(r);\n                sGLThreadManager.notifyAll();\n            }\n        }\n\n        // Once the thread is started, all accesses to the following member\n        // variables are protected by the sGLThreadManager monitor\n        private boolean mShouldExit;\n        private boolean mExited;\n        private boolean mRequestPaused;\n        private boolean mPaused;\n        private boolean mHasSurface;\n        private boolean mSurfaceIsBad;\n        private boolean mWaitingForSurface;\n        private boolean mHaveEglContext;\n        private boolean mHaveEglSurface;\n        private boolean mFinishedCreatingEglSurface;\n        private boolean mShouldReleaseEglContext;\n        private int mWidth;\n        private int mHeight;\n        private int mRenderMode;\n        private boolean mRequestRender;\n        private boolean mRenderComplete;\n        private ArrayList<Runnable> mEventQueue = new ArrayList<Runnable>();\n        private boolean mSizeChanged = true;\n\n        // End of member variables protected by the sGLThreadManager monitor.\n\n        private EglHelper mEglHelper;\n\n        /**\n         * Set once at thread construction time, nulled out when the parent view is garbage\n         * called. This weak reference allows the GLSurfaceView to be garbage collected while\n         * the GLThread is still alive.\n         */\n        private WeakReference<GLTextureView> mGLTextureViewWeakRef;\n\n    }\n\n    static class LogWriter extends Writer {\n\n        @Override\n        public void close() {\n            flushBuilder();\n        }\n\n        @Override\n        public void flush() {\n            flushBuilder();\n        }\n\n        @Override\n        public void write(char[] buf, int offset, int count) {\n            for (int i = 0; i < count; i++) {\n                char c = buf[offset + i];\n                if (c == '\\n') {\n                    flushBuilder();\n                } else {\n                    mBuilder.append(c);\n                }\n            }\n        }\n\n        private void flushBuilder() {\n            if (mBuilder.length() > 0) {\n                Log.v(\"GLSurfaceView\", mBuilder.toString());\n                mBuilder.delete(0, mBuilder.length());\n            }\n        }\n\n        private StringBuilder mBuilder = new StringBuilder();\n    }\n\n\n    private void checkRenderThreadState() {\n        if (mGLThread != null) {\n            throw new IllegalStateException(\n                    \"setRenderer has already been called for this instance.\");\n        }\n    }\n\n    private static class GLThreadManager {\n        private static String TAG = \"GLThreadManager\";\n\n        public synchronized void threadExiting(GLThread thread) {\n            if (LOG_THREADS) {\n                Log.i(\"GLThread\", \"exiting tid=\" + thread.getId());\n            }\n            thread.mExited = true;\n            if (mEglOwner == thread) {\n                mEglOwner = null;\n            }\n            notifyAll();\n        }\n\n        /*\n         * Tries once to acquire the right to use an EGL\n         * context. Does not block. Requires that we are already\n         * in the sGLThreadManager monitor when this is called.\n         *\n         * @return true if the right to use an EGL context was acquired.\n         */\n        public boolean tryAcquireEglContextLocked(GLThread thread) {\n            if (mEglOwner == thread || mEglOwner == null) {\n                mEglOwner = thread;\n                notifyAll();\n                return true;\n            }\n            checkGLESVersion();\n            if (mMultipleGLESContextsAllowed) {\n                return true;\n            }\n            // Notify the owning thread that it should release the context.\n            // TODO: implement a fairness policy. Currently\n            // if the owning thread is drawing continuously it will just\n            // reacquire the EGL context.\n            if (mEglOwner != null) {\n                mEglOwner.requestReleaseEglContextLocked();\n            }\n            return false;\n        }\n\n        /*\n         * Releases the EGL context. Requires that we are already in the\n         * sGLThreadManager monitor when this is called.\n         */\n        public void releaseEglContextLocked(GLThread thread) {\n            if (mEglOwner == thread) {\n                mEglOwner = null;\n            }\n            notifyAll();\n        }\n\n        public synchronized boolean shouldReleaseEGLContextWhenPausing() {\n            // Release the EGL context when pausing even if\n            // the hardware supports multiple EGL contexts.\n            // Otherwise the device could run out of EGL contexts.\n            return mLimitedGLESContexts;\n        }\n\n        public synchronized boolean shouldTerminateEGLWhenPausing() {\n            checkGLESVersion();\n            return !mMultipleGLESContextsAllowed;\n        }\n\n        public synchronized void checkGLDriver(GL10 gl) {\n            if (!mGLESDriverCheckComplete) {\n                checkGLESVersion();\n                String renderer = gl.glGetString(GL10.GL_RENDERER);\n                if (mGLESVersion < kGLES_20) {\n                    mMultipleGLESContextsAllowed =\n                            !renderer.startsWith(kMSM7K_RENDERER_PREFIX);\n                    notifyAll();\n                }\n                mLimitedGLESContexts = !mMultipleGLESContextsAllowed;\n                if (LOG_SURFACE) {\n                    Log.w(TAG, \"checkGLDriver renderer = \\\"\" + renderer + \"\\\" multipleContextsAllowed = \"\n                            + mMultipleGLESContextsAllowed\n                            + \" mLimitedGLESContexts = \" + mLimitedGLESContexts);\n                }\n                mGLESDriverCheckComplete = true;\n            }\n        }\n\n        private void checkGLESVersion() {\n            if (!mGLESVersionCheckComplete) {\n//                mGLESVersion = SystemProperties.getInt(\n//                        \"ro.opengles.version\",\n//                        ConfigurationInfo.GL_ES_VERSION_UNDEFINED);\n//                if (mGLESVersion >= kGLES_20) {\n                mMultipleGLESContextsAllowed = true;\n//                }\n                if (LOG_SURFACE) {\n                    Log.w(TAG, \"checkGLESVersion mGLESVersion =\" +\n                            \" \" + mGLESVersion + \" mMultipleGLESContextsAllowed = \" + mMultipleGLESContextsAllowed);\n                }\n                mGLESVersionCheckComplete = true;\n            }\n        }\n\n        /**\n         * This check was required for some pre-Android-3.0 hardware. Android 3.0 provides\n         * support for hardware-accelerated views, therefore multiple EGL contexts are\n         * supported on all Android 3.0+ EGL drivers.\n         */\n        private boolean mGLESVersionCheckComplete;\n        private int mGLESVersion;\n        private boolean mGLESDriverCheckComplete;\n        private boolean mMultipleGLESContextsAllowed;\n        private boolean mLimitedGLESContexts;\n        private static final int kGLES_20 = 0x20000;\n        private static final String kMSM7K_RENDERER_PREFIX =\n                \"Q3Dimension MSM7500 \";\n        private GLThread mEglOwner;\n    }\n\n    private static final GLThreadManager sGLThreadManager = new GLThreadManager();\n\n    private final WeakReference<GLTextureView> mThisWeakRef =\n            new WeakReference<GLTextureView>(this);\n    private GLThread mGLThread;\n    private GLSurfaceView.Renderer mRenderer;\n    private boolean mDetached;\n    private EGLConfigChooser mEGLConfigChooser;\n    private EGLContextFactory mEGLContextFactory;\n    private EGLWindowSurfaceFactory mEGLWindowSurfaceFactory;\n    private GLWrapper mGLWrapper;\n    private int mDebugFlags;\n    private int mEGLContextClientVersion;\n    private boolean mPreserveEGLContextOnPause;\n}\n\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/render/GLUtil.java",
    "content": "package com.yalin.style.render;\n\nimport android.graphics.Bitmap;\nimport android.opengl.GLES20;\nimport android.opengl.GLUtils;\nimport android.util.Log;\nimport com.yalin.style.BuildConfig;\nimport java.nio.ByteBuffer;\nimport java.nio.ByteOrder;\nimport java.nio.FloatBuffer;\n\n/**\n * YaLin 2016/12/30.\n */\n\npublic class GLUtil {\n\n  private static final String TAG = \"GLUtil\";\n\n  public static final int BYTES_PER_FLOAT = 4;\n\n  public static int loadShader(int type, String shaderCode) {\n    // create a vertex shader type (GLES20.GL_VERTEX_SHADER)\n    // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)\n    int shaderHandle = GLES20.glCreateShader(type);\n\n    // add the source code to the shader and compile it\n    GLES20.glShaderSource(shaderHandle, shaderCode);\n    GLES20.glCompileShader(shaderHandle);\n    checkGlError(\"glCompileShader\");\n    return shaderHandle;\n  }\n\n  public static int createAndLinkProgram(int vertexShaderHandle, int fragShaderHandle,\n      String[] attributes) {\n    int programHandle = GLES20.glCreateProgram();\n    GLUtil.checkGlError(\"glCreateProgram\");\n    GLES20.glAttachShader(programHandle, vertexShaderHandle);\n    GLES20.glAttachShader(programHandle, fragShaderHandle);\n    if (attributes != null) {\n      final int size = attributes.length;\n      for (int i = 0; i < size; i++) {\n        GLES20.glBindAttribLocation(programHandle, i, attributes[i]);\n      }\n    }\n    GLES20.glLinkProgram(programHandle);\n    GLUtil.checkGlError(\"glLinkProgram\");\n    GLES20.glDeleteShader(vertexShaderHandle);\n    GLES20.glDeleteShader(fragShaderHandle);\n    return programHandle;\n  }\n\n  public static int loadTexture(Bitmap bitmap) {\n    final int[] textureHandle = new int[1];\n\n    GLES20.glGenTextures(1, textureHandle, 0);\n    GLUtil.checkGlError(\"glGenTextures\");\n\n    if (textureHandle[0] != 0) {\n      // Bind to the texture in OpenGL\n      GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle[0]);\n\n      // Set filtering\n      GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,\n          GLES20.GL_CLAMP_TO_EDGE);\n      GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,\n          GLES20.GL_CLAMP_TO_EDGE);\n      GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,\n          GLES20.GL_LINEAR);\n      GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER,\n          GLES20.GL_LINEAR);\n\n      // Load the bitmap into the bound texture.\n      GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);\n      GLUtil.checkGlError(\"texImage2D\");\n    }\n\n    if (textureHandle[0] == 0) {\n      Log.e(TAG, \"Error loading texture (empty texture handle)\");\n      if (BuildConfig.DEBUG) {\n        throw new RuntimeException(\"Error loading texture (empty texture handle).\");\n      }\n    }\n\n    return textureHandle[0];\n  }\n\n  public static void checkGlError(String glOperation) {\n    int error;\n    while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {\n      Log.e(TAG, glOperation + \": glError \" + error);\n      if (BuildConfig.DEBUG) {\n        throw new RuntimeException(glOperation + \": glError \" + error);\n      }\n    }\n  }\n\n  public static FloatBuffer asFloatBuffer(float[] array) {\n    FloatBuffer buffer = newFloatBuffer(array.length);\n    buffer.put(array);\n    buffer.position(0);\n    return buffer;\n  }\n\n  public static FloatBuffer newFloatBuffer(int size) {\n    FloatBuffer buffer = ByteBuffer.allocateDirect(size * BYTES_PER_FLOAT)\n        .order(ByteOrder.nativeOrder())\n        .asFloatBuffer();\n    buffer.position(0);\n    return buffer;\n  }\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/render/ImageBlurrer.java",
    "content": "/*\n * Copyright 2014 Google Inc.\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\npackage com.yalin.style.render;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.renderscript.Allocation;\nimport android.renderscript.Element;\nimport android.renderscript.Matrix3f;\nimport android.renderscript.RenderScript;\nimport android.renderscript.ScriptIntrinsicBlur;\nimport android.renderscript.ScriptIntrinsicColorMatrix;\n\nimport com.yalin.style.util.MathUtil;\n\npublic class ImageBlurrer {\n    public static final int MAX_SUPPORTED_BLUR_PIXELS = 25;\n\n    private final RenderScript mRS;\n    private final ScriptIntrinsicBlur mSIBlur;\n    private final ScriptIntrinsicColorMatrix mSIGrey;\n    private final Bitmap mSourceBitmap;\n    private final Allocation mAllocationSrc;\n\n    public ImageBlurrer(Context context, Bitmap src) {\n        mRS = RenderScript.create(context);\n        mSIBlur = ScriptIntrinsicBlur.create(mRS, Element.U8_4(mRS));\n        mSIGrey = ScriptIntrinsicColorMatrix.create(mRS);\n\n        mSourceBitmap = src;\n        mAllocationSrc = src != null ? Allocation.createFromBitmap(mRS, src) : null;\n    }\n\n    public Bitmap blurBitmap(float radius, float desaturateAmount) {\n        if (mSourceBitmap == null) {\n            return null;\n        }\n\n        Bitmap dest = mSourceBitmap.copy(mSourceBitmap.getConfig(), true);\n        if (radius == 0f && desaturateAmount == 0f) {\n            return dest;\n        }\n\n        Allocation allocationDest = Allocation.createFromBitmap(mRS, dest);\n\n        if (radius > 0f && desaturateAmount > 0f) {\n            doBlur(radius, mAllocationSrc, allocationDest);\n            doDesaturate(MathUtil.constrain(0, 1, desaturateAmount), allocationDest, mAllocationSrc);\n            mAllocationSrc.copyTo(dest);\n        } else if (radius > 0f) {\n            doBlur(radius, mAllocationSrc, allocationDest);\n            allocationDest.copyTo(dest);\n        } else {\n            doDesaturate(MathUtil.constrain(0, 1, desaturateAmount), mAllocationSrc, allocationDest);\n            allocationDest.copyTo(dest);\n        }\n        allocationDest.destroy();\n        return dest;\n    }\n\n    private void doBlur(float amount, Allocation input, Allocation output) {\n        mSIBlur.setRadius(amount);\n        mSIBlur.setInput(input);\n        mSIBlur.forEach(output);\n    }\n\n    private void doDesaturate(float normalizedAmount, Allocation input, Allocation output) {\n        Matrix3f m = new Matrix3f(new float[]{\n                MathUtil.interpolate(1, 0.299f, normalizedAmount),\n                MathUtil.interpolate(0, 0.299f, normalizedAmount),\n                MathUtil.interpolate(0, 0.299f, normalizedAmount),\n\n                MathUtil.interpolate(0, 0.587f, normalizedAmount),\n                MathUtil.interpolate(1, 0.587f, normalizedAmount),\n                MathUtil.interpolate(0, 0.587f, normalizedAmount),\n\n                MathUtil.interpolate(0, 0.114f, normalizedAmount),\n                MathUtil.interpolate(0, 0.114f, normalizedAmount),\n                MathUtil.interpolate(1, 0.114f, normalizedAmount),\n        });\n        mSIGrey.setColorMatrix(m);\n        mSIGrey.forEach(input, output);\n    }\n\n    public void destroy() {\n        mSIBlur.destroy();\n        mSIGrey.destroy();\n        if (mAllocationSrc != null) {\n            mAllocationSrc.destroy();\n        }\n        mRS.destroy();\n    }\n}"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/render/ImageUtil.java",
    "content": "/*\n * Copyright 2014 Google Inc.\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\npackage com.yalin.style.render;\n\nimport android.graphics.Bitmap;\nimport android.graphics.Color;\n\npublic class ImageUtil {\n    // Make sure input images are very small!\n    public static float calculateDarkness(Bitmap bitmap) {\n        if (bitmap == null) {\n            return 0;\n        }\n\n        int width = bitmap.getWidth();\n        int height = bitmap.getHeight();\n\n        int totalLum = 0;\n        int n = 0;\n        int x, y, color;\n        for (y = 0; y < height; y++) {\n            for (x = 0; x < width; x++) {\n                ++n;\n                color = bitmap.getPixel(x, y);\n                totalLum += (0.21f * Color.red(color)\n                        + 0.71f * Color.green(color)\n                        + 0.07f * Color.blue(color));\n            }\n        }\n\n        return (totalLum / n) / 256f;\n    }\n\n    private ImageUtil() {\n    }\n\n    public static int calculateSampleSize(int rawSize, int targetSize) {\n        int sampleSize = 1;\n        while (rawSize / (sampleSize << 1) > targetSize) {\n            sampleSize <<= 1;\n        }\n        return sampleSize;\n    }\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/render/RenderController.kt",
    "content": "package com.yalin.style.render\n\nimport android.content.Context\nimport android.content.SharedPreferences\nimport android.os.Handler\nimport android.os.Message\n\nimport com.yalin.style.data.log.LogUtil\nimport com.yalin.style.domain.Wallpaper\nimport com.yalin.style.domain.interactor.DefaultObserver\nimport com.yalin.style.domain.interactor.GetWallpaper\nimport com.yalin.style.domain.interactor.ObserverWallpaper\nimport com.yalin.style.domain.interactor.OpenWallpaperInputStream\nimport com.yalin.style.mapper.WallpaperItemMapper\nimport com.yalin.style.settings.Prefs\n\nimport java.io.InputStream\n\nimport javax.inject.Inject\n\n/**\n * YaLin 2016/12/30.\n */\n\nopen class RenderController @Inject\nconstructor(protected var mContext: Context, private val getWallpaperUseCase: GetWallpaper,\n            private val observerWallpaper: ObserverWallpaper,\n            private val openWallpaperInputStreamUseCase: OpenWallpaperInputStream,\n            private val wallpaperItemMapper: WallpaperItemMapper) {\n    protected var mRenderer: StyleBlurRenderer? = null\n    protected var mCallbacks: Callbacks? = null\n    protected var mVisible: Boolean = false\n    private var mQueuedBitmapRegionLoader: BitmapRegionLoader? = null\n    private val wallpaperRefreshObserver: WallpaperRefreshObserver\n    private val mOnSharedPreferenceChangeListener =\n            SharedPreferences.OnSharedPreferenceChangeListener { _, key ->\n                mRenderer?.apply {\n                    if (Prefs.PREF_BLUR_AMOUNT == key) {\n                        recomputeMaxPrescaledBlurPixels()\n                        throttledForceReloadCurrentArtwork()\n                    } else if (Prefs.PREF_DIM_AMOUNT == key) {\n                        recomputeMaxDimAmount()\n                        throttledForceReloadCurrentArtwork()\n                    } else if (Prefs.PREF_GREY_AMOUNT == key) {\n                        recomputeGreyAmount()\n                        throttledForceReloadCurrentArtwork()\n                    }\n                }\n            }\n\n    init {\n        wallpaperRefreshObserver = WallpaperRefreshObserver()\n        observerWallpaper.registerObserver(wallpaperRefreshObserver)\n\n        Prefs.getSharedPreferences(mContext)\n                .registerOnSharedPreferenceChangeListener(mOnSharedPreferenceChangeListener)\n    }\n\n    fun setComponent(renderer: StyleBlurRenderer, callbacks: Callbacks) {\n        this.mRenderer = renderer\n        this.mCallbacks = callbacks\n        reloadCurrentWallpaper()\n    }\n\n    open fun destroy() {\n        if (mQueuedBitmapRegionLoader != null) {\n            mQueuedBitmapRegionLoader!!.destroy()\n        }\n        Prefs.getSharedPreferences(mContext)\n                .unregisterOnSharedPreferenceChangeListener(mOnSharedPreferenceChangeListener)\n        observerWallpaper.unregisterObserver(wallpaperRefreshObserver)\n    }\n\n    @Throws(Exception::class)\n    private fun createBitmapRegionLoader(inputStream: InputStream): BitmapRegionLoader {\n        val bitmapRegionLoader = BitmapRegionLoader.newInstance(inputStream) ?:\n                throw IllegalStateException(\"Bitmap region loader create failed.\")\n        return bitmapRegionLoader\n    }\n\n    fun reloadCurrentWallpaper() {\n        getWallpaperUseCase.execute(WallpaperItemObserver(), null)\n    }\n\n    fun setVisible(visible: Boolean) {\n        mVisible = visible\n        if (visible) {\n            mCallbacks?.apply {\n                queueEventOnGlThread(Runnable {\n                    if (mQueuedBitmapRegionLoader != null) {\n                        mRenderer!!.setAndConsumeBitmapRegionLoader(mQueuedBitmapRegionLoader)\n                        mQueuedBitmapRegionLoader = null\n                    }\n                })\n                requestRender()\n            }\n        }\n    }\n\n    private fun setBitmapRegionLoader(bitmapRegionLoader: BitmapRegionLoader) {\n        mCallbacks!!.queueEventOnGlThread(Runnable {\n            if (mVisible) {\n                mRenderer!!.setAndConsumeBitmapRegionLoader(bitmapRegionLoader)\n            } else {\n                mQueuedBitmapRegionLoader = bitmapRegionLoader\n            }\n        })\n    }\n\n    private fun throttledForceReloadCurrentArtwork() {\n        mThrottledForceReloadHandler.removeMessages(0)\n        mThrottledForceReloadHandler.sendEmptyMessageDelayed(0, 250)\n    }\n\n    private val mThrottledForceReloadHandler = object : Handler() {\n        override fun handleMessage(msg: Message) {\n            reloadCurrentWallpaper()\n        }\n    }\n\n    private inner class WallpaperItemObserver : DefaultObserver<Wallpaper>() {\n        override fun onNext(wallpaper: Wallpaper) {\n            val wallpaperItem = wallpaperItemMapper.transform(wallpaper)\n            openWallpaperInputStreamUseCase.execute(WallpaperInputStreamObserver(),\n                    OpenWallpaperInputStream.Params.openInputStream(wallpaperItem.wallpaperId))\n        }\n\n        override fun onError(exception: Throwable) {\n            LogUtil.E(TAG, \"Load wallpaper failed.\", exception)\n        }\n    }\n\n\n    private inner class WallpaperInputStreamObserver : DefaultObserver<InputStream>() {\n        override fun onNext(inputStream: InputStream) {\n            try {\n                val bitmapRegionLoader = createBitmapRegionLoader(inputStream)\n                LogUtil.D(TAG, \"Create bitmap region loader success.\")\n                setBitmapRegionLoader(bitmapRegionLoader)\n            } catch (e: Exception) {\n                onError(e)\n            }\n\n        }\n\n        override fun onError(exception: Throwable) {\n            LogUtil.E(TAG, \"Open input stream failed. \", exception)\n        }\n    }\n\n    private inner class WallpaperRefreshObserver : DefaultObserver<Void>() {\n        override fun onComplete() {\n            LogUtil.D(TAG, \"Wallpaper update,reload wallpaper.\")\n            reloadCurrentWallpaper()\n        }\n    }\n\n    interface Callbacks {\n\n        fun queueEventOnGlThread(runnable: Runnable)\n\n        fun requestRender()\n    }\n\n    companion object {\n        private val TAG = \"RenderController\"\n    }\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/render/StyleBlurRenderer.java",
    "content": "package com.yalin.style.render;\n\nimport android.app.ActivityManager;\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.graphics.Color;\nimport android.graphics.Rect;\nimport android.graphics.RectF;\nimport android.opengl.GLES20;\nimport android.opengl.GLSurfaceView;\nimport android.opengl.Matrix;\nimport android.util.DisplayMetrics;\nimport android.util.Log;\nimport android.view.animation.AccelerateDecelerateInterpolator;\nimport android.view.animation.Interpolator;\n\nimport com.yalin.style.WallpaperDetailViewport;\nimport com.yalin.style.event.StyleWallpaperSizeChangedEvent;\nimport com.yalin.style.event.SwitchingPhotosStateChangedEvent;\nimport com.yalin.style.settings.Prefs;\nimport com.yalin.style.util.MathUtil;\n\nimport org.greenrobot.eventbus.EventBus;\n\nimport javax.microedition.khronos.egl.EGLConfig;\nimport javax.microedition.khronos.opengles.GL10;\n\n/**\n * YaLin 2016/12/30.\n */\n\npublic class StyleBlurRenderer implements GLSurfaceView.Renderer {\n    private static final String TAG = \"StyleBlurRenderer\";\n\n    private static final int CROSSFADE_ANIMATION_DURATION = 750;\n    private static final int BLUR_ANIMATION_DURATION = 750;\n\n    public static final int DEFAULT_BLUR = 150; // max 500\n    public static final int DEFAULT_GREY = 0; // max 500\n    public static final int DEMO_BLUR = 250;\n    public static final int DEMO_DIM = 64;\n    public static final int DEMO_GREY = 0;\n    public static final int DEFAULT_MAX_DIM = 128; // technical max 255\n    public static final float DIM_RANGE = 0.5f; // percent of max dim\n\n    private boolean mDemoMode;\n    private boolean mPreview;\n    private int mMaxPrescaledBlurPixels;\n    private int mBlurKeyframes;\n    private int mBlurredSampleSize;\n    private int mMaxDim;\n    private int mMaxGrey;\n\n    // Model and view matrices. Projection and MVP stored in picture set\n    private final float[] mMMatrix = new float[16];\n    private final float[] mVMatrix = new float[16];\n\n    private Callbacks mCallbacks;\n\n    private float mAspectRatio;\n    private int mHeight;\n\n    private GLPictureSet mCurrentGLPictureSet;\n    private GLPictureSet mNextGLPictureSet;\n    private GLColorOverlay mColorOverlay;\n\n    private BitmapRegionLoader mQueuedNextBitmapRegionLoader;\n\n    private boolean mSurfaceCreated;\n\n    private volatile float mNormalOffsetX;\n    private volatile RectF mCurrentViewport = new RectF(); // [-1, -1] to [1, 1], flipped\n\n    private Context mContext;\n\n    private boolean mIsBlurred = true;\n    private boolean mBlurRelatedToArtDetailMode = false;\n    private Interpolator mBlurInterpolator = new AccelerateDecelerateInterpolator();\n    private TickingFloatAnimator mBlurAnimator;\n    private TickingFloatAnimator mCrossfadeAnimator = TickingFloatAnimator.create().from(0);\n\n    public StyleBlurRenderer(Context context, Callbacks callbacks) {\n        mContext = context;\n        mCallbacks = callbacks;\n\n        mBlurKeyframes = getNumberOfKeyframes();\n        mBlurAnimator = TickingFloatAnimator.create().from(mBlurKeyframes);\n\n        mCurrentGLPictureSet = new GLPictureSet(0);\n        mNextGLPictureSet = new GLPictureSet(1); // for transitioning to next pictures\n        setNormalOffsetX(0);\n        recomputeMaxPrescaledBlurPixels();\n        recomputeMaxDimAmount();\n        recomputeGreyAmount();\n    }\n\n    private int getNumberOfKeyframes() {\n        ActivityManager activityManager = (ActivityManager)\n                mContext.getSystemService(Context.ACTIVITY_SERVICE);\n        return activityManager.isLowRamDevice() ? 1 : 2;\n    }\n\n    public void recomputeMaxPrescaledBlurPixels() {\n        // Compute blur sizes\n        int blurAmount = mDemoMode\n                ? DEMO_BLUR\n                : Prefs.getSharedPreferences(mContext)\n                .getInt(Prefs.PREF_BLUR_AMOUNT, DEFAULT_BLUR);\n        float maxBlurRadiusOverScreenHeight = blurAmount * 0.0001f;\n        DisplayMetrics dm = mContext.getResources().getDisplayMetrics();\n        int maxBlurPx = (int) (dm.heightPixels * maxBlurRadiusOverScreenHeight);\n        mBlurredSampleSize = 4;\n        while (maxBlurPx / mBlurredSampleSize > ImageBlurrer.MAX_SUPPORTED_BLUR_PIXELS) {\n            mBlurredSampleSize <<= 1;\n        }\n        mMaxPrescaledBlurPixels = maxBlurPx / mBlurredSampleSize;\n    }\n\n    public void recomputeMaxDimAmount() {\n        mMaxDim = Prefs.getSharedPreferences(mContext).getInt(\n                Prefs.PREF_DIM_AMOUNT, DEFAULT_MAX_DIM);\n    }\n\n    public void recomputeGreyAmount() {\n        mMaxGrey = mDemoMode\n                ? DEMO_GREY\n                : Prefs.getSharedPreferences(mContext)\n                .getInt(Prefs.PREF_GREY_AMOUNT, DEFAULT_GREY);\n    }\n\n    public void onSurfaceCreated(GL10 unused, EGLConfig config) {\n        mSurfaceCreated = false;\n        GLES20.glEnable(GLES20.GL_BLEND);\n//        GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);\n        GLES20.glBlendFuncSeparate(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA,\n                GLES20.GL_ONE, GLES20.GL_ONE);\n        GLES20.glClearColor(0, 0, 0, 0);\n\n        // Set the camera position (View matrix)\n        Matrix.setLookAtM(mVMatrix, 0,\n                0, 0, 1,\n                0, 0, -1,\n                0, 1, 0);\n\n        GLColorOverlay.initGl();\n        GLPicture.initGl();\n\n        mColorOverlay = new GLColorOverlay();\n\n        mSurfaceCreated = true;\n        if (mQueuedNextBitmapRegionLoader != null) {\n            BitmapRegionLoader loader = mQueuedNextBitmapRegionLoader;\n            mQueuedNextBitmapRegionLoader = null;\n            setAndConsumeBitmapRegionLoader(loader);\n        }\n    }\n\n    public void onSurfaceChanged(GL10 unused, int width, int height) {\n        GLES20.glViewport(0, 0, width, height);\n        hintViewportSize(width, height);\n        if (!mDemoMode && !mPreview) {\n            // Reset art detail viewports\n            WallpaperDetailViewport.Companion.getInstance().setViewport(0, 0, 0, 0, 0, false);\n            WallpaperDetailViewport.Companion.getInstance().setViewport(1, 0, 0, 0, 0, false);\n        }\n        mCurrentGLPictureSet.recomputeTransformMatrices();\n        mNextGLPictureSet.recomputeTransformMatrices();\n        recomputeMaxPrescaledBlurPixels();\n    }\n\n    public void hintViewportSize(int width, int height) {\n        mHeight = height;\n        mAspectRatio = width * 1f / height;\n    }\n\n    public void onDrawFrame(GL10 unused) {\n        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);\n\n        Matrix.setIdentityM(mMMatrix, 0);\n\n        boolean stillAnimating = mCrossfadeAnimator.tick();\n        stillAnimating |= mBlurAnimator.tick();\n\n        if (mBlurRelatedToArtDetailMode) {\n            mCurrentGLPictureSet.recomputeTransformMatrices();\n            mNextGLPictureSet.recomputeTransformMatrices();\n        }\n\n        float dimAmount = mCurrentGLPictureSet.mDimAmount;\n        mCurrentGLPictureSet.drawFrame(1);\n        if (mCrossfadeAnimator.isRunning()) {\n            dimAmount = MathUtil.interpolate(dimAmount, mNextGLPictureSet.mDimAmount,\n                    mCrossfadeAnimator.currentValue());\n            mNextGLPictureSet.drawFrame(mCrossfadeAnimator.currentValue());\n        }\n\n        mColorOverlay.setColor(Color.argb((int) (dimAmount\n                * mBlurAnimator.currentValue() / mBlurKeyframes), 0, 0, 0));\n        mColorOverlay.draw(mMMatrix); // don't need any perspective or anything for color overlay\n\n        if (stillAnimating) {\n            mCallbacks.requestRender();\n        }\n    }\n\n    public void setNormalOffsetX(float x) {\n        mNormalOffsetX = MathUtil.constrain(0, 1, x);\n        onViewportChanged();\n    }\n\n    private void onViewportChanged() {\n        mCurrentGLPictureSet.recomputeTransformMatrices();\n        mNextGLPictureSet.recomputeTransformMatrices();\n        if (mSurfaceCreated) {\n            mCallbacks.requestRender();\n        }\n    }\n\n    private float blurRadiusAtFrame(float f) {\n        return mMaxPrescaledBlurPixels * mBlurInterpolator.getInterpolation(f / mBlurKeyframes);\n    }\n\n    public void setAndConsumeBitmapRegionLoader(final BitmapRegionLoader bitmapRegionLoader) {\n        if (!mSurfaceCreated) {\n            mQueuedNextBitmapRegionLoader = bitmapRegionLoader;\n            return;\n        }\n\n        if (mCrossfadeAnimator.isRunning()) {\n            if (mQueuedNextBitmapRegionLoader != null) {\n                mQueuedNextBitmapRegionLoader.destroy();\n            }\n            mQueuedNextBitmapRegionLoader = bitmapRegionLoader;\n            return;\n        }\n\n        if (!mDemoMode && !mPreview) {\n            EventBus.getDefault().postSticky(new SwitchingPhotosStateChangedEvent(\n                    mNextGLPictureSet.mId, true));\n            EventBus.getDefault().postSticky(new StyleWallpaperSizeChangedEvent(\n                    bitmapRegionLoader.getWidth(), bitmapRegionLoader.getHeight()));\n            WallpaperDetailViewport.Companion.getInstance()\n                    .setDefaultViewport(mNextGLPictureSet.mId,\n                            bitmapRegionLoader.getWidth() * 1f / bitmapRegionLoader.getHeight(),\n                            mAspectRatio);\n        }\n\n        mNextGLPictureSet.load(bitmapRegionLoader);\n\n        mCrossfadeAnimator\n                .from(0).to(1)\n                .withDuration(CROSSFADE_ANIMATION_DURATION)\n                .withEndListener(new Runnable() {\n                    @Override\n                    public void run() {\n                        // swap current and next picturesets\n                        final GLPictureSet oldGLPictureSet = mCurrentGLPictureSet;\n                        mCurrentGLPictureSet = mNextGLPictureSet;\n                        mNextGLPictureSet = new GLPictureSet(oldGLPictureSet.mId);\n                        mCallbacks.requestRender();\n                        oldGLPictureSet.destroyPictures();\n                        if (!mDemoMode) {\n                            EventBus.getDefault().postSticky(new SwitchingPhotosStateChangedEvent(\n                                    mCurrentGLPictureSet.mId, false));\n                        }\n                        System.gc();\n                        if (mQueuedNextBitmapRegionLoader != null) {\n                            BitmapRegionLoader queuedNextBitmapRegionLoader\n                                    = mQueuedNextBitmapRegionLoader;\n                            mQueuedNextBitmapRegionLoader = null;\n                            setAndConsumeBitmapRegionLoader(queuedNextBitmapRegionLoader);\n                        }\n                    }\n                })\n                .start();\n        mCallbacks.requestRender();\n    }\n\n    public void setDemoMode(boolean demoMode) {\n        mDemoMode = demoMode;\n        recomputeGreyAmount();\n    }\n\n    public void setIsPreview(boolean preview) {\n        mPreview = preview;\n    }\n\n    private class GLPictureSet {\n        private int mId;\n        private volatile float[] mPMatrix = new float[16];\n        private final float[] mMVPMatrix = new float[16];\n        private GLPicture[] mPictures = new GLPicture[mBlurKeyframes + 1];\n        private boolean mHasBitmap = false;\n        private float mBitmapAspectRatio = 1f;\n        private int mDimAmount = 0;\n\n        public GLPictureSet(int id) {\n            mId = id;\n        }\n\n        public void load(BitmapRegionLoader bitmapRegionLoader) {\n            mHasBitmap = bitmapRegionLoader != null\n                    && bitmapRegionLoader.getWidth() != 0 && bitmapRegionLoader.getHeight() != 0;\n            mBitmapAspectRatio = mHasBitmap\n                    ? bitmapRegionLoader.getWidth() * 1f / bitmapRegionLoader.getHeight()\n                    : 1f;\n\n            mDimAmount = DEFAULT_MAX_DIM;\n\n            destroyPictures();\n\n            if (mHasBitmap) {\n                BitmapFactory.Options options = new BitmapFactory.Options();\n                Rect rect = new Rect();\n                int originalWidth = bitmapRegionLoader.getWidth();\n                int originalHeight = bitmapRegionLoader.getHeight();\n\n                // Calculate image darkness to determine dim amount\n                rect.set(0, 0, originalWidth, originalHeight);\n                options.inSampleSize = ImageUtil.calculateSampleSize(originalHeight, 64);\n                Bitmap tempBitmap = bitmapRegionLoader.decodeRegion(rect, options);\n                float darkness = ImageUtil.calculateDarkness(tempBitmap);\n                mDimAmount = mDemoMode\n                        ? DEMO_DIM\n                        : (int) (mMaxDim * ((1 - DIM_RANGE) + DIM_RANGE * Math.sqrt(darkness)));\n                if (tempBitmap != null) {\n                    tempBitmap.recycle();\n                }\n\n                // Create the GLPicture objects\n                mPictures[0] = new GLPicture(bitmapRegionLoader, mHeight);\n                if (mMaxPrescaledBlurPixels == 0 && mMaxGrey == 0) {\n                    for (int f = 1; f <= mBlurKeyframes; f++) {\n                        mPictures[f] = mPictures[0];\n                    }\n                } else {\n                    int sampleSizeTargetHeight, scaledHeight, scaledWidth;\n                    if (mMaxPrescaledBlurPixels > 0) {\n                        sampleSizeTargetHeight = mHeight / mBlurredSampleSize;\n                    } else {\n                        sampleSizeTargetHeight = mHeight;\n                    }\n\n                    // Note that image width should be a multiple of 4 to avoid\n                    // issues with RenderScript allocations.\n                    scaledHeight = Math.max(2, MathUtil.floorEven(\n                            sampleSizeTargetHeight));\n                    scaledWidth = Math.max(4, MathUtil.roundMult4(\n                            (int) (scaledHeight * mBitmapAspectRatio)));\n\n                    // To blur, first load the entire bitmap region, but at a very large\n                    // sample size that's appropriate for the final blurred image\n                    options.inSampleSize = ImageUtil.calculateSampleSize(\n                            originalHeight, sampleSizeTargetHeight);\n                    rect.set(0, 0, originalWidth, originalHeight);\n                    tempBitmap = bitmapRegionLoader.decodeRegion(rect, options);\n\n                    if (tempBitmap != null\n                            && tempBitmap.getWidth() != 0 && tempBitmap.getHeight() != 0) {\n                        // Next, create a scaled down version of the bitmap so that the blur radius\n                        // looks appropriate (tempBitmap will likely be bigger than the final\n                        // blurred bitmap, and thus the blur may look smaller if we just used\n                        // tempBitmap as the final blurred bitmap).\n\n                        // Note that image width should be a multiple of 4 to avoid\n                        // issues with RenderScript allocations.\n                        Bitmap scaledBitmap = Bitmap.createScaledBitmap(\n                                tempBitmap, scaledWidth, scaledHeight, true);\n                        if (tempBitmap != scaledBitmap) {\n                            tempBitmap.recycle();\n                        }\n\n                        // And finally, create a blurred copy for each keyframe.\n                        ImageBlurrer blurrer = new ImageBlurrer(mContext, scaledBitmap);\n                        for (int f = 1; f <= mBlurKeyframes; f++) {\n                            float desaturateAmount = mMaxGrey / 500f * f / mBlurKeyframes;\n                            float blurRadius = 0f;\n                            if (mMaxPrescaledBlurPixels > 0) {\n                                blurRadius = blurRadiusAtFrame(f);\n                            }\n                            Bitmap blurredBitmap = blurrer.blurBitmap(blurRadius, desaturateAmount);\n                            mPictures[f] = new GLPicture(blurredBitmap);\n                            if (blurredBitmap != null) {\n                                blurredBitmap.recycle();\n                            }\n                        }\n                        blurrer.destroy();\n\n                        scaledBitmap.recycle();\n                    } else {\n                        Log.e(TAG, \"BitmapRegionLoader failed to decode the region, rect=\"\n                                + rect.toShortString());\n                        for (int f = 1; f <= mBlurKeyframes; f++) {\n                            mPictures[f] = null;\n                        }\n                    }\n                }\n            }\n\n            recomputeTransformMatrices();\n            mCallbacks.requestRender();\n        }\n\n        private void recomputeTransformMatrices() {\n            float screenToBitmapAspectRatio = mAspectRatio / mBitmapAspectRatio;\n            if (screenToBitmapAspectRatio == 0) {\n                return;\n            }\n\n            // Ensure the bitmap is wider than the screen relatively by applying zoom\n            // if necessary. Vary width but liked height the same.\n            float zoom = Math.max(1f, 1.15f * screenToBitmapAspectRatio);\n\n            // Total scale factors in both zoom and scale due to aspect ratio.\n            float scaledBitmapToScreenAspectRatio = zoom / screenToBitmapAspectRatio;\n\n            // At most pan across 1.8 screenfuls (2 screenfuls + some parallax)\n            // TODO: if we know the number of home screen pages, use that number here\n            float maxPanScreenWidths = Math.min(1.8f, scaledBitmapToScreenAspectRatio);\n\n            mCurrentViewport.left = MathUtil.interpolate(-1f, 1f,\n                    MathUtil.interpolate(\n                            (1 - maxPanScreenWidths / scaledBitmapToScreenAspectRatio) / 2,\n                            (1 + (maxPanScreenWidths - 2) / scaledBitmapToScreenAspectRatio) / 2,\n                            mNormalOffsetX));\n            mCurrentViewport.right = mCurrentViewport.left + 2f / scaledBitmapToScreenAspectRatio;\n            mCurrentViewport.bottom = -1f / zoom;\n            mCurrentViewport.top = 1f / zoom;\n\n            float focusAmount = (mBlurKeyframes - mBlurAnimator.currentValue()) / mBlurKeyframes;\n            if (mBlurRelatedToArtDetailMode && focusAmount > 0) {\n                RectF artDetailViewport = WallpaperDetailViewport.Companion\n                        .getInstance().getViewport(mId);\n                if (artDetailViewport.width() == 0 || artDetailViewport.height() == 0) {\n                    if (!mDemoMode && !mPreview) {\n                        // reset art detail viewport\n                        WallpaperDetailViewport.Companion\n                                .getInstance().setViewport(mId,\n                                MathUtil.uninterpolate(-1, 1, mCurrentViewport.left),\n                                MathUtil.uninterpolate(1, -1, mCurrentViewport.top),\n                                MathUtil.uninterpolate(-1, 1, mCurrentViewport.right),\n                                MathUtil.uninterpolate(1, -1, mCurrentViewport.bottom),\n                                false);\n                    }\n                } else {\n                    // interpolate\n                    mCurrentViewport.left = MathUtil.interpolate(\n                            mCurrentViewport.left,\n                            MathUtil.interpolate(-1, 1, artDetailViewport.left),\n                            focusAmount);\n                    mCurrentViewport.top = MathUtil.interpolate(\n                            mCurrentViewport.top,\n                            MathUtil.interpolate(1, -1, artDetailViewport.top),\n                            focusAmount);\n                    mCurrentViewport.right = MathUtil.interpolate(\n                            mCurrentViewport.right,\n                            MathUtil.interpolate(-1, 1, artDetailViewport.right),\n                            focusAmount);\n                    mCurrentViewport.bottom = MathUtil.interpolate(\n                            mCurrentViewport.bottom,\n                            MathUtil.interpolate(1, -1, artDetailViewport.bottom),\n                            focusAmount);\n                }\n            }\n\n            Matrix.orthoM(mPMatrix, 0,\n                    mCurrentViewport.left, mCurrentViewport.right,\n                    mCurrentViewport.bottom, mCurrentViewport.top,\n                    1, 10);\n        }\n\n        public void drawFrame(float globalAlpha) {\n            if (!mHasBitmap) {\n                return;\n            }\n\n            Matrix.multiplyMM(mMVPMatrix, 0, mVMatrix, 0, mMMatrix, 0);\n            Matrix.multiplyMM(mMVPMatrix, 0, mPMatrix, 0, mMVPMatrix, 0);\n\n            float blurFrame = mBlurAnimator.currentValue();\n            int lo = (int) Math.floor(blurFrame);\n            int hi = (int) Math.ceil(blurFrame);\n\n            float localHiAlpha = (blurFrame - lo);\n            if (globalAlpha <= 0) {\n                // Nothing to draw\n            } else if (lo == hi) {\n                // Just draw one\n                if (mPictures[lo] == null) {\n                    return;\n                }\n\n                mPictures[lo].draw(mMVPMatrix, globalAlpha);\n            } else if (globalAlpha == 1) {\n                // Simple drawing\n                if (mPictures[lo] == null || mPictures[hi] == null) {\n                    return;\n                }\n\n                mPictures[lo].draw(mMVPMatrix, 1);\n                mPictures[hi].draw(mMVPMatrix, localHiAlpha);\n            } else {\n                // If there's both a global and local alpha, re-compose alphas, to\n                // effectively compose hi and lo before composing the result\n                // with the background.\n                //\n                // The math, where a1,a2 are previous alphas and b1,b2 are new alphas:\n                //   b1 = a1 * (a2 - 1) / (a1 * a2 - 1)\n                //   b2 = a1 * a2\n                if (mPictures[lo] == null || mPictures[hi] == null) {\n                    return;\n                }\n\n                float newLocalLoAlpha = globalAlpha * (localHiAlpha - 1)\n                        / (globalAlpha * localHiAlpha - 1);\n                float newLocalHiAlpha = globalAlpha * localHiAlpha;\n                mPictures[lo].draw(mMVPMatrix, newLocalLoAlpha);\n                mPictures[hi].draw(mMVPMatrix, newLocalHiAlpha);\n            }\n        }\n\n        public void destroyPictures() {\n            for (int i = 0; i < mPictures.length; i++) {\n                if (mPictures[i] != null) {\n                    mPictures[i].destroy();\n                    mPictures[i] = null;\n                }\n            }\n        }\n    }\n\n    public void destroy() {\n        mCurrentGLPictureSet.destroyPictures();\n        mNextGLPictureSet.destroyPictures();\n    }\n\n    public boolean isBlurred() {\n        return mIsBlurred;\n    }\n\n    public void setIsBlurred(final boolean isBlurred, final boolean artDetailMode) {\n        if (artDetailMode && !isBlurred && !mDemoMode && !mPreview) {\n            // Reset art detail viewport\n            WallpaperDetailViewport.Companion.getInstance().setViewport(0, 0, 0, 0, 0, false);\n            WallpaperDetailViewport.Companion.getInstance().setViewport(1, 0, 0, 0, 0, false);\n        }\n\n        mBlurRelatedToArtDetailMode = artDetailMode;\n        mIsBlurred = isBlurred;\n        mBlurAnimator.cancel();\n        mBlurAnimator\n                .to(isBlurred ? mBlurKeyframes : 0)\n                .withDuration(BLUR_ANIMATION_DURATION * (mDemoMode ? 5 : 1))\n                .withEndListener(new Runnable() {\n                    @Override\n                    public void run() {\n                        if (isBlurred && artDetailMode) {\n                            System.gc();\n                        }\n                    }\n                })\n                .start();\n        mCallbacks.requestRender();\n    }\n\n    public interface Callbacks {\n        void requestRender();\n    }\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/render/TickingFloatAnimator.java",
    "content": "/*\n * Copyright 2014 Google Inc.\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\npackage com.yalin.style.render;\n\nimport android.animation.TimeInterpolator;\nimport android.os.SystemClock;\nimport android.view.animation.AccelerateDecelerateInterpolator;\n\n// Non thread-safe\npublic class TickingFloatAnimator {\n    private float mStartValue = 0;\n    private float mCurrentValue;\n    private float mEndValue;\n    private boolean mRunning = false;\n    private long mStartTime;\n    private int mDuration = 1000;\n    private Runnable mEndCallback;\n    private TimeInterpolator mInterpolator = new AccelerateDecelerateInterpolator();\n\n    public static TickingFloatAnimator create() {\n        return new TickingFloatAnimator();\n    }\n\n    public TickingFloatAnimator from(float startValue) {\n        cancel();\n        mStartValue = startValue;\n        mCurrentValue = startValue;\n        return this;\n    }\n\n    public TickingFloatAnimator to(float endValue) {\n        mEndValue = endValue;\n        return this;\n    }\n\n    public TickingFloatAnimator withDuration(int duration) {\n        mDuration = Math.max(0, duration);\n        return this;\n    }\n\n    public TickingFloatAnimator withInterpolator(TimeInterpolator interpolator) {\n        mInterpolator = interpolator;\n        return this;\n    }\n\n    public TickingFloatAnimator withEndListener(Runnable listener) {\n        mEndCallback = listener;\n        return this;\n    }\n\n    public void cancel() {\n        mRunning = false;\n        mEndValue = mCurrentValue;\n    }\n\n    public boolean tick() {\n        if (!mRunning) {\n            return false;\n        }\n\n        float t;\n        if (mDuration <= 0) {\n            t = 1;\n        } else {\n            t = (float) (SystemClock.elapsedRealtime() - mStartTime) * 1f / mDuration;\n            if (t >= 1) {\n                t = 1;\n            }\n        }\n\n        if (t >= 1) {\n            // Ended\n            mRunning = false;\n            mCurrentValue = mEndValue;\n            if (mEndCallback != null) {\n                mEndCallback.run();\n            }\n            return false;\n        }\n\n        // Still running; compute value\n        mCurrentValue = mStartValue + mInterpolator.getInterpolation(t) * (mEndValue - mStartValue);\n        mRunning = true;\n        return true;\n    }\n\n    public void start() {\n        mRunning = true;\n        mStartValue = mCurrentValue;\n        mStartTime = SystemClock.elapsedRealtime();\n        tick();\n    }\n\n    public boolean isRunning() {\n        return mRunning;\n    }\n\n    public float currentValue() {\n        return mCurrentValue;\n    }\n\n    private TickingFloatAnimator() {\n    }\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/settings/Prefs.java",
    "content": "package com.yalin.style.settings;\n\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.support.v4.content.ContextCompat;\n\n/**\n * @author jinyalin\n * @since 2017/5/2.\n */\n\npublic class Prefs {\n    public static final String PREF_GREY_AMOUNT = \"grey_amount\";\n    public static final String PREF_DIM_AMOUNT = \"dim_amount\";\n    public static final String PREF_BLUR_AMOUNT = \"blur_amount\";\n    public static final String PREF_DISABLE_BLUR_WHEN_LOCKED\n            = \"disable_blur_when_screen_locked_enabled\";\n\n    private static final String WALLPAPER_PREFERENCES_NAME = \"wallpaper_preferences\";\n    private static final String PREF_MIGRATED = \"migrated_from_default\";\n\n    public synchronized static SharedPreferences getSharedPreferences(Context context) {\n        Context deviceProtectedContext =\n                ContextCompat.createDeviceProtectedStorageContext(context.getApplicationContext());\n\n        Context contextToUse = deviceProtectedContext != null ? deviceProtectedContext : context;\n        return contextToUse.getSharedPreferences(WALLPAPER_PREFERENCES_NAME, Context.MODE_PRIVATE);\n    }\n\n    private Prefs() {\n    }\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/util/ImageLoader.java",
    "content": "package com.yalin.style.util;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport android.support.annotation.DrawableRes;\nimport android.widget.ImageView;\n\nimport com.bumptech.glide.BitmapRequestBuilder;\nimport com.bumptech.glide.BitmapTypeRequest;\nimport com.bumptech.glide.Glide;\nimport com.bumptech.glide.load.model.GlideUrl;\nimport com.bumptech.glide.load.model.ModelCache;\nimport com.bumptech.glide.load.model.stream.BaseGlideUrlLoader;\nimport com.bumptech.glide.load.resource.bitmap.CenterCrop;\nimport com.bumptech.glide.request.RequestListener;\nimport com.yalin.style.R;\n\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/**\n * @author jinyalin\n * @since 2017/4/19.\n */\npublic class ImageLoader {\n    private static final String TAG = \"ImageLoader\";\n\n    private static final ModelCache<String, GlideUrl> urlCache =\n            new ModelCache<>(150);\n\n    private final BitmapTypeRequest<String> mGlideModelRequest;\n    private final CenterCrop mCenterCrop;\n\n    private int mPlaceHolderResId = -1;\n\n    public ImageLoader(Context context) {\n        VariableWidthImageLoader imageLoader = new VariableWidthImageLoader(context);\n        mGlideModelRequest = Glide.with(context).using(imageLoader).from(String.class).asBitmap();\n        mCenterCrop = new CenterCrop(Glide.get(context).getBitmapPool());\n    }\n\n    /**\n     * Construct an ImageLoader with a default placeholder drawable.\n     */\n    public ImageLoader(Context context, int placeHolderResId) {\n        this(context);\n        mPlaceHolderResId = placeHolderResId;\n    }\n\n    /**\n     * Load an image from a url into an ImageView using the default placeholder\n     * drawable if available.\n     *\n     * @param url             The web URL of an image.\n     * @param imageView       The target ImageView to load the image into.\n     * @param requestListener A listener to monitor the request result.\n     */\n    public void loadImage(String url, ImageView imageView,\n                          RequestListener<String, Bitmap> requestListener) {\n        loadImage(url, imageView, requestListener, null, false);\n    }\n\n    /**\n     * Load an image from a url into an ImageView using the given placeholder drawable.\n     *\n     * @param url                 The web URL of an image.\n     * @param imageView           The target ImageView to load the image into.\n     * @param requestListener     A listener to monitor the request result.\n     * @param placeholderOverride A placeholder to use in place of the default placholder.\n     */\n    public void loadImage(String url, ImageView imageView,\n                          RequestListener<String, Bitmap> requestListener,\n                          Drawable placeholderOverride) {\n        loadImage(url, imageView, requestListener, placeholderOverride, false /*crop*/);\n    }\n\n    /**\n     * Load an image from a url into an ImageView using the default placeholder\n     * drawable if available.\n     *\n     * @param url                 The web URL of an image.\n     * @param imageView           The target ImageView to load the image into.\n     * @param requestListener     A listener to monitor the request result.\n     * @param placeholderOverride A drawable to use as a placeholder for this specific image.\n     *                            If this parameter is present, {@link #mPlaceHolderResId}\n     *                            if ignored for this request.\n     */\n    public void loadImage(String url, ImageView imageView,\n                          RequestListener<String, Bitmap> requestListener,\n                          Drawable placeholderOverride, boolean crop) {\n        BitmapRequestBuilder request = beginImageLoad(url, requestListener, crop)\n                .animate(R.anim.image_fade_in);\n        if (placeholderOverride != null) {\n            request.placeholder(placeholderOverride);\n        } else if (mPlaceHolderResId != -1) {\n            request.placeholder(mPlaceHolderResId);\n        }\n        request.into(imageView);\n    }\n\n    public BitmapRequestBuilder beginImageLoad(String url,\n                                               RequestListener<String, Bitmap> requestListener,\n                                               boolean crop) {\n        if (crop) {\n            return mGlideModelRequest.load(url)\n                    .listener(requestListener)\n                    .transform(mCenterCrop);\n        } else {\n            return mGlideModelRequest.load(url)\n                    .listener(requestListener);\n        }\n    }\n\n    /**\n     * Load an image from a url into the given image view using the default placeholder if\n     * available.\n     *\n     * @param url       The web URL of an image.\n     * @param imageView The target ImageView to load the image into.\n     */\n    public void loadImage(String url, ImageView imageView) {\n        loadImage(url, imageView, false /*crop*/);\n    }\n\n    /**\n     * Load an image from a url into an ImageView using the default placeholder\n     * drawable if available.\n     *\n     * @param url       The web URL of an image.\n     * @param imageView The target ImageView to load the image into.\n     * @param crop      True to apply a center crop to the image.\n     */\n    public void loadImage(String url, ImageView imageView, boolean crop) {\n        loadImage(url, imageView, null, null, crop);\n    }\n\n    public void loadImage(Context context, @DrawableRes int drawableResId, ImageView imageView) {\n        Glide.with(context).load(drawableResId).into(imageView);\n    }\n\n    private static class VariableWidthImageLoader extends BaseGlideUrlLoader<String> {\n        private static final Pattern PATTERN = Pattern.compile(\"__w-((?:-?\\\\d+)+)__\");\n\n        public VariableWidthImageLoader(Context context) {\n            super(context, urlCache);\n        }\n\n        /**\n         * If the URL contains a special variable width indicator (eg \"__w-200-400-800__\")\n         * we get the buckets from the URL (200, 400 and 800 in the example) and replace\n         * the URL with the best bucket for the requested width (the bucket immediately\n         * larger than the requested width).\n         */\n        @Override\n        protected String getUrl(String model, int width, int height) {\n            Matcher m = PATTERN.matcher(model);\n            int bestBucket = 0;\n            if (m.find()) {\n                String[] found = m.group(1).split(\"-\");\n                for (String bucketStr : found) {\n                    bestBucket = Integer.parseInt(bucketStr);\n                    if (bestBucket >= width) {\n                        // the best bucket is the first immediately bigger than the requested width\n                        break;\n                    }\n                }\n                if (bestBucket > 0) {\n                    model = m.replaceFirst(\"w\" + bestBucket);\n                }\n            }\n            return model;\n        }\n    }\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/util/MathUtil.java",
    "content": "package com.yalin.style.util;\n\n/**\n * YaLin 2016/12/30.\n */\n\npublic class MathUtil {\n\n  public static float constrain(float min, float max, float v) {\n    return Math.max(min, Math.min(max, v));\n  }\n\n  public static float interpolate(float x1, float x2, float f) {\n    return x1 + (x2 - x1) * f;\n  }\n\n  public static float uninterpolate(float x1, float x2, float v) {\n    if (x2 - x1 == 0) {\n      throw new IllegalArgumentException(\"Can't reverse interpolate with domain size of 0\");\n    }\n    return (v - x1) / (x2 - x1);\n  }\n\n  public static int floorEven(int num) {\n    return num & ~0x01;\n  }\n\n  public static int roundMult4(int num) {\n    return (num + 2) & ~0x03;\n  }\n\n  // divide two integers but round up\n  // see http://stackoverflow.com/a/7446742/102703\n  public static int intDivideRoundUp(int num, int divisor) {\n    int sign = (num > 0 ? 1 : -1) * (divisor > 0 ? 1 : -1);\n    return sign * (Math.abs(num) + Math.abs(divisor) - 1) / Math.abs(divisor);\n  }\n\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/util/MultiSelectionController.kt",
    "content": "package com.yalin.style.util\n\nimport android.os.Bundle\nimport android.os.Parcelable\nimport java.util.HashSet\n\n/**\n * @author jinyalin\n * @since 2017/5/25.\n */\nclass MultiSelectionController<T : Parcelable>(val stateKey: String) {\n    private val mSelection = HashSet<T>()\n    private val DUMMY_CALLBACKS: Callbacks = object : Callbacks {\n        override fun onSelectionChanged(restored: Boolean, fromUser: Boolean) {}\n    }\n    private var mCallbacks: Callbacks? = DUMMY_CALLBACKS\n\n    @Suppress(\"UNCHECKED_CAST\")\n    fun restoreInstanceState(savedInstanceState: Bundle?) {\n        if (savedInstanceState != null) {\n            mSelection.clear()\n            val selection = savedInstanceState.getParcelableArray(stateKey)\n            if (selection != null && selection.isNotEmpty()) {\n                selection.mapTo(mSelection) { it as T }\n            }\n        }\n\n        mCallbacks?.onSelectionChanged(true, false)\n    }\n\n    fun saveInstanceState(outBundle: Bundle) {\n        val selection = arrayOfNulls<Parcelable>(mSelection.size)\n        for ((i, item) in mSelection.withIndex()) {\n            selection[i] = item\n        }\n\n        outBundle.putParcelableArray(stateKey, selection)\n    }\n\n    fun setCallbacks(callbacks: Callbacks) {\n        mCallbacks = callbacks\n        if (mCallbacks == null) {\n            mCallbacks = DUMMY_CALLBACKS\n        }\n    }\n\n    fun getSelection(): Set<T> {\n        return HashSet(mSelection)\n    }\n\n    fun getSelectedCount(): Int {\n        return mSelection.size\n    }\n\n    fun isSelecting(): Boolean {\n        return mSelection.size > 0\n    }\n\n    fun toggle(item: T, fromUser: Boolean) {\n        if (mSelection.contains(item)) {\n            mSelection.remove(item)\n        } else {\n            mSelection.add(item)\n        }\n\n        mCallbacks?.onSelectionChanged(false, fromUser)\n    }\n\n    fun reset(fromUser: Boolean) {\n        mSelection.clear()\n        mCallbacks?.onSelectionChanged(false, fromUser)\n    }\n\n    fun isSelected(item: T): Boolean {\n        return mSelection.contains(item)\n    }\n\n    interface Callbacks {\n        fun onSelectionChanged(restored: Boolean, fromUser: Boolean)\n    }\n}"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/util/ScrimUtil.java",
    "content": "package com.yalin.style.util;\n\nimport android.graphics.Color;\nimport android.graphics.LinearGradient;\nimport android.graphics.Shader;\nimport android.graphics.drawable.Drawable;\nimport android.graphics.drawable.PaintDrawable;\nimport android.graphics.drawable.ShapeDrawable;\nimport android.graphics.drawable.shapes.RectShape;\nimport android.util.LruCache;\nimport android.view.Gravity;\n\n/**\n * @author jinyalin\n * @since 2017/4/21.\n * Utility methods for creating prettier gradient scrims.\n */\npublic class ScrimUtil {\n\n    private static final LruCache<Integer, Drawable> cubicGradientScrimCache = new LruCache<>(10);\n\n    private ScrimUtil() {\n    }\n\n    /**\n     * Creates an approximated cubic gradient using a multi-stop linear gradient. See\n     * <a href=\"https://plus.google.com/+RomanNurik/posts/2QvHVFWrHZf\">this post</a> for more\n     * details.\n     */\n    public static Drawable makeCubicGradientScrimDrawable(int baseColor,\n                                                          int numStops, int gravity) {\n\n        // Generate a cache key by hashing together the inputs,\n        // based on the method described in the Effective Java book\n        int cacheKeyHash = baseColor;\n        cacheKeyHash = 31 * cacheKeyHash + numStops;\n        cacheKeyHash = 31 * cacheKeyHash + gravity;\n\n        Drawable cachedGradient = cubicGradientScrimCache.get(cacheKeyHash);\n        if (cachedGradient != null) {\n            return cachedGradient;\n        }\n\n        numStops = Math.max(numStops, 2);\n\n        PaintDrawable paintDrawable = new PaintDrawable();\n        paintDrawable.setShape(new RectShape());\n\n        final int[] stopColors = new int[numStops];\n\n        int red = Color.red(baseColor);\n        int green = Color.green(baseColor);\n        int blue = Color.blue(baseColor);\n        int alpha = Color.alpha(baseColor);\n\n        for (int i = 0; i < numStops; i++) {\n            float x = i * 1f / (numStops - 1);\n            float opacity = MathUtil.constrain(0, 1, (float) Math.pow(x, 3));\n            stopColors[i] = Color.argb((int) (alpha * opacity), red, green, blue);\n        }\n\n        final float x0, x1, y0, y1;\n        switch (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) {\n            case Gravity.LEFT:\n                x0 = 1;\n                x1 = 0;\n                break;\n            case Gravity.RIGHT:\n                x0 = 0;\n                x1 = 1;\n                break;\n            default:\n                x0 = 0;\n                x1 = 0;\n                break;\n        }\n        switch (gravity & Gravity.VERTICAL_GRAVITY_MASK) {\n            case Gravity.TOP:\n                y0 = 1;\n                y1 = 0;\n                break;\n            case Gravity.BOTTOM:\n                y0 = 0;\n                y1 = 1;\n                break;\n            default:\n                y0 = 0;\n                y1 = 0;\n                break;\n        }\n\n        paintDrawable.setShaderFactory(new ShapeDrawable.ShaderFactory() {\n            @Override\n            public Shader resize(int width, int height) {\n                return new LinearGradient(\n                        width * x0,\n                        height * y0,\n                        width * x1,\n                        height * y1,\n                        stopColors, null,\n                        Shader.TileMode.CLAMP);\n            }\n        });\n\n        cubicGradientScrimCache.put(cacheKeyHash, paintDrawable);\n        return paintDrawable;\n    }\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/util/SettingsUtil.java",
    "content": "package com.yalin.style.util;\n\n/**\n * YaLin On 2017/1/2.\n */\n\npublic class SettingsUtil {\n\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/util/ShareUtil.kt",
    "content": "package com.yalin.style.util\n\nimport android.content.Context\nimport android.content.Intent\nimport com.yalin.style.R\nimport com.yalin.style.model.WallpaperItem\n\n/**\n * @author jinyalin\n * @since 2017/5/14.\n */\nclass ShareUtil {\n    companion object {\n        fun getShareString(context: Context, wallpaperItem: WallpaperItem) = with(wallpaperItem) {\n            val detailUrl = \"www.kinglloy.com\"\n            val artist = byline.replaceFirst(\"\\\\.\\\\s*($|\\\\n).*\".toRegex(), \"\").trim()\n\n            val result = String.format(\n                    context.getString(R.string.share_text), title, artist, detailUrl)\n            result\n        }\n\n        fun createShareIntent(context: Context, wallpaperItem: WallpaperItem): Intent {\n            var shareIntent = Intent(Intent.ACTION_SEND)\n            shareIntent.type = \"text/plain\"\n            shareIntent.putExtra(Intent.EXTRA_TEXT, ShareUtil.Companion\n                    .getShareString(context, wallpaperItem))\n            shareIntent = Intent.createChooser(shareIntent,\n                    context.getString(R.string.share_title))\n            return shareIntent;\n        }\n    }\n}"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/util/SvgPathParser.java",
    "content": "package com.yalin.style.util;\n\nimport android.graphics.Path;\nimport android.graphics.PointF;\nimport java.text.ParseException;\n\n/**\n * YaLin 2017/1/4.\n */\n\npublic class SvgPathParser {\n\n  private static final int TOKEN_ABSOLUTE_COMMAND = 1;\n  private static final int TOKEN_RELATIVE_COMMAND = 2;\n  private static final int TOKEN_VALUE = 3;\n  private static final int TOKEN_EOF = 4;\n\n  private int mCurrentToken;\n  private PointF mCurrentPoint = new PointF();\n  private int mLength;\n  private int mIndex;\n  private String mPathString;\n\n  protected float transformX(float x) {\n    return x;\n  }\n\n  protected float transformY(float y) {\n    return y;\n  }\n\n  public Path parsePath(String s) throws ParseException {\n    mCurrentPoint.set(Float.NaN, Float.NaN);\n    mPathString = s;\n    mIndex = 0;\n    mLength = mPathString.length();\n\n    PointF tempPoint1 = new PointF();\n    PointF tempPoint2 = new PointF();\n    PointF tempPoint3 = new PointF();\n\n    Path p = new Path();\n    p.setFillType(Path.FillType.WINDING);\n\n    boolean firstMove = true;\n    while (mIndex < mLength) {\n      char command = consumeCommand();\n      boolean relative = (mCurrentToken == TOKEN_RELATIVE_COMMAND);\n      switch (command) {\n        case 'M':\n        case 'm': {\n          // move command\n          boolean firstPoint = true;\n          while (advanceToNextToken() == TOKEN_VALUE) {\n            consumeAndTransformPoint(tempPoint1,\n                relative && mCurrentPoint.x != Float.NaN);\n            if (firstPoint) {\n              p.moveTo(tempPoint1.x, tempPoint1.y);\n              firstPoint = false;\n              if (firstMove) {\n                mCurrentPoint.set(tempPoint1);\n                firstMove = false;\n              }\n            } else {\n              p.lineTo(tempPoint1.x, tempPoint1.y);\n            }\n          }\n          mCurrentPoint.set(tempPoint1);\n          break;\n        }\n\n        case 'C':\n        case 'c': {\n          // curve command\n          if (mCurrentPoint.x == Float.NaN) {\n            throw new ParseException(\"Relative commands require current point\", mIndex);\n          }\n\n          while (advanceToNextToken() == TOKEN_VALUE) {\n            consumeAndTransformPoint(tempPoint1, relative);\n            consumeAndTransformPoint(tempPoint2, relative);\n            consumeAndTransformPoint(tempPoint3, relative);\n            p.cubicTo(tempPoint1.x, tempPoint1.y, tempPoint2.x, tempPoint2.y,\n                tempPoint3.x, tempPoint3.y);\n          }\n          mCurrentPoint.set(tempPoint3);\n          break;\n        }\n\n        case 'L':\n        case 'l': {\n          // line command\n          if (mCurrentPoint.x == Float.NaN) {\n            throw new ParseException(\"Relative commands require current point\", mIndex);\n          }\n\n          while (advanceToNextToken() == TOKEN_VALUE) {\n            consumeAndTransformPoint(tempPoint1, relative);\n            p.lineTo(tempPoint1.x, tempPoint1.y);\n          }\n          mCurrentPoint.set(tempPoint1);\n          break;\n        }\n\n        case 'H':\n        case 'h': {\n          // horizontal line command\n          if (mCurrentPoint.x == Float.NaN) {\n            throw new ParseException(\"Relative commands require current point\", mIndex);\n          }\n\n          while (advanceToNextToken() == TOKEN_VALUE) {\n            float x = transformX(consumeValue());\n            if (relative) {\n              x += mCurrentPoint.x;\n            }\n            p.lineTo(x, mCurrentPoint.y);\n          }\n          mCurrentPoint.set(tempPoint1);\n          break;\n        }\n\n        case 'V':\n        case 'v': {\n          // vertical line command\n          if (mCurrentPoint.x == Float.NaN) {\n            throw new ParseException(\"Relative commands require current point\", mIndex);\n          }\n\n          while (advanceToNextToken() == TOKEN_VALUE) {\n            float y = transformY(consumeValue());\n            if (relative) {\n              y += mCurrentPoint.y;\n            }\n            p.lineTo(mCurrentPoint.x, y);\n          }\n          mCurrentPoint.set(tempPoint1);\n          break;\n        }\n\n        case 'Q':\n        case 'q': {\n          // curve command\n          if (mCurrentPoint.x == Float.NaN) {\n            throw new ParseException(\"Relative commands require current point\", mIndex);\n          }\n          while (advanceToNextToken() == TOKEN_VALUE) {\n            consumeAndTransformPoint(tempPoint1, relative);\n            consumeAndTransformPoint(tempPoint2, relative);\n            p.quadTo(tempPoint1.x, tempPoint1.y, tempPoint2.x, tempPoint2.y);\n          }\n          mCurrentPoint.set(tempPoint2);\n          break;\n        }\n\n        case 'Z':\n        case 'z': {\n          // close command\n          p.close();\n          break;\n        }\n      }\n\n    }\n\n    return p;\n  }\n\n  private int advanceToNextToken() {\n    while (mIndex < mLength) {\n      char c = mPathString.charAt(mIndex);\n      if ('a' <= c && c <= 'z') {\n        return (mCurrentToken = TOKEN_RELATIVE_COMMAND);\n      } else if ('A' <= c && c <= 'Z') {\n        return (mCurrentToken = TOKEN_ABSOLUTE_COMMAND);\n      } else if (('0' <= c && c <= '9') || c == '.' || c == '-') {\n        return (mCurrentToken = TOKEN_VALUE);\n      }\n\n      // skip unrecognized character\n      ++mIndex;\n    }\n\n    return (mCurrentToken = TOKEN_EOF);\n  }\n\n  private char consumeCommand() throws ParseException {\n    advanceToNextToken();\n    if (mCurrentToken != TOKEN_RELATIVE_COMMAND && mCurrentToken != TOKEN_ABSOLUTE_COMMAND) {\n      throw new ParseException(\"Expected command\", mIndex);\n    }\n\n    return mPathString.charAt(mIndex++);\n  }\n\n  private void consumeAndTransformPoint(PointF out, boolean relative) throws ParseException {\n    out.x = transformX(consumeValue());\n    out.y = transformY(consumeValue());\n    if (relative) {\n      out.x += mCurrentPoint.x;\n      out.y += mCurrentPoint.y;\n    }\n  }\n\n  private float consumeValue() throws ParseException {\n    advanceToNextToken();\n    if (mCurrentToken != TOKEN_VALUE) {\n      throw new ParseException(\"Expected value\", mIndex);\n    }\n\n    boolean start = true;\n    boolean seenDot = false;\n    int index = mIndex;\n    while (index < mLength) {\n      char c = mPathString.charAt(index);\n      if (!('0' <= c && c <= '9') && (c != '.' || seenDot) && (c != '-' || !start)) {\n        // end of value\n        break;\n      }\n      if (c == '.') {\n        seenDot = true;\n      }\n      start = false;\n      ++index;\n    }\n\n    if (index == mIndex) {\n      throw new ParseException(\"Expected value\", mIndex);\n    }\n\n    String str = mPathString.substring(mIndex, index);\n    try {\n      float value = Float.parseFloat(str);\n      mIndex = index;\n      return value;\n    } catch (NumberFormatException e) {\n      throw new ParseException(\"Invalid float value '\" + str + \"'.\", mIndex);\n    }\n  }\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/util/TypefaceUtil.java",
    "content": "package com.yalin.style.util;\n\nimport android.content.Context;\nimport android.graphics.Typeface;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * @author jinyalin\n * @since 2017/4/21.\n */\n\npublic class TypefaceUtil {\n    private static final Map<String, Typeface> sTypefaceCache = new HashMap<>();\n\n    public static Typeface getAndCache(Context context, String assetPath) {\n        synchronized (sTypefaceCache) {\n            if (!sTypefaceCache.containsKey(assetPath)) {\n                Typeface tf = Typeface.createFromAsset(\n                        context.getApplicationContext().getAssets(), assetPath);\n                sTypefaceCache.put(assetPath, tf);\n            }\n            return sTypefaceCache.get(assetPath);\n        }\n    }\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/view/AdvanceSettingView.kt",
    "content": "package com.yalin.style.view\n\nimport com.yalin.style.model.AdvanceWallpaperItem\n\n/**\n * @author jinyalin\n * @since 2017/7/28.\n */\ninterface AdvanceSettingView : LoadingDataView {\n    fun renderWallpapers(wallpapers: List<AdvanceWallpaperItem>)\n\n    fun showEmpty()\n\n    fun complete()\n\n    fun wallpaperSelected(wallpaperId: String)\n\n    fun showDownloadHintDialog(item: AdvanceWallpaperItem)\n\n    fun showDownloadingDialog(item: AdvanceWallpaperItem)\n\n    fun updateDownloadingProgress(downloaded: Long)\n\n    fun downloadComplete(item: AdvanceWallpaperItem)\n\n    fun showDownloadError(item: AdvanceWallpaperItem, e: Exception)\n\n    fun showAd(item: AdvanceWallpaperItem)\n\n    fun adViewed(item: AdvanceWallpaperItem)\n}"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/view/GallerySettingView.kt",
    "content": "package com.yalin.style.view\n\nimport com.yalin.style.model.GalleryWallpaperItem\n\n/**\n * @author jinyalin\n * @since 2017/5/25.\n */\ninterface GallerySettingView : LoadingDataView {\n    fun renderGalleryWallpapers(wallpaperItems: List<GalleryWallpaperItem>)\n\n    fun renderUpdateInterval(intervalMin: Int)\n}"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/view/LoadingDataView.kt",
    "content": "package com.yalin.style.view\n\nimport android.content.Context\n\n/**\n * @author jinyalin\n * *\n * @since 2017/4/20.\n */\n\ninterface LoadingDataView {\n    /**\n     * Show a view with a progress bar indicating a loading process.\n     */\n    fun showLoading()\n\n    /**\n     * Hide a loading view.\n     */\n    fun hideLoading()\n\n    /**\n     * Show a retry view in case of an error when retrieving data.\n     */\n    fun showRetry()\n\n    /**\n     * Hide a retry view shown if there was an error when retrieving data.\n     */\n    fun hideRetry()\n\n    /**\n     * Show an error message\n\n     * @param message A string representing an error.\n     */\n    fun showError(message: String)\n\n    /**\n     * Get a [android.content.Context].\n     */\n    fun context(): Context\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/view/SourceChooseView.kt",
    "content": "package com.yalin.style.view\n\nimport com.yalin.style.model.SourceItem\n\n/**\n * @author jinyalin\n * @since 2017/5/23.\n */\ninterface SourceChooseView : LoadingDataView {\n    fun renderSources(sources: List<SourceItem>)\n\n    fun sourceSelected(sources: List<SourceItem>, selectedItem: SourceItem)\n}"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/view/WallpaperDetailView.kt",
    "content": "package com.yalin.style.view\n\nimport android.content.Intent\n\nimport com.yalin.style.model.WallpaperItem\n\n/**\n * @author jinyalin\n * *\n * @since 2017/4/20.\n */\n\ninterface WallpaperDetailView : LoadingDataView {\n\n    fun renderWallpaper(wallpaperItem: WallpaperItem)\n\n    fun showNextButton(show: Boolean)\n\n    fun shareWallpaper(shareIntent: Intent)\n\n    fun validLikeAction(valid: Boolean)\n\n    fun updateLikeState(item: WallpaperItem, liked: Boolean)\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/view/activity/AboutActivity.kt",
    "content": "package com.yalin.style.view.activity\n\nimport android.content.Intent\nimport android.os.Bundle\nimport android.support.v7.app.AppCompatActivity\nimport android.text.Html\nimport android.text.method.LinkMovementMethod\nimport android.view.View\nimport android.view.ViewPropertyAnimator\n\nimport com.yalin.style.BuildConfig\nimport com.yalin.style.R\nimport com.yalin.style.analytics.Analytics\nimport com.yalin.style.analytics.Event\nimport com.yalin.style.view.fragment.AnimatedStyleLogoFragment\nimport com.yalin.style.view.fragment.StyleRenderFragment\nimport kotlinx.android.synthetic.main.activity_about.*\nimport kotlinx.android.synthetic.main.layout_include_about_content.*\n\n/**\n * @author jinyalin\n * *\n * @since 2017/5/3.\n */\n\nclass AboutActivity : AppCompatActivity() {\n\n    private var mAnimator: ViewPropertyAnimator? = null\n\n    public override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.activity_about)\n        maybeLogEvent(intent)\n\n        window.decorView.systemUiVisibility =\n                View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or\n                        View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or\n                        View.SYSTEM_UI_FLAG_LAYOUT_STABLE\n\n        appBar.setNavigationOnClickListener { onNavigateUp() }\n\n        if (savedInstanceState == null) {\n            fragmentManager.beginTransaction()\n                    .add(R.id.demoViewContainer,\n                            StyleRenderFragment.createInstance(true, true))\n                    .commit()\n        }\n\n        // Build the about body view and append the link to see OSS licenses\n        appVersion.text = Html.fromHtml(\n                getString(R.string.about_version_template, BuildConfig.VERSION_NAME))\n\n        aboutBody.text = Html.fromHtml(getString(R.string.about_body))\n        aboutBody.movementMethod = LinkMovementMethod()\n    }\n\n    private fun maybeLogEvent(intent: Intent) {\n        val uri = intent.data\n        uri?.let {\n            Analytics.logEvent(this, Event.SHORTCUTS_ABOUT)\n        }\n    }\n\n    override fun onPostCreate(savedInstanceState: Bundle?) {\n        super.onPostCreate(savedInstanceState)\n\n        demoViewContainer.alpha = 0f\n        mAnimator = demoViewContainer.animate()\n                .alpha(1f)\n                .setStartDelay(250)\n                .setDuration(1000)\n                .withEndAction {\n                    val logoFragment = fragmentManager.findFragmentById(R.id.animatedLogoFragment)\n                            as AnimatedStyleLogoFragment\n                    logoFragment.start()\n                }\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        if (mAnimator != null) {\n            mAnimator!!.cancel()\n        }\n    }\n}\n\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/view/activity/AdvanceSettingActivity.kt",
    "content": "package com.yalin.style.view.activity\n\nimport android.content.Context\nimport android.graphics.drawable.ColorDrawable\nimport android.os.Bundle\nimport android.support.v4.content.ContextCompat\nimport android.support.v4.view.ViewCompat\nimport android.support.v7.widget.DefaultItemAnimator\nimport android.support.v7.widget.GridLayoutManager\nimport android.support.v7.widget.RecyclerView\nimport android.text.Html\nimport android.text.TextUtils\nimport android.view.*\nimport android.widget.ImageView\nimport android.widget.TextView\nimport com.afollestad.materialdialogs.MaterialDialog\nimport com.bumptech.glide.Glide\nimport com.google.android.gms.ads.AdListener\nimport com.yalin.style.R\nimport com.yalin.style.StyleApplication\nimport com.yalin.style.analytics.Analytics\nimport com.yalin.style.analytics.Event\nimport com.yalin.style.data.log.LogUtil\nimport com.yalin.style.data.utils.WallpaperFileHelper\nimport com.yalin.style.exception.ErrorMessageFactory\nimport com.yalin.style.model.AdvanceWallpaperItem\nimport com.yalin.style.presenter.AdvanceSettingPresenter\nimport com.yalin.style.util.ImageLoader\nimport com.yalin.style.util.maybeAttachAd\nimport com.yalin.style.util.maybeAttachInterstitialAd\nimport com.yalin.style.util.maybeShowInterstitialAd\nimport com.yalin.style.view.AdvanceSettingView\nimport com.yalin.style.view.component.DownloadingDialog\nimport kotlinx.android.synthetic.main.activity_advance_setting.*\nimport org.jetbrains.anko.toast\nimport java.util.ArrayList\nimport javax.inject.Inject\n\n\n/**\n * @author jinyalin\n * @since 2017/7/28.\n */\nclass AdvanceSettingActivity : BaseActivity(), AdvanceSettingView {\n    companion object {\n        val TAG = \"AdvanceSettingActivity\"\n        val LOAD_STATE = \"load_state\"\n\n        val LOAD_STATE_NORMAL = 0\n        val LOAD_STATE_LOADING = 1\n        val LOAD_STATE_RETRY = 2\n    }\n\n    @Inject\n    lateinit internal var presenter: AdvanceSettingPresenter\n\n    val wallpapers = ArrayList<AdvanceWallpaperItem>()\n\n    var imageLoader: ImageLoader? = null\n\n    private var loadState = LOAD_STATE_NORMAL\n\n    private var placeHolderDrawable: ColorDrawable? = null\n    private var mItemSize = 10\n\n    private var downloadDialog: DownloadingDialog? = null\n\n    private val insertAdListener = object : AdListener() {\n        var currentAdItem: AdvanceWallpaperItem? = null\n\n        override fun onAdLeftApplication() {\n            super.onAdLeftApplication()\n        }\n\n        override fun onAdFailedToLoad(p0: Int) {\n            super.onAdFailedToLoad(p0)\n            adLoaded = true\n            maybeShowWallpaper()\n        }\n\n        override fun onAdClosed() {\n            super.onAdClosed()\n            if (currentAdItem != null) {\n                presenter.adViewed(currentAdItem!!)\n                currentAdItem = null\n            }\n        }\n\n        override fun onAdOpened() {\n            super.onAdOpened()\n        }\n\n        override fun onAdLoaded() {\n            super.onAdLoaded()\n            adLoaded = true\n            maybeShowWallpaper()\n        }\n    }\n\n    private var adLoaded = false\n    private var wallpaperLoaded = false\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        StyleApplication.instance.applicationComponent.inject(this)\n        setContentView(R.layout.activity_advance_setting)\n\n        setSupportActionBar(appBar)\n\n        placeHolderDrawable = ColorDrawable(ContextCompat.getColor(this,\n                R.color.gallery_chosen_photo_placeholder))\n        initViews()\n\n        imageLoader = ImageLoader(this)\n        presenter.setView(this)\n\n        if (savedInstanceState != null) {\n            loadState = savedInstanceState.getInt(LOAD_STATE)\n            presenter.onRestoreInstanceState(savedInstanceState)\n        }\n\n        handleState()\n\n        maybeAttachAd(this)\n        maybeAttachInterstitialAd(this, insertAdListener)\n    }\n\n    private fun handleState() {\n        if (loadState == LOAD_STATE_NORMAL) {\n            presenter.initialize()\n        } else if (loadState == LOAD_STATE_LOADING) {\n            presenter.loadAdvanceWallpaper()\n        } else if (loadState == LOAD_STATE_RETRY) {\n            showRetry()\n        }\n    }\n\n    private fun initViews() {\n        val itemAnimator = DefaultItemAnimator()\n        itemAnimator.supportsChangeAnimations = false\n        wallpaperList.itemAnimator = itemAnimator\n\n        val gridLayoutManager = GridLayoutManager(this, 1)\n        wallpaperList.layoutManager = gridLayoutManager\n\n        btnLoadAdvanceWallpaper.setOnClickListener {\n            presenter.loadAdvanceWallpaper()\n            Analytics.logEvent(this@AdvanceSettingActivity, Event.LOAD_ADVANCES)\n        }\n        btnRetry.setOnClickListener {\n            presenter.loadAdvanceWallpaper()\n            Analytics.logEvent(this@AdvanceSettingActivity, Event.LOAD_ADVANCES)\n        }\n\n        wallpaperList.viewTreeObserver\n                .addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {\n                    override fun onGlobalLayout() {\n                        val width = wallpaperList.width -\n                                wallpaperList.paddingStart - wallpaperList.paddingEnd\n                        if (width <= 0) {\n                            return\n                        }\n\n                        // Compute number of columns\n                        val maxItemWidth = resources.getDimensionPixelSize(\n                                R.dimen.advance_grid_max_item_size)\n                        var numColumns = 1\n                        while (true) {\n                            if (width / numColumns > maxItemWidth) {\n                                ++numColumns\n                            } else {\n                                break\n                            }\n                        }\n\n                        val spacing = resources.getDimensionPixelSize(\n                                R.dimen.gallery_chosen_photo_grid_spacing)\n                        mItemSize = (width - spacing * (numColumns - 1)) / numColumns\n\n                        // Complete setup\n                        gridLayoutManager.spanCount = numColumns\n                        advanceWallpaperAdapter.setHasStableIds(true)\n                        wallpaperList.adapter = advanceWallpaperAdapter\n\n                        wallpaperList.viewTreeObserver.removeOnGlobalLayoutListener(this)\n                    }\n                })\n\n        ViewCompat.setOnApplyWindowInsetsListener(wallpaperList) { v, insets ->\n            val gridSpacing = resources\n                    .getDimensionPixelSize(R.dimen.gallery_chosen_photo_grid_spacing)\n            ViewCompat.onApplyWindowInsets(v, insets.replaceSystemWindowInsets(\n                    insets.systemWindowInsetLeft + gridSpacing,\n                    gridSpacing,\n                    insets.systemWindowInsetRight + gridSpacing,\n                    insets.systemWindowInsetBottom + insets.systemWindowInsetTop + gridSpacing))\n\n            insets\n        }\n\n        downloadDialog = DownloadingDialog(this)\n    }\n\n    override fun onSaveInstanceState(outState: Bundle) {\n        outState.putInt(LOAD_STATE, loadState)\n        presenter.onSaveInstanceState(outState)\n        super.onSaveInstanceState(outState)\n    }\n\n    override fun onResume() {\n        super.onResume()\n        presenter.resume()\n    }\n\n    override fun onPause() {\n        super.onPause()\n        presenter.pause()\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        presenter.destroy()\n    }\n\n    override fun onCreateOptionsMenu(menu: Menu): Boolean {\n        super.onCreateOptionsMenu(menu)\n        menuInflater.inflate(R.menu.advance_activity, menu)\n        return true\n    }\n\n    override fun onOptionsItemSelected(item: MenuItem): Boolean {\n        if (item.itemId == R.id.action_advance_hint) {\n            val dialogBuilder = MaterialDialog.Builder(this)\n                    .iconRes(R.drawable.advance_wallpaper_msg)\n                    .title(R.string.hint)\n                    .content(Html.fromHtml(getString(R.string.advance_hint)))\n                    .positiveText(R.string.confirm)\n\n            dialogBuilder.build().show()\n            return true\n        }\n        return super.onOptionsItemSelected(item)\n    }\n\n    override fun renderWallpapers(wallpapers: List<AdvanceWallpaperItem>) {\n        loadState = LOAD_STATE_NORMAL\n\n        this.wallpapers.clear()\n        this.wallpapers.addAll(wallpapers)\n        wallpaperLoaded = true\n        maybeShowWallpaper()\n    }\n\n    private fun maybeShowWallpaper() {\n        if (wallpaperList.visibility == View.VISIBLE) {\n            return\n        }\n        if (adLoaded && wallpaperLoaded) {\n            wallpaperList.visibility = View.VISIBLE\n            empty.visibility = View.GONE\n            loading.visibility = View.GONE\n            retry.visibility = View.GONE\n            advanceWallpaperAdapter.notifyDataSetChanged()\n        }\n    }\n\n    override fun showLoading() {\n        loadState = LOAD_STATE_LOADING\n\n        wallpaperList.visibility = View.GONE\n        empty.visibility = View.GONE\n        loading.visibility = View.VISIBLE\n        retry.visibility = View.GONE\n    }\n\n    override fun hideLoading() {\n\n    }\n\n    override fun showRetry() {\n        loadState = LOAD_STATE_RETRY\n\n        wallpaperList.visibility = View.GONE\n        empty.visibility = View.GONE\n        loading.visibility = View.GONE\n        retry.visibility = View.VISIBLE\n    }\n\n    override fun hideRetry() {\n\n    }\n\n    override fun showError(message: String) {\n        toast(message)\n    }\n\n    override fun showEmpty() {\n        wallpaperList.visibility = View.GONE\n        empty.visibility = View.VISIBLE\n        loading.visibility = View.GONE\n        retry.visibility = View.GONE\n    }\n\n    override fun context(): Context {\n        return applicationContext\n    }\n\n    override fun complete() {\n        finish()\n    }\n\n    override fun wallpaperSelected(wallpaperId: String) {\n        wallpapers.forEach { it ->\n            it.isSelected = TextUtils.equals(it.wallpaperId, wallpaperId)\n        }\n        advanceWallpaperAdapter.notifyDataSetChanged()\n    }\n\n    override fun showDownloadHintDialog(item: AdvanceWallpaperItem) {\n        val needAd = item.needAd\n        val downloadCallback =\n                MaterialDialog.SingleButtonCallback { _, _ ->\n                    presenter.requestDownload(item)\n                    Analytics.logEvent(this@AdvanceSettingActivity,\n                            Event.DOWNLOAD_COMPONENT, item.name)\n                }\n        val adCallback = MaterialDialog.SingleButtonCallback { dialog, which ->\n            downloadCallback.onClick(dialog, which)\n            showAd(item)\n        }\n        val dialogBuilder = MaterialDialog.Builder(this)\n                .iconRes(R.drawable.advance_wallpaper_msg)\n                .title(R.string.hint)\n                .content(if (needAd)\n                    Html.fromHtml(getString(R.string.advance_ad_download_hint)) else\n                    Html.fromHtml(getString(R.string.advance_download_hint)))\n                .positiveText(if (needAd)\n                    R.string.advance_ad_download_msg else R.string.advance_download_msg)\n                .onPositive(if (needAd) adCallback else downloadCallback)\n\n        dialogBuilder.build().show()\n    }\n\n    override fun showDownloadingDialog(item: AdvanceWallpaperItem) {\n        LogUtil.D(TAG, \"showDownloadingDialog ${item.name}\")\n        downloadDialog!!.show()\n    }\n\n    override fun updateDownloadingProgress(downloaded: Long) {\n        LogUtil.D(TAG, \"updateDownloadingProgress $downloaded\")\n        downloadDialog!!.updateProgress(downloaded)\n    }\n\n    override fun downloadComplete(item: AdvanceWallpaperItem) {\n        val position = wallpapers.indices.firstOrNull {\n            TextUtils.equals(wallpapers[it].wallpaperId, item.wallpaperId)\n        } ?: -1\n        if (position >= 0) {\n            advanceWallpaperAdapter.notifyItemChanged(position)\n        }\n        downloadDialog!!.dismiss()\n    }\n\n    override fun showDownloadError(item: AdvanceWallpaperItem, e: Exception) {\n        downloadDialog!!.dismiss()\n        showError(ErrorMessageFactory.create(this, e))\n    }\n\n    override fun showAd(item: AdvanceWallpaperItem) {\n        if (maybeShowInterstitialAd()) {\n            insertAdListener.currentAdItem = item\n        } else {\n            adViewed(item)\n        }\n    }\n\n    override fun adViewed(item: AdvanceWallpaperItem) {\n        val position = wallpapers.indices.firstOrNull {\n            TextUtils.equals(wallpapers[it].wallpaperId, item.wallpaperId)\n        } ?: -1\n        if (position >= 0) {\n            wallpapers[position].needAd = false\n        }\n    }\n\n    class AdvanceViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {\n        var checkedOverlayView: View = itemView.findViewById(R.id.checked_overlay)\n        var downloadOverlayView: View = itemView.findViewById(R.id.download_overlay)\n        var thumbnail: ImageView = itemView.findViewById(R.id.thumbnail) as ImageView\n        var tvName: TextView = itemView.findViewById(R.id.tvName) as TextView\n    }\n\n    private val advanceWallpaperAdapter = object : RecyclerView.Adapter<AdvanceViewHolder>() {\n        override fun onBindViewHolder(holder: AdvanceViewHolder, position: Int) {\n            val item = wallpapers[position]\n            holder.thumbnail.layoutParams.width = mItemSize\n            holder.thumbnail.layoutParams.height = mItemSize\n            Glide.with(this@AdvanceSettingActivity)\n                    .load(item.iconUrl)\n                    .override(mItemSize, mItemSize)\n                    .placeholder(placeHolderDrawable)\n                    .into(holder.thumbnail)\n\n            if (item.isSelected) {\n                holder.checkedOverlayView.visibility = View.VISIBLE\n            } else {\n                holder.checkedOverlayView.visibility = View.GONE\n            }\n            val downloadingItem = presenter.getDownloadingItem()\n            if (WallpaperFileHelper.isNeedDownloadAdvanceComponent(item.lazyDownload,\n                    item.storePath) || (downloadingItem != null\n                    && TextUtils.equals(downloadingItem.wallpaperId, item.wallpaperId))) {\n                holder.downloadOverlayView.visibility = View.VISIBLE\n            } else {\n                holder.downloadOverlayView.visibility = View.GONE\n            }\n\n            holder.tvName.text = item.name\n        }\n\n        override fun getItemCount(): Int {\n            return wallpapers.size\n        }\n\n        override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): AdvanceViewHolder {\n            val view = LayoutInflater.from(this@AdvanceSettingActivity)\n                    .inflate(R.layout.advance_chosen_wallpaper_item, parent, false)\n\n            val vh = AdvanceViewHolder(view)\n            view.setOnClickListener {\n                val item = wallpapers[vh.adapterPosition]\n                presenter.selectAdvanceWallpaper(item)\n            }\n\n            return vh\n        }\n\n        override fun getItemId(position: Int): Long {\n            return wallpapers[position].id\n        }\n    }\n}"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/view/activity/BaseActivity.kt",
    "content": "package com.yalin.style.view.activity\n\nimport android.support.v7.app.AppCompatActivity\n\nimport com.yalin.style.StyleApplication\nimport com.yalin.style.injection.component.ApplicationComponent\n\n/**\n * @author jinyalin\n * *\n * @since 2017/4/20.\n */\n\nabstract class BaseActivity : AppCompatActivity() {\n    protected val applicationComponent: ApplicationComponent\n        get() = (application as StyleApplication).applicationComponent\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/view/activity/GallerySettingActivity.kt",
    "content": "package com.yalin.style.view.activity\n\nimport android.Manifest\nimport android.animation.Animator\nimport android.animation.AnimatorListenerAdapter\nimport android.annotation.TargetApi\nimport android.app.Activity\nimport android.content.*\nimport android.content.pm.ActivityInfo\nimport android.content.pm.PackageManager\nimport android.graphics.drawable.ColorDrawable\nimport android.net.Uri\nimport android.os.Build\nimport android.os.Bundle\nimport android.os.Handler\nimport android.provider.Settings\nimport android.support.design.widget.Snackbar\nimport android.support.v4.app.ActivityCompat\nimport android.support.v4.content.ContextCompat\nimport android.support.v4.view.ViewCompat\nimport android.support.v7.app.AlertDialog\nimport android.support.v7.widget.DefaultItemAnimator\nimport android.support.v7.widget.GridLayoutManager\nimport android.support.v7.widget.RecyclerView\nimport android.support.v7.widget.Toolbar\nimport android.text.TextUtils\nimport android.util.SparseIntArray\nimport android.view.*\nimport android.widget.ImageView\nimport com.bumptech.glide.Glide\nimport com.yalin.style.R\nimport com.yalin.style.StyleApplication\nimport com.yalin.style.analytics.Analytics\nimport com.yalin.style.analytics.Event\nimport com.yalin.style.data.utils.getDisplayNameForTreeUri\nimport com.yalin.style.data.utils.getImagesFromTreeUri\nimport com.yalin.style.model.GalleryWallpaperItem\nimport com.yalin.style.presenter.GallerySettingPresenter\nimport com.yalin.style.util.MultiSelectionController\nimport com.yalin.style.util.maybeAttachAd\nimport com.yalin.style.view.GallerySettingView\nimport kotlinx.android.synthetic.main.activity_gallery_setting.*\nimport org.jetbrains.anko.toast\nimport java.util.*\nimport javax.inject.Inject\n\n/**\n * @author jinyalin\n * @since 2017/5/24.\n */\nclass GallerySettingActivity : BaseActivity(), GallerySettingView {\n\n    companion object {\n        private val REQUEST_CHOOSE_PHOTOS = 1\n        private val REQUEST_CHOOSE_FOLDER = 2\n        private val REQUEST_STORAGE_PERMISSION = 3\n\n        private val SHARED_PREF_NAME = \"GallerySettingsActivity\"\n        private val SHOW_INTERNAL_STORAGE_MESSAGE = \"show_internal_storage_message\"\n\n        private val ITEM_TYPE_URI = 0\n        private val ITEM_TYPE_TREE = 1\n\n        private val STATE_SELECTION = \"selection\"\n    }\n\n    @Inject\n    lateinit internal var presenter: GallerySettingPresenter\n\n    private var mPlaceholderDrawable: ColorDrawable? = null\n    private var mPlaceholderSmallDrawable: ColorDrawable? = null\n\n    private var mItemSize = 10\n\n    private val mMultiSelectionController =\n            MultiSelectionController<GalleryWallpaperItem>(STATE_SELECTION)\n\n    private var mUpdatePosition = -1\n\n    private val mWallpapers = ArrayList<GalleryWallpaperItem>()\n\n    private var mLastTouchPosition: Int = 0\n    private var mLastTouchX: Int = 0\n    private var mLastTouchY: Int = 0\n\n    private val mGetContentActivities = ArrayList<ActivityInfo>()\n\n    private val sRotateMenuIdsByMin = SparseIntArray()\n    private val sRotateMinsByMenuId = SparseIntArray()\n\n    init {\n        sRotateMenuIdsByMin.put(0, R.id.action_rotate_interval_none)\n        sRotateMenuIdsByMin.put(60, R.id.action_rotate_interval_1h)\n        sRotateMenuIdsByMin.put(60 * 3, R.id.action_rotate_interval_3h)\n        sRotateMenuIdsByMin.put(60 * 6, R.id.action_rotate_interval_6h)\n        sRotateMenuIdsByMin.put(60 * 24, R.id.action_rotate_interval_24h)\n        sRotateMenuIdsByMin.put(60 * 72, R.id.action_rotate_interval_72h)\n        for (i in 0..(sRotateMenuIdsByMin.size() - 1)) {\n            sRotateMinsByMenuId.put(sRotateMenuIdsByMin.valueAt(i), sRotateMenuIdsByMin.keyAt(i))\n        }\n    }\n\n    private var mOptionsMenu: Menu? = null\n    private var mUpdateInterval: Int = 0\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        StyleApplication.instance.applicationComponent.inject(this)\n        setContentView(R.layout.activity_gallery_setting)\n\n        setSupportActionBar(appBar)\n\n        mPlaceholderDrawable = ColorDrawable(ContextCompat.getColor(this,\n                R.color.gallery_chosen_photo_placeholder))\n        mPlaceholderSmallDrawable = ColorDrawable(ContextCompat.getColor(this,\n                R.color.gallery_chosen_photo_placeholder))\n\n        initViews()\n\n        presenter.setView(this)\n        presenter.initialize()\n\n        maybeAttachAd(this)\n    }\n\n    override fun onResume() {\n        super.onResume()\n        presenter.resume()\n        onDataSetChanged()\n    }\n\n    override fun onPause() {\n        super.onPause()\n        presenter.pause()\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        presenter.destroy()\n    }\n\n    private fun initViews() {\n        val itemAnimator = DefaultItemAnimator()\n        itemAnimator.supportsChangeAnimations = false\n        photoGrid.itemAnimator = itemAnimator\n\n        setupMultiSelect()\n\n        val gridLayoutManager = GridLayoutManager(this, 1)\n        photoGrid.layoutManager = gridLayoutManager\n\n        val vto = photoGrid.viewTreeObserver\n        vto.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {\n            override fun onGlobalLayout() {\n                val width = photoGrid.width - photoGrid.paddingStart - photoGrid.paddingEnd\n                if (width <= 0) {\n                    return\n                }\n\n                // Compute number of columns\n                val maxItemWidth = resources.getDimensionPixelSize(\n                        R.dimen.gallery_chosen_photo_grid_max_item_size)\n                var numColumns = 1\n                while (true) {\n                    if (width / numColumns > maxItemWidth) {\n                        ++numColumns\n                    } else {\n                        break\n                    }\n                }\n\n                val spacing = resources.getDimensionPixelSize(\n                        R.dimen.gallery_chosen_photo_grid_spacing)\n                mItemSize = (width - spacing * (numColumns - 1)) / numColumns\n\n                // Complete setup\n                gridLayoutManager.spanCount = numColumns\n                mChosenPhotosAdapter.setHasStableIds(true)\n                photoGrid.adapter = mChosenPhotosAdapter\n\n                photoGrid.viewTreeObserver.removeOnGlobalLayoutListener(this)\n                tryUpdateSelection(false)\n            }\n        })\n\n        ViewCompat.setOnApplyWindowInsetsListener(photoGrid) { v, insets ->\n            val gridSpacing = resources\n                    .getDimensionPixelSize(R.dimen.gallery_chosen_photo_grid_spacing)\n            ViewCompat.onApplyWindowInsets(v, insets.replaceSystemWindowInsets(\n                    insets.systemWindowInsetLeft + gridSpacing,\n                    gridSpacing,\n                    insets.systemWindowInsetRight + gridSpacing,\n                    insets.systemWindowInsetBottom + insets.systemWindowInsetTop + gridSpacing +\n                            resources.getDimensionPixelSize(R.dimen.gallery_fab_space)))\n\n            insets\n        }\n\n        btnGalleryEnableRandom.setOnClickListener {\n            requestPermissions(arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE),\n                    REQUEST_STORAGE_PERMISSION)\n        }\n\n        btnGalleryEditPermissionSettings.setOnClickListener {\n            val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,\n                    Uri.fromParts(\"package\", packageName, null))\n            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)\n            startActivity(intent)\n        }\n\n        addFab.setOnClickListener {\n            Analytics.logEvent(this, Event.ADD_PHOTO_CLICK)\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n                // On Lollipop and higher, we show the add toolbar to allow users to add either\n                // individual photos or a whole directory\n                showAddToolbar()\n            } else {\n                requestPhotos()\n            }\n        }\n\n        addPhotos.setOnClickListener {\n            requestPhotos()\n        }\n\n        addFolder.setOnClickListener {\n            val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)\n            try {\n                startActivityForResult(intent, REQUEST_CHOOSE_FOLDER)\n                val preferences = getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE)\n                if (preferences.getBoolean(SHOW_INTERNAL_STORAGE_MESSAGE, true)) {\n                    toast(R.string.gallery_internal_storage_message)\n                }\n            } catch (e: ActivityNotFoundException) {\n                Snackbar.make(photoGrid, R.string.gallery_add_folder_error,\n                        Snackbar.LENGTH_LONG).show()\n                hideAddToolbar(true)\n            }\n        }\n    }\n\n    override fun onRequestPermissionsResult(requestCode: Int,\n                                            permissions: Array<out String>,\n                                            grantResults: IntArray) {\n        super.onRequestPermissionsResult(requestCode, permissions, grantResults)\n        if (requestCode != REQUEST_STORAGE_PERMISSION) {\n            return\n        }\n        onDataSetChanged()\n    }\n\n    override fun renderGalleryWallpapers(wallpaperItems: List<GalleryWallpaperItem>) {\n        mWallpapers.clear()\n        mWallpapers.addAll(wallpaperItems)\n        mChosenPhotosAdapter.notifyDataSetChanged()\n        onDataSetChanged()\n    }\n\n    override fun renderUpdateInterval(intervalMin: Int) {\n        mUpdateInterval = intervalMin\n        val menuId = sRotateMenuIdsByMin[intervalMin]\n        if (menuId != 0 && mOptionsMenu != null) {\n            val item = mOptionsMenu!!.findItem(menuId)\n            item?.isChecked = true\n        }\n    }\n\n    override fun showLoading() {\n    }\n\n    override fun hideLoading() {\n    }\n\n    override fun showRetry() {\n    }\n\n    override fun hideRetry() {\n    }\n\n    override fun showError(message: String) {\n        toast(message)\n    }\n\n    override fun context(): Context {\n        return this.applicationContext\n    }\n\n    override fun onCreateOptionsMenu(menu: Menu): Boolean {\n        super.onCreateOptionsMenu(menu)\n        menuInflater.inflate(R.menu.gallery_activity, menu)\n        mOptionsMenu = menu\n\n        val menuId = sRotateMenuIdsByMin[mUpdateInterval]\n        if (menuId != 0 && mOptionsMenu != null) {\n            val item = mOptionsMenu!!.findItem(menuId)\n            item?.isChecked = true\n        }\n\n        return true\n    }\n\n    override fun onPrepareOptionsMenu(menu: Menu): Boolean {\n        super.onPrepareOptionsMenu(menu)\n        // Make sure the 'Import photos' MenuItem is set up properly based on the number of\n        // activities that handle ACTION_GET_CONTENT\n        // 0 = hide the MenuItem\n        // 1 = show 'Import photos from APP_NAME' to go to the one app that exists\n        // 2 = show 'Import photos...' to have the user pick which app to import photos from\n        val intent = Intent(Intent.ACTION_GET_CONTENT)\n        intent.type = \"image/*\"\n        intent.addCategory(Intent.CATEGORY_OPENABLE)\n        val getContentActivities = packageManager.queryIntentActivities(intent, 0)\n        mGetContentActivities.clear()\n        for (info in getContentActivities) {\n            // Filter out the default system UI\n            if (TextUtils.equals(info.activityInfo.packageName, \"com.android.documentsui\")) {\n                continue\n            }\n            // Filter out non-exported activities\n            if (!info.activityInfo.exported) {\n                continue\n            }\n            // Filter out activities we don't have permission to start\n            if (!TextUtils.isEmpty(info.activityInfo.permission)\n                    && packageManager.checkPermission(info.activityInfo.permission,\n                    packageName) != PackageManager.PERMISSION_GRANTED) {\n                continue\n            }\n            mGetContentActivities.add(info.activityInfo)\n        }\n\n        // Hide the 'Import photos' action if there are no activities found\n        val importPhotosMenuItem = menu.findItem(R.id.action_import_photos)\n        importPhotosMenuItem.isVisible = !mGetContentActivities.isEmpty()\n        // If there's only one app that supports ACTION_GET_CONTENT, tell the user what that app is\n        if (mGetContentActivities.size == 1) {\n            importPhotosMenuItem.title = getString(R.string.gallery_action_import_photos_from,\n                    mGetContentActivities[0].loadLabel(packageManager))\n        } else {\n            importPhotosMenuItem.setTitle(R.string.gallery_action_import_photos)\n        }\n        return true\n    }\n\n    override fun onOptionsItemSelected(item: MenuItem): Boolean {\n        val itemId = item.itemId\n        val rotateMin = sRotateMinsByMenuId.get(itemId, -1)\n        if (rotateMin != -1) {\n            Analytics.logEvent(this, Event.SETUP_UPDATE_INTERVAL, rotateMin.toString())\n            presenter.setUpdateInterval(rotateMin)\n            item.isChecked = true\n            return true\n        }\n\n        if (itemId == R.id.action_import_photos) {\n            Analytics.logEvent(this, Event.IMPORT_FROM_GALLERY)\n            if (mGetContentActivities.size == 1) {\n                // Just start the one ACTION_GET_CONTENT app\n                requestGetContent(mGetContentActivities[0])\n            } else {\n                // Let the user pick which app they want to import photos from\n                val packageManager = packageManager\n                val items = arrayOfNulls<CharSequence>(mGetContentActivities.size)\n                for (h in mGetContentActivities.indices) {\n                    items[h] = mGetContentActivities[h].loadLabel(packageManager)\n                }\n                AlertDialog.Builder(this)\n                        .setTitle(R.string.gallery_import_dialog_title)\n                        .setItems(items, { _, which ->\n                            requestGetContent(mGetContentActivities[which])\n                        })\n                        .show()\n            }\n            return true\n        } else if (itemId == R.id.action_clear_photos) {\n            Analytics.logEvent(this, Event.CLEAR_WALLPAPER)\n            presenter.removeGalleryWallpaper(mWallpapers)\n            return true\n        }\n\n        return super.onOptionsItemSelected(item)\n    }\n\n    private fun requestGetContent(info: ActivityInfo) {\n        val intent = Intent(Intent.ACTION_GET_CONTENT)\n        intent.type = \"image/*\"\n        intent.addCategory(Intent.CATEGORY_OPENABLE)\n        intent.setClassName(info.packageName, info.name)\n        intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)\n        startActivityForResult(intent, REQUEST_CHOOSE_PHOTOS)\n    }\n\n    private fun requestPhotos() {\n        // Use ACTION_OPEN_DOCUMENT by default for adding photos.\n        // This allows us to use persistent URI permissions to access the underlying photos\n        // meaning we don't need to use additional storage space and will pull in edits automatically\n        // in addition to syncing deletions.\n        // (There's a separate 'Import photos' option which uses ACTION_GET_CONTENT to support legacy apps)\n        val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)\n        intent.type = \"image/*\"\n        intent.addCategory(Intent.CATEGORY_OPENABLE)\n        intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)\n        try {\n            startActivityForResult(intent, REQUEST_CHOOSE_PHOTOS)\n        } catch (e: ActivityNotFoundException) {\n            Snackbar.make(photoGrid, R.string.gallery_add_photos_error,\n                    Snackbar.LENGTH_LONG).show()\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n                hideAddToolbar(true)\n            }\n        }\n\n    }\n\n    override fun onActivityResult(requestCode: Int, resultCode: Int, result: Intent?) {\n        super.onActivityResult(requestCode, resultCode, result)\n        if (requestCode != REQUEST_CHOOSE_PHOTOS && requestCode != REQUEST_CHOOSE_FOLDER) {\n            return\n        }\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            if (!addToolbar.isAttachedToWindow) {\n                // Can't animate detached Views\n                addToolbar.visibility = View.INVISIBLE\n                addFab.visibility = View.VISIBLE\n            } else {\n                hideAddToolbar(true)\n            }\n        }\n\n        if (resultCode != Activity.RESULT_OK) {\n            return\n        }\n\n        if (result == null) {\n            return\n        }\n\n        if (requestCode == REQUEST_CHOOSE_FOLDER) {\n            val preferences = getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE)\n            preferences.edit().putBoolean(SHOW_INTERNAL_STORAGE_MESSAGE, false).apply()\n        }\n\n        // Add chosen items\n        val uris = HashSet<Uri>()\n        if (result.data != null) {\n            uris.add(result.data)\n        }\n        // When selecting multiple images, \"Photos\" returns the first URI in getData and all URIs\n        // in getClipData.\n        val clipData = result.clipData\n        if (clipData != null) {\n            val count = clipData.itemCount\n            for (i in 0..count - 1) {\n                val uri = clipData.getItemAt(i).uri\n                if (uri != null) {\n                    uris.add(uri)\n                }\n            }\n        }\n\n        if (uris.isEmpty()) {\n            // Nothing to do, so we can avoid posting the runnable at all\n            return\n        }\n\n        presenter.addGalleryWallpaper(uris)\n    }\n\n    private fun onDataSetChanged() {\n        if (mWallpapers.size > 0 && ContextCompat.checkSelfPermission(this,\n                Manifest.permission.READ_EXTERNAL_STORAGE)\n                == PackageManager.PERMISSION_GRANTED) {\n            photoGrid.visibility = View.VISIBLE\n            addFab.visibility = View.VISIBLE\n            empty.visibility = View.GONE\n        } else {\n            // No chosen images, show the empty View\n            photoGrid.visibility = View.GONE\n            addFab.visibility = View.GONE\n            empty.visibility = View.VISIBLE\n            if (ContextCompat.checkSelfPermission(this,\n                    Manifest.permission.READ_EXTERNAL_STORAGE)\n                    == PackageManager.PERMISSION_GRANTED) {\n                // Permission is granted, we can show the random camera photos image\n                emptyAnimator.displayedChild = 0\n                emptyDescription.setText(R.string.gallery_empty)\n                addFab.visibility = View.VISIBLE\n            } else {\n                if (ActivityCompat.shouldShowRequestPermissionRationale(this,\n                        Manifest.permission.READ_EXTERNAL_STORAGE)) {\n                    // We should show rationale on why they should enable the storage permission and\n                    // random camera photos\n                    emptyAnimator.displayedChild = 1\n                    emptyDescription.setText(R.string.gallery_permission_rationale)\n                } else {\n                    // The user has permanently denied the storage permission. Give them a link to app settings\n                    emptyAnimator.displayedChild = 2\n                    emptyDescription.setText(R.string.gallery_denied_explanation)\n                }\n            }\n        }\n    }\n\n    @TargetApi(Build.VERSION_CODES.LOLLIPOP)\n    private fun showAddToolbar() {\n        // Divide by two since we're doing two animations but we want the total time to the short animation time\n        val duration = resources.getInteger(android.R.integer.config_shortAnimTime) / 2\n        // Hide the add button\n        addFab.animate()\n                .scaleX(0f)\n                .scaleY(0f)\n                .translationY(resources.getDimension(R.dimen.gallery_fab_margin))\n                .setDuration(duration.toLong())\n                .withEndAction {\n                    addFab.visibility = View.INVISIBLE\n                    // Then show the toolbar\n                    addToolbar.visibility = View.VISIBLE\n                    ViewAnimationUtils.createCircularReveal(\n                            addToolbar,\n                            addToolbar.width / 2,\n                            addToolbar.height / 2,\n                            0f,\n                            (addToolbar.width / 2).toFloat())\n                            .setDuration(duration.toLong())\n                            .start()\n                }\n    }\n\n    @TargetApi(Build.VERSION_CODES.LOLLIPOP)\n    private fun hideAddToolbar(showAddButton: Boolean) {\n        // Divide by two since we're doing two animations but we want the total time to the short animation time\n        val duration = resources.getInteger(android.R.integer.config_shortAnimTime) / 2\n        // Hide the toolbar\n        val hideAnimator = ViewAnimationUtils.createCircularReveal(\n                addToolbar,\n                addToolbar.width / 2,\n                addToolbar.height / 2,\n                (addToolbar.width / 2).toFloat(),\n                0f).setDuration((if (showAddButton) duration else duration * 2).toLong())\n        hideAnimator.addListener(object : AnimatorListenerAdapter() {\n            override fun onAnimationEnd(animation: Animator) {\n                addToolbar.visibility = View.INVISIBLE\n                if (showAddButton) {\n                    addFab.visibility = View.VISIBLE\n                    addFab.animate()\n                            .scaleX(1f)\n                            .scaleY(1f)\n                            .translationY(0f).duration = duration.toLong()\n                } else {\n                    // Just reset the translationY\n                    addFab.translationY = 0f\n                }\n            }\n        })\n        hideAnimator.start()\n    }\n\n    override fun onBackPressed() {\n        if (mMultiSelectionController.getSelectedCount() > 0) {\n            mMultiSelectionController.reset(true)\n        } else if (addToolbar.visibility == View.VISIBLE) {\n            hideAddToolbar(true)\n        } else {\n            super.onBackPressed()\n        }\n    }\n\n    private fun tryUpdateSelection(allowAnimate: Boolean) {\n        if (mUpdatePosition >= 0) {\n            mChosenPhotosAdapter.notifyItemChanged(mUpdatePosition)\n            mUpdatePosition = -1\n        } else {\n            mChosenPhotosAdapter.notifyDataSetChanged()\n        }\n\n        val selectedCount = mMultiSelectionController.getSelectedCount()\n        val toolbarVisible = selectedCount > 0\n        var showForceNow = selectedCount == 1\n        if (showForceNow) {\n            // Double check to make sure we can force a URI for the selected URI\n            val selectedItem = mMultiSelectionController.getSelection().iterator().next()\n            // Only show the force now icon if it isn't a tree URI or there is at least one image in the tree\n            showForceNow = !selectedItem.isTreeUri\n                    || !getImagesFromTreeUri(this, Uri.parse(selectedItem.uri), 1).isEmpty()\n        }\n        selectionToolbar.menu.findItem(R.id.action_force_now).isVisible = showForceNow\n\n        val tag = selectionToolbarContainer.getTag(0xDEADBEEF.toInt())\n        val previouslyVisible = if (tag == null) false else tag as Boolean\n\n        if (previouslyVisible != toolbarVisible) {\n            selectionToolbarContainer.setTag(0xDEADBEEF.toInt(), toolbarVisible)\n\n            val duration = if (allowAnimate)\n                resources.getInteger(android.R.integer.config_shortAnimTime)\n            else\n                0\n            if (toolbarVisible) {\n                selectionToolbarContainer.visibility = View.VISIBLE\n                selectionToolbarContainer.translationY =\n                        (-selectionToolbarContainer.height).toFloat()\n                selectionToolbarContainer.animate()\n                        .translationY(0f)\n                        .setDuration(duration.toLong())\n                        .withEndAction(null)\n\n                if (addToolbar.visibility == View.VISIBLE) {\n                    hideAddToolbar(false)\n                } else {\n                    addFab.animate()\n                            .scaleX(0f)\n                            .scaleY(0f)\n                            .setDuration(duration.toLong())\n                            .withEndAction({ addFab.visibility = View.INVISIBLE })\n                }\n            } else {\n                selectionToolbarContainer.animate()\n                        .translationY((-selectionToolbarContainer.height).toFloat())\n                        .setDuration(duration.toLong())\n                        .withEndAction { selectionToolbarContainer.visibility = View.INVISIBLE }\n\n                addFab.visibility = View.VISIBLE\n                addFab.animate()\n                        .scaleY(1f)\n                        .scaleX(1f)\n                        .setDuration(duration.toLong())\n                        .withEndAction(null)\n            }\n        }\n\n        if (toolbarVisible) {\n            var title = Integer.toString(selectedCount)\n            if (selectedCount == 1) {\n                // If they've selected a tree URI, show the DISPLAY_NAME instead of just '1'\n                val selectedItem = mMultiSelectionController.getSelection().iterator().next()\n                if (selectedItem.isTreeUri) {\n                    val displayName = getDisplayNameForTreeUri(this, Uri.parse(selectedItem.uri))\n                    if (!TextUtils.isEmpty(displayName)) {\n                        title = displayName\n                    }\n                }\n            }\n            selectionToolbar.title = title\n        }\n    }\n\n    private fun setupMultiSelect() {\n        // Set up toolbar\n        selectionToolbar.setNavigationOnClickListener {\n            mMultiSelectionController.reset(true)\n        }\n\n        selectionToolbar.inflateMenu(R.menu.gallery_selection)\n        selectionToolbar.setOnMenuItemClickListener(Toolbar.OnMenuItemClickListener { item ->\n            val itemId = item.itemId\n            if (itemId == R.id.action_force_now) {\n                val selection = mMultiSelectionController.getSelection()\n                if (selection.isNotEmpty()) {\n                    val selectedItems = selection.iterator().next()\n                    presenter.forceNow(selectedItems.uri)\n                    toast(R.string.gallery_temporary_force_image)\n                }\n                mMultiSelectionController.reset(true)\n                return@OnMenuItemClickListener true\n            } else if (itemId == R.id.action_remove) {\n                val removeItems = ArrayList<GalleryWallpaperItem>(\n                        mMultiSelectionController.getSelection())\n                presenter.removeGalleryWallpaper(removeItems)\n                mMultiSelectionController.reset(true)\n                return@OnMenuItemClickListener true\n            }\n            false\n        })\n\n        // Set up controller\n        mMultiSelectionController.setCallbacks(object : MultiSelectionController.Callbacks {\n            override fun onSelectionChanged(restored: Boolean, fromUser: Boolean) {\n                tryUpdateSelection(!restored)\n            }\n        })\n    }\n\n    open class CheckableViewHolder(root: View) : RecyclerView.ViewHolder(root) {\n        var mRootView: View = root\n        var mCheckedOverlayView: View = root.findViewById(R.id.checked_overlay)\n    }\n\n    internal class PhotoViewHolder(root: View) : CheckableViewHolder(root) {\n        val mThumbView: ImageView = root.findViewById(R.id.thumbnail) as ImageView\n    }\n\n    internal class TreeViewHolder(root: View) : CheckableViewHolder(root) {\n        val mThumbViews: MutableList<ImageView> = ArrayList()\n\n        init {\n            mThumbViews.add(root.findViewById(R.id.thumbnail1) as ImageView)\n            mThumbViews.add(root.findViewById(R.id.thumbnail2) as ImageView)\n            mThumbViews.add(root.findViewById(R.id.thumbnail3) as ImageView)\n            mThumbViews.add(root.findViewById(R.id.thumbnail4) as ImageView)\n        }\n    }\n\n    private val mChosenPhotosAdapter = object : RecyclerView.Adapter<CheckableViewHolder>() {\n        override fun getItemViewType(position: Int): Int {\n            val wallpaperItem = mWallpapers[position]\n            return if (wallpaperItem.isTreeUri)\n                ITEM_TYPE_TREE\n            else ITEM_TYPE_URI\n        }\n\n        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CheckableViewHolder {\n            val isTreeUri = viewType != 0\n            val v: View\n            val vh: CheckableViewHolder\n            if (isTreeUri) {\n                v = LayoutInflater.from(this@GallerySettingActivity)\n                        .inflate(R.layout.gallery_chosen_photo_tree_item, parent, false)\n                vh = TreeViewHolder(v)\n            } else {\n                v = LayoutInflater.from(this@GallerySettingActivity)\n                        .inflate(R.layout.gallery_chosen_photo_item, parent, false)\n                vh = PhotoViewHolder(v)\n            }\n\n            v.layoutParams.height = mItemSize\n            v.setOnTouchListener { _, motionEvent ->\n                if (motionEvent.actionMasked != MotionEvent.ACTION_CANCEL) {\n                    mLastTouchPosition = vh.adapterPosition\n                    mLastTouchX = motionEvent.x.toInt()\n                    mLastTouchY = motionEvent.y.toInt()\n                }\n                false\n            }\n            v.setOnClickListener {\n                mUpdatePosition = vh.adapterPosition\n                if (mUpdatePosition != RecyclerView.NO_POSITION) {\n                    mMultiSelectionController.toggle(mWallpapers[mUpdatePosition], true)\n                }\n            }\n            return vh\n        }\n\n        override fun onBindViewHolder(vh: CheckableViewHolder, position: Int) {\n            val wallpaperItem = mWallpapers[position]\n            val isTreeUri = getItemViewType(position) != 0\n            if (isTreeUri) {\n                val treeVh = vh as TreeViewHolder\n                val maxImages = treeVh.mThumbViews.size\n                val imageUri = Uri.parse(wallpaperItem.uri)\n                val images = getImagesFromTreeUri(this@GallerySettingActivity, imageUri, maxImages)\n                val numImages = images.size\n                for (h in 0..numImages - 1) {\n                    Glide.with(this@GallerySettingActivity)\n                            .load(images[h])\n                            .override(mItemSize / 2, mItemSize / 2)\n                            .placeholder(mPlaceholderSmallDrawable)\n                            .into(treeVh.mThumbViews[h])\n                }\n                for (h in numImages..maxImages - 1) {\n                    treeVh.mThumbViews[h].setImageDrawable(mPlaceholderSmallDrawable)\n                }\n            } else {\n                val photoVh = vh as PhotoViewHolder\n                Glide.with(this@GallerySettingActivity)\n                        .load(Uri.parse(wallpaperItem.uri))\n                        .override(mItemSize, mItemSize)\n                        .placeholder(mPlaceholderDrawable)\n                        .into(photoVh.mThumbView)\n            }\n\n            val checked = mMultiSelectionController.isSelected(wallpaperItem)\n            vh.mRootView.setTag(R.id.gallery_viewtag_position, position)\n            if (mLastTouchPosition == vh.adapterPosition\n                    && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n                Handler().post {\n                    if (!vh.mCheckedOverlayView.isAttachedToWindow) {\n                        // Can't animate detached Views\n                        vh.mCheckedOverlayView.visibility = if (checked) View.VISIBLE else View.GONE\n                        return@post\n                    }\n                    if (checked) {\n                        vh.mCheckedOverlayView.visibility = View.VISIBLE\n                    }\n\n                    // find the smallest radius that'll cover the item\n                    val coverRadius = maxDistanceToCorner(\n                            mLastTouchX, mLastTouchY,\n                            0, 0, vh.mRootView.width, vh.mRootView.height)\n\n                    val revealAnim = ViewAnimationUtils.createCircularReveal(\n                            vh.mCheckedOverlayView,\n                            mLastTouchX,\n                            mLastTouchY,\n                            if (checked) 0f else coverRadius,\n                            if (checked) coverRadius else 0f)\n                            .setDuration(150)\n\n                    if (!checked) {\n                        revealAnim.addListener(object : AnimatorListenerAdapter() {\n                            override fun onAnimationEnd(animation: Animator) {\n                                vh.mCheckedOverlayView.visibility = View.GONE\n                            }\n                        })\n                    }\n                    revealAnim.start()\n                }\n            } else {\n                vh.mCheckedOverlayView.visibility = if (checked) View.VISIBLE else View.GONE\n            }\n        }\n\n        private fun maxDistanceToCorner(x: Int, y: Int, left: Int, top: Int,\n                                        right: Int, bottom: Int): Float {\n            var maxDistance = 0f\n            maxDistance = Math.max(maxDistance,\n                    Math.hypot((x - left).toDouble(), (y - top).toDouble()).toFloat())\n            maxDistance = Math.max(maxDistance,\n                    Math.hypot((x - right).toDouble(), (y - top).toDouble()).toFloat())\n            maxDistance = Math.max(maxDistance,\n                    Math.hypot((x - left).toDouble(), (y - bottom).toDouble()).toFloat())\n            maxDistance = Math.max(maxDistance,\n                    Math.hypot((x - right).toDouble(), (y - bottom).toDouble()).toFloat())\n            return maxDistance\n        }\n\n        override fun getItemCount(): Int {\n            return mWallpapers.size\n        }\n\n        override fun getItemId(position: Int): Long {\n            return mWallpapers[position].id\n        }\n    }\n}"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/view/activity/SettingsActivity.kt",
    "content": "package com.yalin.style.view.activity\n\nimport android.animation.ObjectAnimator\nimport android.app.Fragment\nimport android.app.FragmentTransaction\nimport android.content.Intent\nimport android.os.Bundle\nimport android.support.v7.widget.Toolbar\nimport android.view.LayoutInflater\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.AdapterView\nimport android.widget.BaseAdapter\nimport android.widget.TextView\n\nimport com.yalin.style.R\nimport com.yalin.style.analytics.Analytics\nimport com.yalin.style.analytics.Event\nimport com.yalin.style.injection.HasComponent\nimport com.yalin.style.injection.component.DaggerSourceComponent\nimport com.yalin.style.injection.component.SourceComponent\nimport com.yalin.style.view.fragment.SettingsAdvanceFragment\nimport com.yalin.style.view.fragment.SettingsChooseSourceFragment\nimport kotlinx.android.synthetic.main.activity_settings.*\n\n/**\n * @author jinyalin\n * *\n * @since 2017/5/2.\n */\n\nclass SettingsActivity : BaseActivity(), HasComponent<SourceComponent>,\n        SettingsChooseSourceFragment.Callbacks {\n\n    companion object {\n        val START_SECTION_SOURCE = 0\n        val START_SECTION_ADVANCED = 1\n\n        val SECTION_LABELS =\n                intArrayOf(R.string.section_choose_source, R.string.section_advanced)\n\n        private val SECTION_FRAGMENTS =\n                arrayOf<Class<*>>(SettingsChooseSourceFragment::class.java,\n                        SettingsAdvanceFragment::class.java)\n    }\n\n    private val sourceComponent: SourceComponent by lazy { initializeInjector() }\n\n    private var mBackgroundAnimator: ObjectAnimator? = null\n    private var mStartSection = START_SECTION_SOURCE\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        window.decorView.systemUiVisibility =\n                View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or\n                        View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or\n                        View.SYSTEM_UI_FLAG_LAYOUT_STABLE\n        setContentView(R.layout.activity_settings)\n\n        getSectionFromIntent(intent)\n        setupAppBar()\n\n        drawInsetsFrameLayout.setOnInsetsCallback { insets ->\n            val lp = mainContainer.layoutParams as ViewGroup.MarginLayoutParams\n            lp.leftMargin = insets.left\n            lp.topMargin = insets.top\n            lp.rightMargin = insets.right\n            lp.bottomMargin = insets.bottom\n            mainContainer.layoutParams = lp\n        }\n\n        if (mBackgroundAnimator != null) {\n            mBackgroundAnimator!!.cancel()\n        }\n\n        mBackgroundAnimator = ObjectAnimator.ofFloat(this, \"backgroundOpacity\", 0f, 1f)\n        mBackgroundAnimator!!.duration = 1000\n        mBackgroundAnimator!!.start()\n    }\n\n    private fun getSectionFromIntent(intent: Intent) {\n        val uri = intent.data\n        uri?.let {\n            val path = uri.pathSegments[0]\n            when (path) {\n                \"advance\" -> {\n                    mStartSection = START_SECTION_ADVANCED\n                    Analytics.logEvent(this, Event.SHORTCUTS_ADVANCE_SETTINGS)\n                }\n                else -> {\n                    mStartSection = START_SECTION_SOURCE\n                    Analytics.logEvent(this, Event.SHORTCUTS_SETTINGS)\n                }\n            }\n        }\n    }\n\n    private fun initializeInjector() = DaggerSourceComponent.builder()\n            .applicationComponent(applicationComponent)\n            .build()\n\n    private fun setupAppBar() {\n        appBar.setNavigationOnClickListener { onNavigateUp() }\n\n        val inflater = LayoutInflater.from(this)\n        sectionSpinner.adapter = object : BaseAdapter() {\n            override fun getCount(): Int {\n                return SECTION_LABELS.size\n            }\n\n            override fun getItem(position: Int): Any {\n                return SECTION_LABELS[position]\n            }\n\n            override fun getItemId(position: Int): Long {\n                return (position + 1).toLong()\n            }\n\n            override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {\n                var view = convertView\n                if (view == null) {\n                    view = inflater.inflate(R.layout.settings_ab_spinner_list_item,\n                            parent, false)\n                }\n                (view!!.findViewById(android.R.id.text1) as TextView).text =\n                        getString(SECTION_LABELS[position])\n                return view\n            }\n\n            override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup):\n                    View {\n                var view = convertView\n                if (view == null) {\n                    view = inflater.inflate(R.layout.settings_ab_spinner_list_item_dropdown,\n                            parent, false)\n                }\n                (view!!.findViewById(android.R.id.text1) as TextView).text =\n                        getString(SECTION_LABELS[position])\n                return view as View\n            }\n        }\n\n        sectionSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {\n            override fun onItemSelected(spinner: AdapterView<*>?, view: View?,\n                                        position: Int, id: Long) {\n                val fragmentClass = SECTION_FRAGMENTS[position]\n                val currentFragment = fragmentManager.findFragmentById(\n                        R.id.contentContainer)\n                if (currentFragment != null && fragmentClass == currentFragment.javaClass) {\n                    return\n                }\n\n                inflateMenuFromFragment(0)\n\n                try {\n                    val newFragment = fragmentClass.newInstance()\n                    fragmentManager.beginTransaction()\n                            .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)\n                            .setTransitionStyle(R.style.Style_SimpleFadeFragmentAnimation)\n                            .replace(R.id.contentContainer, newFragment as Fragment)\n                            .commitAllowingStateLoss()\n                } catch (e: Exception) {\n                    throw RuntimeException(e)\n                }\n\n            }\n\n            override fun onNothingSelected(spinner: AdapterView<*>) {}\n        }\n\n        sectionSpinner.setSelection(mStartSection)\n\n        inflateMenuFromFragment(0)\n        appBar.setOnMenuItemClickListener(Toolbar.OnMenuItemClickListener { item ->\n            when (item.itemId) {\n                R.id.action_reset -> {\n                    val currentFragment = fragmentManager.findFragmentById(\n                            R.id.contentContainer)\n                    if (currentFragment != null\n                            && currentFragment is SettingsActivityMenuListener) {\n                        currentFragment\n                                .onSettingsActivityMenuItemClick(item)\n                    }\n                    return@OnMenuItemClickListener true\n                }\n                R.id.action_about -> {\n                    Analytics.logEvent(this, Event.ABOUT_OPEN)\n                    startActivity(Intent(this, AboutActivity::class.java))\n                    return@OnMenuItemClickListener true\n                }\n            }\n\n            val currentFragment = supportFragmentManager.findFragmentById(\n                    R.id.contentContainer)\n            if (currentFragment != null && currentFragment is SettingsActivityMenuListener) {\n                currentFragment.onSettingsActivityMenuItemClick(item)\n            }\n\n            false\n        })\n    }\n\n    fun inflateMenuFromFragment(menuResId: Int) {\n        appBar.menu.clear()\n        if (menuResId != 0) {\n            appBar.inflateMenu(menuResId)\n        }\n        appBar.inflateMenu(R.menu.menu_settings)\n    }\n\n    override val component: SourceComponent\n        get() = sourceComponent\n\n    override fun onRequestCloseActivity() {\n        finish()\n    }\n\n    interface SettingsActivityMenuListener {\n        fun onSettingsActivityMenuItemClick(item: MenuItem)\n    }\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/view/activity/StyleActivity.kt",
    "content": "package com.yalin.style.view.activity\n\nimport android.Manifest\nimport android.app.Fragment\nimport android.app.WallpaperManager\nimport android.content.ActivityNotFoundException\nimport android.content.ComponentName\nimport android.content.Intent\nimport android.content.pm.PackageManager\nimport android.os.Bundle\nimport android.os.Handler\nimport android.preference.PreferenceManager\nimport android.support.v4.app.ActivityCompat\nimport android.support.v4.content.ContextCompat\nimport android.view.View\nimport com.yalin.style.R\nimport com.yalin.style.StyleApplication\nimport com.yalin.style.StyleWallpaperService\nimport com.yalin.style.analytics.Analytics\nimport com.yalin.style.analytics.Event\nimport com.yalin.style.data.BuildConfig\nimport com.yalin.style.data.log.LogUtil\nimport com.yalin.style.data.repository.AdvanceWallpaperDataRepository\nimport com.yalin.style.event.MainContainerInsetsChangedEvent\nimport com.yalin.style.event.SeenTutorialEvent\nimport com.yalin.style.event.WallpaperActivateEvent\nimport com.yalin.style.event.WallpaperDetailOpenedEvent\nimport com.yalin.style.injection.HasComponent\nimport com.yalin.style.injection.component.DaggerWallpaperComponent\nimport com.yalin.style.injection.component.WallpaperComponent\nimport com.yalin.style.view.component.PanScaleProxyView\nimport com.yalin.style.view.fragment.AnimatedStyleLogoFragment\nimport com.yalin.style.view.fragment.StyleRenderFragment\nimport com.yalin.style.view.fragment.TutorialFragment\nimport com.yalin.style.view.fragment.WallpaperDetailFragment\n\nimport kotlinx.android.synthetic.main.activity_main.*\nimport kotlinx.android.synthetic.main.layout_include_wallpaper_detail.*\nimport kotlinx.android.synthetic.main.layout_include_wallpaper_tutorial.*\nimport kotlinx.android.synthetic.main.layout_include_active.*\nimport org.greenrobot.eventbus.EventBus\nimport org.greenrobot.eventbus.Subscribe\nimport org.jetbrains.anko.toast\nimport javax.inject.Inject\n\n/**\n * @author jinyalin\n * @since 2017/5/9.\n */\nclass StyleActivity : BaseActivity(), HasComponent<WallpaperComponent>,\n        PanScaleProxyView.OnOtherGestureListener {\n\n    companion object {\n        private val TAG = \"StyleActivity\"\n        // ui mode\n        private val MODE_UNKNOWN = -1\n        private val MODE_ACTIVATE = 0\n        private val MODE_DETAIL = 1\n        private val MODE_TUTORIAL = 2\n\n        private val PREF_SEEN_TUTORIAL = \"seen_tutorial\"\n\n        private val REQUEST_PERMISSION_CODE = 10000\n\n        private val mHandler = Handler()\n\n    }\n\n    private val wallpaperComponent: WallpaperComponent by lazy { initializeInjector() }\n\n    private var mUiMode = MODE_UNKNOWN\n\n    private var mWindowHasFocus = false\n    private var mPaused = false\n\n    private var mStyleActive = false\n\n    private var mSeenTutorial = false\n\n    private var wallpaperDetailFragment: WallpaperDetailFragment? = null\n\n    private var startLogAnim: Runnable? = null\n\n    @Inject\n    lateinit var advanceWallpaperRepository: AdvanceWallpaperDataRepository\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.activity_main)\n\n        StyleApplication.instance.applicationComponent.inject(this)\n        advanceWallpaperRepository.maybeRollback()\n\n        Analytics.setUserProperty(this, \"device_type\", \"Android\")\n        Analytics.onStartSession(this)\n\n        setupActiveView()\n        setupDetailView()\n        setupTutorialView()\n\n        mainContainer.setOnInsetsCallback {\n            insets ->\n            EventBus.getDefault().postSticky(MainContainerInsetsChangedEvent(insets))\n        }\n\n        showHideChrome(true)\n\n        EventBus.getDefault().register(this)\n\n        val sp = PreferenceManager.getDefaultSharedPreferences(this)\n        mSeenTutorial = sp.getBoolean(PREF_SEEN_TUTORIAL, false)\n    }\n\n    override fun onStart() {\n        super.onStart()\n        if (BuildConfig.ENABLE_EXTERNAL_LOG &&\n                ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)\n                        != PackageManager.PERMISSION_GRANTED) {\n            ActivityCompat.requestPermissions(this,\n                    arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),\n                    REQUEST_PERMISSION_CODE)\n        }\n    }\n\n    override fun onPostResume() {\n        super.onPostResume()\n        mPaused = false\n\n        // update intro mode UI to latest wallpaper active state\n        val e = EventBus.getDefault()\n                .getStickyEvent(WallpaperActivateEvent::class.java)\n        if (e != null) {\n            onEventMainThread(e)\n        } else {\n            onEventMainThread(WallpaperActivateEvent(false))\n        }\n\n        updateUi()\n\n        val decorView = window.decorView\n        decorView.alpha = 0f\n        decorView.animate().cancel()\n        decorView.animate()\n                .setStartDelay(500)\n                .alpha(1f).duration = 300\n\n        maybeUpdateWallpaperDetailOpenedClosed()\n    }\n\n    override fun onPause() {\n        super.onPause()\n        mPaused = true\n        maybeUpdateWallpaperDetailOpenedClosed()\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        EventBus.getDefault().unregister(this)\n        Analytics.onEndSession(this)\n\n        if (startLogAnim != null) mHandler.removeCallbacks(startLogAnim)\n    }\n\n    private fun showHideChrome(show: Boolean) {\n        var flags = if (show) 0 else View.SYSTEM_UI_FLAG_LOW_PROFILE\n        flags = flags or\n                (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN\n                        or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION\n                        or View.SYSTEM_UI_FLAG_LAYOUT_STABLE)\n        if (!show) {\n            flags = flags or\n                    (View.SYSTEM_UI_FLAG_HIDE_NAVIGATION\n                            or View.SYSTEM_UI_FLAG_FULLSCREEN\n                            or View.SYSTEM_UI_FLAG_IMMERSIVE)\n        }\n        mainContainer.systemUiVisibility = flags\n    }\n\n    private fun setupActiveView() {\n        activateStyleButton.setOnClickListener {\n            Analytics.logEvent(this, Event.ACTIVATE)\n            setWallpaper()\n        }\n    }\n\n    private fun setupDetailView() {\n    }\n\n    private fun setupTutorialView() {\n    }\n\n    private fun getContainerFromMode(uiMode: Int): View {\n        when (uiMode) {\n            MODE_DETAIL -> return detailContainer\n            MODE_TUTORIAL -> return tutorialContainer\n            else -> return activeContainer\n        }\n    }\n\n    private fun updateUi() {\n        // default activate mode\n        var newMode = MODE_ACTIVATE\n        if (mStyleActive) {\n            newMode = MODE_TUTORIAL\n            if (mSeenTutorial) {\n                newMode = MODE_DETAIL\n            }\n        }\n        if (mUiMode == newMode) {\n            return\n        }\n\n        LogUtil.D(TAG, \"update UI\")\n\n        val oldModeView = getContainerFromMode(mUiMode)\n        val newModeView = getContainerFromMode(newMode)\n\n        oldModeView.animate()\n                .alpha(0f)\n                .setDuration(1000)\n                .withEndAction { oldModeView.visibility = View.GONE }\n\n        if (newModeView.alpha == 1f) {\n            newModeView.alpha = 0f\n        }\n        newModeView.visibility = View.VISIBLE\n        newModeView.animate()\n                .alpha(1f)\n                .setDuration(1000)\n                .withEndAction(null)\n\n        if (newMode == MODE_ACTIVATE) {\n            val logoFragment = fragmentManager.findFragmentById(R.id.animatedLogoFragment)\n                    as AnimatedStyleLogoFragment\n            logoFragment.reset()\n            logoFragment.setOnFillStartedCallback {\n                activateStyleButton.animate().alpha(1f).duration = 500\n            }\n            startLogAnim = Runnable { logoFragment.start() }\n            mHandler.postDelayed(startLogAnim, 1000)\n        }\n\n        if (mUiMode == MODE_ACTIVATE || newMode == MODE_ACTIVATE) {\n            val demoFragment = fragmentManager.findFragmentById(R.id.demoContainerLayout)\n            if (newMode == MODE_ACTIVATE && demoFragment == null) {\n                fragmentManager.beginTransaction()\n                        .add(R.id.demoContainerLayout,\n                                StyleRenderFragment.createInstance(true, true))\n                        .commit()\n            } else if (mUiMode == MODE_ACTIVATE && demoFragment != null) {\n                fragmentManager.beginTransaction()\n                        .remove(demoFragment)\n                        .commit()\n            }\n        }\n\n        if (newMode == MODE_DETAIL) {\n            val detailFragment = fragmentManager.findFragmentById(R.id.detailContainer)\n            if (detailFragment == null) {\n                wallpaperDetailFragment = WallpaperDetailFragment.createInstance()\n                fragmentManager.beginTransaction()\n                        .add(R.id.detailContainer, wallpaperDetailFragment)\n                        .commit()\n            } else {\n                wallpaperDetailFragment = detailFragment as WallpaperDetailFragment?\n            }\n\n            mainContainer.setOnSystemUiVisibilityChangeListener(wallpaperDetailFragment)\n        }\n\n        if (mUiMode == MODE_TUTORIAL || newMode == MODE_TUTORIAL) {\n            var tutorialFragment: Fragment? = fragmentManager.findFragmentById(R.id.mainContainer)\n            if (newMode == MODE_TUTORIAL && tutorialFragment == null) {\n                Analytics.logEvent(this, Event.TUTORIAL_BEGIN)\n                tutorialFragment = TutorialFragment.newInstance()\n                fragmentManager.beginTransaction()\n                        .add(R.id.mainContainer, tutorialFragment)\n                        .commit()\n            } else if (mUiMode == MODE_TUTORIAL && tutorialFragment != null) {\n                Analytics.logEvent(this, Event.TUTORIAL_COMPLETE)\n                fragmentManager.beginTransaction()\n                        .remove(tutorialFragment)\n                        .commit()\n            }\n        }\n\n        mUiMode = newMode\n\n        maybeUpdateWallpaperDetailOpenedClosed()\n    }\n\n    private fun maybeUpdateWallpaperDetailOpenedClosed() {\n        var currentlyOpened = false\n        val wdoe = EventBus.getDefault()\n                .getStickyEvent(WallpaperDetailOpenedEvent::class.java)\n        if (wdoe != null) {\n            currentlyOpened = wdoe.isWallpaperDetailOpened\n        }\n\n        var shouldBeOpened = false\n\n        if (mUiMode == MODE_DETAIL) {\n            val overflowMenuVisible = wallpaperDetailFragment != null\n                    && wallpaperDetailFragment!!.isOverflowMenuVisible\n            if ((mWindowHasFocus || overflowMenuVisible) && !mPaused) {\n                shouldBeOpened = true\n            }\n        }\n\n        if (currentlyOpened != shouldBeOpened) {\n            EventBus.getDefault().postSticky(WallpaperDetailOpenedEvent(shouldBeOpened))\n        }\n    }\n\n    @Subscribe\n    fun onEventMainThread(e: WallpaperActivateEvent) {\n        if (mPaused) {\n            return\n        }\n\n        mStyleActive = e.isWallpaperActivate\n        updateUi()\n    }\n\n    @Subscribe\n    fun onEventMainThread(e: SeenTutorialEvent) {\n        mSeenTutorial = true\n        PreferenceManager.getDefaultSharedPreferences(this).edit()\n                .putBoolean(PREF_SEEN_TUTORIAL, true)\n                .apply()\n        updateUi()\n    }\n\n    override fun onWindowFocusChanged(hasFocus: Boolean) {\n        super.onWindowFocusChanged(hasFocus)\n        mWindowHasFocus = hasFocus\n        maybeUpdateWallpaperDetailOpenedClosed()\n    }\n\n    private fun initializeInjector() = DaggerWallpaperComponent.builder()\n            .applicationComponent(applicationComponent)\n            .build()\n\n\n    private fun setWallpaper() {\n        try {\n            startActivity(Intent(WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER)\n                    .putExtra(WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT,\n                            ComponentName(this, StyleWallpaperService::class.java))\n                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))\n        } catch (e: ActivityNotFoundException) {\n            try {\n                startActivity(Intent(WallpaperManager.ACTION_LIVE_WALLPAPER_CHOOSER)\n                        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))\n            } catch (e2: ActivityNotFoundException) {\n                toast(R.string.exception_message_device_unsupported)\n                Analytics.logEvent(this, Event.DEVICE_UNSUPPORTED)\n            }\n        }\n    }\n\n    override val component: WallpaperComponent\n        get() = wallpaperComponent\n\n    override fun onSingleTapUp() {\n        if (mUiMode == MODE_DETAIL) {\n            showHideChrome(mainContainer.systemUiVisibility\n                    and View.SYSTEM_UI_FLAG_LOW_PROFILE != 0)\n        }\n    }\n}"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/view/component/CircleProgressView.java",
    "content": "package com.yalin.style.view.component;\n\n\nimport android.animation.Animator;\nimport android.animation.ObjectAnimator;\nimport android.animation.ValueAnimator;\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport android.graphics.Canvas;\nimport android.graphics.Color;\nimport android.graphics.Paint;\nimport android.graphics.Paint.Cap;\nimport android.graphics.RectF;\nimport android.support.v4.content.ContextCompat;\nimport android.util.AttributeSet;\nimport android.util.Property;\nimport android.view.View;\nimport android.view.animation.AccelerateDecelerateInterpolator;\nimport android.view.animation.Interpolator;\nimport android.view.animation.LinearInterpolator;\n\nimport com.yalin.style.R;\n\n/**\n * @author jinyalin\n * @since 2017/7/31.\n */\n\npublic class CircleProgressView extends View {\n\n    private static final Interpolator ANGLE_INTERPOLATOR = new LinearInterpolator();\n    private static final Interpolator SWEEP_INTERPOLATOR = new AccelerateDecelerateInterpolator();\n    private static final int ANGLE_ANIMATOR_DURATION = 2000;\n    private static final int SWEEP_ANIMATOR_DURATION = 900;\n    private static final int MIN_SWEEP_ANGLE = 30;\n    private static final int DEFAULT_BORDER_WIDTH = 3;\n    private final RectF fBounds = new RectF();\n\n    private ObjectAnimator mObjectAnimatorSweep;\n    private ObjectAnimator mObjectAnimatorAngle;\n    private boolean mModeAppearing = true;\n    private Paint mPaint;\n    private float mCurrentGlobalAngleOffset;\n    private float mCurrentGlobalAngle;\n    private float mCurrentSweepAngle;\n    private float mBorderWidth;\n    private boolean mRunning;\n    private int[] mColors;\n    private int mCurrentColorIndex;\n    private int mNextColorIndex;\n\n    public CircleProgressView(Context context) {\n        this(context, null);\n    }\n\n    public CircleProgressView(Context context, AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    public CircleProgressView(Context context, AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n\n        float density = context.getResources().getDisplayMetrics().density;\n        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircularProgress, defStyleAttr, 0);\n        mBorderWidth = a.getDimension(R.styleable.CircularProgress_progress_borderWidth,\n                DEFAULT_BORDER_WIDTH * density);\n        a.recycle();\n        mColors = new int[4];\n        mColors[0] = ContextCompat.getColor(context, R.color.red);\n        mColors[1] = ContextCompat.getColor(context, R.color.yellow);\n        mColors[2] = ContextCompat.getColor(context, R.color.green);\n        mColors[3] = ContextCompat.getColor(context, R.color.blue);\n        mCurrentColorIndex = 0;\n        mNextColorIndex = 1;\n\n        mPaint = new Paint();\n        mPaint.setAntiAlias(true);\n        mPaint.setStyle(Paint.Style.STROKE);\n        mPaint.setStrokeCap(Cap.ROUND);\n        mPaint.setStrokeWidth(mBorderWidth);\n        mPaint.setColor(mColors[mCurrentColorIndex]);\n\n        setupAnimations();\n    }\n\n    private void start() {\n        if (isRunning()) {\n            return;\n        }\n        mRunning = true;\n        mObjectAnimatorAngle.start();\n        mObjectAnimatorSweep.start();\n        invalidate();\n    }\n\n    private void stop() {\n        if (!isRunning()) {\n            return;\n        }\n        mRunning = false;\n        mObjectAnimatorAngle.cancel();\n        mObjectAnimatorSweep.cancel();\n        invalidate();\n    }\n\n    private boolean isRunning() {\n        return mRunning;\n    }\n\n    @Override\n    protected void onVisibilityChanged(View changedView, int visibility) {\n        super.onVisibilityChanged(changedView, visibility);\n        if (visibility == VISIBLE) {\n            start();\n        } else {\n            stop();\n        }\n    }\n\n    @Override\n    protected void onAttachedToWindow() {\n        start();\n        super.onAttachedToWindow();\n    }\n\n    @Override\n    protected void onDetachedFromWindow() {\n        stop();\n        super.onDetachedFromWindow();\n    }\n\n    @Override\n    protected void onSizeChanged(int w, int h, int oldw, int oldh) {\n        super.onSizeChanged(w, h, oldw, oldh);\n        fBounds.left = mBorderWidth / 2f + .5f;\n        fBounds.right = w - mBorderWidth / 2f - .5f;\n        fBounds.top = mBorderWidth / 2f + .5f;\n        fBounds.bottom = h - mBorderWidth / 2f - .5f;\n    }\n\n    @Override\n    public void draw(Canvas canvas) {\n        super.draw(canvas);\n        float startAngle = mCurrentGlobalAngle - mCurrentGlobalAngleOffset;\n        float sweepAngle = mCurrentSweepAngle;\n        if (mModeAppearing) {\n            mPaint.setColor(gradient(mColors[mCurrentColorIndex], mColors[mNextColorIndex],\n                    mCurrentSweepAngle / (360 - MIN_SWEEP_ANGLE * 2)));\n            sweepAngle += MIN_SWEEP_ANGLE;\n        } else {\n            startAngle = startAngle + sweepAngle;\n            sweepAngle = 360 - sweepAngle - MIN_SWEEP_ANGLE;\n        }\n        canvas.drawArc(fBounds, startAngle, sweepAngle, false, mPaint);\n    }\n\n    private static int gradient(int color1, int color2, float p) {\n        int r1 = (color1 & 0xff0000) >> 16;\n        int g1 = (color1 & 0xff00) >> 8;\n        int b1 = color1 & 0xff;\n        int r2 = (color2 & 0xff0000) >> 16;\n        int g2 = (color2 & 0xff00) >> 8;\n        int b2 = color2 & 0xff;\n        int newr = (int) (r2 * p + r1 * (1 - p));\n        int newg = (int) (g2 * p + g1 * (1 - p));\n        int newb = (int) (b2 * p + b1 * (1 - p));\n        return Color.argb(255, newr, newg, newb);\n    }\n\n    private void toggleAppearingMode() {\n        mModeAppearing = !mModeAppearing;\n        if (mModeAppearing) {\n            mCurrentColorIndex = ++mCurrentColorIndex % 4;\n            mNextColorIndex = ++mNextColorIndex % 4;\n            mCurrentGlobalAngleOffset = (mCurrentGlobalAngleOffset + MIN_SWEEP_ANGLE * 2) % 360;\n        }\n    }\n    // ////////////////////////////////////////////////////////////////////////////\n    // ////////////// Animation\n\n    private Property<CircleProgressView, Float> mAngleProperty = new Property<CircleProgressView, Float>(Float.class, \"angle\") {\n        @Override\n        public Float get(CircleProgressView object) {\n            return object.getCurrentGlobalAngle();\n        }\n\n        @Override\n        public void set(CircleProgressView object, Float value) {\n            object.setCurrentGlobalAngle(value);\n        }\n    };\n\n    private Property<CircleProgressView, Float> mSweepProperty = new Property<CircleProgressView, Float>(Float.class, \"arc\") {\n        @Override\n        public Float get(CircleProgressView object) {\n            return object.getCurrentSweepAngle();\n        }\n\n        @Override\n        public void set(CircleProgressView object, Float value) {\n            object.setCurrentSweepAngle(value);\n        }\n    };\n\n    private void setupAnimations() {\n        mObjectAnimatorAngle = ObjectAnimator.ofFloat(this, mAngleProperty, 360f);\n        mObjectAnimatorAngle.setInterpolator(ANGLE_INTERPOLATOR);\n        mObjectAnimatorAngle.setDuration(ANGLE_ANIMATOR_DURATION);\n        mObjectAnimatorAngle.setRepeatMode(ValueAnimator.RESTART);\n        mObjectAnimatorAngle.setRepeatCount(ValueAnimator.INFINITE);\n\n        mObjectAnimatorSweep = ObjectAnimator.ofFloat(this, mSweepProperty, 360f - MIN_SWEEP_ANGLE * 2);\n        mObjectAnimatorSweep.setInterpolator(SWEEP_INTERPOLATOR);\n        mObjectAnimatorSweep.setDuration(SWEEP_ANIMATOR_DURATION);\n        mObjectAnimatorSweep.setRepeatMode(ValueAnimator.RESTART);\n        mObjectAnimatorSweep.setRepeatCount(ValueAnimator.INFINITE);\n        mObjectAnimatorSweep.addListener(new Animator.AnimatorListener() {\n            @Override\n            public void onAnimationStart(Animator animation) {\n\n            }\n\n            @Override\n            public void onAnimationEnd(Animator animation) {\n\n            }\n\n            @Override\n            public void onAnimationCancel(Animator animation) {\n\n            }\n\n            @Override\n            public void onAnimationRepeat(Animator animation) {\n                toggleAppearingMode();\n            }\n        });\n    }\n\n    public void setCurrentGlobalAngle(float currentGlobalAngle) {\n        mCurrentGlobalAngle = currentGlobalAngle;\n        invalidate();\n    }\n\n    public float getCurrentGlobalAngle() {\n        return mCurrentGlobalAngle;\n    }\n\n    public void setCurrentSweepAngle(float currentSweepAngle) {\n        mCurrentSweepAngle = currentSweepAngle;\n        invalidate();\n    }\n\n    public float getCurrentSweepAngle() {\n        return mCurrentSweepAngle;\n    }\n}"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/view/component/DownloadingDialog.kt",
    "content": "package com.yalin.style.view.component\n\nimport android.content.Context\nimport android.widget.TextView\nimport com.afollestad.materialdialogs.MaterialDialog\nimport com.yalin.style.R\nimport com.yalin.style.model.AdvanceWallpaperItem\n\n/**\n * @author jinyalin\n * *\n * @since 2017/8/11.\n */\n\nclass DownloadingDialog constructor(context: Context) {\n    private val dialog = MaterialDialog.Builder(context)\n            .iconRes(R.drawable.advance_downloading)\n            .title(R.string.downloading)\n            .cancelable(false)\n            .customView(R.layout.dialog_downloading, false).build()\n\n    private val progressView = dialog.findViewById(R.id.downloadProgress) as TextView\n\n    fun show() {\n        dialog.show()\n    }\n\n    fun dismiss() {\n        dialog.dismiss()\n    }\n\n    fun updateProgress(progress: Long) {\n        progressView.text = formatSize(progress)\n    }\n\n    fun showError(item: AdvanceWallpaperItem, e: Exception) {\n\n    }\n\n    companion object {\n        val C = 1024\n    }\n\n    private fun formatSize(progress: Long): String {\n        if (progress < C) {\n            return \"$progress B\"\n        }\n        if (progress < C * C) {\n            return \"${progress / C} KB\"\n        }\n        return \"%.2f MB\".format(progress / (C * C).toFloat())\n    }\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/view/component/DrawInsetsFrameLayout.java",
    "content": "package com.yalin.style.view.component;\n\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport android.graphics.Canvas;\nimport android.graphics.Rect;\nimport android.graphics.drawable.Drawable;\nimport android.util.AttributeSet;\nimport android.widget.FrameLayout;\n\nimport com.yalin.style.R;\n\n/**\n * @author jinyalin\n * @since 2017/4/21.\n */\n\npublic class DrawInsetsFrameLayout extends FrameLayout {\n    private Drawable mInsetBackground;\n    private Drawable mTopInsetBackground;\n    private Drawable mBottomInsetBackground;\n    private Drawable mSideInsetBackground;\n\n    private Rect mInsets;\n    private Rect mTempRect = new Rect();\n    private OnInsetsCallback mOnInsetsCallback;\n\n    public DrawInsetsFrameLayout(Context context) {\n        super(context);\n        init(context, null, 0);\n    }\n\n    public DrawInsetsFrameLayout(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        init(context, attrs, 0);\n    }\n\n    public DrawInsetsFrameLayout(Context context, AttributeSet attrs, int defStyle) {\n        super(context, attrs, defStyle);\n        init(context, attrs, defStyle);\n    }\n\n    private void init(Context context, AttributeSet attrs, int defStyle) {\n        final TypedArray a = context.obtainStyledAttributes(attrs,\n                R.styleable.DrawInsetsFrameLayout, defStyle, 0);\n        assert a != null;\n\n        mInsetBackground = a.getDrawable(R.styleable.DrawInsetsFrameLayout_insetBackground);\n        mTopInsetBackground = a.getDrawable(R.styleable.DrawInsetsFrameLayout_topInsetBackground);\n        mBottomInsetBackground =\n                a.getDrawable(R.styleable.DrawInsetsFrameLayout_bottomInsetBackground);\n        mSideInsetBackground = a.getDrawable(R.styleable.DrawInsetsFrameLayout_sideInsetBackground);\n\n        a.recycle();\n    }\n\n    @Override\n    protected void onAttachedToWindow() {\n        super.onAttachedToWindow();\n        if (mInsetBackground != null) {\n            mInsetBackground.setCallback(this);\n        }\n        if (mTopInsetBackground != null) {\n            mTopInsetBackground.setCallback(this);\n        }\n        if (mBottomInsetBackground != null) {\n            mBottomInsetBackground.setCallback(this);\n        }\n        if (mSideInsetBackground != null) {\n            mSideInsetBackground.setCallback(this);\n        }\n    }\n\n    @Override\n    protected void onDetachedFromWindow() {\n        super.onDetachedFromWindow();\n        if (mInsetBackground != null) {\n            mInsetBackground.setCallback(null);\n        }\n        if (mTopInsetBackground != null) {\n            mTopInsetBackground.setCallback(null);\n        }\n        if (mBottomInsetBackground != null) {\n            mBottomInsetBackground.setCallback(null);\n        }\n        if (mSideInsetBackground != null) {\n            mSideInsetBackground.setCallback(null);\n        }\n    }\n\n    public void setOnInsetsCallback(OnInsetsCallback onInsetsCallback) {\n        mOnInsetsCallback = onInsetsCallback;\n    }\n\n    @Override\n    protected boolean fitSystemWindows(Rect insets) {\n        mInsets = new Rect(insets);\n        setWillNotDraw(false);\n        postInvalidateOnAnimation();\n        if (mOnInsetsCallback != null) {\n            mOnInsetsCallback.onInsetsChanged(insets);\n        }\n        return true;\n    }\n\n    @Override\n    protected void onDraw(Canvas canvas) {\n        super.onDraw(canvas);\n        int width = getWidth();\n        int height = getHeight();\n\n        if (mInsets != null) {\n            // Top\n            mTempRect.set(0, 0, width, mInsets.top);\n            if (mInsetBackground != null) {\n                mInsetBackground.setBounds(mTempRect);\n                mInsetBackground.draw(canvas);\n            }\n            if (mTopInsetBackground != null) {\n                mTopInsetBackground.setBounds(mTempRect);\n                mTopInsetBackground.draw(canvas);\n            }\n\n            // Bottom\n            mTempRect.set(0, height - mInsets.bottom, width, height);\n            if (mInsetBackground != null) {\n                mInsetBackground.setBounds(mTempRect);\n                mInsetBackground.draw(canvas);\n            }\n            if (mTopInsetBackground != null) {\n                mBottomInsetBackground.setBounds(mTempRect);\n                mBottomInsetBackground.draw(canvas);\n            }\n\n            // Left\n            mTempRect.set(0, mInsets.top, mInsets.left, height - mInsets.bottom);\n            if (mInsetBackground != null) {\n                mInsetBackground.setBounds(mTempRect);\n                mInsetBackground.draw(canvas);\n            }\n            if (mSideInsetBackground != null) {\n                mSideInsetBackground.setBounds(mTempRect);\n                mSideInsetBackground.draw(canvas);\n            }\n\n            // Right\n            mTempRect.set(width - mInsets.right, mInsets.top, width, height - mInsets.bottom);\n            if (mInsetBackground != null) {\n                mInsetBackground.setBounds(mTempRect);\n                mInsetBackground.draw(canvas);\n            }\n            if (mSideInsetBackground != null) {\n                mSideInsetBackground.setBounds(mTempRect);\n                mSideInsetBackground.draw(canvas);\n            }\n        }\n    }\n\n    public interface OnInsetsCallback {\n        void onInsetsChanged(Rect insets);\n    }\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/view/component/GalleryEmptyStateGraphicView.kt",
    "content": "package com.yalin.style.view.component\n\nimport android.content.Context\nimport android.graphics.Canvas\nimport android.graphics.Paint\nimport android.graphics.RectF\nimport android.os.SystemClock\nimport android.support.v4.content.ContextCompat\nimport android.util.AttributeSet\nimport android.util.TypedValue\nimport android.view.View\nimport com.yalin.style.R\nimport java.util.*\n\n/**\n * @author jinyalin\n * @since 2017/5/24.\n */\nclass GalleryEmptyStateGraphicView(context: Context,\n                                   attrs: AttributeSet) : View(context, attrs) {\n    private val BITMAP = intArrayOf(\n            0, 0, 1, 1, 1, 1, 0, 0,\n            1, 1, 1, 1, 1, 1, 1, 1,\n            1, 1, 1, 0, 0, 1, 1, 1,\n            1, 1, 0, 1, 1, 0, 1, 1,\n            1, 1, 0, 1, 1, 0, 1, 1,\n            1, 1, 1, 0, 0, 1, 1, 1,\n            1, 1, 1, 1, 1, 1, 1, 1)\n\n    private val COLS = 8\n    private val ROWS = BITMAP.size / COLS\n\n    private val CELL_SPACING_DIP = 2\n    private val CELL_ROUNDING_DIP = 1\n    private val CELL_SIZE_DIP = 8\n\n    private val ON_TIME_MILLIS = 400\n    private val FADE_TIME_MILLIS = 100\n    private val OFF_TIME_MILLIS = 50\n\n    private val mOffPaint = Paint()\n    private val mOnPaint = Paint()\n    private var mWidth: Int = 0\n    private var mHeight: Int = 0\n    private var mOnTime: Long = 0\n    private var mOnX: Int = 0\n    private var mOnY: Int = 0\n    private val mRandom = Random()\n    private val mTempRectF = RectF()\n    private var mCellSpacing: Int\n    private var mCellRounding: Int\n    private var mCellSize: Int\n\n    init {\n        val res = resources\n        mOffPaint.isAntiAlias = true\n        mOffPaint.color = ContextCompat.getColor(context, R.color.gallery_empty_state_dark)\n        mOnPaint.isAntiAlias = true\n        mOnPaint.color = ContextCompat.getColor(context, R.color.gallery_empty_state_light)\n\n        mCellSpacing = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,\n                CELL_SPACING_DIP.toFloat(), res.displayMetrics).toInt()\n        mCellSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,\n                CELL_SIZE_DIP.toFloat(), res.displayMetrics).toInt()\n        mCellRounding = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,\n                CELL_ROUNDING_DIP.toFloat(), res.displayMetrics).toInt()\n    }\n\n    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {\n        super.onSizeChanged(w, h, oldw, oldh)\n        mWidth = w\n        mHeight = h\n    }\n\n    override fun onVisibilityChanged(changedView: View, visibility: Int) {\n        super.onVisibilityChanged(changedView, visibility)\n        if (isShown) {\n            postInvalidateOnAnimation()\n        }\n    }\n\n    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {\n        super.onMeasure(widthMeasureSpec, heightMeasureSpec)\n        setMeasuredDimension(\n                View.resolveSize(COLS * mCellSize + (COLS - 1) * mCellSpacing, widthMeasureSpec),\n                View.resolveSize(ROWS * mCellSize + (ROWS - 1) * mCellSpacing, heightMeasureSpec))\n    }\n\n    override fun onDraw(canvas: Canvas) {\n        super.onDraw(canvas)\n        if (!isShown || mWidth == 0 || mHeight == 0) {\n            return\n        }\n\n        // tick timer\n        val nowElapsed = SystemClock.elapsedRealtime()\n        if (nowElapsed > mOnTime + ON_TIME_MILLIS.toLong() +\n                (FADE_TIME_MILLIS * 2).toLong() + OFF_TIME_MILLIS.toLong()) {\n            mOnTime = nowElapsed\n            while (true) {\n                val x = mRandom.nextInt(COLS)\n                val y = mRandom.nextInt(ROWS)\n                if ((x != mOnX || y != mOnY) && BITMAP[y * COLS + x] == 1) {\n                    mOnX = x\n                    mOnY = y\n                    break\n                }\n            }\n        }\n\n        val t = (nowElapsed - mOnTime).toInt()\n        for (y in 0..ROWS - 1) {\n            for (x in 0..COLS - 1) {\n                if (BITMAP[y * COLS + x] != 1) {\n                    continue\n                }\n\n                mTempRectF.set(\n                        (x * (mCellSize + mCellSpacing)).toFloat(),\n                        (y * (mCellSize + mCellSpacing)).toFloat(),\n                        (x * (mCellSize + mCellSpacing) + mCellSize).toFloat(),\n                        (y * (mCellSize + mCellSpacing) + mCellSize).toFloat())\n\n                canvas.drawRoundRect(mTempRectF,\n                        mCellRounding.toFloat(),\n                        mCellRounding.toFloat(),\n                        mOffPaint)\n\n                if (nowElapsed <= mOnTime + ON_TIME_MILLIS.toLong()\n                        + (FADE_TIME_MILLIS * 2).toLong()\n                        && mOnX == x && mOnY == y) {\n                    // draw items\n                    if (t < FADE_TIME_MILLIS) {\n                        mOnPaint.alpha = t * 255 / FADE_TIME_MILLIS\n                    } else if (t < FADE_TIME_MILLIS + ON_TIME_MILLIS) {\n                        mOnPaint.alpha = 255\n                    } else {\n                        mOnPaint.alpha = 255 -\n                                (t - ON_TIME_MILLIS - FADE_TIME_MILLIS) * 255 / FADE_TIME_MILLIS\n                    }\n\n                    canvas.drawRoundRect(mTempRectF,\n                            mCellRounding.toFloat(),\n                            mCellRounding.toFloat(),\n                            mOnPaint)\n                }\n            }\n        }\n\n        postInvalidateOnAnimation()\n    }\n}"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/view/component/ObservableHorizontalScrollView.kt",
    "content": "package com.yalin.style.view.component\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.MotionEvent\nimport android.widget.HorizontalScrollView\n\n/**\n * @author jinyalin\n * @since 2017/5/22.\n */\nclass ObservableHorizontalScrollView(context: Context, attrs: AttributeSet) :\n        HorizontalScrollView(context, attrs) {\n    private var mCallbacks: Callbacks? = null\n\n    override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) {\n        super.onScrollChanged(l, t, oldl, oldt)\n        mCallbacks?.onScrollChanged(l)\n    }\n\n    override fun onTouchEvent(ev: MotionEvent): Boolean {\n        when (ev.actionMasked) {\n            MotionEvent.ACTION_DOWN -> mCallbacks?.onDownMotionEvent()\n        }\n        return super.onTouchEvent(ev)\n    }\n\n    public override fun computeHorizontalScrollRange(): Int {\n        return super.computeHorizontalScrollRange()\n    }\n\n    fun setCallbacks(listener: Callbacks) {\n        mCallbacks = listener\n    }\n\n    interface Callbacks {\n        fun onScrollChanged(scrollX: Int)\n        fun onDownMotionEvent()\n    }\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/view/component/PanScaleProxyView.java",
    "content": "package com.yalin.style.view.component;\n\nimport android.content.Context;\nimport android.graphics.Point;\nimport android.graphics.PointF;\nimport android.graphics.RectF;\nimport android.os.Handler;\nimport android.os.Parcel;\nimport android.os.Parcelable;\nimport android.support.v4.os.ParcelableCompat;\nimport android.support.v4.os.ParcelableCompatCreatorCallbacks;\nimport android.support.v4.view.GestureDetectorCompat;\nimport android.util.AttributeSet;\nimport android.view.GestureDetector;\nimport android.view.MotionEvent;\nimport android.view.ScaleGestureDetector;\nimport android.view.View;\nimport android.widget.OverScroller;\n\nimport com.yalin.style.util.MathUtil;\n\n/**\n * @author jinyalin\n * @since 2017/4/21.\n */\n\npublic class PanScaleProxyView extends View {\n    /**\n     * The current viewport. This rectangle represents the currently visible chart domain\n     * and range. The currently visible chart X values are from this rectangle's left to its right.\n     * The currently visible chart Y values are from this rectangle's top to its bottom.\n     */\n    private RectF mCurrentViewport = new RectF(0, 0, 1, 1);\n\n    private Point mSurfaceSizeBuffer = new Point();\n\n    private int mWidth = 1;\n    private int mHeight = 1;\n    private float mRelativeAspectRatio = 1f;\n    private boolean mPanScaleEnabled = true;\n    private float mMinViewportWidthOrHeight = 0.01f;\n\n    // State objects and values related to gesture tracking.\n    private ScaleGestureDetector mScaleGestureDetector;\n    private GestureDetectorCompat mGestureDetector;\n    private OverScroller mScroller;\n    private Zoomer mZoomer;\n    private PointF mZoomFocalPoint = new PointF();\n    private RectF mScrollerStartViewport = new RectF(); // Used only for zooms and flings.\n    private boolean mDragZoomed = false;\n    private boolean mMotionEventDown;\n\n    private Handler mHandler = new Handler();\n\n    private OnOtherGestureListener mOnOtherGestureListener;\n    private OnViewportChangedListener mOnViewportChangedListener;\n\n    public PanScaleProxyView(Context context) {\n        this(context, null, 0);\n    }\n\n    public PanScaleProxyView(Context context, AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    public PanScaleProxyView(Context context, AttributeSet attrs, int defStyle) {\n        super(context, attrs, defStyle);\n\n        setWillNotDraw(true);\n\n        // Sets up interactions\n        mScaleGestureDetector = new ScaleGestureDetector(context, mScaleGestureListener);\n        mScaleGestureDetector.setQuickScaleEnabled(true);\n        mGestureDetector = new GestureDetectorCompat(context, mGestureListener);\n\n        mScroller = new OverScroller(context);\n        mZoomer = new Zoomer(context);\n    }\n\n    @Override\n    protected void onSizeChanged(int w, int h, int oldw, int oldh) {\n        super.onSizeChanged(w, h, oldw, oldh);\n        mWidth = Math.max(1, w);\n        mHeight = Math.max(1, h);\n    }\n\n    ////////////////////////////////////////////////////////////////////////////////////////////////\n    //\n    //     Methods and objects related to gesture handling\n    //\n    ////////////////////////////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Finds the chart point (i.e. within the chart's domain and range) represented by the\n     * given pixel coordinates. The \"dest\" argument is set to the point and\n     * this function returns true.\n     */\n    private void hitTest(float x, float y, PointF dest) {\n        dest.set(mCurrentViewport.left + mCurrentViewport.width() * x / mWidth,\n                mCurrentViewport.top + mCurrentViewport.height() * y / mHeight);\n    }\n\n    @Override\n    public boolean onTouchEvent(MotionEvent event) {\n        if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {\n            mMotionEventDown = true;\n        }\n        boolean retVal = mScaleGestureDetector.onTouchEvent(event);\n        retVal = mGestureDetector.onTouchEvent(event) || retVal;\n        if (mMotionEventDown && event.getActionMasked() == MotionEvent.ACTION_UP) {\n            mMotionEventDown = false;\n        }\n        return retVal || super.onTouchEvent(event);\n    }\n\n    /**\n     * The scale listener, used for handling multi-finger scale gestures.\n     */\n    private final ScaleGestureDetector.OnScaleGestureListener mScaleGestureListener\n            = new ScaleGestureDetector.SimpleOnScaleGestureListener() {\n        /**\n         * This is the active focal point in terms of the viewport. Could be a local\n         * variable but kept here to minimize per-frame allocations.\n         */\n        private PointF viewportFocus = new PointF();\n\n        @Override\n        public boolean onScaleBegin(ScaleGestureDetector detector) {\n            if (!mPanScaleEnabled) {\n                return false;\n            }\n\n            mDragZoomed = true;\n            return true;\n        }\n\n        @Override\n        public boolean onScale(ScaleGestureDetector scaleGestureDetector) {\n            if (!mPanScaleEnabled) {\n                return false;\n            }\n\n            float newWidth = 1 / scaleGestureDetector.getScaleFactor() * mCurrentViewport.width();\n            float newHeight = 1 / scaleGestureDetector.getScaleFactor() * mCurrentViewport.height();\n\n            float focusX = scaleGestureDetector.getFocusX();\n            float focusY = scaleGestureDetector.getFocusY();\n            hitTest(focusX, focusY, viewportFocus);\n\n            mCurrentViewport.set(\n                    viewportFocus.x - newWidth * focusX / mWidth,\n                    viewportFocus.y - newHeight * focusY / mHeight,\n                    0,\n                    0);\n            mCurrentViewport.right = mCurrentViewport.left + newWidth;\n            mCurrentViewport.bottom = mCurrentViewport.top + newHeight;\n            constrainViewport();\n            triggerViewportChangedListener();\n            return true;\n        }\n    };\n\n    /**\n     * Ensures that current viewport is inside the viewport extremes and original\n     * aspect ratio is kept.\n     */\n    private void constrainViewport() {\n        if (mRelativeAspectRatio > 1) {\n            if (mCurrentViewport.top < 0) {\n                mCurrentViewport.offset(0, -mCurrentViewport.top);\n            }\n            if (mCurrentViewport.bottom > 1) {\n                float requestedHeight = mCurrentViewport.height();\n                mCurrentViewport.bottom = 1;\n                mCurrentViewport.top = Math.max(0, mCurrentViewport.bottom - requestedHeight);\n            }\n            if (mCurrentViewport.height() < mMinViewportWidthOrHeight) {\n                mCurrentViewport.bottom = (mCurrentViewport.bottom + mCurrentViewport.top) / 2\n                        + mMinViewportWidthOrHeight / 2;\n                mCurrentViewport.top = mCurrentViewport.bottom - mMinViewportWidthOrHeight;\n            }\n            float halfWidth = mCurrentViewport.height() / mRelativeAspectRatio / 2;\n            float centerX = MathUtil.constrain(halfWidth, 1 - halfWidth,\n                    (mCurrentViewport.right + mCurrentViewport.left) / 2);\n            mCurrentViewport.left = centerX - halfWidth;\n            mCurrentViewport.right = centerX + halfWidth;\n        } else {\n            if (mCurrentViewport.left < 0) {\n                mCurrentViewport.offset(-mCurrentViewport.left, 0);\n            }\n            if (mCurrentViewport.right > 1) {\n                float requestedWidth = mCurrentViewport.width();\n                mCurrentViewport.right = 1;\n                mCurrentViewport.left = Math.max(0, mCurrentViewport.right - requestedWidth);\n            }\n            if (mCurrentViewport.width() < mMinViewportWidthOrHeight) {\n                mCurrentViewport.right = (mCurrentViewport.right + mCurrentViewport.left) / 2\n                        + mMinViewportWidthOrHeight / 2;\n                mCurrentViewport.left = mCurrentViewport.right - mMinViewportWidthOrHeight;\n            }\n            float halfHeight = mCurrentViewport.width() * mRelativeAspectRatio / 2;\n            float centerY = MathUtil.constrain(halfHeight, 1 - halfHeight,\n                    (mCurrentViewport.bottom + mCurrentViewport.top) / 2);\n            mCurrentViewport.top = centerY - halfHeight;\n            mCurrentViewport.bottom = centerY + halfHeight;\n        }\n    }\n\n    /**\n     * The gesture listener, used for handling simple gestures such as double touches, scrolls,\n     * and flings.\n     */\n    private final GestureDetector.SimpleOnGestureListener mGestureListener\n            = new GestureDetector.SimpleOnGestureListener() {\n        @Override\n        public boolean onDown(MotionEvent e) {\n            if (!mPanScaleEnabled) {\n                return false;\n            }\n\n            mDragZoomed = false;\n            mScrollerStartViewport.set(mCurrentViewport);\n            mScroller.forceFinished(true);\n            return true;\n        }\n\n        @Override\n        public boolean onSingleTapConfirmed(MotionEvent e) {\n            if (mOnOtherGestureListener != null) {\n                mOnOtherGestureListener.onSingleTapUp();\n            }\n            return true;\n        }\n\n        @Override\n        public boolean onDoubleTapEvent(MotionEvent e) {\n            if (!mPanScaleEnabled || mDragZoomed || e.getActionMasked() != MotionEvent.ACTION_UP) {\n                return false;\n            }\n\n            mZoomer.forceFinished(true);\n            hitTest(e.getX(), e.getY(), mZoomFocalPoint);\n            float startZoom;\n            if (mRelativeAspectRatio > 1) {\n                startZoom = 1 / mCurrentViewport.height();\n            } else {\n                startZoom = 1 / mCurrentViewport.width();\n            }\n            boolean zoomIn = (startZoom < 1.5f);\n            mZoomer.startZoom(startZoom, zoomIn ? 2f : 1f);\n            triggerViewportChangedListener();\n            postAnimateTick();\n\n            // Workaround for 11952668; blow away the entire scale gesture detector after\n            // a double tap\n            mScaleGestureDetector = new ScaleGestureDetector(getContext(), mScaleGestureListener);\n            mScaleGestureDetector.setQuickScaleEnabled(true);\n            return true;\n        }\n\n        @Override\n        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {\n            if (!mPanScaleEnabled) {\n                return false;\n            }\n\n            // Scrolling uses math based on the viewport (as opposed to math using pixels).\n            /*\n             * Pixel offset is the offset in screen pixels, while viewport offset is the\n             * offset within the current viewport. For additional information on surface sizes\n             * and pixel offsets, see the docs for {@link computeScrollSurfaceSize()}. For\n             * additional information about the viewport, see the comments for\n             * {@link mCurrentViewport}.\n             */\n            float viewportOffsetX = distanceX * mCurrentViewport.width() / mWidth;\n            float viewportOffsetY = distanceY * mCurrentViewport.height() / mHeight;\n            computeScrollSurfaceSize(mSurfaceSizeBuffer);\n            setViewportTopLeft(\n                    mCurrentViewport.left + viewportOffsetX,\n                    mCurrentViewport.top + viewportOffsetY);\n            return true;\n        }\n\n        @Override\n        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {\n            if (!mPanScaleEnabled) {\n                return false;\n            }\n\n            fling((int) -velocityX, (int) -velocityY);\n            return true;\n        }\n    };\n\n    private void fling(int velocityX, int velocityY) {\n        // Flings use math in pixels (as opposed to math based on the viewport).\n        computeScrollSurfaceSize(mSurfaceSizeBuffer);\n        mScrollerStartViewport.set(mCurrentViewport);\n        int startX = (int) (mSurfaceSizeBuffer.x * mScrollerStartViewport.left);\n        int startY = (int) (mSurfaceSizeBuffer.y * mScrollerStartViewport.top);\n        mScroller.forceFinished(true);\n        mScroller.fling(\n                startX,\n                startY,\n                velocityX,\n                velocityY,\n                0, mSurfaceSizeBuffer.x - mWidth,\n                0, mSurfaceSizeBuffer.y - mHeight,\n                mWidth / 2,\n                mHeight / 2);\n        postAnimateTick();\n        triggerViewportChangedListener();\n    }\n\n    private void postAnimateTick() {\n        mHandler.removeCallbacks(mAnimateTickRunnable);\n        mHandler.post(mAnimateTickRunnable);\n    }\n\n    private Runnable mAnimateTickRunnable = new Runnable() {\n        @Override\n        public void run() {\n            boolean needsInvalidate = false;\n\n            if (mScroller.computeScrollOffset()) {\n                // The scroller isn't finished, meaning a fling or programmatic pan operation is\n                // currently active.\n\n                computeScrollSurfaceSize(mSurfaceSizeBuffer);\n                int currX = mScroller.getCurrX();\n                int currY = mScroller.getCurrY();\n\n                float currXRange = currX * 1f / mSurfaceSizeBuffer.x;\n                float currYRange = currY * 1f / mSurfaceSizeBuffer.y;\n                setViewportTopLeft(currXRange, currYRange);\n                needsInvalidate = true;\n            }\n\n            if (mZoomer.computeZoom()) {\n                // Performs the zoom since a zoom is in progress.\n                float newWidth, newHeight;\n                if (mRelativeAspectRatio > 1) {\n                    newHeight = 1 / mZoomer.getCurrZoom();\n                    newWidth = newHeight / mRelativeAspectRatio;\n                } else {\n                    newWidth = 1 / mZoomer.getCurrZoom();\n                    newHeight = newWidth * mRelativeAspectRatio;\n                }\n                // focalPointOnScreen... 0 = left/top edge of screen, 1 = right/bottom edge of sreen\n                float focalPointOnScreenX = (mZoomFocalPoint.x - mScrollerStartViewport.left)\n                        / mScrollerStartViewport.width();\n                float focalPointOnScreenY = (mZoomFocalPoint.y - mScrollerStartViewport.top)\n                        / mScrollerStartViewport.height();\n                mCurrentViewport.set(\n                        mZoomFocalPoint.x - newWidth * focalPointOnScreenX,\n                        mZoomFocalPoint.y - newHeight * focalPointOnScreenY,\n                        mZoomFocalPoint.x + newWidth * (1 - focalPointOnScreenX),\n                        mZoomFocalPoint.y + newHeight * (1 - focalPointOnScreenY));\n                constrainViewport();\n                needsInvalidate = true;\n            }\n\n            if (needsInvalidate) {\n                triggerViewportChangedListener();\n                postAnimateTick();\n            }\n        }\n    };\n\n    /**\n     * Computes the current scrollable surface size, in pixels. For example, if the entire chart\n     * area is visible, this is simply the current view width and height. If the chart\n     * is zoomed in 200% in both directions, the returned size will be twice as large horizontally\n     * and vertically.\n     */\n    private void computeScrollSurfaceSize(Point out) {\n        out.set(\n                (int) (mWidth / mCurrentViewport.width()),\n                (int) (mHeight / mCurrentViewport.height()));\n    }\n\n    /**\n     * Sets the current viewport (defined by {@link #mCurrentViewport}) to the given\n     * X and Y positions. Note that the Y value represents the topmost pixel position, and thus\n     * the bottom of the {@link #mCurrentViewport} rectangle. For more details on why top and\n     * bottom are flipped, see {@link #mCurrentViewport}.\n     */\n    private void setViewportTopLeft(float x, float y) {\n        /**\n         * Constrains within the scroll range. The scroll range is simply the viewport extremes\n         * (AXIS_X_MAX, etc.) minus the viewport size. For example, if the extrema were 0 and 10,\n         * and the viewport size was 2, the scroll range would be 0 to 8.\n         */\n\n        float curWidth = mCurrentViewport.width();\n        float curHeight = mCurrentViewport.height();\n        x = Math.max(0, Math.min(x, 1 - curWidth));\n        y = Math.max(0, Math.min(y, 1 - curHeight));\n\n        mCurrentViewport.set(x, y, x + curWidth, y + curHeight);\n        triggerViewportChangedListener();\n    }\n\n    ////////////////////////////////////////////////////////////////////////////////////////////////\n    //\n    //     Methods for programmatically changing the viewport\n    //\n    ////////////////////////////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Returns the current viewport (visible extremes for the chart domain and range.)\n     */\n    public RectF getCurrentViewport() {\n        return new RectF(mCurrentViewport);\n    }\n\n    private void triggerViewportChangedListener() {\n        if (mOnViewportChangedListener != null) {\n            mOnViewportChangedListener.onViewportChanged();\n        }\n    }\n\n    ////////////////////////////////////////////////////////////////////////////////////////////////\n    //\n    //     Methods and classes related to view state persistence.\n    //\n    ////////////////////////////////////////////////////////////////////////////////////////////////\n\n    @Override\n    public Parcelable onSaveInstanceState() {\n        Parcelable superState = super.onSaveInstanceState();\n        SavedState ss = new SavedState(superState);\n        ss.viewport = mCurrentViewport;\n        return ss;\n    }\n\n    @Override\n    public void onRestoreInstanceState(Parcelable state) {\n        if (!(state instanceof SavedState)) {\n            super.onRestoreInstanceState(state);\n            return;\n        }\n\n        SavedState ss = (SavedState) state;\n        super.onRestoreInstanceState(ss.getSuperState());\n\n        mCurrentViewport = ss.viewport;\n    }\n\n    public void setRelativeAspectRatio(float relativeAspectRatio) {\n        mRelativeAspectRatio = relativeAspectRatio;\n        constrainViewport();\n        triggerViewportChangedListener();\n    }\n\n    public void setViewport(RectF viewport) {\n        mCurrentViewport.set(viewport);\n        triggerViewportChangedListener();\n    }\n\n    public void setMaxZoom(int maxZoom) {\n        mMinViewportWidthOrHeight = 1f / maxZoom;\n    }\n\n    public void setOnViewportChangedListener(OnViewportChangedListener onViewportChangedListener) {\n        mOnViewportChangedListener = onViewportChangedListener;\n    }\n\n    public void setOnOtherGestureListener(OnOtherGestureListener onOtherGestureListener) {\n        mOnOtherGestureListener = onOtherGestureListener;\n    }\n\n    public void enablePanScale(boolean panScaleEnabled) {\n        mPanScaleEnabled = panScaleEnabled;\n    }\n\n    /**\n     * Persistent state that is saved by PanScaleProxyView.\n     */\n    public static class SavedState extends BaseSavedState {\n        private RectF viewport;\n\n        public SavedState(Parcelable superState) {\n            super(superState);\n        }\n\n        @Override\n        public void writeToParcel(Parcel out, int flags) {\n            super.writeToParcel(out, flags);\n            out.writeFloat(viewport.left);\n            out.writeFloat(viewport.top);\n            out.writeFloat(viewport.right);\n            out.writeFloat(viewport.bottom);\n        }\n\n        @Override\n        public String toString() {\n            return \"PanScaleProxyView.SavedState{\"\n                    + Integer.toHexString(System.identityHashCode(this))\n                    + \" viewport=\" + viewport.toString() + \"}\";\n        }\n\n        public static final Parcelable.Creator<SavedState> CREATOR\n                = ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks<SavedState>() {\n            @Override\n            public SavedState createFromParcel(Parcel in, ClassLoader loader) {\n                return new SavedState(in);\n            }\n\n            @Override\n            public SavedState[] newArray(int size) {\n                return new SavedState[size];\n            }\n        });\n\n        SavedState(Parcel in) {\n            super(in);\n            viewport = new RectF(in.readFloat(), in.readFloat(), in.readFloat(), in.readFloat());\n        }\n    }\n\n    public interface OnViewportChangedListener {\n        void onViewportChanged();\n    }\n\n    public interface OnOtherGestureListener {\n        void onSingleTapUp();\n    }\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/view/component/Scrollbar.kt",
    "content": "package com.yalin.style.view.component\n\nimport android.content.Context\nimport android.graphics.Canvas\nimport android.graphics.Paint\nimport android.graphics.Path\nimport android.graphics.RectF\nimport android.util.AttributeSet\nimport android.view.View\nimport com.yalin.style.R\nimport com.yalin.style.util.MathUtil\n\n/**\n * @author jinyalin\n * @since 2017/5/22.\n */\n\nclass Scrollbar @JvmOverloads\nconstructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) :\n        View(context, attrs, defStyle) {\n\n\n    companion object {\n        private val DEFAULT_BACKGROUND_COLOR = 0x80000000.toInt()\n        private val DEFAULT_INDICATOR_COLOR = 0xff000000.toInt()\n    }\n\n    private var mHidden = true\n\n    private val mAnimationDuration: Int =\n            resources.getInteger(android.R.integer.config_shortAnimTime)\n    private var mIndicatorWidth: Float = 0.toFloat()\n    private val mBackgroundPaint: Paint\n    private val mIndicatorPaint: Paint\n\n    private val mTempPath = Path()\n    private val mTempRectF = RectF()\n\n    private var mWidth: Int = 0\n    private var mHeight: Int = 0\n\n    private var mScrollRange: Int = 0\n    private var mViewportWidth: Int = 0\n    private var mPosition: Float = 0.toFloat()\n\n    init {\n        val a = context.obtainStyledAttributes(attrs, R.styleable.Scrollbar)\n        val mBackgroundColor = a.getColor(R.styleable.Scrollbar_backgroundColor,\n                DEFAULT_BACKGROUND_COLOR)\n        val mIndicatorColor = a.getColor(R.styleable.Scrollbar_indicatorColor,\n                DEFAULT_INDICATOR_COLOR)\n        a.recycle()\n\n        mBackgroundPaint = Paint()\n        mBackgroundPaint.color = mBackgroundColor\n        mBackgroundPaint.isAntiAlias = true\n\n        mIndicatorPaint = Paint()\n        mIndicatorPaint.color = mIndicatorColor\n        mIndicatorPaint.isAntiAlias = true\n\n        alpha = 0f\n    }\n\n    override fun onDraw(canvas: Canvas) {\n        super.onDraw(canvas)\n        if (mScrollRange <= mViewportWidth) {\n            return\n        }\n\n        mTempRectF.top = 0f\n        mTempRectF.bottom = mHeight.toFloat()\n        mTempRectF.left = 0f\n        mTempRectF.right = mWidth.toFloat()\n\n        drawPill(canvas, mTempRectF, mBackgroundPaint)\n\n        mTempRectF.top = 0f\n        mTempRectF.bottom = mHeight.toFloat()\n        mTempRectF.left = mPosition * 1f / (mScrollRange - mViewportWidth) *\n                mWidth.toFloat() * (1 - mIndicatorWidth)\n        mTempRectF.right = mTempRectF.left + mIndicatorWidth * mWidth\n\n        drawPill(canvas, mTempRectF, mIndicatorPaint)\n    }\n\n    private fun drawPill(canvas: Canvas, rectF: RectF, paint: Paint) {\n        val radius = rectF.height() / 2\n        var temp: Float\n\n        mTempPath.reset()\n        mTempPath.moveTo(rectF.left + radius, rectF.top)\n        mTempPath.lineTo(rectF.right - radius, rectF.top)\n\n        temp = rectF.left\n        rectF.left = rectF.right - 2 * radius\n        mTempPath.arcTo(rectF, 270f, 180f)\n        rectF.left = temp\n\n        mTempPath.lineTo(rectF.left + radius, rectF.bottom)\n\n        temp = rectF.right\n        rectF.right = rectF.left + rectF.height()\n        mTempPath.arcTo(rectF, 90f, 180f)\n        rectF.right = temp\n\n        mTempPath.close()\n        canvas.drawPath(mTempPath, paint)\n    }\n\n    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {\n        setMeasuredDimension(\n                View.resolveSize(0, widthMeasureSpec),\n                View.resolveSize(0, heightMeasureSpec))\n    }\n\n    fun setScrollPosition(position: Int) {\n        mPosition = MathUtil.constrain(0f, mScrollRange.toFloat(), position.toFloat())\n        postInvalidateOnAnimation()\n    }\n\n    fun setScrollRangeAndViewportWidth(scrollRange: Int, viewportWidth: Int) {\n        mScrollRange = scrollRange\n        mViewportWidth = viewportWidth\n        mIndicatorWidth = 0.1f\n        if (mScrollRange > 0) {\n            mIndicatorWidth = MathUtil.constrain(mIndicatorWidth, 1f,\n                    mViewportWidth * 1f / mScrollRange)\n        }\n        postInvalidateOnAnimation()\n    }\n\n    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {\n        super.onSizeChanged(w, h, oldw, oldh)\n        mHeight = h\n        mWidth = w\n    }\n\n    fun show() {\n        if (!mHidden) {\n            return\n        }\n\n        mHidden = false\n        animate().cancel()\n        animate().alpha(1f).duration = mAnimationDuration.toLong()\n    }\n\n    fun hide() {\n        if (mHidden) {\n            return\n        }\n\n        mHidden = true\n        animate().cancel()\n        animate().alpha(0f).duration = mAnimationDuration.toLong()\n    }\n\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/view/component/ShadowDipsTextView.kt",
    "content": "/*\n * Copyright 2014 Google Inc.\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\npackage com.yalin.style.view.component\n\nimport android.content.Context\nimport android.content.res.TypedArray\nimport android.support.v7.widget.AppCompatTextView\nimport android.util.AttributeSet\n\nimport com.yalin.style.R\n\n\nclass ShadowDipsTextView : AppCompatTextView {\n    constructor(context: Context) : super(context) {\n        init(context, null, 0)\n    }\n\n    constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {\n        init(context, attrs, 0)\n    }\n\n    constructor(context: Context, attrs: AttributeSet, defStyle: Int) :\n            super(context, attrs, defStyle) {\n        init(context, attrs, defStyle)\n    }\n\n    private fun init(context: Context, attrs: AttributeSet?, defStyle: Int) {\n        val a = context.obtainStyledAttributes(attrs,\n                R.styleable.ShadowDipsTextView, defStyle, 0)\n        val shadowDx = a.getDimensionPixelSize(R.styleable.ShadowDipsTextView_shadowDx, 0)\n        val shadowDy = a.getDimensionPixelSize(R.styleable.ShadowDipsTextView_shadowDy, 0)\n        val shadowRadius = a.getDimensionPixelSize(R.styleable.ShadowDipsTextView_shadowRadius, 0)\n        val shadowColor = a.getColor(R.styleable.ShadowDipsTextView_shadowColor, 0)\n        if (shadowColor != 0) {\n            setShadowLayer(shadowRadius.toFloat(),\n                    shadowDx.toFloat(), shadowDy.toFloat(), shadowColor)\n        }\n        a.recycle()\n    }\n}\n\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/view/component/TintableImageButton.kt",
    "content": "package com.yalin.style.view.component\n\nimport android.content.Context\nimport android.content.res.ColorStateList\nimport android.util.AttributeSet\n\nimport com.yalin.style.R\n\n/**\n * @author jinyalin\n * *\n * @since 2017/4/30.\n */\n\nclass TintableImageButton : android.support.v7.widget.AppCompatImageButton {\n\n    private var tint: ColorStateList? = null\n\n    constructor(context: Context) : super(context)\n\n    constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {\n        init(context, attrs, 0)\n    }\n\n    constructor(context: Context, attrs: AttributeSet, defStyle: Int) :\n            super(context, attrs, defStyle) {\n        init(context, attrs, defStyle)\n    }\n\n    private fun init(context: Context, attrs: AttributeSet, defStyle: Int) {\n        val a = context.obtainStyledAttributes(attrs,\n                R.styleable.TintableImageButton, defStyle, 0)\n        tint = a.getColorStateList(R.styleable.TintableImageButton_tint)\n        a.recycle()\n    }\n\n    override fun drawableStateChanged() {\n        super.drawableStateChanged()\n        if (tint != null && tint!!.isStateful) {\n            updateTintColor()\n        }\n    }\n\n    fun setColorFilter(tint: ColorStateList) {\n        this.tint = tint\n        super.setColorFilter(tint.getColorForState(drawableState, 0))\n    }\n\n    private fun updateTintColor() {\n        val color = tint!!.getColorForState(drawableState, 0)\n        setColorFilter(color)\n    }\n\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/view/component/Zoomer.kt",
    "content": "package com.yalin.style.view.component\n\nimport android.content.Context\nimport android.os.SystemClock\nimport android.view.animation.DecelerateInterpolator\nimport android.view.animation.Interpolator\n\nimport com.yalin.style.util.MathUtil\n\n/**\n * @author jinyalin\n * *\n * @since 2017/4/21.\n * * A simple class that animates double-touch zoom gestures. Functionally similar to a [ ].\n */\ninternal class Zoomer(context: Context) {\n    /**\n     * The interpolator, used for making zooms animate 'naturally.'\n     */\n    private val mInterpolator: Interpolator\n\n    /**\n     * The total animation duration for a zoom.\n     */\n    private val mAnimationDurationMillis: Int\n\n    /**\n     * Whether or not the current zoom has finished.\n     */\n    private var mFinished = true\n\n    /**\n     * The starting zoom value.\n     */\n    private var mStartZoom = 1f\n\n    /**\n     * The current zoom value; computed by [.computeZoom].\n     */\n    /**\n     * Returns the current zoom level.\n\n     * @see android.widget.Scroller.getCurrX\n     */\n    var currZoom: Float = 0.toFloat()\n        private set\n\n    /**\n     * The time the zoom started, computed using [android.os.SystemClock.elapsedRealtime].\n     */\n    private var mStartRTC: Long = 0\n\n    /**\n     * The destination zoom factor.\n     */\n    private var mEndZoom: Float = 0.toFloat()\n\n    init {\n        mInterpolator = DecelerateInterpolator()\n        mAnimationDurationMillis = context.resources.getInteger(\n                android.R.integer.config_shortAnimTime)\n    }\n\n    /**\n     * Forces the zoom finished state to the given value. Unlike [.abortAnimation], the\n     * current zoom value isn't set to the ending value.\n\n     * @see android.widget.Scroller.forceFinished\n     */\n    fun forceFinished(finished: Boolean) {\n        mFinished = finished\n    }\n\n    /**\n     * Aborts the animation, setting the current zoom value to the ending value.\n\n     * @see android.widget.Scroller.abortAnimation\n     */\n    fun abortAnimation() {\n        mFinished = true\n        currZoom = mEndZoom\n    }\n\n    /**\n     * Starts a zoom from startZoom to endZoom. That is, to zoom from 100% to 125%, endZoom should\n     * by 0.25f.\n\n     * @see android.widget.Scroller.startScroll\n     */\n    fun startZoom(startZoom: Float, endZoom: Float) {\n        mStartRTC = SystemClock.elapsedRealtime()\n        mEndZoom = endZoom\n\n        mFinished = false\n        mStartZoom = startZoom\n        currZoom = startZoom\n    }\n\n    /**\n     * Computes the current zoom level, returning true if the zoom is still active and false if the\n     * zoom has finished.\n\n     * @see android.widget.Scroller.computeScrollOffset\n     */\n    fun computeZoom(): Boolean {\n        if (mFinished) {\n            return false\n        }\n\n        val tRTC = SystemClock.elapsedRealtime() - mStartRTC\n        if (tRTC >= mAnimationDurationMillis) {\n            mFinished = true\n            currZoom = mEndZoom\n            return false\n        }\n\n        val t = tRTC * 1f / mAnimationDurationMillis\n        currZoom = MathUtil.interpolate(\n                mStartZoom, mEndZoom, mInterpolator.getInterpolation(t))\n        return true\n    }\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/view/fragment/AnimatedStyleLogoFragment.kt",
    "content": "package com.yalin.style.view.fragment\n\nimport android.app.Fragment\nimport android.os.Bundle\nimport android.util.TypedValue\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.view.ViewPropertyAnimator\nimport android.view.animation.OvershootInterpolator\nimport com.yalin.style.R\nimport kotlinx.android.synthetic.main.animated_logo_fragment.*\n\n/**\n * YaLin 2017/1/4.\n */\n\nclass AnimatedStyleLogoFragment : Fragment() {\n\n    private var mOnFillStartedCallback: (() -> Unit)? = null\n    private var mInitialLogoOffset: Float = 0.toFloat()\n    private var mAnimator: ViewPropertyAnimator? = null\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        mInitialLogoOffset = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16f,\n                resources.displayMetrics)\n    }\n\n    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,\n                              savedInstanceState: Bundle?): View =\n            inflater.inflate(R.layout.animated_logo_fragment, container, false)\n\n\n    override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        reset()\n    }\n\n    fun start() {\n        mAnimator = logoView.animate().translationY(0f)\n                .withEndAction {\n                    with(logoSubtitle) {\n                        visibility = View.VISIBLE\n                        translationY = (-height).toFloat()\n                        val interpolator = OvershootInterpolator()\n                        animate()\n                                .translationY(0f)\n                                .setInterpolator(interpolator).setDuration(500)\n                                .start()\n                    }\n\n                    mOnFillStartedCallback?.invoke()\n                }.setDuration(500)\n        mAnimator!!.start()\n\n    }\n\n    override fun onDetach() {\n        super.onDetach()\n        if (mAnimator != null) {\n            mAnimator!!.cancel()\n        }\n    }\n\n    fun setOnFillStartedCallback(fillStartedCallback: () -> Unit) {\n        mOnFillStartedCallback = fillStartedCallback\n    }\n\n    fun reset() {\n        logoView.translationY = mInitialLogoOffset\n        logoSubtitle.visibility = View.INVISIBLE\n    }\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/view/fragment/BaseFragment.kt",
    "content": "package com.yalin.style.view.fragment\n\nimport android.app.Fragment\nimport android.widget.Toast\n\nimport com.yalin.style.injection.HasComponent\n\n/**\n * @author jinyalin\n * *\n * @since 2017/4/20.\n */\n\nabstract class BaseFragment : Fragment() {\n    /**\n     * Shows a [android.widget.Toast] message.\n\n     * @param message An string representing a message to be shown.\n     */\n    protected fun showToastMessage(message: String) {\n        Toast.makeText(activity, message, Toast.LENGTH_SHORT).show()\n    }\n\n    /**\n     * Gets a component for dependency injection by its type.\n     */\n    protected fun <C> getComponent(componentType: Class<C>): C {\n        @Suppress(\"UNCHECKED_CAST\")\n        return componentType.cast((activity as HasComponent<C>).component)\n    }\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/view/fragment/SettingsAdvanceFragment.kt",
    "content": "package com.yalin.style.view.fragment\n\nimport android.app.Activity\nimport android.os.Bundle\nimport android.os.Handler\nimport android.view.LayoutInflater\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.SeekBar\n\nimport com.yalin.style.R\nimport com.yalin.style.render.StyleBlurRenderer\nimport com.yalin.style.settings.Prefs\nimport com.yalin.style.view.activity.SettingsActivity\nimport kotlinx.android.synthetic.main.layout_include_settings_content.*\n\n/**\n * @author jinyalin\n * *\n * @since 2017/5/2.\n */\n\nclass SettingsAdvanceFragment : BaseFragment(), SettingsActivity.SettingsActivityMenuListener {\n\n\n    companion object {\n        private val mHandler = Handler()\n        fun newInstance(): SettingsAdvanceFragment {\n            return SettingsAdvanceFragment()\n        }\n    }\n\n    override fun onCreateView(inflater: LayoutInflater,\n                              container: ViewGroup?, savedInstanceState: Bundle?): View =\n            inflater.inflate(R.layout.layout_style_settings, container, false)\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        setupViews()\n    }\n\n    override fun onAttach(activity: Activity) {\n        (activity as SettingsActivity).inflateMenuFromFragment(R.menu.menu_settings_advanced)\n        super.onAttach(activity)\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        mHandler.removeCallbacksAndMessages(null)\n    }\n\n    private fun setupViews() {\n        blurAmount.apply {\n            progress = Prefs.getSharedPreferences(activity)\n                    .getInt(Prefs.PREF_BLUR_AMOUNT, StyleBlurRenderer.DEFAULT_BLUR)\n            setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {\n                override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {\n                    if (fromUser) {\n                        mHandler.removeCallbacks(mUpdateBlurRunnable)\n                        mHandler.postDelayed(mUpdateBlurRunnable, 750)\n                    }\n                }\n\n                override fun onStartTrackingTouch(seekBar: SeekBar) {}\n\n                override fun onStopTrackingTouch(seekBar: SeekBar) {}\n            })\n        }\n        dimAmount.apply {\n            progress = Prefs.getSharedPreferences(activity)\n                    .getInt(Prefs.PREF_DIM_AMOUNT, StyleBlurRenderer.DEFAULT_MAX_DIM)\n            setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {\n                override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {\n                    if (fromUser) {\n                        mHandler.removeCallbacks(mUpdateDimRunnable)\n                        mHandler.postDelayed(mUpdateDimRunnable, 750)\n                    }\n                }\n\n                override fun onStartTrackingTouch(seekBar: SeekBar) {}\n\n                override fun onStopTrackingTouch(seekBar: SeekBar) {}\n            })\n        }\n        greyAmount.apply {\n            progress = Prefs.getSharedPreferences(activity)\n                    .getInt(Prefs.PREF_GREY_AMOUNT, StyleBlurRenderer.DEFAULT_GREY)\n            setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {\n                override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {\n                    if (fromUser) {\n                        mHandler.removeCallbacks(mUpdateGreyRunnable)\n                        mHandler.postDelayed(mUpdateGreyRunnable, 750)\n                    }\n                }\n\n                override fun onStartTrackingTouch(seekBar: SeekBar) {}\n\n                override fun onStopTrackingTouch(seekBar: SeekBar) {\n\n                }\n            })\n        }\n\n        blurOnLockscreen.apply {\n            setOnCheckedChangeListener { _, checked ->\n                Prefs.getSharedPreferences(activity).edit()\n                        .putBoolean(Prefs.PREF_DISABLE_BLUR_WHEN_LOCKED, !checked)\n                        .apply()\n            }\n            isChecked = !Prefs.getSharedPreferences(activity)\n                    .getBoolean(Prefs.PREF_DISABLE_BLUR_WHEN_LOCKED, false)\n        }\n    }\n\n    private val mUpdateBlurRunnable = Runnable {\n        Prefs.getSharedPreferences(activity).edit()\n                .putInt(Prefs.PREF_BLUR_AMOUNT, blurAmount.progress)\n                .apply()\n    }\n\n    private val mUpdateDimRunnable = Runnable {\n        Prefs.getSharedPreferences(activity).edit()\n                .putInt(Prefs.PREF_DIM_AMOUNT, dimAmount.progress)\n                .apply()\n    }\n\n    private val mUpdateGreyRunnable = Runnable {\n        Prefs.getSharedPreferences(activity).edit()\n                .putInt(Prefs.PREF_GREY_AMOUNT, greyAmount.progress)\n                .apply()\n    }\n\n    override fun onSettingsActivityMenuItemClick(item: MenuItem) {\n        if (item.itemId == R.id.action_reset) {\n            Prefs.getSharedPreferences(activity).edit()\n                    .putInt(Prefs.PREF_BLUR_AMOUNT, StyleBlurRenderer.DEFAULT_BLUR)\n                    .putInt(Prefs.PREF_DIM_AMOUNT, StyleBlurRenderer.DEFAULT_MAX_DIM)\n                    .putInt(Prefs.PREF_GREY_AMOUNT, StyleBlurRenderer.DEFAULT_GREY)\n                    .apply()\n            blurAmount.progress = StyleBlurRenderer.DEFAULT_BLUR\n            dimAmount.progress = StyleBlurRenderer.DEFAULT_MAX_DIM\n            greyAmount.progress = StyleBlurRenderer.DEFAULT_GREY\n        }\n    }\n\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/view/fragment/SettingsChooseSourceFragment.kt",
    "content": "package com.yalin.style.view.fragment\n\nimport android.animation.ObjectAnimator\nimport android.content.ActivityNotFoundException\nimport android.content.Context\nimport android.content.Intent\nimport android.graphics.*\nimport android.graphics.drawable.BitmapDrawable\nimport android.graphics.drawable.Drawable\nimport android.os.Bundle\nimport android.os.Handler\nimport android.support.v4.content.ContextCompat\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.TextView\nimport com.yalin.style.R\nimport com.yalin.style.analytics.Analytics\nimport com.yalin.style.analytics.Event\nimport com.yalin.style.data.log.LogUtil\nimport com.yalin.style.domain.repository.SourcesRepository\nimport com.yalin.style.injection.component.SourceComponent\nimport com.yalin.style.model.SourceItem\nimport com.yalin.style.presenter.SettingsChooseSourcePresenter\nimport com.yalin.style.view.SourceChooseView\nimport com.yalin.style.view.activity.AdvanceSettingActivity\nimport com.yalin.style.view.activity.GallerySettingActivity\nimport com.yalin.style.view.component.ObservableHorizontalScrollView\nimport kotlinx.android.synthetic.main.layout_settings_choose_source.*\nimport javax.inject.Inject\nimport kotlin.collections.HashMap\n\n/**\n * @author jinyalin\n * @since 2017/5/22.\n */\nclass SettingsChooseSourceFragment : BaseFragment(), SourceChooseView {\n\n    companion object {\n        private val TAG = \"SettingsChooseSourceFragment\"\n        private val SCROLLBAR_HIDE_DELAY_MILLIS = 1000\n        private val ALPHA_UNSELECTED = 0.4f\n    }\n\n    private val mHandler = Handler()\n    private var mCurrentScroller: ObjectAnimator? = null\n\n    private val mHideScrollbarRunnable = {\n        if (sourceScrollbar != null) {\n            sourceScrollbar.hide()\n        }\n    }\n\n    private var mAnimationDuration: Int = 0\n    private var mItemWidth: Int = 0\n    private var mItemImageSize: Int = 0\n    private var mItemEstimatedHeight: Int = 0\n\n    private val mTempRectF = RectF()\n    private val mImageFillPaint = Paint()\n    private val mAlphaPaint = Paint()\n    private var mSelectedSourceImage: Drawable? = null\n\n    @Inject\n    lateinit internal var settingsPresenter: SettingsChooseSourcePresenter\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        mAnimationDuration = resources.getInteger(android.R.integer.config_shortAnimTime)\n        mItemWidth = resources.getDimensionPixelSize(\n                R.dimen.settings_choose_source_item_width)\n        mItemEstimatedHeight = resources.getDimensionPixelSize(\n                R.dimen.settings_choose_source_item_estimated_height)\n        mItemImageSize = resources.getDimensionPixelSize(\n                R.dimen.settings_choose_source_item_image_size)\n\n        getComponent(SourceComponent::class.java).inject(this)\n\n        prepareGenerateSourceImages()\n\n    }\n\n    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,\n                              savedInstanceState: Bundle?) =\n            inflater.inflate(R.layout.layout_settings_choose_source, container, false)!!\n\n    override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        settingsPresenter.setView(this)\n\n        sourceScroller.setCallbacks(object : ObservableHorizontalScrollView.Callbacks {\n            override fun onScrollChanged(scrollX: Int) {\n                showScrollbar()\n            }\n\n            override fun onDownMotionEvent() {\n                mCurrentScroller?.cancel()\n            }\n\n        })\n\n        settingsPresenter.restoreInstanceState(savedInstanceState)\n        settingsPresenter.initialize()\n    }\n\n    override fun onResume() {\n        super.onResume()\n        settingsPresenter.resume()\n    }\n\n    override fun onPause() {\n        super.onPause()\n        settingsPresenter.pause()\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        settingsPresenter.destroy()\n    }\n\n    override fun onSaveInstanceState(outState: Bundle) {\n        settingsPresenter.saveInstanceState(outState)\n        super.onSaveInstanceState(outState)\n    }\n\n    override fun renderSources(sources: List<SourceItem>) {\n        redrawSources(sources)\n    }\n\n    override fun sourceSelected(sources: List<SourceItem>, selectedItem: SourceItem) {\n        updateSelectedItem(sources, selectedItem, true)\n    }\n\n    override fun showLoading() {\n    }\n\n    override fun hideLoading() {\n    }\n\n    override fun showRetry() {\n    }\n\n    override fun hideRetry() {\n    }\n\n    override fun showError(message: String) {\n    }\n\n    override fun context(): Context {\n        return activity\n    }\n\n    val sourcesMap = HashMap<Int, View>()\n    private fun redrawSources(sources: List<SourceItem>) {\n        if (!isAdded) {\n            return\n        }\n        sourcesMap.clear()\n        sourceContainer.removeAllViews()\n        for (source in sources) {\n            val rootView = LayoutInflater.from(activity).inflate(\n                    R.layout.settings_choose_source_item, sourceContainer, false)\n            sourcesMap.put(source.id, rootView)\n\n            with(rootView!!) {\n                alpha = ALPHA_UNSELECTED\n\n                val selectSourceButton = findViewById(R.id.source_image)\n                selectSourceButton.setOnClickListener {\n                    if (source.selected) {\n                        (activity as Callbacks).onRequestCloseActivity()\n                    } else {\n                        Analytics.logEvent(context, Event.SELECT_WALLPAPER_SOURCE, source.title!!)\n                        settingsPresenter.selectSource(source.id)\n                    }\n                }\n                val icon = generateSourceImage(source.iconId)\n                icon.setColorFilter(source.color, PorterDuff.Mode.SRC_ATOP)\n                selectSourceButton.background = icon\n\n                adjustSourceColor(source)\n\n                val titleView = findViewById(R.id.source_title) as TextView\n                titleView.text = source.title\n                titleView.setTextColor(source.color)\n\n                (findViewById(R.id.source_status) as TextView).text =\n                        source.description\n\n                val settingsButton = findViewById(R.id.source_settings_button)\n                settingsButton.setOnClickListener {\n                    Analytics.logEvent(context, Event.CUSTOM_WALLPAPER_SETTINGS)\n                    launchSourceSettings(source)\n                }\n\n                animateSettingsButton(settingsButton, false, false)\n\n                sourceContainer.addView(rootView)\n            }\n        }\n    }\n\n    private fun updateSelectedItem(sources: List<SourceItem>,\n                                   selectedItem: SourceItem, allowAnimate: Boolean) {\n        var selectedIndex = -1\n        var index = -1\n        for (source in sources) {\n            index++\n            val selected = source.id == selectedItem.id\n            selectedIndex = if (selected) index else selectedIndex\n\n            with(sourcesMap[source.id]!!) {\n                val sourceImageButton = findViewById(R.id.source_image)\n                val drawable = if (selected)\n                    mSelectedSourceImage else\n                    generateSourceImage(source.iconId)\n                drawable!!.setColorFilter(source.color, PorterDuff.Mode.SRC_ATOP)\n                sourceImageButton.background = drawable\n\n                val alpha = if (source.selected) 1f else ALPHA_UNSELECTED\n                animate().alpha(alpha).duration = mAnimationDuration.toLong()\n\n                val settingsButton = findViewById(R.id.source_settings_button)\n                animateSettingsButton(settingsButton,\n                        source.selected && source.hasSetting, allowAnimate)\n            }\n        }\n\n        if (selectedIndex >= 0 && allowAnimate) {\n            mCurrentScroller?.cancel()\n\n            // For some reason smoothScrollTo isn't very smooth..\n            mCurrentScroller = ObjectAnimator.ofInt(sourceScroller, \"scrollX\",\n                    mItemWidth * selectedIndex)\n            mCurrentScroller!!.duration = mAnimationDuration.toLong()\n            mCurrentScroller!!.start()\n        }\n    }\n\n    private fun animateSettingsButton(settingsButton: View, show: Boolean,\n                                      allowAnimate: Boolean) {\n        if (show && settingsButton.visibility == View.VISIBLE ||\n                !show && settingsButton.visibility == View.INVISIBLE) {\n            return\n        }\n        settingsButton.visibility = View.VISIBLE\n        settingsButton.animate()\n                .translationY((if (show)\n                    0\n                else\n                    -resources.getDimensionPixelSize(\n                            R.dimen.settings_choose_source_settings_button_animate_distance))\n                        .toFloat())\n                .alpha(if (show) 1f else 0f)\n                .rotation((if (show) 0 else -90).toFloat())\n                .setDuration((if (allowAnimate) 300 else 0).toLong())\n                .setStartDelay((if (show && allowAnimate) 200 else 0).toLong())\n                .withLayer()\n                .withEndAction {\n                    if (!show) {\n                        settingsButton.visibility = View.INVISIBLE\n                    }\n                }\n    }\n\n    private fun showScrollbar() {\n        mHandler.removeCallbacks(mHideScrollbarRunnable)\n        sourceScrollbar?.setScrollRangeAndViewportWidth(\n                sourceScroller.computeHorizontalScrollRange(),\n                sourceScroller.width)\n        sourceScrollbar?.setScrollPosition(sourceScroller.scrollX)\n        sourceScrollbar?.show()\n        mHandler.postDelayed(mHideScrollbarRunnable, SCROLLBAR_HIDE_DELAY_MILLIS.toLong())\n    }\n\n    private fun generateSourceImage(iconId: Int): BitmapDrawable {\n        val image = ContextCompat.getDrawable(activity, iconId)\n\n        val bitmap = Bitmap.createBitmap(mItemImageSize, mItemImageSize,\n                Bitmap.Config.ARGB_8888)\n        val canvas = Canvas(bitmap)\n        mTempRectF.set(0f, 0f, mItemImageSize.toFloat(), mItemImageSize.toFloat())\n        canvas.drawOval(mTempRectF, mImageFillPaint)\n        if (image != null) {\n            canvas.saveLayer(0f, 0f, mItemImageSize.toFloat(), mItemImageSize.toFloat(),\n                    mAlphaPaint, Canvas.ALL_SAVE_FLAG)\n            image.setBounds(0, 0, mItemImageSize, mItemImageSize)\n            image.draw(canvas)\n            canvas.restore()\n        }\n        return BitmapDrawable(resources, bitmap)\n    }\n\n    private fun prepareGenerateSourceImages() {\n        mImageFillPaint.color = Color.WHITE\n        mImageFillPaint.isAntiAlias = true\n        mAlphaPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OUT)\n        mSelectedSourceImage = generateSourceImage(R.drawable.ic_source_selected)\n    }\n\n    private fun adjustSourceColor(source: SourceItem) = with(source) {\n        try {\n            val hsv = FloatArray(3)\n            Color.colorToHSV(color, hsv)\n            var adjust = false\n            if (hsv[2] < 0.8f) {\n                hsv[2] = 0.8f\n                adjust = true\n            }\n            if (hsv[1] > 0.4f) {\n                hsv[1] = 0.4f\n                adjust = true\n            }\n            if (adjust) {\n                color = Color.HSVToColor(hsv)\n            }\n            if (Color.alpha(color) != 255) {\n                color = Color.argb(255,\n                        Color.red(color),\n                        Color.green(color),\n                        Color.blue(color))\n            }\n        } catch (ignored: IllegalArgumentException) {\n        }\n    }\n\n    private fun launchSourceSettings(source: SourceItem) {\n        try {\n            if (source.id == SourcesRepository.SOURCE_ID_CUSTOM) {\n                val settingsIntent = Intent(activity, GallerySettingActivity::class.java)\n                startActivity(settingsIntent)\n            } else if (source.id == SourcesRepository.SOURCE_ID_ADVANCE) {\n                val settingsIntent = Intent(activity, AdvanceSettingActivity::class.java)\n                startActivity(settingsIntent)\n            }\n        } catch (e: ActivityNotFoundException) {\n            LogUtil.E(TAG, \"Can't launch source settings.\", e)\n        } catch (e: SecurityException) {\n            LogUtil.E(TAG, \"Can't launch source settings.\", e)\n        }\n\n    }\n\n    interface Callbacks {\n        fun onRequestCloseActivity()\n    }\n}"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/view/fragment/StyleRenderFragment.java",
    "content": "package com.yalin.style.view.fragment;\n\nimport android.app.ActivityManager;\nimport android.app.Fragment;\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.Canvas;\nimport android.graphics.Color;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.util.DisplayMetrics;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\n\nimport com.bumptech.glide.request.animation.GlideAnimation;\nimport com.bumptech.glide.request.target.BaseTarget;\nimport com.bumptech.glide.request.target.SizeReadyCallback;\nimport com.bumptech.glide.request.target.Target;\nimport com.yalin.style.StyleApplication;\nimport com.yalin.style.render.DemoRenderController;\nimport com.yalin.style.render.GLTextureView;\nimport com.yalin.style.render.ImageBlurrer;\nimport com.yalin.style.render.RenderController;\nimport com.yalin.style.render.StyleBlurRenderer;\nimport com.yalin.style.util.ImageLoader;\n\nimport javax.inject.Inject;\n\n/**\n * @author jinyalin\n * @since 2017/4/19.\n */\n\npublic class StyleRenderFragment extends Fragment implements RenderController.Callbacks,\n        StyleBlurRenderer.Callbacks {\n\n    private static final String ARG_DEMO_MODE = \"demo_mode\";\n    private static final String ARG_DEMO_FOCUS = \"demo_focus\";\n\n    private boolean mDemoMode = false;\n    private boolean mDemoFocus = false;\n\n    private StyleView mView;\n    private ImageView mSimpleDemoModeImageView;\n\n    private ImageLoader mImageLoader;\n\n    public static StyleRenderFragment createInstance(boolean demoMode, boolean demoFocus) {\n        StyleRenderFragment fragment = new StyleRenderFragment();\n        Bundle args = new Bundle();\n        args.putBoolean(ARG_DEMO_MODE, demoMode);\n        args.putBoolean(ARG_DEMO_FOCUS, demoFocus);\n        fragment.setArguments(args);\n        return fragment;\n    }\n\n    @Override\n    public void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        if (savedInstanceState == null) {\n            Bundle args = getArguments();\n            mDemoMode = args.getBoolean(ARG_DEMO_MODE);\n            mDemoFocus = args.getBoolean(ARG_DEMO_FOCUS);\n        } else {\n            mDemoMode = savedInstanceState.getBoolean(ARG_DEMO_MODE);\n            mDemoFocus = savedInstanceState.getBoolean(ARG_DEMO_FOCUS);\n        }\n    }\n\n    @Override\n    public void onSaveInstanceState(Bundle outState) {\n        outState.putBoolean(ARG_DEMO_MODE, mDemoMode);\n        outState.putBoolean(ARG_DEMO_FOCUS, mDemoFocus);\n        super.onSaveInstanceState(outState);\n    }\n\n    @Nullable\n    @Override\n    public View onCreateView(LayoutInflater inflater,\n                             @Nullable ViewGroup container, Bundle savedInstanceState) {\n        ActivityManager activityManager = (ActivityManager)\n                getActivity().getSystemService(Context.ACTIVITY_SERVICE);\n        if (mDemoMode && activityManager.isLowRamDevice()) {\n            DisplayMetrics dm = getResources().getDisplayMetrics();\n            int targetWidth = dm.widthPixels;\n            int targetHeight = dm.heightPixels;\n\n            mSimpleDemoModeImageView = new ImageView(getActivity());\n            mSimpleDemoModeImageView.setScaleType(ImageView.ScaleType.CENTER_CROP);\n\n            mImageLoader = new ImageLoader(getActivity());\n            mImageLoader.beginImageLoad(\"file:///android_asset/painterly-architectonic.jpg\",\n                    null, true)\n                    .override(targetWidth, targetHeight)\n                    .into(mSimpleDemoModeLoadTarget);\n            return mSimpleDemoModeImageView;\n        } else {\n            mView = new StyleView(getActivity());\n            mView.setPreserveEGLContextOnPause(true);\n            return mView;\n        }\n    }\n\n    @Override\n    public void onHiddenChanged(boolean hidden) {\n        super.onHiddenChanged(hidden);\n        if (mView != null) {\n            mView.mRenderController.setVisible(!hidden);\n        }\n    }\n\n    @Override\n    public void onDestroyView() {\n        super.onDestroyView();\n        mView = null;\n    }\n\n    @Override\n    public void onResume() {\n        super.onResume();\n        if (mView != null) {\n            mView.onResume();\n        }\n    }\n\n    @Override\n    public void onPause() {\n        super.onPause();\n        if (mView != null) {\n            mView.onPause();\n        }\n    }\n\n    @Override\n    public void queueEventOnGlThread(Runnable runnable) {\n        if (mView != null) {\n            mView.queueEvent(runnable);\n        }\n    }\n\n    @Override\n    public void requestRender() {\n        if (mView != null) {\n            mView.requestRender();\n        }\n    }\n\n    private Target<Bitmap> mSimpleDemoModeLoadTarget = new BaseTarget<Bitmap>() {\n\n        @Override\n        public void onResourceReady(Bitmap resource,\n                                    GlideAnimation<? super Bitmap> glideAnimation) {\n            if (!mDemoFocus) {\n                ImageBlurrer imageBlurrer = new ImageBlurrer(getActivity(), resource);\n                Bitmap blurred = imageBlurrer.blurBitmap(ImageBlurrer.MAX_SUPPORTED_BLUR_PIXELS, 0);\n                imageBlurrer.destroy();\n\n                Canvas c = new Canvas(blurred);\n                c.drawColor(Color.argb(255 - StyleBlurRenderer.DEFAULT_MAX_DIM,\n                        0, 0, 0));\n\n                resource = blurred;\n            }\n            mSimpleDemoModeImageView.setImageBitmap(resource);\n        }\n\n        @Override\n        public void getSize(SizeReadyCallback cb) {\n\n        }\n    };\n\n    public class StyleView extends GLTextureView {\n        @Inject\n        DemoRenderController mRenderController;\n\n        private StyleBlurRenderer mRenderer;\n\n        public StyleView(Context context) {\n            super(context);\n            mRenderer = new StyleBlurRenderer(context, StyleRenderFragment.this);\n            setEGLContextClientVersion(2);\n            setEGLConfigChooser(8, 8, 8, 8, 0, 0);\n            setRenderer(mRenderer);\n            setRenderMode(RENDERMODE_WHEN_DIRTY);\n\n            mRenderer.setDemoMode(mDemoMode);\n\n            StyleApplication.Companion.getInstance().getApplicationComponent().inject(this);\n            mRenderController.setComponent(mRenderer, StyleRenderFragment.this);\n\n            mRenderController.setVisible(true);\n\n            mRenderController.start(mDemoFocus);\n        }\n\n        @Override\n        protected void onSizeChanged(int w, int h, int oldw, int oldh) {\n            super.onSizeChanged(w, h, oldw, oldh);\n            mRenderer.hintViewportSize(w, h);\n            mRenderController.reloadCurrentWallpaper();\n        }\n\n        @Override\n        protected void onDetachedFromWindow() {\n            mRenderController.destroy();\n            queueEventOnGlThread(new Runnable() {\n                @Override\n                public void run() {\n                    mRenderer.destroy();\n                }\n            });\n            super.onDetachedFromWindow();\n        }\n    }\n\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/view/fragment/TutorialFragment.kt",
    "content": "package com.yalin.style.view.fragment\n\nimport android.animation.Animator\nimport android.animation.AnimatorListenerAdapter\nimport android.animation.AnimatorSet\nimport android.animation.ObjectAnimator\nimport android.graphics.drawable.AnimatedVectorDrawable\nimport android.os.Build\nimport android.os.Bundle\nimport android.util.TypedValue\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.view.animation.OvershootInterpolator\nimport android.widget.ImageView\n\nimport com.yalin.style.R\nimport com.yalin.style.event.MainContainerInsetsChangedEvent\nimport com.yalin.style.event.SeenTutorialEvent\nimport kotlinx.android.synthetic.main.layout_include_tutorial_content.*\n\nimport org.greenrobot.eventbus.EventBus\nimport org.greenrobot.eventbus.Subscribe\n\n/**\n * @author jinyalin\n * *\n * @since 2017/5/2.\n */\n\nclass TutorialFragment : BaseFragment() {\n\n    companion object {\n        fun newInstance(): TutorialFragment {\n            return TutorialFragment()\n        }\n    }\n\n    private var animatorSet: AnimatorSet? = null\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        EventBus.getDefault().register(this)\n    }\n\n    override fun onCreateView(inflater: LayoutInflater,\n                              container: ViewGroup?, savedInstanceState: Bundle?): View? {\n        return inflater.inflate(R.layout.layout_include_tutorial_content, container, false)\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        tutorialIconAffordance.setOnClickListener {\n            EventBus.getDefault().post(SeenTutorialEvent())\n        }\n\n        if (savedInstanceState == null) {\n            val animateDistance = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 100f,\n                    resources.displayMetrics)\n            tutorialMainText.alpha = 0f\n            tutorialMainText.translationY = -animateDistance / 5\n\n            tutorialSubText.alpha = 0f\n            tutorialSubText.translationY = -animateDistance / 5\n\n            tutorialIconAffordance.alpha = 0f\n            tutorialIconAffordance.translationY = animateDistance\n\n            tutorialIconText.alpha = 0f\n            tutorialIconText.translationY = animateDistance\n\n            AnimatorSet().apply {\n                startDelay = 500\n                duration = 250\n                playTogether(\n                        ObjectAnimator.ofFloat(tutorialMainText, View.ALPHA, 1f),\n                        ObjectAnimator.ofFloat(tutorialSubText, View.ALPHA, 1f))\n                start()\n            }\n\n            animatorSet = AnimatorSet().apply {\n                startDelay = 2000\n                // Bug in older versions where set.setInterpolator didn't work\n                val interpolator = OvershootInterpolator()\n                val a1 = ObjectAnimator.ofFloat<View>(tutorialIconAffordance, View.TRANSLATION_Y, 0f)\n                val a2 = ObjectAnimator.ofFloat<View>(tutorialIconText, View.TRANSLATION_Y, 0f)\n                val a3 = ObjectAnimator.ofFloat<View>(tutorialMainText, View.TRANSLATION_Y, 0f)\n                val a4 = ObjectAnimator.ofFloat<View>(tutorialSubText, View.TRANSLATION_Y, 0f)\n                a1.interpolator = interpolator\n                a2.interpolator = interpolator\n                a3.interpolator = interpolator\n                a4.interpolator = interpolator\n                duration = 500\n                playTogether(\n                        ObjectAnimator.ofFloat(tutorialIconAffordance, View.ALPHA, 1f),\n                        ObjectAnimator.ofFloat(tutorialIconText, View.ALPHA, 1f),\n                        a1, a2, a3, a4)\n                if (Build.VERSION.SDK_INT >= 21) {\n                    addListener(object : AnimatorListenerAdapter() {\n                        override fun onAnimationEnd(animation: Animator) {\n                            playImageAvd(tutorialIconEmanate)\n                        }\n                    })\n                }\n                start()\n            }\n        } else {\n            playImageAvd(tutorialIconEmanate)\n        }\n\n        val mcisce = EventBus.getDefault().getStickyEvent(\n                MainContainerInsetsChangedEvent::class.java)\n        if (mcisce != null) {\n            onEventMainThread(mcisce)\n        }\n    }\n\n    private fun playImageAvd(emanateView: ImageView) {\n        if (Build.VERSION.SDK_INT >= 21) {\n            val avd = resources.getDrawable(\n                    R.drawable.avd_tutorial_icon_emanate,\n                    activity.theme) as AnimatedVectorDrawable\n            emanateView.setImageDrawable(avd)\n            avd.start()\n        }\n    }\n\n    override fun onDetach() {\n        super.onDetach()\n        animatorSet?.cancel()\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        EventBus.getDefault().unregister(this)\n    }\n\n    @Subscribe\n    fun onEventMainThread(spe: MainContainerInsetsChangedEvent) {\n        val insets = spe.insets\n        tutorialContainer.setPadding(\n                insets.left, insets.top, insets.right, insets.bottom)\n    }\n\n}\n"
  },
  {
    "path": "presentation/src/main/java/com/yalin/style/view/fragment/WallpaperDetailFragment.kt",
    "content": "package com.yalin.style.view.fragment\n\nimport android.content.Context\nimport android.content.Intent\nimport android.net.Uri\nimport android.os.Build\nimport android.os.Bundle\nimport android.util.TypedValue\nimport android.view.Gravity\nimport android.view.LayoutInflater\nimport android.view.Menu\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.PopupMenu\n\nimport com.yalin.style.WallpaperDetailViewport\nimport com.yalin.style.R\nimport com.yalin.style.analytics.Analytics\nimport com.yalin.style.analytics.Event\nimport com.yalin.style.data.log.LogUtil\nimport com.yalin.style.event.MainContainerInsetsChangedEvent\nimport com.yalin.style.event.StyleWallpaperSizeChangedEvent\nimport com.yalin.style.event.SwitchingPhotosStateChangedEvent\nimport com.yalin.style.event.SystemWallpaperSizeChangedEvent\nimport com.yalin.style.injection.component.WallpaperComponent\nimport com.yalin.style.model.WallpaperItem\nimport com.yalin.style.presenter.WallpaperDetailPresenter\nimport com.yalin.style.util.ScrimUtil\nimport com.yalin.style.util.TypefaceUtil\nimport com.yalin.style.view.WallpaperDetailView\nimport com.yalin.style.view.activity.SettingsActivity\nimport com.yalin.style.view.component.PanScaleProxyView\nimport kotlinx.android.synthetic.main.layout_wallpaper_detail.*\n\nimport org.greenrobot.eventbus.EventBus\nimport org.greenrobot.eventbus.Subscribe\nimport org.jetbrains.anko.toast\n\nimport javax.inject.Inject\n\n/**\n * @author jinyalin\n * *\n * @since 2017/4/20.\n */\n\nclass WallpaperDetailFragment : BaseFragment(),\n        WallpaperDetailView, View.OnSystemUiVisibilityChangeListener {\n\n    companion object {\n        val TAG = \"WallpaperDetailFragment\"\n        fun createInstance(): WallpaperDetailFragment {\n            return WallpaperDetailFragment()\n        }\n    }\n\n    @Inject\n    lateinit internal var presenter: WallpaperDetailPresenter\n\n    private val overflowMenu: PopupMenu by lazy { PopupMenu(activity, btnOverflow) }\n\n    private var currentViewportId = 0\n    private var systemWallpaperAspectRatio: Float = 0.toFloat()\n    private var styleWallpaperAspectRatio: Float = 0.toFloat()\n    private var deferResetViewport: Boolean = false\n\n    private var mGuardViewportChangeListener = false\n    var isOverflowMenuVisible = false\n        private set\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        getComponent(WallpaperComponent::class.java).inject(this)\n\n        EventBus.getDefault().register(this)\n    }\n\n    override fun onCreateView(inflater: LayoutInflater,\n                              container: ViewGroup?, savedInstanceState: Bundle?): View? {\n        return inflater.inflate(R.layout.layout_wallpaper_detail, container, false)\n    }\n\n    override fun onDestroyView() {\n        super.onDestroyView()\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        setupDetailViews()\n        presenter.setView(this)\n\n        val syswsce = EventBus.getDefault().getStickyEvent(\n                SystemWallpaperSizeChangedEvent::class.java)\n        if (syswsce != null) {\n            onEventMainThread(syswsce)\n        }\n\n        val swsce = EventBus.getDefault().getStickyEvent(\n                StyleWallpaperSizeChangedEvent::class.java)\n        if (swsce != null) {\n            onEventMainThread(swsce)\n        }\n\n        val wdvp = EventBus.getDefault().getStickyEvent(\n                WallpaperDetailViewport::class.java)\n        if (wdvp != null) {\n            onEventMainThread(wdvp)\n        }\n\n        val mcisce = EventBus.getDefault().getStickyEvent(\n                MainContainerInsetsChangedEvent::class.java)\n        if (mcisce != null) {\n            onEventMainThread(mcisce)\n        }\n\n        val spsce = EventBus.getDefault().getStickyEvent(\n                SwitchingPhotosStateChangedEvent::class.java)\n        if (spsce != null) {\n            onEventMainThread(spsce)\n        }\n\n        if (savedInstanceState == null) {\n            loadWallpaper()\n        } else {\n            presenter.restoreInstanceState(savedInstanceState)\n        }\n    }\n\n    override fun onResume() {\n        super.onResume()\n        presenter.resume()\n    }\n\n    override fun onPause() {\n        super.onPause()\n        presenter.pause()\n    }\n\n    override fun onStop() {\n        super.onStop()\n        overflowMenu.dismiss()\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        presenter.destroy()\n        EventBus.getDefault().unregister(this)\n    }\n\n    override fun onSaveInstanceState(outState: Bundle) {\n        presenter.saveInstanceState(outState)\n        super.onSaveInstanceState(outState)\n    }\n\n\n    private fun setupDetailViews() {\n        metadata.setOnClickListener {\n            val uri = \"http://www.kinglloy.com\"\n            val viewIntent = Intent(Intent.ACTION_VIEW, Uri.parse(uri))\n            viewIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)\n\n            Analytics.logEvent(context(), Event.VIEW_WALLPAPER_DETAIL)\n            try {\n                startActivity(viewIntent)\n            } catch (e: RuntimeException) {\n                toast(R.string.error_view_details)\n                LogUtil.E(TAG, \"Error viewing wallpaper details.\", e)\n                Analytics.logEvent(context(), Event.VIEW_WALLPAPER_DETAIL_FAILED)\n            }\n        }\n        chromeContainer.background = ScrimUtil.makeCubicGradientScrimDrawable(\n                0xaa000000.toInt(), 8, Gravity.BOTTOM)\n\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {\n            statusBarScrimView.visibility = View.GONE\n        } else {\n            statusBarScrimView.background = ScrimUtil.makeCubicGradientScrimDrawable(\n                    0x44000000, 8, Gravity.TOP)\n        }\n\n        panScaleProxyView.setMaxZoom(5)\n        panScaleProxyView.setOnViewportChangedListener(\n                PanScaleProxyView.OnViewportChangedListener {\n                    if (mGuardViewportChangeListener\n                            || panScaleProxyView == null) {\n                        return@OnViewportChangedListener\n                    }\n                    WallpaperDetailViewport.instance.setViewport(\n                            currentViewportId, panScaleProxyView.currentViewport, true)\n                })\n        if (activity is PanScaleProxyView.OnOtherGestureListener) {\n            panScaleProxyView.setOnOtherGestureListener(\n                    activity as PanScaleProxyView.OnOtherGestureListener)\n        }\n\n        btnNext.setOnClickListener {\n            Analytics.logEvent(activity, Event.SWITCH)\n            presenter.getNextWallpaper()\n        }\n        setupOverflowButton()\n    }\n\n    private fun setupOverflowButton() {\n        btnOverflow.setOnTouchListener(overflowMenu.dragToOpenListener)\n        btnOverflow.setOnClickListener {\n            isOverflowMenuVisible = true\n            overflowMenu.show()\n        }\n        overflowMenu.setOnDismissListener { isOverflowMenuVisible = false }\n        overflowMenu.setOnMenuItemClickListener(PopupMenu.OnMenuItemClickListener { menuItem ->\n            when (menuItem.itemId) {\n                R.id.action_like -> {\n                    presenter.likeWallpaper()\n                    return@OnMenuItemClickListener true\n                }\n                R.id.action_share -> {\n                    Analytics.logEvent(activity, Event.SHARE)\n                    presenter.shareWallpaper()\n                    return@OnMenuItemClickListener true\n                }\n                R.id.action_settings -> {\n                    Analytics.logEvent(activity, Event.SETTINGS_OPEN)\n                    startActivity(Intent(activity, SettingsActivity::class.java))\n                    return@OnMenuItemClickListener true\n                }\n            }\n            false\n        })\n        overflowMenu.menu.clear()\n        overflowMenu.inflate(R.menu.style_overflow)\n        overflowMenu.menu.add(0, R.id.action_share, 0, R.string.action_share)\n    }\n\n    private fun loadWallpaper() {\n        presenter.initialize()\n    }\n\n    @Subscribe\n    fun onEventMainThread(syswsce: SystemWallpaperSizeChangedEvent) {\n        if (syswsce.height > 0) {\n            systemWallpaperAspectRatio = syswsce.width * 1f / syswsce.height\n        } else {\n            systemWallpaperAspectRatio = panScaleProxyView.width * 1f / panScaleProxyView.height\n        }\n        resetProxyViewport()\n    }\n\n    @Subscribe\n    fun onEventMainThread(swsce: StyleWallpaperSizeChangedEvent) {\n        styleWallpaperAspectRatio = swsce.width * 1f / swsce.height\n        resetProxyViewport()\n    }\n\n    @Subscribe\n    fun onEventMainThread(e: WallpaperDetailViewport) {\n        if (!e.isFromUser && panScaleProxyView != null) {\n            mGuardViewportChangeListener = true\n            panScaleProxyView.setViewport(e.getViewport(currentViewportId))\n            mGuardViewportChangeListener = false\n        }\n    }\n\n    @Subscribe\n    fun onEventMainThread(spe: SwitchingPhotosStateChangedEvent) {\n        currentViewportId = spe.currentId\n        if (panScaleProxyView != null) {\n            panScaleProxyView.enablePanScale(!spe.isSwitchingPhotos)\n        }\n        // Process deferred wallpaper size change when done switching\n        if (!spe.isSwitchingPhotos && deferResetViewport) {\n            resetProxyViewport()\n        }\n    }\n\n    @Subscribe\n    fun onEventMainThread(spe: MainContainerInsetsChangedEvent) {\n        val insets = spe.insets\n        chromeContainer.setPadding(\n                insets.left, insets.top, insets.right, insets.bottom)\n    }\n\n    private fun resetProxyViewport() {\n        if (systemWallpaperAspectRatio == 0f || styleWallpaperAspectRatio == 0f) {\n            return\n        }\n\n        deferResetViewport = false\n        val spe = EventBus.getDefault()\n                .getStickyEvent(SwitchingPhotosStateChangedEvent::class.java)\n        if (spe != null && spe.isSwitchingPhotos) {\n            deferResetViewport = true\n            return\n        }\n\n        panScaleProxyView.setRelativeAspectRatio(\n                styleWallpaperAspectRatio / systemWallpaperAspectRatio)\n    }\n\n    override fun renderWallpaper(wallpaperItem: WallpaperItem) {\n        val titleFont = \"AlegreyaSans-Black.ttf\"\n        val bylineFont = \"AlegreyaSans-Medium.ttf\"\n        tvTitle.typeface = TypefaceUtil.getAndCache(context(), titleFont)\n        tvTitle.text = wallpaperItem.title\n        tvAttribution.text = wallpaperItem.attribution\n        tvByline.typeface = TypefaceUtil.getAndCache(context(), bylineFont)\n        tvByline.text = wallpaperItem.byline\n\n        Analytics.logEvent(activity, Event.PRESENT_WALLPAPER, wallpaperItem.title)\n    }\n\n    override fun showNextButton(show: Boolean) {\n        btnNext.visibility = if (show) View.VISIBLE else View.GONE\n    }\n\n    override fun shareWallpaper(shareIntent: Intent) {\n        shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)\n        startActivity(shareIntent)\n    }\n\n    override fun validLikeAction(valid: Boolean) {\n        if (valid) {\n            val keepItem = overflowMenu.menu.findItem(R.id.action_like)\n            if (keepItem == null) {\n                overflowMenu.menu.add(0, R.id.action_like, Menu.FIRST,\n                        R.string.action_like)\n            }\n        } else {\n            overflowMenu.menu.removeItem(R.id.action_like)\n            btnNext.isActivated = false\n        }\n    }\n\n    override fun updateLikeState(item: WallpaperItem, liked: Boolean) {\n        overflowMenu.menu\n                .findItem(R.id.action_like)\n                .setTitle(if (liked) R.string.action_unlike else R.string.action_like)\n        btnNext.isActivated = liked\n    }\n\n    override fun showLoading() {\n\n    }\n\n    override fun hideLoading() {\n\n    }\n\n    override fun showRetry() {\n\n    }\n\n    override fun hideRetry() {\n\n    }\n\n    override fun showError(message: String) {\n        showToastMessage(message)\n    }\n\n    override fun context(): Context {\n        return activity\n    }\n\n    override fun onSystemUiVisibilityChange(visibility: Int) {\n        val visible = visibility and View.SYSTEM_UI_FLAG_LOW_PROFILE == 0\n\n        val metadataSlideDistance = TypedValue.applyDimension(\n                TypedValue.COMPLEX_UNIT_DIP, 8f, resources.displayMetrics)\n        chromeContainer.visibility = View.VISIBLE\n        chromeContainer.animate()\n                .alpha(if (visible) 1f else 0f)\n                .translationY(if (visible) 0f else metadataSlideDistance)\n                .setDuration(200)\n                .withEndAction {\n                    if (!visible) {\n                        chromeContainer.visibility = View.GONE\n                    }\n                }\n\n        statusBarScrimView.visibility = View.VISIBLE\n        statusBarScrimView.animate()\n                .alpha(if (visible) 1f else 0f)\n                .setDuration(200)\n                .withEndAction {\n                    if (!visible) {\n                        statusBarScrimView.visibility = View.GONE\n                    }\n                }\n    }\n\n}\n"
  },
  {
    "path": "presentation/src/main/res/anim/image_fade_in.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<alpha xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:duration=\"@android:integer/config_shortAnimTime\"\n    android:fromAlpha=\"0.0\"\n    android:interpolator=\"@android:anim/decelerate_interpolator\"\n    android:toAlpha=\"1.0\" />"
  },
  {
    "path": "presentation/src/main/res/anim-v21/tutorial_icon_emanate_interpolator.xml",
    "content": "<pathInterpolator xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:controlX1=\"0\"\n    android:controlY1=\"0\"\n    android:controlX2=\"0.2\"\n    android:controlY2=\"1\"/>"
  },
  {
    "path": "presentation/src/main/res/animator/fade_in.xml",
    "content": "<objectAnimator xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:duration=\"@android:integer/config_shortAnimTime\"\n    android:propertyName=\"alpha\"\n    android:valueFrom=\"0\"\n    android:valueTo=\"1\"\n    android:valueType=\"floatType\" />\n"
  },
  {
    "path": "presentation/src/main/res/animator/fade_out.xml",
    "content": "<objectAnimator xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:duration=\"@android:integer/config_shortAnimTime\"\n    android:propertyName=\"alpha\"\n    android:valueFrom=\"1\"\n    android:valueTo=\"0\"\n    android:valueType=\"floatType\" />\n"
  },
  {
    "path": "presentation/src/main/res/animator/tutorial_icon_emanate_wave1.xml",
    "content": "<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <objectAnimator\n        android:duration=\"@integer/tutorial_icon_emanate_duration\"\n        android:interpolator=\"@anim/tutorial_icon_emanate_interpolator\"\n        android:propertyName=\"scaleX\"\n        android:repeatCount=\"-1\"\n        android:repeatMode=\"restart\"\n        android:valueFrom=\"0\"\n        android:valueTo=\"1\"\n        android:valueType=\"floatType\" />\n    <objectAnimator\n        android:duration=\"@integer/tutorial_icon_emanate_duration\"\n        android:interpolator=\"@anim/tutorial_icon_emanate_interpolator\"\n        android:propertyName=\"scaleY\"\n        android:repeatCount=\"-1\"\n        android:repeatMode=\"restart\"\n        android:valueFrom=\"0\"\n        android:valueTo=\"1\"\n        android:valueType=\"floatType\" />\n</set>\n"
  },
  {
    "path": "presentation/src/main/res/animator/tutorial_icon_emanate_wave1_path.xml",
    "content": "<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <objectAnimator\n        android:duration=\"@integer/tutorial_icon_emanate_duration\"\n        android:interpolator=\"@anim/tutorial_icon_emanate_interpolator\"\n        android:propertyName=\"strokeAlpha\"\n        android:repeatCount=\"-1\"\n        android:repeatMode=\"restart\"\n        android:valueFrom=\"0.4\"\n        android:valueTo=\"0\"\n        android:valueType=\"floatType\" />\n    <objectAnimator\n        android:duration=\"@integer/tutorial_icon_emanate_duration\"\n        android:interpolator=\"@anim/tutorial_icon_emanate_interpolator\"\n        android:propertyName=\"strokeWidth\"\n        android:repeatCount=\"-1\"\n        android:repeatMode=\"restart\"\n        android:valueFrom=\"15\"\n        android:valueTo=\"0\"\n        android:valueType=\"floatType\" />\n</set>\n"
  },
  {
    "path": "presentation/src/main/res/animator/tutorial_icon_emanate_wave2.xml",
    "content": "<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <objectAnimator\n        android:duration=\"@integer/tutorial_icon_emanate_duration\"\n        android:interpolator=\"@anim/tutorial_icon_emanate_interpolator\"\n        android:propertyName=\"scaleX\"\n        android:repeatCount=\"-1\"\n        android:repeatMode=\"restart\"\n        android:startOffset=\"@integer/tutorial_icon_emanate_wave2_delay\"\n        android:valueFrom=\"0\"\n        android:valueTo=\"0.85\"\n        android:valueType=\"floatType\" />\n    <objectAnimator\n        android:duration=\"@integer/tutorial_icon_emanate_duration\"\n        android:interpolator=\"@anim/tutorial_icon_emanate_interpolator\"\n        android:propertyName=\"scaleY\"\n        android:repeatCount=\"-1\"\n        android:repeatMode=\"restart\"\n        android:startOffset=\"@integer/tutorial_icon_emanate_wave2_delay\"\n        android:valueFrom=\"0\"\n        android:valueTo=\"0.85\"\n        android:valueType=\"floatType\" />\n</set>\n"
  },
  {
    "path": "presentation/src/main/res/animator/tutorial_icon_emanate_wave2_path.xml",
    "content": "<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <objectAnimator\n        android:duration=\"@integer/tutorial_icon_emanate_duration\"\n        android:interpolator=\"@anim/tutorial_icon_emanate_interpolator\"\n        android:propertyName=\"strokeAlpha\"\n        android:repeatCount=\"-1\"\n        android:repeatMode=\"restart\"\n        android:startOffset=\"@integer/tutorial_icon_emanate_wave2_delay\"\n        android:valueFrom=\"0.6\"\n        android:valueTo=\"0\"\n        android:valueType=\"floatType\" />\n    <objectAnimator\n        android:duration=\"@integer/tutorial_icon_emanate_duration\"\n        android:interpolator=\"@anim/tutorial_icon_emanate_interpolator\"\n        android:propertyName=\"strokeWidth\"\n        android:repeatCount=\"-1\"\n        android:repeatMode=\"restart\"\n        android:startOffset=\"0\"\n        android:valueFrom=\"5\"\n        android:valueTo=\"0\"\n        android:valueType=\"floatType\" />\n</set>\n"
  },
  {
    "path": "presentation/src/main/res/animator/tutorial_icon_overlay_state_list.xml",
    "content": "<!-- animate the translationZ property of a view when pressed -->\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:state_pressed=\"true\">\n        <objectAnimator\n            android:duration=\"100\"\n            android:propertyName=\"alpha\"\n            android:valueTo=\"1\"\n            android:valueType=\"floatType\" />\n    </item>\n    <item android:state_focused=\"true\">\n        <objectAnimator\n            android:duration=\"100\"\n            android:propertyName=\"alpha\"\n            android:valueTo=\"1\"\n            android:valueType=\"floatType\" />\n    </item>\n    <item>\n        <objectAnimator\n            android:duration=\"100\"\n            android:propertyName=\"alpha\"\n            android:valueTo=\"0\"\n            android:valueType=\"floatType\" />\n    </item>\n</selector>"
  },
  {
    "path": "presentation/src/main/res/color/selector_skip_tint.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:color=\"@color/colorAccent\" android:state_activated=\"true\" />\n    <item android:color=\"@android:color/transparent\" android:state_activated=\"false\" />\n</selector>"
  },
  {
    "path": "presentation/src/main/res/drawable/gallery_ic_add.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportHeight=\"24.0\"\n    android:viewportWidth=\"24.0\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z\" />\n</vector>\n"
  },
  {
    "path": "presentation/src/main/res/drawable/gallery_ic_add_folder.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportHeight=\"24.0\"\n    android:viewportWidth=\"24.0\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M10,4H4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V8c0,-1.1 -0.9,-2 -2,-2h-8l-2,-2z\" />\n</vector>\n"
  },
  {
    "path": "presentation/src/main/res/drawable/gallery_ic_add_photo.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportHeight=\"24.0\"\n    android:viewportWidth=\"24.0\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z\" />\n</vector>\n"
  },
  {
    "path": "presentation/src/main/res/drawable/gallery_ic_folder.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:autoMirrored=\"true\"\n    android:viewportHeight=\"24.0\"\n    android:viewportWidth=\"24.0\">\n    <path\n        android:fillColor=\"#FFFFFF\"\n        android:pathData=\"M20,6h-8l-2,-2L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,8c0,-1.1 -0.9,-2 -2,-2zM20,18L4,18L4,8h16v10z\" />\n</vector>\n"
  },
  {
    "path": "presentation/src/main/res/drawable/grey_selectable_item_background_circle.xml",
    "content": "<!--\n  Copyright 2014 Google Inc.\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<selector xmlns:android=\"http://schemas.android.com/apk/res/android\" android:enterFadeDuration=\"100\" android:exitFadeDuration=\"100\">\n  <item android:state_pressed=\"true\">\n    <shape android:shape=\"oval\">\n      <solid android:color=\"#3000\" />\n    </shape>\n  </item>\n  <item android:state_focused=\"true\">\n    <shape android:shape=\"oval\">\n      <solid android:color=\"#2000\" />\n    </shape>\n  </item>\n  <item android:drawable=\"@android:color/transparent\" />\n</selector>\n"
  },
  {
    "path": "presentation/src/main/res/drawable/intro_background_protection.xml",
    "content": "<!--\n  Copyright 2014 Google Inc.\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  <gradient\n    android:angle=\"270\"\n    android:endColor=\"#c000\"\n    android:startColor=\"#0000\" />\n</shape>\n"
  },
  {
    "path": "presentation/src/main/res/drawable/popup_background.xml",
    "content": "<shape xmlns:android=\"http://schemas.android.com/apk/res/android\" android:shape=\"rectangle\">\n    <corners android:radius=\"2dp\" />\n    <solid android:color=\"#111\" />\n</shape>\n"
  },
  {
    "path": "presentation/src/main/res/drawable/scrubber_control_selector.xml",
    "content": "<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:drawable=\"@drawable/scrubber_control_disabled\" android:state_enabled=\"false\" />\n    <item android:drawable=\"@drawable/scrubber_control_pressed\" android:state_pressed=\"true\" />\n    <item android:drawable=\"@drawable/scrubber_control_focused\" android:state_selected=\"true\" />\n    <item android:drawable=\"@drawable/scrubber_control_normal\" />\n</selector>\n"
  },
  {
    "path": "presentation/src/main/res/drawable/scrubber_progress_blur_amount.xml",
    "content": "<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item\n        android:id=\"@android:id/background\"\n        android:drawable=\"@drawable/scrubber_track_blur_amount\" />\n    <item\n        android:id=\"@android:id/secondaryProgress\"\n        android:drawable=\"@android:color/transparent\" />\n    <item\n        android:id=\"@android:id/progress\"\n        android:drawable=\"@android:color/transparent\" />\n</layer-list>\n"
  },
  {
    "path": "presentation/src/main/res/drawable/scrubber_progress_dim_amount.xml",
    "content": "<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item\n        android:id=\"@android:id/background\"\n        android:drawable=\"@drawable/scrubber_track_dim_amount\" />\n    <item\n        android:id=\"@android:id/secondaryProgress\"\n        android:drawable=\"@android:color/transparent\" />\n    <item\n        android:id=\"@android:id/progress\"\n        android:drawable=\"@android:color/transparent\" />\n</layer-list>\n"
  },
  {
    "path": "presentation/src/main/res/drawable/scrubber_progress_grey_amount.xml",
    "content": "<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item\n        android:id=\"@android:id/background\"\n        android:drawable=\"@drawable/scrubber_track_grey_amount\" />\n    <item\n        android:id=\"@android:id/secondaryProgress\"\n        android:drawable=\"@android:color/transparent\" />\n    <item\n        android:id=\"@android:id/progress\"\n        android:drawable=\"@android:color/transparent\" />\n</layer-list>\n"
  },
  {
    "path": "presentation/src/main/res/drawable/scrubber_progress_horizontal.xml",
    "content": "<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item\n        android:id=\"@android:id/background\"\n        android:drawable=\"@drawable/scrubber_track\" />\n    <item android:id=\"@android:id/secondaryProgress\">\n        <scale\n            android:drawable=\"@drawable/scrubber_secondary\"\n            android:scaleWidth=\"100%\" />\n    </item>\n    <item android:id=\"@android:id/progress\">\n        <scale\n            android:drawable=\"@drawable/scrubber_primary\"\n            android:scaleWidth=\"100%\" />\n    </item>\n</layer-list>\n"
  },
  {
    "path": "presentation/src/main/res/drawable/settings_source_item_image_overlay.xml",
    "content": "<selector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:enterFadeDuration=\"100\"\n    android:exitFadeDuration=\"100\">\n    <item android:state_pressed=\"true\">\n        <shape android:shape=\"oval\">\n            <solid android:color=\"#3000\" />\n        </shape>\n    </item>\n    <item android:state_focused=\"true\">\n        <shape android:shape=\"oval\">\n            <solid android:color=\"#2000\" />\n        </shape>\n    </item>\n</selector>\n"
  },
  {
    "path": "presentation/src/main/res/drawable/tutorial_icon_on_overlay.xml",
    "content": "<selector xmlns:android=\"http://schemas.android.com/apk/res/android\" android:constantSize=\"true\">\n    <item android:drawable=\"@drawable/tutorial_icon_on_overlay_base\" android:state_pressed=\"true\" />\n    <item android:drawable=\"@drawable/tutorial_icon_on_overlay_base\" android:state_focused=\"true\" />\n    <item android:drawable=\"@android:color/transparent\" />\n</selector>\n"
  },
  {
    "path": "presentation/src/main/res/drawable/white_circle_button.xml",
    "content": "<!--\n  Copyright 2014 Google Inc.\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<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item>\n        <shape android:shape=\"oval\">\n            <solid android:color=\"#fff\" />\n        </shape>\n    </item>\n    <item android:drawable=\"@drawable/grey_selectable_item_background_circle\" />\n</layer-list>"
  },
  {
    "path": "presentation/src/main/res/drawable-v21/avd_tutorial_icon_emanate.xml",
    "content": "<animated-vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:drawable=\"@drawable/tutorial_icon_emanate\">\n    <target\n        android:name=\"wave1\"\n        android:animation=\"@animator/tutorial_icon_emanate_wave1\" />\n    <target\n        android:name=\"wave1_path\"\n        android:animation=\"@animator/tutorial_icon_emanate_wave1_path\" />\n    <target\n        android:name=\"wave2\"\n        android:animation=\"@animator/tutorial_icon_emanate_wave2\" />\n    <target\n        android:name=\"wave2_path\"\n        android:animation=\"@animator/tutorial_icon_emanate_wave2_path\" />\n</animated-vector>"
  },
  {
    "path": "presentation/src/main/res/drawable-v21/grey_selectable_item_background_circle.xml",
    "content": "<!--\n  Copyright 2014 Google Inc.\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=\"#3000\">\n  <item android:id=\"@android:id/mask\">\n    <shape android:shape=\"oval\">\n      <solid android:color=\"#fff\" />\n    </shape>\n  </item>\n</ripple>"
  },
  {
    "path": "presentation/src/main/res/drawable-v21/settings_source_item_image_overlay.xml",
    "content": "<ripple xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:color=\"#3000\">\n    <item android:id=\"@android:id/mask\">\n        <shape android:shape=\"oval\">\n            <solid android:color=\"#fff\" />\n        </shape>\n    </item>\n</ripple>\n"
  },
  {
    "path": "presentation/src/main/res/drawable-v21/tutorial_icon_emanate.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"64dp\"\n    android:height=\"64dp\"\n    android:viewportHeight=\"64\"\n    android:viewportWidth=\"64\">\n    <group\n        android:name=\"wave1\"\n        android:pivotX=\"32\"\n        android:pivotY=\"32\"\n        android:scaleX=\"0\"\n        android:scaleY=\"0\">\n        <path\n            android:name=\"wave1_path\"\n            android:pathData=\"M64 32A32 32 0 0 1 32 64 32 32 0 0 1 0 32 32 32 0 0 1 32 0 32 32 0 0 1 64 32Z\"\n            android:strokeColor=\"#fff\"\n            android:strokeWidth=\"1\" />\n    </group>\n    <group\n        android:name=\"wave2\"\n        android:pivotX=\"32\"\n        android:pivotY=\"32\"\n        android:scaleX=\"0\"\n        android:scaleY=\"0\">\n        <path\n            android:name=\"wave2_path\"\n            android:pathData=\"M64 32A32 32 0 0 1 32 64 32 32 0 0 1 0 32 32 32 0 0 1 32 0 32 32 0 0 1 64 32Z\"\n            android:strokeColor=\"#fff\"\n            android:strokeWidth=\"1\" />\n    </group>\n</vector>"
  },
  {
    "path": "presentation/src/main/res/drawable-v21/tutorial_icon_on_overlay.xml",
    "content": "<bitmap xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:src=\"@drawable/tutorial_icon_on_overlay_base\" />"
  },
  {
    "path": "presentation/src/main/res/layout/activity_about.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <FrameLayout\n        android:id=\"@+id/demoViewContainer\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:background=\"@android:color/transparent\"\n        android:fitsSystemWindows=\"true\"\n        android:orientation=\"vertical\">\n\n        <android.support.v7.widget.Toolbar\n            android:id=\"@+id/appBar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"?actionBarSize\"\n            app:contentInsetStart=\"@dimen/keyline_2\"\n            app:navigationIcon=\"@drawable/ic_ab_up\" />\n\n        <include\n            layout=\"@layout/layout_include_about_content\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"0dp\"\n            android:layout_weight=\"1\" />\n    </LinearLayout>\n</FrameLayout>\n"
  },
  {
    "path": "presentation/src/main/res/layout/activity_advance_setting.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<android.support.design.widget.CoordinatorLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:fitsSystemWindows=\"true\">\n\n    <android.support.v7.widget.RecyclerView\n        android:id=\"@+id/wallpaperList\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:clipToPadding=\"false\"\n        android:drawSelectorOnTop=\"true\"\n        android:fitsSystemWindows=\"true\"\n        android:horizontalSpacing=\"@dimen/gallery_chosen_photo_grid_spacing\"\n        android:scrollbarStyle=\"outsideOverlay\"\n        android:scrollbars=\"vertical\"\n        android:stretchMode=\"columnWidth\"\n        android:verticalSpacing=\"@dimen/gallery_chosen_photo_grid_spacing\"\n        app:layout_behavior=\"@string/appbar_scrolling_view_behavior\" />\n\n    <android.support.design.widget.AppBarLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:theme=\"@style/ThemeOverlay.AppCompat.Dark.ActionBar\">\n\n        <android.support.v7.widget.Toolbar\n            android:id=\"@+id/appBar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"?actionBarSize\"\n            app:layout_scrollFlags=\"scroll|enterAlways\"\n            app:title=\"@string/advance_source_title\" />\n\n    </android.support.design.widget.AppBarLayout>\n\n    <com.google.android.gms.ads.AdView\n        android:id=\"@+id/adView\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"bottom|center\"\n        app:adSize=\"BANNER\"\n        app:adUnitId=\"@string/advance_banner_ad_unit_id\" />\n\n    <LinearLayout\n        android:id=\"@android:id/empty\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:gravity=\"center\"\n        android:orientation=\"vertical\"\n        android:padding=\"32dp\"\n        android:visibility=\"gone\">\n\n        <TextView\n            android:id=\"@+id/emptyDescription\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginBottom=\"16dp\"\n            android:layout_marginTop=\"16dp\"\n            android:breakStrategy=\"balanced\"\n            android:fontFamily=\"sans-serif-condensed\"\n            android:gravity=\"center_horizontal\"\n            android:text=\"@string/advance_empty_description\"\n            android:textColor=\"@color/gallery_theme\"\n            android:textSize=\"22sp\"\n            android:textStyle=\"italic\"\n            tools:targetApi=\"M\" />\n\n        <Button\n            android:id=\"@+id/btnLoadAdvanceWallpaper\"\n            style=\"@style/Widget.AppCompat.Button.Colored\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/advance_empty_load\" />\n\n    </LinearLayout>\n\n    <LinearLayout\n        android:id=\"@+id/loading\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:gravity=\"center\"\n        android:orientation=\"vertical\"\n        android:padding=\"32dp\"\n        android:visibility=\"gone\">\n\n        <me.zhanghai.android.materialprogressbar.MaterialProgressBar\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\" />\n\n    </LinearLayout>\n\n    <LinearLayout\n        android:id=\"@+id/retry\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:gravity=\"center\"\n        android:orientation=\"vertical\"\n        android:padding=\"32dp\"\n        android:visibility=\"gone\">\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginBottom=\"16dp\"\n            android:layout_marginTop=\"16dp\"\n            android:breakStrategy=\"balanced\"\n            android:fontFamily=\"sans-serif-condensed\"\n            android:gravity=\"center_horizontal\"\n            android:text=\"@string/advance_retry_description\"\n            android:textColor=\"@color/gallery_theme\"\n            android:textSize=\"22sp\"\n            android:textStyle=\"italic\"\n            tools:targetApi=\"M\" />\n\n        <Button\n            android:id=\"@+id/btnRetry\"\n            style=\"@style/Widget.AppCompat.Button.Colored\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/advance_retry\" />\n    </LinearLayout>\n\n</android.support.design.widget.CoordinatorLayout>"
  },
  {
    "path": "presentation/src/main/res/layout/activity_gallery_setting.xml",
    "content": "<android.support.design.widget.CoordinatorLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:fitsSystemWindows=\"true\">\n\n    <android.support.v7.widget.RecyclerView\n        android:id=\"@+id/photoGrid\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:clipToPadding=\"false\"\n        android:drawSelectorOnTop=\"true\"\n        android:fitsSystemWindows=\"true\"\n        android:gravity=\"center\"\n        android:horizontalSpacing=\"@dimen/gallery_chosen_photo_grid_spacing\"\n        android:scrollbarStyle=\"outsideOverlay\"\n        android:scrollbars=\"vertical\"\n        android:stretchMode=\"columnWidth\"\n        android:verticalSpacing=\"@dimen/gallery_chosen_photo_grid_spacing\"\n        app:layout_behavior=\"@string/appbar_scrolling_view_behavior\" />\n\n    <android.support.design.widget.AppBarLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:theme=\"@style/ThemeOverlay.AppCompat.Dark.ActionBar\">\n\n        <android.support.v7.widget.Toolbar\n            android:id=\"@+id/appBar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"?actionBarSize\"\n            app:layout_scrollFlags=\"scroll|enterAlways\"\n            app:title=\"@string/gallery_title\" />\n\n    </android.support.design.widget.AppBarLayout>\n\n    <com.google.android.gms.ads.AdView\n        android:id=\"@+id/adView\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"bottom|center\"\n        app:adSize=\"BANNER\"\n        app:adUnitId=\"@string/gallery_banner_ad_unit_id\" />\n\n    <FrameLayout\n        android:id=\"@android:id/empty\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n\n        <LinearLayout\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:gravity=\"center\"\n            android:orientation=\"vertical\"\n            android:padding=\"32dp\">\n\n            <ViewAnimator\n                android:id=\"@+id/emptyAnimator\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center\"\n                android:measureAllChildren=\"false\">\n\n                <com.yalin.style.view.component.GalleryEmptyStateGraphicView\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\" />\n\n                <Button\n                    android:id=\"@+id/btnGalleryEnableRandom\"\n                    style=\"@style/Widget.AppCompat.Button.Colored\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/gallery_enable_random\" />\n\n                <Button\n                    android:id=\"@+id/btnGalleryEditPermissionSettings\"\n                    style=\"@style/Widget.AppCompat.Button.Colored\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/gallery_edit_settings\" />\n            </ViewAnimator>\n\n            <TextView\n                android:id=\"@+id/emptyDescription\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginBottom=\"64dp\"\n                android:layout_marginTop=\"16dp\"\n                android:breakStrategy=\"balanced\"\n                android:fontFamily=\"sans-serif-condensed\"\n                android:gravity=\"center_horizontal\"\n                android:text=\"@string/gallery_empty\"\n                android:textColor=\"@color/gallery_theme\"\n                android:textSize=\"22sp\"\n                android:textStyle=\"italic\"\n                tools:targetApi=\"M\" />\n        </LinearLayout>\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"bottom|center_horizontal\"\n            android:layout_marginBottom=\"88dp\"\n            android:fontFamily=\"sans-serif-condensed\"\n            android:text=\"@string/gallery_empty_subtitle\"\n            android:textColor=\"@color/gallery_theme_dark\"\n            android:textSize=\"16sp\"\n            android:textStyle=\"italic\" />\n\n    </FrameLayout>\n\n    <android.support.design.widget.FloatingActionButton\n        android:id=\"@+id/addFab\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"bottom|center_horizontal\"\n        android:layout_marginBottom=\"@dimen/gallery_fab_margin\"\n        android:contentDescription=\"@string/gallery_add_fab\"\n        android:src=\"@drawable/gallery_ic_add\" />\n\n    <LinearLayout\n        android:id=\"@+id/addToolbar\"\n        style=\"?android:attr/buttonBarStyle\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"@dimen/gallery_fab_toolbar_height\"\n        android:layout_gravity=\"bottom\"\n        android:background=\"@color/gallery_theme_dark\"\n        android:orientation=\"horizontal\"\n        android:visibility=\"invisible\">\n\n        <ImageButton\n            android:id=\"@+id/addPhotos\"\n            style=\"?android:attr/buttonBarButtonStyle\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_weight=\"1\"\n            android:contentDescription=\"@string/gallery_add_photos\"\n            android:src=\"@drawable/gallery_ic_add_photo\" />\n\n        <ImageButton\n            android:id=\"@+id/addFolder\"\n            style=\"?android:attr/buttonBarButtonStyle\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_weight=\"1\"\n            android:contentDescription=\"@string/gallery_add_folder\"\n            android:src=\"@drawable/gallery_ic_add_folder\" />\n    </LinearLayout>\n\n    <LinearLayout\n        android:id=\"@+id/selectionToolbarContainer\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:elevation=\"@dimen/gallery_header_elevation\"\n        android:orientation=\"vertical\"\n        android:theme=\"@style/ThemeOverlay.AppCompat.Dark.ActionBar\"\n        android:visibility=\"invisible\"\n        tools:targetApi=\"LOLLIPOP\">\n\n        <android.support.v7.widget.Toolbar\n            android:id=\"@+id/selectionToolbar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"?actionBarSize\"\n            android:background=\"@color/gallery_theme_dark\"\n            app:navigationIcon=\"?attr/homeAsUpIndicator\"\n            app:title=\"@string/gallery_title\" />\n\n    </LinearLayout>\n</android.support.design.widget.CoordinatorLayout>\n"
  },
  {
    "path": "presentation/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.yalin.style.view.component.DrawInsetsFrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/mainContainer\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    tools:context=\"com.yalin.style.view.activity.StyleActivity\">\n\n    <include layout=\"@layout/layout_include_wallpaper_detail\" />\n\n    <include layout=\"@layout/layout_include_wallpaper_tutorial\" />\n\n    <include layout=\"@layout/layout_include_active\" />\n</com.yalin.style.view.component.DrawInsetsFrameLayout>\n"
  },
  {
    "path": "presentation/src/main/res/layout/activity_settings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<com.yalin.style.view.component.DrawInsetsFrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:id=\"@+id/drawInsetsFrameLayout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <FrameLayout\n        android:id=\"@+id/localRenderContainer\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n    <LinearLayout\n        android:id=\"@+id/mainContainer\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\">\n\n        <android.support.v7.widget.Toolbar\n            android:id=\"@+id/appBar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"?actionBarSize\"\n            app:contentInsetStart=\"@dimen/keyline_2_minus_16dp\"\n            app:navigationContentDescription=\"@string/done\"\n            app:navigationIcon=\"@drawable/ic_ab_done\"\n            app:popupTheme=\"@style/ThemeOverlay.Style.Toolbar\"\n            app:theme=\"@style/ThemeOverlay.Style.Toolbar\">\n\n            <Spinner\n                android:id=\"@+id/sectionSpinner\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:background=\"?selectableItemBackground\" />\n\n        </android.support.v7.widget.Toolbar>\n\n        <FrameLayout\n            android:id=\"@+id/contentContainer\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"0dp\"\n            android:layout_weight=\"1\" />\n    </LinearLayout>\n</com.yalin.style.view.component.DrawInsetsFrameLayout>\n"
  },
  {
    "path": "presentation/src/main/res/layout/advance_chosen_wallpaper_item.xml",
    "content": "<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_margin=\"2dp\"\n    android:foreground=\"?selectableItemBackground\">\n\n    <FrameLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n\n        <ImageView\n            android:id=\"@+id/thumbnail\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:scaleType=\"centerCrop\" />\n\n        <com.yalin.style.view.component.ShadowDipsTextView\n            android:id=\"@+id/tvName\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"bottom|start\"\n            android:background=\"#8666\"\n            android:paddingBottom=\"@dimen/advance_name_top_margin\"\n            android:paddingEnd=\"@dimen/advance_name_margin\"\n            android:paddingStart=\"@dimen/advance_name_margin\"\n            android:paddingTop=\"@dimen/advance_name_top_margin\"\n            android:textColor=\"@color/wallpaper_detail_text_color\"\n            android:textSize=\"@dimen/text_size_medium\"\n            app:shadowColor=\"@color/shadow_color\"\n            app:shadowDx=\"0dp\"\n            app:shadowDy=\"1dp\"\n            app:shadowRadius=\"3dp\" />\n    </FrameLayout>\n\n    <FrameLayout\n        android:id=\"@+id/checked_overlay\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:background=\"#8fff\"\n        android:visibility=\"gone\">\n\n        <ImageView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:src=\"@drawable/gallery_chosen_photo_selected\"\n            android:tint=\"@color/gallery_theme_dark\" />\n\n    </FrameLayout>\n\n    <FrameLayout\n        android:id=\"@+id/download_overlay\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:background=\"#3fff\"\n        android:visibility=\"gone\">\n\n        <ImageView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:src=\"@drawable/advance_chosen_download\"\n            android:tint=\"@color/gallery_theme_dark\" />\n\n    </FrameLayout>\n\n</FrameLayout>"
  },
  {
    "path": "presentation/src/main/res/layout/animated_logo_fragment.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:padding=\"4dp\"\n  android:clipToPadding=\"false\"\n  android:gravity=\"center\"\n  android:orientation=\"vertical\">\n\n  <!--<com.yalin.style.ui.AnimatedStyleLogoView-->\n  <!--android:wallpaperId=\"@+wallpaperId/animated_logo\"-->\n  <!--android:layout_width=\"225dp\"-->\n  <!--android:layout_height=\"75dp\"-->\n  <!--android:layout_marginBottom=\"16dp\" />-->\n\n  <ImageView\n    android:id=\"@+id/logoView\"\n    android:layout_width=\"250dp\"\n    android:layout_height=\"80dp\"\n    android:layout_marginBottom=\"16dp\"\n    android:contentDescription=\"@string/app_name\"\n    android:src=\"@drawable/logo\" />\n\n  <ImageView\n    android:id=\"@+id/logoSubtitle\"\n    android:layout_width=\"250dp\"\n    android:layout_height=\"wrap_content\"\n    android:contentDescription=\"@string/app_name\"\n    android:src=\"@drawable/logo_subtitle\" />\n\n</LinearLayout>"
  },
  {
    "path": "presentation/src/main/res/layout/dialog_downloading.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:gravity=\"center\"\n    android:orientation=\"horizontal\">\n\n    <me.zhanghai.android.materialprogressbar.MaterialProgressBar\n        android:layout_width=\"64dp\"\n        android:layout_height=\"64dp\" />\n\n    <com.yalin.style.view.component.ShadowDipsTextView\n        android:id=\"@+id/downloadProgress\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:gravity=\"center\"\n        android:paddingBottom=\"@dimen/advance_name_top_margin\"\n        android:paddingEnd=\"@dimen/advance_name_margin\"\n        android:paddingStart=\"@dimen/advance_name_margin\"\n        android:paddingTop=\"@dimen/advance_name_top_margin\"\n        android:textSize=\"@dimen/text_size_medium\"\n        app:shadowColor=\"@color/shadow_color\"\n        app:shadowDx=\"0dp\"\n        app:shadowDy=\"1dp\"\n        app:shadowRadius=\"3dp\" />\n</LinearLayout>"
  },
  {
    "path": "presentation/src/main/res/layout/gallery_chosen_photo_item.xml",
    "content": "<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:layout_margin=\"2dp\"\n    android:foreground=\"?selectableItemBackground\">\n\n    <ImageView\n        android:id=\"@+id/thumbnail\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:scaleType=\"centerCrop\" />\n\n    <FrameLayout\n        android:id=\"@+id/checked_overlay\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:background=\"#bfff\"\n        android:visibility=\"gone\">\n\n        <ImageView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:src=\"@drawable/gallery_chosen_photo_selected\"\n            android:tint=\"@color/gallery_theme_dark\" />\n\n    </FrameLayout>\n\n</FrameLayout>"
  },
  {
    "path": "presentation/src/main/res/layout/gallery_chosen_photo_tree_item.xml",
    "content": "<android.support.percent.PercentFrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:layout_margin=\"2dp\"\n    android:foreground=\"?attr/selectableItemBackground\">\n\n    <ImageView\n        android:id=\"@+id/thumbnail1\"\n        app:layout_widthPercent=\"50%\"\n        app:layout_heightPercent=\"50%\"\n        android:scaleType=\"centerCrop\" />\n\n    <ImageView\n        android:id=\"@+id/thumbnail2\"\n        app:layout_widthPercent=\"50%\"\n        app:layout_heightPercent=\"50%\"\n        android:layout_gravity=\"end\"\n        android:scaleType=\"centerCrop\" />\n\n    <ImageView\n        android:id=\"@+id/thumbnail3\"\n        app:layout_widthPercent=\"50%\"\n        app:layout_heightPercent=\"50%\"\n        android:layout_gravity=\"bottom\"\n        android:scaleType=\"centerCrop\" />\n\n    <ImageView\n        android:id=\"@+id/thumbnail4\"\n        app:layout_widthPercent=\"50%\"\n        app:layout_heightPercent=\"50%\"\n        android:layout_gravity=\"bottom|end\"\n        android:scaleType=\"centerCrop\" />\n\n    <ImageView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"bottom\"\n        android:padding=\"@dimen/gallery_chosen_photo_folder_padding\"\n        android:src=\"@drawable/gallery_ic_folder\" />\n\n    <FrameLayout\n        android:id=\"@+id/checked_overlay\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:background=\"#bfff\"\n        android:visibility=\"gone\">\n\n        <ImageView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:src=\"@drawable/gallery_chosen_photo_selected\"\n            android:tint=\"@color/gallery_theme_dark\" />\n\n    </FrameLayout>\n\n</android.support.percent.PercentFrameLayout>"
  },
  {
    "path": "presentation/src/main/res/layout/layout_include_about_content.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<ScrollView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:layout_marginBottom=\"16dp\"\n    android:layout_marginTop=\"16dp\"\n    android:fadingEdgeLength=\"32dp\"\n    android:fitsSystemWindows=\"true\"\n    android:overScrollMode=\"never\"\n    android:paddingBottom=\"32dp\"\n    android:paddingEnd=\"32dp\"\n    android:paddingStart=\"32dp\"\n    android:requiresFadingEdge=\"vertical\"\n    android:scrollbars=\"none\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:gravity=\"center\"\n        android:orientation=\"vertical\">\n\n        <fragment\n            android:id=\"@+id/animatedLogoFragment\"\n            android:name=\"com.yalin.style.view.fragment.AnimatedStyleLogoFragment\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\" />\n\n        <TextView\n            android:id=\"@+id/appVersion\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"8dp\"\n            android:fontFamily=\"sans-serif-condensed\"\n            android:textColor=\"#afff\"\n            android:textSize=\"@dimen/settings_text_size_normal\" />\n\n        <TextView\n            android:id=\"@+id/aboutBody\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"32dp\"\n            android:fontFamily=\"sans-serif-condensed\"\n            android:lineSpacingMultiplier=\"1.2\"\n            android:textColor=\"#afff\"\n            android:textColorLink=\"#fff\"\n            android:textSize=\"@dimen/settings_text_size_normal\" />\n\n    </LinearLayout>\n</ScrollView>\n"
  },
  {
    "path": "presentation/src/main/res/layout/layout_include_active.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/activeContainer\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:alpha=\"0\"\n    android:visibility=\"gone\">\n\n    <FrameLayout\n        android:id=\"@+id/demoContainerLayout\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n    <View\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:background=\"@drawable/intro_background_protection\" />\n\n    <fragment\n        android:id=\"@+id/animatedLogoFragment\"\n        android:name=\"com.yalin.style.view.fragment.AnimatedStyleLogoFragment\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_centerInParent=\"true\" />\n\n    <Button\n        android:id=\"@+id/activateStyleButton\"\n        android:layout_width=\"@dimen/activate_button_size\"\n        android:layout_height=\"@dimen/activate_button_size\"\n        android:layout_below=\"@+id/animatedLogoFragment\"\n        android:layout_centerHorizontal=\"true\"\n        android:layout_marginTop=\"48dp\"\n        android:background=\"@drawable/white_circle_button\"\n        android:fontFamily=\"sans-serif-condensed\"\n        android:text=\"@string/action_activate\"\n        android:textAllCaps=\"true\"\n        android:textColor=\"#333\"\n        android:textSize=\"18sp\"\n        android:textStyle=\"bold\" />\n\n</RelativeLayout>"
  },
  {
    "path": "presentation/src/main/res/layout/layout_include_settings_content.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<GridLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:columnCount=\"3\"\n    android:orientation=\"horizontal\">\n\n    <TextView\n        style=\"@style/Widget.Style.TextView.SettingsSeekBarLabel\"\n        android:layout_gravity=\"fill_vertical|end\"\n        android:text=\"@string/settings_blur_amount_title\" />\n\n    <SeekBar\n        android:id=\"@+id/blurAmount\"\n        style=\"@style/Settings.Widget.SeekBar.BlurAmount\"\n        android:layout_columnSpan=\"2\"\n        android:layout_gravity=\"center_vertical|fill_horizontal\"\n        android:max=\"500\" />\n\n    <Space\n        android:layout_height=\"16dp\"\n        android:layout_columnSpan=\"3\" />\n\n    <TextView\n        style=\"@style/Widget.Style.TextView.SettingsSeekBarLabel\"\n        android:layout_gravity=\"fill_vertical|end\"\n        android:text=\"@string/settings_dim_amount_title\" />\n\n    <SeekBar\n        android:id=\"@+id/dimAmount\"\n        style=\"@style/Settings.Widget.SeekBar.DimAmount\"\n        android:layout_columnSpan=\"2\"\n        android:layout_gravity=\"center_vertical|fill_horizontal\"\n        android:max=\"255\" />\n\n    <Space\n        android:layout_height=\"16dp\"\n        android:layout_columnSpan=\"3\" />\n\n    <TextView\n        style=\"@style/Widget.Style.TextView.SettingsSeekBarLabel\"\n        android:layout_gravity=\"fill_vertical|end\"\n        android:text=\"@string/settings_grey_amount_title\" />\n\n    <SeekBar\n        android:id=\"@+id/greyAmount\"\n        style=\"@style/Settings.Widget.SeekBar.GreyAmount\"\n        android:layout_columnSpan=\"2\"\n        android:layout_gravity=\"center_vertical|fill_horizontal\"\n        android:max=\"500\" />\n\n    <CheckBox\n        android:id=\"@+id/blurOnLockscreen\"\n        style=\"@style/Widget.Style.CheckBox.Settings\"\n        android:layout_column=\"@integer/settings_checkbox_column\"\n        android:layout_columnSpan=\"@integer/settings_checkbox_column_span\"\n        android:layout_marginStart=\"@dimen/settings_checkbox_margin_start\"\n        android:layout_marginTop=\"16dp\"\n        android:text=\"@string/settings_blur_on_lockscreen\" />\n\n</GridLayout>"
  },
  {
    "path": "presentation/src/main/res/layout/layout_include_tutorial_content.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:id=\"@+id/tutorialContainer\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:gravity=\"center\"\n    android:orientation=\"vertical\">\n\n    <RelativeLayout\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"0dp\"\n        android:layout_marginTop=\"@dimen/tutorial_content_margin\"\n        android:layout_weight=\"1\">\n\n        <ImageView\n            android:id=\"@+id/tutorialIconEmanate\"\n            android:layout_width=\"144dp\"\n            android:layout_height=\"144dp\"\n            android:layout_centerInParent=\"true\" />\n\n        <FrameLayout\n            android:id=\"@+id/tutorialIconAffordance\"\n            android:layout_width=\"88dp\"\n            android:layout_height=\"88dp\"\n            android:layout_centerInParent=\"true\"\n            android:clickable=\"true\"\n            android:focusable=\"true\">\n\n            <ImageView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:contentDescription=\"@string/app_name\"\n                android:src=\"@drawable/tutorial_icon\" />\n\n            <ImageView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:layout_gravity=\"center\"\n                android:duplicateParentState=\"true\"\n                android:src=\"@drawable/tutorial_icon_on_overlay\"\n                android:stateListAnimator=\"@animator/tutorial_icon_overlay_state_list\" />\n        </FrameLayout>\n\n        <com.yalin.style.view.component.ShadowDipsTextView\n            android:id=\"@+id/tutorialIconText\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_below=\"@id/tutorialIconAffordance\"\n            android:layout_centerHorizontal=\"true\"\n            android:fontFamily=\"sans-serif-condensed\"\n            android:paddingBottom=\"16dp\"\n            android:paddingTop=\"4dp\"\n            android:text=\"@string/app_name\"\n            android:textColor=\"#fff\"\n            android:textSize=\"@dimen/settings_text_size_large\"\n            app:shadowColor=\"#8000\"\n            app:shadowDx=\"0dp\"\n            app:shadowDy=\"1dp\"\n            app:shadowRadius=\"3dp\" />\n\n    </RelativeLayout>\n\n    <com.yalin.style.view.component.ShadowDipsTextView\n        android:id=\"@+id/tutorialMainText\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:fontFamily=\"sans-serif-condensed\"\n        android:paddingBottom=\"8dp\"\n        android:paddingEnd=\"@dimen/tutorial_content_margin\"\n        android:paddingStart=\"@dimen/tutorial_content_margin\"\n        android:paddingTop=\"4dp\"\n        android:text=\"@string/tutorial_main\"\n        android:textColor=\"#fff\"\n        android:textSize=\"22sp\"\n        app:shadowColor=\"#8000\"\n        app:shadowDx=\"0dp\"\n        app:shadowDy=\"1dp\"\n        app:shadowRadius=\"3dp\" />\n\n    <com.yalin.style.view.component.ShadowDipsTextView\n        android:id=\"@+id/tutorialSubText\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:fontFamily=\"sans-serif-condensed\"\n        android:paddingBottom=\"@dimen/tutorial_content_margin\"\n        android:paddingEnd=\"@dimen/tutorial_content_margin\"\n        android:paddingStart=\"@dimen/tutorial_content_margin\"\n        android:text=\"@string/tutorial_subtitle\"\n        android:textColor=\"#afff\"\n        android:textSize=\"@dimen/text_size_large\"\n        app:shadowColor=\"#8000\"\n        app:shadowDx=\"0dp\"\n        app:shadowDy=\"1dp\"\n        app:shadowRadius=\"3dp\" />\n\n</LinearLayout>"
  },
  {
    "path": "presentation/src/main/res/layout/layout_include_wallpaper_detail.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/detailContainer\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:alpha=\"0\"\n    android:visibility=\"gone\" />"
  },
  {
    "path": "presentation/src/main/res/layout/layout_include_wallpaper_tutorial.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/tutorialContainer\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:alpha=\"0\"\n    android:visibility=\"gone\" />"
  },
  {
    "path": "presentation/src/main/res/layout/layout_settings_choose_source.xml",
    "content": "<!--\n  Copyright 2014 Google Inc.\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<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:id=\"@+id/container\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <com.yalin.style.view.component.ObservableHorizontalScrollView\n        android:id=\"@+id/sourceScroller\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_marginBottom=\"16dp\"\n        android:layout_weight=\"1\"\n        android:overScrollMode=\"never\"\n        android:saveEnabled=\"false\"\n        android:scrollbars=\"none\">\n\n        <LinearLayout\n            android:id=\"@+id/sourceContainer\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"match_parent\"\n            android:gravity=\"center_vertical\"\n            android:orientation=\"horizontal\"\n            android:paddingEnd=\"64dp\"\n            android:paddingLeft=\"64dp\"\n            android:paddingRight=\"64dp\"\n            android:paddingStart=\"64dp\" />\n\n    </com.yalin.style.view.component.ObservableHorizontalScrollView>\n\n    <com.yalin.style.view.component.Scrollbar\n        android:id=\"@+id/sourceScrollbar\"\n        android:layout_width=\"200dp\"\n        android:layout_height=\"3dp\"\n        android:layout_gravity=\"center_horizontal|bottom\"\n        android:layout_marginBottom=\"16dp\"\n        app:backgroundColor=\"#3fff\"\n        app:indicatorColor=\"#fff\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "presentation/src/main/res/layout/layout_style_settings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/mainContainer\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:padding=\"32dp\">\n\n    <include\n        layout=\"@layout/layout_include_settings_content\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\" />\n\n</FrameLayout>"
  },
  {
    "path": "presentation/src/main/res/layout/layout_wallpaper_detail.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.yalin.style.view.component.DrawInsetsFrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:id=\"@+id/mainContainer\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <com.yalin.style.view.component.PanScaleProxyView\n        android:id=\"@+id/panScaleProxyView\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n    <View\n        android:id=\"@+id/statusBarScrimView\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"160dp\"\n        android:layout_gravity=\"top\"\n        android:background=\"@android:color/transparent\" />\n\n    <LinearLayout\n        android:id=\"@+id/chromeContainer\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"bottom\"\n        android:gravity=\"bottom\"\n        android:orientation=\"horizontal\">\n\n        <FrameLayout\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\">\n\n            <LinearLayout\n                android:id=\"@+id/metadata\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:background=\"?selectableItemBackground\"\n                android:clickable=\"true\"\n                android:focusable=\"true\"\n                android:orientation=\"vertical\">\n\n                <com.yalin.style.view.component.ShadowDipsTextView\n                    android:id=\"@+id/tvAttribution\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginBottom=\"@dimen/wallpaper_detail_attribution_title_margin\"\n                    android:fontFamily=\"sans-serif-condensed\"\n                    android:paddingEnd=\"@dimen/wallpaper_detail_metadata_margin\"\n                    android:paddingStart=\"@dimen/wallpaper_detail_metadata_margin\"\n                    android:paddingTop=\"@dimen/wallpaper_detail_metadata_margin\"\n                    android:textColor=\"@color/attribution_text_color\"\n                    android:textSize=\"@dimen/text_size_medium\"\n                    app:shadowColor=\"@color/shadow_color\"\n                    app:shadowDx=\"0dp\"\n                    app:shadowDy=\"1dp\"\n                    app:shadowRadius=\"3dp\" />\n\n                <com.yalin.style.view.component.ShadowDipsTextView\n                    android:id=\"@+id/tvTitle\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginBottom=\"@dimen/wallpaper_detail_title_byline_margin\"\n                    android:paddingBottom=\"@dimen/wallpaper_detail_title_bottom_padding\"\n                    android:paddingEnd=\"@dimen/wallpaper_detail_metadata_margin\"\n                    android:paddingStart=\"@dimen/wallpaper_detail_metadata_margin\"\n                    android:paddingTop=\"@dimen/wallpaper_detail_metadata_margin\"\n                    android:textColor=\"@color/wallpaper_detail_text_color\"\n                    android:textSize=\"@dimen/wallpaper_detail_title_text_size\"\n                    app:shadowColor=\"@color/shadow_color\"\n                    app:shadowDx=\"0dp\"\n                    app:shadowDy=\"1dp\"\n                    app:shadowRadius=\"3dp\" />\n\n                <com.yalin.style.view.component.ShadowDipsTextView\n                    android:id=\"@+id/tvByline\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:paddingBottom=\"@dimen/wallpaper_detail_metadata_margin_bottom\"\n                    android:paddingEnd=\"@dimen/wallpaper_detail_metadata_margin\"\n                    android:paddingStart=\"@dimen/wallpaper_detail_metadata_margin\"\n                    android:textColor=\"@color/wallpaper_detail_text_color\"\n                    android:textSize=\"@dimen/wallpaper_detail_byline_text_size\"\n                    app:shadowColor=\"@color/shadow_color\"\n                    app:shadowDx=\"0dp\"\n                    app:shadowDy=\"1dp\"\n                    app:shadowRadius=\"3dp\" />\n            </LinearLayout>\n        </FrameLayout>\n\n        <com.yalin.style.view.component.TintableImageButton\n            android:id=\"@+id/btnNext\"\n            android:layout_width=\"@dimen/wallpaper_detail_button_width\"\n            android:layout_height=\"@dimen/wallpaper_detail_button_height\"\n            android:background=\"?selectableItemBackgroundBorderless\"\n            android:contentDescription=\"@string/app_name\"\n            android:scaleType=\"center\"\n            android:src=\"@drawable/ic_skip\"\n            android:visibility=\"gone\"\n            app:tint=\"@color/selector_skip_tint\" />\n\n        <ImageButton\n            android:id=\"@+id/btnOverflow\"\n            android:layout_width=\"@dimen/wallpaper_detail_button_width\"\n            android:layout_height=\"@dimen/wallpaper_detail_button_height\"\n            android:background=\"?selectableItemBackgroundBorderless\"\n            android:contentDescription=\"@string/app_name\"\n            android:scaleType=\"center\"\n            android:src=\"@drawable/ic_overflow\" />\n    </LinearLayout>\n\n</com.yalin.style.view.component.DrawInsetsFrameLayout>"
  },
  {
    "path": "presentation/src/main/res/layout/settings_ab_spinner_list_item.xml",
    "content": "<TextView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@android:id/text1\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:drawableEnd=\"@drawable/spinner_triangle\"\n    android:drawablePadding=\"8dp\"\n    android:ellipsize=\"marquee\"\n    android:maxLines=\"1\"\n    android:paddingEnd=\"4dp\"\n    android:paddingStart=\"16dp\"\n    android:textAppearance=\"@style/TextAppearance.ActionBar.Title\" />\n\n"
  },
  {
    "path": "presentation/src/main/res/layout/settings_ab_spinner_list_item_dropdown.xml",
    "content": "<CheckedTextView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@android:id/text1\"\n    style=\"?android:spinnerDropDownItemStyle\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"?actionBarSize\"\n    android:ellipsize=\"marquee\"\n    android:gravity=\"center_vertical|start\"\n    android:maxLines=\"1\"\n    android:paddingEnd=\"16dp\"\n    android:paddingStart=\"16dp\"\n    android:textAppearance=\"@style/TextAppearance.ActionBar.Title\" />\n"
  },
  {
    "path": "presentation/src/main/res/layout/settings_choose_source_item.xml",
    "content": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"@dimen/settings_choose_source_item_width\"\n    android:layout_height=\"match_parent\"\n    android:gravity=\"center\"\n    android:orientation=\"vertical\">\n\n    <FrameLayout\n        android:id=\"@+id/source_image\"\n        android:layout_width=\"@dimen/settings_choose_source_item_image_size\"\n        android:layout_height=\"@dimen/settings_choose_source_item_image_size\"\n        android:layout_marginBottom=\"16dp\"\n        android:clickable=\"true\"\n        android:focusable=\"true\"\n        android:foreground=\"@drawable/settings_source_item_image_overlay\" />\n\n    <TextView\n        android:id=\"@+id/source_title\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"8dp\"\n        android:fontFamily=\"sans-serif-condensed\"\n        android:gravity=\"center_horizontal\"\n        android:lineSpacingMultiplier=\"0.8\"\n        android:maxLines=\"3\"\n        android:paddingEnd=\"8dp\"\n        android:paddingStart=\"8dp\"\n        android:textAllCaps=\"true\"\n        android:textSize=\"@dimen/settings_text_size_large\"\n        android:textStyle=\"bold\" />\n\n    <TextView\n        android:id=\"@+id/source_status\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:gravity=\"center_horizontal\"\n        android:lineSpacingMultiplier=\"1.1\"\n        android:maxLines=\"4\"\n        android:paddingEnd=\"8dp\"\n        android:paddingStart=\"8dp\"\n        android:textColor=\"#fff\"\n        android:textSize=\"@dimen/settings_text_size_normal\"\n        android:textStyle=\"italic\" />\n\n    <ImageButton\n        android:id=\"@+id/source_settings_button\"\n        android:layout_width=\"56dp\"\n        android:layout_height=\"56dp\"\n        android:layout_marginTop=\"16dp\"\n        android:background=\"?selectableItemBackgroundBorderless\"\n        android:contentDescription=\"@string/action_source_settings\"\n        android:scaleType=\"center\"\n        android:src=\"@drawable/ic_source_settings\"\n        android:visibility=\"invisible\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "presentation/src/main/res/menu/advance_activity.xml",
    "content": "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n    <item\n        android:id=\"@+id/action_advance_hint\"\n        android:icon=\"@drawable/advance_wallpaper_msg\"\n        android:title=\"@string/advance_action_msg\"\n        app:showAsAction=\"ifRoom\" />\n</menu>\n"
  },
  {
    "path": "presentation/src/main/res/menu/gallery_activity.xml",
    "content": "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n    <item\n        android:title=\"@string/gallery_action_rotate_interval\"\n        android:icon=\"@drawable/gallery_ic_action_rotate_interval\"\n        app:showAsAction=\"ifRoom\">\n        <menu>\n            <group android:checkableBehavior=\"single\">\n                <item\n                    android:id=\"@+id/action_rotate_interval_none\"\n                    android:title=\"@string/gallery_action_rotate_interval_none\" />\n                <item\n                    android:id=\"@+id/action_rotate_interval_1h\"\n                    android:title=\"@string/gallery_action_rotate_interval_1h\" />\n                <item\n                    android:id=\"@+id/action_rotate_interval_3h\"\n                    android:title=\"@string/gallery_action_rotate_interval_3h\" />\n                <item\n                    android:id=\"@+id/action_rotate_interval_6h\"\n                    android:title=\"@string/gallery_action_rotate_interval_6h\" />\n                <item\n                    android:id=\"@+id/action_rotate_interval_24h\"\n                    android:title=\"@string/gallery_action_rotate_interval_24h\" />\n                <item\n                    android:id=\"@+id/action_rotate_interval_72h\"\n                    android:title=\"@string/gallery_action_rotate_interval_72h\" />\n            </group>\n        </menu>\n    </item>\n    <item\n        android:id=\"@+id/action_import_photos\"\n        android:title=\"@string/gallery_action_import_photos\" />\n    <item\n        android:id=\"@+id/action_clear_photos\"\n        android:title=\"@string/gallery_action_clear_photos\"\n        android:orderInCategory=\"10000\"\n        app:showAsAction=\"never\" />\n</menu>\n"
  },
  {
    "path": "presentation/src/main/res/menu/gallery_selection.xml",
    "content": "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n    <item\n        android:id=\"@+id/action_force_now\"\n        android:icon=\"@drawable/gallery_ic_action_play\"\n        android:title=\"@string/gallery_action_force_now\"\n        app:showAsAction=\"ifRoom\" />\n    <item\n        android:id=\"@+id/action_remove\"\n        android:icon=\"@drawable/gallery_ic_action_remove\"\n        android:title=\"@string/gallery_action_remove\"\n        app:showAsAction=\"ifRoom\" />\n</menu>\n"
  },
  {
    "path": "presentation/src/main/res/menu/menu_settings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n    <item\n        android:id=\"@+id/action_about\"\n        android:title=\"@string/about_title\"\n        app:showAsAction=\"never\" />\n</menu>"
  },
  {
    "path": "presentation/src/main/res/menu/menu_settings_advanced.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n    <item\n        android:id=\"@+id/action_reset\"\n        android:title=\"@string/reset_settings_defaults\"\n        app:showAsAction=\"never\" />\n</menu>"
  },
  {
    "path": "presentation/src/main/res/menu/style_overflow.xml",
    "content": "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n    <item android:id=\"@+id/action_settings\"\n        android:title=\"@string/action_settings\"\n        android:orderInCategory=\"10000\"\n        app:showAsAction=\"never\" />\n</menu>\n"
  },
  {
    "path": "presentation/src/main/res/values/attrs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <declare-styleable name=\"Scrollbar\">\n        <attr name=\"backgroundColor\" format=\"color\" />\n        <attr name=\"indicatorColor\" format=\"color\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"ShadowDipsTextView\">\n        <attr name=\"shadowDx\" format=\"dimension\" />\n        <attr name=\"shadowDy\" format=\"dimension\" />\n        <attr name=\"shadowRadius\" format=\"dimension\" />\n        <attr name=\"shadowColor\" format=\"color\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"DrawInsetsFrameLayout\">\n        <attr name=\"topInsetBackground\" format=\"reference|color\" />\n        <attr name=\"bottomInsetBackground\" format=\"reference|color\" />\n        <attr name=\"sideInsetBackground\" format=\"reference|color\" />\n        <attr name=\"insetBackground\" format=\"reference|color\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"TintableImageButton\">\n        <attr name=\"tint\" format=\"reference|color\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"CircularProgress\">\n        <attr name=\"progress_borderWidth\" format=\"dimension\" />\n    </declare-styleable>\n</resources>"
  },
  {
    "path": "presentation/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#3F51B5</color>\n    <color name=\"colorPrimaryDark\">#303F9F</color>\n    <color name=\"colorAccent\">#FF4081</color>\n\n    <color name=\"shadow_color\">#8000</color>\n    <color name=\"attribution_text_color\">#cfff</color>\n    <color name=\"wallpaper_detail_text_color\">#fff</color>\n\n    <color name=\"gallery_theme_dark\">#00796B</color>\n    <color name=\"gallery_theme\">#009688</color>\n    <color name=\"gallery_theme_light\">#4DB6AC</color>\n    <color name=\"gallery_empty_state_dark\">@color/gallery_theme_dark</color>\n    <color name=\"gallery_empty_state_light\">@color/gallery_theme_light</color>\n\n    <color name=\"gallery_window_background\">#111</color>\n    <color name=\"gallery_chosen_photo_placeholder\">#5666</color>\n\n    <color name=\"advance_window_background\">#EEE</color>\n\n    <color name=\"red\">#FFC93437</color>\n    <color name=\"blue\">#FF375BF1</color>\n    <color name=\"yellow\">#FFF7D23E</color>\n    <color name=\"green\">#FF34A350</color>\n</resources>\n"
  },
  {
    "path": "presentation/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\n    <dimen name=\"activate_button_size\">100dp</dimen>\n\n    <dimen name=\"wallpaper_detail_button_width\">56dp</dimen>\n    <dimen name=\"wallpaper_detail_button_height\">72dp</dimen>\n    <dimen name=\"wallpaper_detail_metadata_margin\">16dp</dimen>\n    <dimen name=\"wallpaper_detail_attribution_title_margin\">-12sp</dimen>\n    <dimen name=\"wallpaper_detail_title_bottom_padding\">16sp</dimen>\n    <dimen name=\"wallpaper_detail_title_byline_margin\">-16sp</dimen>\n    <dimen name=\"wallpaper_detail_metadata_margin_bottom\">20dp</dimen>\n    <dimen name=\"wallpaper_detail_byline_text_size\">22sp</dimen>\n    <dimen name=\"wallpaper_detail_title_text_size\">28sp</dimen>\n\n    <dimen name=\"text_size_small\">12sp</dimen>\n    <dimen name=\"text_size_medium\">14sp</dimen>\n    <dimen name=\"text_size_large\">16sp</dimen>\n\n    <dimen name=\"settings_text_size_large\">20sp</dimen>\n    <dimen name=\"settings_text_size_normal\">16sp</dimen>\n\n    <dimen name=\"keyline_2\">72dp</dimen>\n    <dimen name=\"keyline_2_minus_16dp\">56dp</dimen>\n\n    <dimen name=\"tutorial_content_margin\">32dp</dimen>\n\n    <dimen name=\"settings_checkbox_margin_start\">0dp</dimen>\n    <integer name=\"settings_checkbox_column\">0</integer>\n    <integer name=\"settings_checkbox_column_span\">3</integer>\n\n    <dimen name=\"settings_choose_source_item_width\">200dp</dimen>\n    <dimen name=\"settings_choose_source_item_image_size\">100dp</dimen>\n    <dimen name=\"settings_choose_source_item_estimated_height\">220dp</dimen>\n    <dimen name=\"settings_choose_source_min_side_padding\">64dp</dimen>\n    <dimen name=\"settings_choose_source_settings_button_animate_distance\">24dp</dimen>\n\n    <dimen name=\"gallery_chosen_photo_grid_max_item_size\">200dp</dimen>\n    <dimen name=\"gallery_chosen_photo_grid_spacing\">2dp</dimen>\n    <dimen name=\"gallery_chosen_photo_folder_padding\">8dp</dimen>\n    <dimen name=\"gallery_fab_space\">96dp</dimen>\n    <dimen name=\"gallery_fab_margin\">16dp</dimen>\n    <dimen name=\"gallery_fab_toolbar_height\">56dp</dimen>\n    <dimen name=\"gallery_header_elevation\">4dp</dimen>\n\n    <dimen name=\"advance_name_margin\">16dp</dimen>\n    <dimen name=\"advance_name_top_margin\">8dp</dimen>\n\n    <dimen name=\"advance_grid_max_item_size\">300dp</dimen>\n</resources>\n"
  },
  {
    "path": "presentation/src/main/res/values/ids.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <item name=\"action_like\" type=\"id\" />\n    <item name=\"action_share\" type=\"id\" />\n\n    <item name=\"gallery_viewtag_position\" type=\"id\" />\n</resources>"
  },
  {
    "path": "presentation/src/main/res/values/integers.xml",
    "content": "<resources>\n    <integer name=\"tutorial_icon_emanate_duration\">2000</integer>\n    <integer name=\"tutorial_icon_emanate_wave2_delay\">400</integer>\n</resources>"
  },
  {
    "path": "presentation/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">Style</string>\n\n    <string name=\"app_author\">Yalin</string>\n    <string name=\"app_description\">Style Live Wallpaper</string>\n\n    <string name=\"settings_title\">Style Settings</string>\n    <string name=\"settings_blur_amount_title\">Blur</string>\n    <string name=\"settings_dim_amount_title\">Dim</string>\n    <string name=\"settings_grey_amount_title\">Grey</string>\n    <string name=\"settings_blur_on_lockscreen\">Apply blur on lockscreen</string>\n\n    <string name=\"action_activate\">Active</string>\n    <string name=\"action_settings\">Customize</string>\n    <string name=\"action_like\">Like</string>\n    <string name=\"action_unlike\">Unlike</string>\n    <string name=\"action_share\">Share</string>\n\n    <string name=\"done\">Done</string>\n    <string name=\"confirm\">Confirm</string>\n    <string name=\"hint\">Hint</string>\n    <string name=\"downloading\">Downloading</string>\n\n\n    <string name=\"section_choose_source\">Sources</string>\n    <string name=\"section_advanced\">Advanced</string>\n    <string name=\"action_source_settings\">Customize source</string>\n\n    <string name=\"reset_settings_defaults\">Reset sliders</string>\n\n    <string name=\"error_view_details\">Couldn\\'t view wallpaper details.</string>\n\n    <string name=\"exception_message_device_unsupported\">Your device unsupported live wallpaper.</string>\n    <string name=\"exception_message_generic\">There was some error.</string>\n    <string name=\"exception_message_no_connection\">There is no internet connection</string>\n    <string name=\"exception_message_resync\">Sync is processing</string>\n    <string name=\"exception_message_remote_service\">Remote service not available</string>\n    <string name=\"exception_message_download\">Download error</string>\n\n    <string name=\"tutorial_main\">Open the Style app to see your wallpaper</string>\n    <string name=\"tutorial_subtitle\">Double touch the blurred wallpaper on your home screen to temporarily focus\n    \\nStyle will update your wallpaper everyday</string>\n\n    <string name=\"about_title\">About Style</string>\n    <string name=\"about_body\"><![CDATA[\n        Make the most of your desktop and give it the artistic flavor.<br><br>\n\n        Use your famous works of art to easily refresh your screen.<br>Style will make these artwork back into the background to keep your icon in the spotlight.<br><br>\n\n        Style is free and easy to use.<br>You can also feedback to the developer&#39;s mailbox:<br>\n        <b><a href=\"mailto:jinkg@live.cn\">jinkg@live.cn</a></b><br><br>\n\n        <b><a href=\"http://www.kinglloy.com/\">Visit home page</a></b>\n    ]]></string>\n    <string name=\"about_version_template\">v%1$s</string>\n\n    <string name=\"advance_hint\"><![CDATA[\n        Advance wallpaper is developed by different developers.<br><br>\n        Use them may give you the equipment to bring different power consumption, performance consumption, even crash.<br><br>\n        If you find such a problem, you can switch to other advance wallpaper, or contact Style developers.\n\n        <br><br><b><a href=\"mailto:jinkg@live.cn\">jinkg@live.cn</a></b><br>\n\n        <b><a href=\"http://www.kinglloy.com/\">Visit home page</a></b>\n    ]]></string>\n\n    <string name=\"advance_download_hint\"><![CDATA[\n        This advance wallpaper component is a bit big, and you must take a minute to download it(several hundred KB or several MB).<br><br>\n        Press download button if you are ready to download.\n    ]]></string>\n\n    <string name=\"advance_ad_download_hint\"><![CDATA[\n        This advance wallpaper component is a bit big, and you must take a minute to download it(several hundred KB or several MB).<br><br>\n        At the same time, This wallpaper contain a little ad, you must view a minute ad before use it.<br><br>\n        Thank you for your support for the Style developer.<br><br>\n        Press download button if you are ready to download and see ad.\n    ]]></string>\n\n    <string name=\"share_title\">Share wallpaper</string>\n    <string name=\"share_text\">My Android wallpaper today is \\' %1$s \\' by %2$s. #StyleWallpaper\\n\\n\n        %3$s</string>\n\n    <string name=\"advance_empty_load\">Load advance wallpaper</string>\n    <string name=\"advance_empty_description\">There has no advance wallpaper loaded.\\nYou can load the wallpapers from server.</string>\n\n    <string name=\"advance_retry\">Retry</string>\n    <string name=\"advance_retry_description\">There has some problem.\\nYou can reload the wallpaper,\\nOr you can contact the developer.</string>\n\n    <string name=\"advance_action_msg\">Hint</string>\n\n    <string name=\"advance_download_msg\">Download</string>\n    <string name=\"advance_ad_download_msg\">Show Ad and download</string>\n\n    <string name=\"gallery_empty\">Random camera photos</string>\n    <string name=\"gallery_empty_subtitle\">Choose specific photos instead</string>\n\n    <string name=\"gallery_enable_random\">Enable random camera photos</string>\n    <string name=\"gallery_permission_rationale\">Allow the storage permission to show random photos as\n        your background.</string>\n    <string name=\"gallery_edit_settings\">Edit App Permissions</string>\n    <string name=\"gallery_denied_explanation\">You must enable the <b>Storage</b> permission to use\n        random photos as your background.</string>\n\n    <string name=\"gallery_add_fab\">Add photos</string>\n    <string name=\"gallery_add_photos\">Add photos</string>\n    <string name=\"gallery_add_folder\">Import photos from %s</string>\n\n    <string name=\"gallery_internal_storage_message\">Don\\'t see anything? Try enabling internal storage in the overflow\n        menu</string>\n    <string name=\"gallery_add_photos_error\">Your device does not support adding pictures.\\nPlease contact your manufacturer.</string>\n    <string name=\"gallery_add_folder_error\">Your device does not support adding folders.\\nPlease contact your manufacturer.</string>\n\n    <string name=\"gallery_action_force_now\">Set as wallpaper now</string>\n    <string name=\"gallery_action_remove\">Remove</string>\n    <string name=\"gallery_temporary_force_image\">Wallpaper set to selected image.</string>\n\n    <string name=\"gallery_action_clear_photos\">Remove all photos</string>\n    <string name=\"gallery_action_rotate_interval\">Change interval</string>\n    <string name=\"gallery_action_rotate_interval_none\">Don\\'t change wallpaper</string>\n    <string name=\"gallery_action_rotate_interval_1h\">Every hour</string>\n    <string name=\"gallery_action_rotate_interval_3h\">Every 3 hours</string>\n    <string name=\"gallery_action_rotate_interval_6h\">Every 6 hours</string>\n    <string name=\"gallery_action_rotate_interval_24h\">Every 24 hours</string>\n    <string name=\"gallery_action_rotate_interval_72h\">Every 3 days</string>\n    <string name=\"gallery_action_import_photos\">Import photos…</string>\n    <string name=\"gallery_action_import_photos_from\">Import photos from %s</string>\n\n    <string name=\"gallery_import_dialog_title\">Import photos from</string>\n\n    <string name=\"app_context_description\">A living wallpaper for your home screen</string>\n    <string name=\"app_context_uri\">http://www.kinglloy.com/</string>\n\n    <string name=\"shortcut_settings\">Settings</string>\n    <string name=\"shortcut_settings_advance\">Advance Settings</string>\n    <string name=\"shortcut_about\">About</string>\n\n</resources>\n"
  },
  {
    "path": "presentation/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"Theme.Style.Base\" parent=\"Theme.AppCompat.NoActionBar\">\n        <item name=\"android:windowBackground\">@android:color/black</item>\n        <item name=\"android:windowContentOverlay\">@null</item>\n\n        <item name=\"colorPrimary\">@color/colorPrimary</item>\n        <item name=\"colorAccent\">@color/colorAccent</item>\n    </style>\n\n    <style name=\"Theme.Style\" parent=\"Theme.Style.Base\" />\n\n    <style name=\"Theme.Style.Wallpaper\" parent=\"Theme.Style\">\n        <item name=\"android:windowBackground\">@android:color/transparent</item>\n        <item name=\"android:windowShowWallpaper\">true</item>\n    </style>\n\n    <style name=\"Theme.StyleActivity\" parent=\"Theme.Style.Wallpaper\">\n\n    </style>\n\n    <style name=\"Theme.Style.Settings\" parent=\"Theme.Style.Wallpaper\">\n        <item name=\"colorControlActivated\">#fff</item>\n    </style>\n\n    <style name=\"TextAppearance.ActionBar.Title\" parent=\"TextAppearance.AppCompat.Widget.ActionBar.Title\">\n        <item name=\"android:textSize\">20sp</item>\n        <item name=\"android:textColor\">#fff</item>\n    </style>\n\n    <style name=\"Theme.Style.About\" parent=\"Theme.Style.Wallpaper\">\n\n    </style>\n\n    <style name=\"Theme.Style.Gallery.Base\" parent=\"Theme.AppCompat.NoActionBar\">\n        <item name=\"android:windowBackground\">@color/gallery_window_background</item>\n        <item name=\"android:windowContentOverlay\">@null</item>\n\n        <item name=\"colorPrimaryDark\">@color/gallery_theme_dark</item>\n        <item name=\"colorPrimary\">@color/gallery_theme</item>\n        <item name=\"colorAccent\">@color/gallery_theme</item>\n    </style>\n\n    <style name=\"Theme.Style.Gallery\" parent=\"Theme.Style.Gallery.Base\">\n\n    </style>\n\n    <style name=\"Theme.Style.Advance\" parent=\"Theme.Style.Gallery.Base\">\n        <item name=\"android:windowBackground\">@color/advance_window_background</item>\n    </style>\n\n    <style name=\"Style.SimpleFadeFragmentAnimation\" parent=\"\">\n        <item name=\"android:fragmentFadeEnterAnimation\">@animator/fade_in</item>\n        <item name=\"android:fragmentFadeExitAnimation\">@animator/fade_out</item>\n    </style>\n\n    <style name=\"Widget.Style.PopupMenu\" parent=\"Widget.AppCompat.PopupMenu\">\n        <item name=\"android:popupBackground\">@drawable/popup_background</item>\n    </style>\n\n    <style name=\"ThemeOverlay.Style.Toolbar\" parent=\"ThemeOverlay.AppCompat.Dark.ActionBar\">\n        <item name=\"android:popupMenuStyle\">@style/Widget.Style.PopupMenu</item>\n    </style>\n\n    <style name=\"Widget.Style.TextView.SettingsSeekBarLabel\" parent=\"\">\n        <item name=\"android:gravity\">center_vertical</item>\n        <item name=\"android:textSize\">@dimen/text_size_large</item>\n        <item name=\"android:fontFamily\">sans-serif-condensed</item>\n        <item name=\"android:textAllCaps\">true</item>\n        <item name=\"android:textColor\">#fff</item>\n        <item name=\"android:textStyle\">bold</item>\n    </style>\n\n    <style name=\"Widget.Style.CheckBox.Settings\" parent=\"\">\n        <item name=\"android:textSize\">@dimen/text_size_large</item>\n        <item name=\"android:textColor\">#fff</item>\n        <item name=\"android:paddingStart\">4dp</item>\n        <item name=\"android:paddingEnd\">0dp</item>\n    </style>\n\n    <style name=\"Settings.Widget.SeekBar.BlurAmount\" parent=\"Widget.Style.SeekBar\">\n        <item name=\"android:progressDrawable\">@drawable/scrubber_progress_blur_amount</item>\n    </style>\n\n    <style name=\"Settings.Widget.SeekBar.DimAmount\" parent=\"Widget.Style.SeekBar\">\n        <item name=\"android:progressDrawable\">@drawable/scrubber_progress_dim_amount</item>\n    </style>\n\n    <style name=\"Settings.Widget.SeekBar.GreyAmount\" parent=\"Widget.Style.SeekBar\">\n        <item name=\"android:progressDrawable\">@drawable/scrubber_progress_grey_amount</item>\n    </style>\n\n    <style name=\"Widget.Style.SeekBar\" parent=\"android:Widget.Holo.SeekBar\">\n        <item name=\"android:progressDrawable\">@drawable/scrubber_progress_horizontal</item>\n        <item name=\"android:indeterminateDrawable\">@drawable/scrubber_progress_horizontal</item>\n        <item name=\"android:thumb\">@drawable/scrubber_control_selector</item>\n    </style>\n\n</resources>\n"
  },
  {
    "path": "presentation/src/main/res/values-v19/styles.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n  <style name=\"Theme.Style\" parent=\"Theme.Style.Base\">\n    <item name=\"android:windowTranslucentStatus\">true</item>\n    <item name=\"android:windowTranslucentNavigation\">true</item>\n  </style>\n\n</resources>"
  },
  {
    "path": "presentation/src/main/res/values-v21/styles.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <style name=\"Theme.Style\" parent=\"Theme.Style.Base\">\n        <item name=\"android:windowDrawsSystemBarBackgrounds\">true</item>\n        <item name=\"android:statusBarColor\">@android:color/transparent</item>\n        <item name=\"android:navigationBarColor\">@android:color/transparent</item>\n    </style>\n\n    <style name=\"Widget.Style.SeekBar\" parent=\"android:Widget.Material.SeekBar\">\n        <item name=\"android:progressDrawable\">@drawable/scrubber_progress_horizontal</item>\n        <item name=\"android:indeterminateDrawable\">@drawable/scrubber_progress_horizontal</item>\n    </style>\n\n    <style name=\"Theme.Style.Gallery\" parent=\"Theme.Style.Gallery.Base\">\n        <item name=\"android:windowDrawsSystemBarBackgrounds\">true</item>\n        <item name=\"android:windowTranslucentNavigation\">true</item>\n    </style>\n</resources>"
  },
  {
    "path": "presentation/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": "presentation/src/main/res/values-zh-rCN/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"app_name\">Style</string>\n\n    <string name=\"app_author\">Yalin</string>\n    <string name=\"app_description\">Style Live Wallpaper</string>\n\n    <string name=\"settings_title\">Style设置</string>\n    <string name=\"settings_blur_amount_title\">模糊</string>\n    <string name=\"settings_dim_amount_title\">亮度</string>\n    <string name=\"settings_grey_amount_title\">灰度</string>\n    <string name=\"settings_blur_on_lockscreen\">锁屏时应用模糊效果</string>\n\n    <string name=\"action_activate\">开启</string>\n    <string name=\"action_settings\">设置</string>\n    <string name=\"action_like\">收藏</string>\n    <string name=\"action_unlike\">取消收藏</string>\n    <string name=\"action_share\">分享</string>\n\n    <string name=\"done\">完成</string>\n    <string name=\"confirm\">确定</string>\n    <string name=\"hint\">提示</string>\n    <string name=\"downloading\">下载中，请稍后</string>\n\n    <string name=\"section_choose_source\">壁纸来源</string>\n    <string name=\"section_advanced\">个性化设置</string>\n    <string name=\"action_source_settings\">自定义壁纸</string>\n\n    <string name=\"reset_settings_defaults\">重置设置</string>\n\n    <string name=\"error_view_details\">您的设备不支持查看壁纸详情.</string>\n\n    <string name=\"exception_message_device_unsupported\">您的设备不支持动态壁纸。</string>\n    <string name=\"exception_message_generic\">操作出现了一个错误。</string>\n    <string name=\"exception_message_no_connection\">网络不可用，请检查网络连接。</string>\n    <string name=\"exception_message_resync\">正在同步…</string>\n    <string name=\"exception_message_remote_service\">远程服务不可用，请联系开发者。</string>\n    <string name=\"exception_message_download\">下载失败</string>\n\n    <string name=\"tutorial_main\">打开Style以预览当前作品</string>\n    <string name=\"tutorial_subtitle\">在桌面双击空白模糊位置，临时查看清晰作品\n        \\nStyle将每天为您带来新的艺术体验</string>\n\n    <string name=\"about_title\">关于Style</string>\n    <string name=\"about_body\"><![CDATA[\n        充分利用您的桌面，赋予其艺术气息。<br><br>\n\n        用著名的艺术作品轻轻松松的刷新您的屏幕。<br>\n        Style会让这些艺术作品隐退到背景之中，以保证您的图标更加醒目。<br><br>\n\n        Style免费、简单易用。<br>\n        您也可以反馈意见到开发者邮箱：<b><a href=\"mailto:jinkg@live.cn\">jinkg@live.cn</a></b><br><br>\n\n        <b><a href=\"http://www.kinglloy.com/\">访问官方网站</a></b>\n    ]]></string>\n    <string name=\"about_version_template\">v%1$s</string>\n\n    <string name=\"advance_hint\"><![CDATA[\n        特效壁纸是不同的开发者开发的。<br><br>\n        使用他们可能会给你的设备带来不同的电量消耗、性能消耗、甚至Style应用崩溃。<br><br>\n        如果你发现了这样的问题，你可以切换其他的特效壁纸，或者联系Style开发者。\n\n        <br><br><b><a href=\"mailto:jinkg@live.cn\">jinkg@live.cn</a></b><br>\n\n        <b><a href=\"http://www.kinglloy.com/\">访问官方网站</a></b>\n    ]]></string>\n\n    <string name=\"advance_download_hint\"><![CDATA[\n        使用之前这款壁纸之前需要先下载（几百KB或几MB）<br><br>\n        如果你已经链接上WI-FI，准备好下载，请点击下面的下载按钮\n    ]]></string>\n\n    <string name=\"advance_ad_download_hint\"><![CDATA[\n        使用之前这款壁纸之前需要先下载（几百KB或几MB）<br><br>\n        与此同时，这款壁纸包含一则小广告，在使用之前，你必须观看广告<br><br>\n        非常感谢你对Style开发者的支持<br><br>\n        点击下面的按钮开始下载，同时播放广告\n    ]]></string>\n\n    <string name=\"share_title\">分享壁纸</string>\n    <string name=\"share_text\">我今天的壁纸是 \\' %1$s \\' 由 %2$s 所画.  #StyleWallpaper\\n\\n\n        %3$s</string>\n\n    <string name=\"advance_empty_load\">获取特效壁纸</string>\n    <string name=\"advance_empty_description\">还没有特效壁纸，你可以点击按钮从服务器获取。</string>\n\n    <string name=\"advance_retry\">重试获取</string>\n    <string name=\"advance_retry_description\">获取壁纸出现了一些问题，你可以重试获取，也可以联系开发者。</string>\n\n    <string name=\"advance_action_msg\">提示</string>\n\n    <string name=\"advance_download_msg\">下载</string>\n    <string name=\"advance_ad_download_msg\">观看广告并下载</string>\n\n    <string name=\"gallery_empty\">自定义壁纸</string>\n    <string name=\"gallery_empty_subtitle\">选择指定的图片充当壁纸</string>\n\n    <string name=\"gallery_enable_random\">开启自定义壁纸</string>\n    <string name=\"gallery_permission_rationale\">允许Style使用存储权限，以开启自定义壁纸</string>\n    <string name=\"gallery_edit_settings\">编辑应用权限</string>\n    <string name=\"gallery_denied_explanation\">你必须开启<b>存储</b>权限以使用自定义壁纸</string>\n\n    <string name=\"gallery_add_fab\">添加照片</string>\n    <string name=\"gallery_add_photos\">添加照片</string>\n    <string name=\"gallery_add_folder\">从%s导入照片</string>\n\n    <string name=\"gallery_internal_storage_message\">没有内容？请尝试从右上角菜单栏点击显示外部存储</string>\n    <string name=\"gallery_add_photos_error\">你的设备不支持添加图片。请联系设备的生产厂商</string>\n    <string name=\"gallery_add_folder_error\">你的设备不支持添加文件夹。请联系设备的生产厂商</string>\n\n    <string name=\"gallery_action_force_now\">设置为壁纸</string>\n    <string name=\"gallery_action_remove\">移除</string>\n    <string name=\"gallery_temporary_force_image\">壁纸设置已设置为选中的照片</string>\n\n    <string name=\"gallery_action_clear_photos\">移除所有照片</string>\n    <string name=\"gallery_action_rotate_interval\">改变更新间隔</string>\n    <string name=\"gallery_action_rotate_interval_none\">不更新</string>\n    <string name=\"gallery_action_rotate_interval_1h\">每隔1小时</string>\n    <string name=\"gallery_action_rotate_interval_3h\">每隔3小时</string>\n    <string name=\"gallery_action_rotate_interval_6h\">每隔6小时</string>\n    <string name=\"gallery_action_rotate_interval_24h\">每隔24小时</string>\n    <string name=\"gallery_action_rotate_interval_72h\">每隔3天</string>\n    <string name=\"gallery_action_import_photos\">导入照片…</string>\n    <string name=\"gallery_action_import_photos_from\">从%s导入照片</string>\n\n    <string name=\"gallery_import_dialog_title\">导入照片</string>\n\n    <string name=\"app_context_description\">用动态壁纸装点你的屏幕</string>\n\n    <string name=\"app_context_uri\">http://www.kinglloy.com/</string>\n\n    <string name=\"shortcut_settings\">Style设置</string>\n    <string name=\"shortcut_settings_advance\">个性化设置</string>\n    <string name=\"shortcut_about\">关于Style</string>\n</resources>"
  },
  {
    "path": "presentation/src/main/res/xml/shortcuts.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shortcuts xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    tools:targetApi=\"25\">\n\n    <shortcut\n        android:enabled=\"true\"\n        android:icon=\"@drawable/ic_shortcut_banana\"\n        android:shortcutDisabledMessage=\"@string/shortcut_settings\"\n        android:shortcutId=\"settings\"\n        android:shortcutLongLabel=\"@string/shortcut_settings\"\n        android:shortcutShortLabel=\"@string/shortcut_settings\">\n\n        <intent\n            android:action=\"android.intent.action.VIEW\"\n            android:data=\"style://settings/source\"\n            android:targetClass=\"com.yalin.style.view.activity.SettingsActivity\"\n            android:targetPackage=\"com.yalin.style\" />\n\n    </shortcut>\n\n\n    <shortcut\n        android:enabled=\"true\"\n        android:icon=\"@drawable/ic_shortcut_banana\"\n        android:shortcutDisabledMessage=\"@string/shortcut_settings_advance\"\n        android:shortcutId=\"advance_settings\"\n        android:shortcutLongLabel=\"@string/shortcut_settings_advance\"\n        android:shortcutShortLabel=\"@string/shortcut_settings_advance\">\n\n        <intent\n            android:action=\"android.intent.action.VIEW\"\n            android:data=\"style://settings/advance\"\n            android:targetClass=\"com.yalin.style.view.activity.SettingsActivity\"\n            android:targetPackage=\"com.yalin.style\" />\n\n    </shortcut>\n\n\n    <shortcut\n        android:enabled=\"true\"\n        android:icon=\"@drawable/ic_shortcut_strawberry\"\n        android:shortcutDisabledMessage=\"@string/shortcut_about\"\n        android:shortcutId=\"about\"\n        android:shortcutLongLabel=\"@string/shortcut_about\"\n        android:shortcutShortLabel=\"@string/shortcut_about\">\n\n        <intent\n            android:action=\"android.intent.action.VIEW\"\n            android:data=\"style://about\"\n            android:targetClass=\"com.yalin.style.view.activity.AboutActivity\"\n            android:targetPackage=\"com.yalin.style\" />\n\n    </shortcut>\n</shortcuts>"
  },
  {
    "path": "presentation/src/main/res/xml/wallpaper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<wallpaper xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:author=\"@string/app_author\"\n    android:description=\"@string/app_description\"\n    android:contextDescription=\"@string/app_context_description\"\n    android:contextUri=\"@string/app_context_uri\"\n    android:settingsActivity=\"com.yalin.style.view.activity.SettingsActivity\"\n    android:thumbnail=\"@drawable/ic_launcher\" />"
  },
  {
    "path": "presentation/src/production/java/com/yalin/style/util/AdUtil.kt",
    "content": "package com.yalin.style.util\n\nimport android.app.Activity\nimport com.google.android.gms.ads.*\nimport com.yalin.style.R\nimport com.yalin.style.view.activity.AdvanceSettingActivity\n\n/**\n * @author jinyalin\n * @since 2017/8/10.\n */\n\nfun maybeAttachAd(activity: Activity) {\n    MobileAds.initialize(activity.applicationContext,\n            activity.getString(R.string.app_ad_id))\n    val adView = activity.findViewById(R.id.adView) as AdView\n    val adRequest = AdRequest.Builder().build()\n    adView.loadAd(adRequest)\n}\n\nprivate var mInterstitialAd: InterstitialAd? = null\n\nfun maybeAttachInterstitialAd(activity: AdvanceSettingActivity, listener: AdListener) {\n    mInterstitialAd = InterstitialAd(activity)\n    mInterstitialAd!!.adUnitId = activity.getString(R.string.advance_insert_ad_unit_id)\n    mInterstitialAd!!.adListener = object : AdListener() {\n        override fun onAdLeftApplication() {\n            listener.onAdLeftApplication()\n        }\n\n        override fun onAdFailedToLoad(p0: Int) {\n            listener.onAdFailedToLoad(p0)\n        }\n\n        override fun onAdClosed() {\n            mInterstitialAd!!.loadAd(AdRequest.Builder().build())\n            listener.onAdClosed()\n        }\n\n        override fun onAdOpened() {\n            listener.onAdOpened()\n        }\n\n        override fun onAdLoaded() {\n            listener.onAdLoaded()\n        }\n    }\n    mInterstitialAd!!.loadAd(AdRequest.Builder().build())\n}\n\nfun maybeShowInterstitialAd(): Boolean {\n    if (mInterstitialAd != null) {\n        if (mInterstitialAd!!.isLoaded) {\n            mInterstitialAd!!.show()\n            return true\n        }\n    }\n    return false\n}"
  },
  {
    "path": "presentation/src/production/res/values/ad_strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"app_ad_id\" translatable=\"false\">ca-app-pub-8153753100171943~2173921520</string>\n    <string name=\"advance_banner_ad_unit_id\" translatable=\"false\">ca-app-pub-8153753100171943/2066110317</string>\n    <string name=\"gallery_banner_ad_unit_id\" translatable=\"false\">ca-app-pub-8153753100171943/6743721926</string>\n    <string name=\"advance_insert_ad_unit_id\" translatable=\"false\">ca-app-pub-8153753100171943/9887742086</string>\n</resources>"
  },
  {
    "path": "presentation/src/test/java/com/yalin/style/ExampleUnitTest.java",
    "content": "package com.yalin.style;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\n/**\n * Example local unit test, which will execute on the development machine (host).\n *\n * @see <a href=\"http://d.android.com/tools/testing\">Testing documentation</a>\n */\npublic class ExampleUnitTest {\n\n  @Test\n  public void addition_isCorrect() throws Exception {\n    assertEquals(4, 2 + 2);\n  }\n}"
  },
  {
    "path": "settings.gradle",
    "content": "include ':presentation', ':domain', ':data', ':engine'\n"
  },
  {
    "path": "switch.properties",
    "content": "LOG_ENABLE = false\nENABLE_EXTERNAL_LOG = false"
  }
]