[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: \"Short descriptive title\"\nlabels: bug\nassignees: ''\n\n---\n\n# Description #\n<!-- Describe the bug. A clear and concise description of what the bug is. -->\n\n## Context ##\n<!-- Add any context about the problem here. -->\n\n## Steps to Reproduce ##\n<!-- It is important to let developer know how to replicate the problem without re-reading the description -->\nTo reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\n## Expected behavior ##\n<!-- A clear and concise description of what you expected to happen. If you think it would help, provide a modified screenshot. -->\n\n### Screenshots ###\n<!-- If applicable, add screenshots to help explain your problem.\nIt would be best if you could show what was expected or highlight the problem. -->\n\n## Device info ##\n<!-- please complete the following information -->\n\n  * Device: <!-- e.g. Samsung S10+ -->\n  * OS: <!-- e.g. Android 10 -->\n  * TinyBit Launcher version: <!-- e.g. v4.5 -->\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: \"Great idea title\"\nlabels: enhancement\nassignees: ''\n\n---\n\n**Description**\n<!-- \nIs your feature request related to a problem? Please describe.\nA clear and concise description of what the problem is. \nEx. I'm always frustrated when [...]\n-->\n\n**Solution**\n<!--\nDescribe the solution you'd like\nA clear and concise description of what you want to happen.\n-->\n\n**Alternative solutions**\n<!--\nDescribe alternatives you've considered\nA clear and concise description of any alternative solutions or features you've considered.\n-->\n\n**Additional context**\n<!--\nAdd any other *context* or *screenshots* about the feature request here.\n-->\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n- package-ecosystem: gradle\n  directory: \"/\"\n  schedule:\n    interval: daily\n    time: \"03:00\"\n  open-pull-requests-limit: 10\n"
  },
  {
    "path": ".github/workflows/android.yml",
    "content": "name: Android CI\n\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    branches: [ master ]\n\njobs:\n  build:\n\n    runs-on: ubuntu-latest\n\n    steps:\n    - name: checkout\n      uses: actions/checkout@v4\n    - name: set up JDK 17\n      uses: actions/setup-java@v4\n      with:\n        distribution: 'temurin'\n        java-version: '17'\n    - name: Build with Gradle\n      run: bash ./gradlew build --stacktrace\n"
  },
  {
    "path": ".github/workflows/deploy.yml",
    "content": "name: Deploy to Playstore beta\n\non:\n  workflow_dispatch:\n\njobs:\n  distribute:\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v2\n\n      - uses: actions/setup-ruby@v1\n        with:\n          ruby-version: '2.6'\n                \n      - name: Install bundle\n        run: |\n          bundle config path vendor/bundle\n          bundle install --jobs 4 --retry 3\n          \n      - name: Configure Keystore\n        run: |\n          echo \"$ANDROID_KEYSTORE_FILE\" > keystore.jks.b64\n          base64 -d -i keystore.jks.b64 > app/keystore.jks\n          echo \"storeFile=keystore.jks\" >> keystore.properties\n          echo \"keyAlias=$KEYSTORE_KEY_ALIAS\" >> keystore.properties\n          echo \"storePassword=$KEYSTORE_STORE_PASSWORD\" >> keystore.properties\n          echo \"keyPassword=$KEYSTORE_KEY_PASSWORD\" >> keystore.properties\n        env:\n          ANDROID_KEYSTORE_FILE: ${{ secrets.PLAYSTORE_KEYSTORE }}\n          KEYSTORE_KEY_ALIAS: ${{ secrets.PLAYSTORE_KEY_ALIAS }}\n          KEYSTORE_KEY_PASSWORD: ${{ secrets.PLAYSTORE_KEY_PASSWORD }}\n          KEYSTORE_STORE_PASSWORD: ${{ secrets.PLAYSTORE_STORE_PASSWORD }}\n\n      - name: Create Google Play Config file\n        run : |\n          echo \"$PLAY_CONFIG_JSON\" > play_config.json.b64\n          base64 -d -i play_config.json.b64 > play_config.json\n        env:\n          PLAY_CONFIG_JSON: ${{ secrets.PLAYSTORE_API_JSON }}\n          \n      - name: Distribute app to Beta track 🚀\n        run: bundle exec fastlane beta\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "# This is a basic workflow to help you get started with Actions\n\nname: Android Release\n\non:\n  push:\n    branches:\n      - 'release*'\n\n  # Allows you to run this workflow manually from the Actions tab\n  workflow_dispatch:\n\n# A workflow run is made up of one or more jobs that can run sequentially or in parallel\njobs:\n  # This workflow contains a single job\n  apk:\n    name: Generate APK\n    runs-on: ubuntu-latest\n\n    # Steps represent a sequence of tasks that will be executed as part of the job\n    steps:\n      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it\n      - uses: actions/checkout@v4\n\n      # Runs a single command using the runners shell\n      - name: set up JDK 17\n        uses: actions/setup-java@v4\n        with:\n          distribution: 'temurin'\n          java-version: '17'\n\n      - name: Build debug APK\n        run: bash ./gradlew assembleGithubDebug --stacktrace\n        \n      - name: Upload APK\n        uses: actions/upload-artifact@v4\n        with:\n          name: app\n          path: app/build/outputs/apk/github/debug/app-github-debug.apk\n"
  },
  {
    "path": ".gitignore",
    "content": "# Built application files\n*.apk\n*.ap_\n*.aab\n\n# Files for the ART/Dalvik VM\n*.dex\n\n# Java class files\n*.class\n\n# Generated files\nbin/\ngen/\nout/\nrelease/\n\n# Gradle files\n.gradle/\nbuild/\n\n# Local configuration file (sdk path, etc)\nlocal.properties\n\n# Proguard folder generated by Eclipse\nproguard/\n\n# Log Files\n*.log\n\n# Android Studio Navigation editor temp files\n.navigation/\n\n# Android Studio captures folder\ncaptures/\n\n# IntelliJ\n*.iml\n.idea/workspace.xml\n.idea/tasks.xml\n.idea/gradle.xml\n.idea/assetWizardSettings.xml\n.idea/dictionaries\n.idea/libraries\n# Android Studio 3 in .gitignore file.\n.idea/caches\n.idea/modules.xml\n# Comment next line if keeping position of elements in Navigation Editor is relevant for you\n.idea/navEditor.xml\n# TBog: Keeps changing when I use the IDE\n.idea/misc.xml\n.idea/deploymentTargetDropDown.xml\n.idea/kotlinc.xml\n\n# Keystore files\n# Uncomment the following lines if you do not want to check your keystore files in.\n#*.jks\n#*.keystore\n\n# External native build folder generated in Android Studio 2.2 and later\n.externalNativeBuild\n\n# Google Services (e.g. APIs or Firebase)\n# google-services.json\n\n# Freeline\nfreeline.py\nfreeline/\nfreeline_project_description.json\n\n# fastlane\nfastlane/report.xml\nfastlane/Preview.html\nfastlane/screenshots\nfastlane/test_output\nfastlane/readme.md\n\n# Version control\nvcs.xml\n\n# lint\nlint/intermediates/\nlint/generated/\nlint/outputs/\nlint/tmp/\n# lint/reports/\n\nGemfile.lock\n\n.idea/deploymentTargetSelector.xml\n"
  },
  {
    "path": ".idea/codeStyles/Project.xml",
    "content": "<component name=\"ProjectCodeStyleConfiguration\">\n  <code_scheme name=\"Project\" version=\"173\">\n    <codeStyleSettings language=\"JAVA\">\n      <option name=\"KEEP_CONTROL_STATEMENT_IN_ONE_LINE\" value=\"false\" />\n      <indentOptions>\n        <option name=\"CONTINUATION_INDENT_SIZE\" value=\"4\" />\n      </indentOptions>\n    </codeStyleSettings>\n    <codeStyleSettings language=\"XML\">\n      <indentOptions>\n        <option name=\"CONTINUATION_INDENT_SIZE\" value=\"4\" />\n      </indentOptions>\n      <arrangement>\n        <rules>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>xmlns:android</NAME>\n                  <XML_ATTRIBUTE />\n                  <XML_NAMESPACE>^$</XML_NAMESPACE>\n                </AND>\n              </match>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>xmlns:.*</NAME>\n                  <XML_ATTRIBUTE />\n                  <XML_NAMESPACE>^$</XML_NAMESPACE>\n                </AND>\n              </match>\n              <order>BY_NAME</order>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>.*:id</NAME>\n                  <XML_ATTRIBUTE />\n                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n                </AND>\n              </match>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>.*:name</NAME>\n                  <XML_ATTRIBUTE />\n                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n                </AND>\n              </match>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>name</NAME>\n                  <XML_ATTRIBUTE />\n                  <XML_NAMESPACE>^$</XML_NAMESPACE>\n                </AND>\n              </match>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>style</NAME>\n                  <XML_ATTRIBUTE />\n                  <XML_NAMESPACE>^$</XML_NAMESPACE>\n                </AND>\n              </match>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>.*</NAME>\n                  <XML_ATTRIBUTE />\n                  <XML_NAMESPACE>^$</XML_NAMESPACE>\n                </AND>\n              </match>\n              <order>BY_NAME</order>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>.*</NAME>\n                  <XML_ATTRIBUTE />\n                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n                </AND>\n              </match>\n              <order>ANDROID_ATTRIBUTE_ORDER</order>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>.*</NAME>\n                  <XML_ATTRIBUTE />\n                  <XML_NAMESPACE>.*</XML_NAMESPACE>\n                </AND>\n              </match>\n              <order>BY_NAME</order>\n            </rule>\n          </section>\n        </rules>\n      </arrangement>\n    </codeStyleSettings>\n    <codeStyleSettings language=\"kotlin\">\n      <indentOptions>\n        <option name=\"CONTINUATION_INDENT_SIZE\" value=\"4\" />\n      </indentOptions>\n    </codeStyleSettings>\n  </code_scheme>\n</component>"
  },
  {
    "path": ".idea/codeStyles/codeStyleConfig.xml",
    "content": "<component name=\"ProjectCodeStyleConfiguration\">\n  <state>\n    <option name=\"USE_PER_PROJECT_SETTINGS\" value=\"true\" />\n  </state>\n</component>"
  },
  {
    "path": ".idea/compiler.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"CompilerConfiguration\">\n    <bytecodeTargetLevel target=\"17\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/google-java-format.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"GoogleJavaFormatSettings\">\n    <option name=\"enabled\" value=\"false\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/inspectionProfiles/Project_Default.xml",
    "content": "<component name=\"InspectionProjectProfileManager\">\n  <profile version=\"1.0\">\n    <option name=\"myName\" value=\"Project Default\" />\n    <inspection_tool class=\"CastConflictsWithInstanceof\" enabled=\"true\" level=\"WARNING\" enabled_by_default=\"true\" />\n    <inspection_tool class=\"CastToIncompatibleInterface\" enabled=\"true\" level=\"WARNING\" enabled_by_default=\"true\" />\n    <inspection_tool class=\"ComparableImplementedButEqualsNotOverridden\" enabled=\"true\" level=\"WARNING\" enabled_by_default=\"true\" />\n    <inspection_tool class=\"ConstantConditions\" enabled=\"true\" level=\"WARNING\" enabled_by_default=\"true\">\n      <option name=\"SUGGEST_NULLABLE_ANNOTATIONS\" value=\"false\" />\n      <option name=\"DONT_REPORT_TRUE_ASSERT_STATEMENTS\" value=\"false\" />\n    </inspection_tool>\n    <inspection_tool class=\"NullableProblems\" enabled=\"true\" level=\"WARNING\" enabled_by_default=\"true\">\n      <option name=\"REPORT_NULLABLE_METHOD_OVERRIDES_NOTNULL\" value=\"true\" />\n      <option name=\"REPORT_NOT_ANNOTATED_METHOD_OVERRIDES_NOTNULL\" value=\"true\" />\n      <option name=\"REPORT_NOTNULL_PARAMETER_OVERRIDES_NULLABLE\" value=\"true\" />\n      <option name=\"REPORT_NOT_ANNOTATED_PARAMETER_OVERRIDES_NOTNULL\" value=\"true\" />\n      <option name=\"REPORT_NOT_ANNOTATED_GETTER\" value=\"true\" />\n      <option name=\"REPORT_NOTNULL_PARAMETERS_OVERRIDES_NOT_ANNOTATED\" value=\"true\" />\n      <option name=\"REPORT_NOT_ANNOTATED_SETTER_PARAMETER\" value=\"true\" />\n      <option name=\"REPORT_ANNOTATION_NOT_PROPAGATED_TO_OVERRIDERS\" value=\"true\" />\n      <option name=\"REPORT_NULLS_PASSED_TO_NON_ANNOTATED_METHOD\" value=\"true\" />\n    </inspection_tool>\n  </profile>\n</component>"
  },
  {
    "path": ".idea/jarRepositories.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"RemoteRepositoriesConfiguration\">\n    <remote-repository>\n      <option name=\"id\" value=\"central\" />\n      <option name=\"name\" value=\"Maven Central repository\" />\n      <option name=\"url\" value=\"https://repo1.maven.org/maven2\" />\n    </remote-repository>\n    <remote-repository>\n      <option name=\"id\" value=\"jboss.community\" />\n      <option name=\"name\" value=\"JBoss Community repository\" />\n      <option name=\"url\" value=\"https://repository.jboss.org/nexus/content/repositories/public/\" />\n    </remote-repository>\n    <remote-repository>\n      <option name=\"id\" value=\"BintrayJCenter\" />\n      <option name=\"name\" value=\"BintrayJCenter\" />\n      <option name=\"url\" value=\"https://jcenter.bintray.com/\" />\n    </remote-repository>\n    <remote-repository>\n      <option name=\"id\" value=\"Google\" />\n      <option name=\"name\" value=\"Google\" />\n      <option name=\"url\" value=\"https://dl.google.com/dl/android/maven2/\" />\n    </remote-repository>\n    <remote-repository>\n      <option name=\"id\" value=\"MavenRepo\" />\n      <option name=\"name\" value=\"MavenRepo\" />\n      <option name=\"url\" value=\"https://repo.maven.apache.org/maven2/\" />\n    </remote-repository>\n    <remote-repository>\n      <option name=\"id\" value=\"maven\" />\n      <option name=\"name\" value=\"maven\" />\n      <option name=\"url\" value=\"https://jitpack.io\" />\n    </remote-repository>\n  </component>\n</project>"
  },
  {
    "path": ".idea/migrations.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"ProjectMigrations\">\n    <option name=\"MigrateToGradleLocalJavaHome\">\n      <set>\n        <option value=\"$PROJECT_DIR$\" />\n      </set>\n    </option>\n  </component>\n</project>"
  },
  {
    "path": ".idea/palantir-java-format.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"PalantirJavaFormatSettings\">\n    <option name=\"enabled\" value=\"false\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/render.experimental.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"RenderSettings\">\n    <option name=\"quality\" value=\"1.0\" />\n  </component>\n</project>"
  },
  {
    "path": "Gemfile",
    "content": "source \"https://rubygems.org\"\n\ngem \"fastlane\"\ngem \"rake\"\n"
  },
  {
    "path": "LICENSE.md",
    "content": "\n                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    {project}  Copyright (C) {year}  {fullname}\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<http://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<http://www.gnu.org/philosophy/why-not-lgpl.html>.\n"
  },
  {
    "path": "Privacy-Policy.md",
    "content": "---\ntitle: TinyBit launcher\nlayout: simple\n---\n\n# Privacy policy\n\nThis privacy policy (\"Policy\") describes how the personally identifiable information (\"Personal Information\") you may provide in the \"TBLauncher\" mobile application (\"Mobile Application\" or \"Service\") and any of its related products and services (collectively, \"Services\") is collected, protected and used. It also describes the choices available to you regarding our use of your Personal Information and how you can access and update this information. This Policy is a legally binding agreement between you (\"User\", \"you\" or \"your\") and this Mobile Application developer (\"Operator\", \"we\", \"us\" or \"our\"). By accessing and using the Mobile Application and Services, you acknowledge that you have read, understood, and agree to be bound by the terms of this Policy. This Policy does not apply to the practices of companies that we do not own or control, or to individuals that we do not employ or manage.\n\n## Collection of information\n\nOur top priority is customer data security and, as such, we exercise the no logs policy. We may process only minimal user data, only as much as it is absolutely necessary to maintain the Mobile Application and Services. Information collected automatically is used only to identify potential cases of abuse and establish statistical information regarding the usage of the Mobile Application and Services. This statistical information is not otherwise aggregated in such a way that would identify any particular user of the system.\n\n## Use and processing of collected information\n\nIn order to make the Mobile Application and Services available to you, or to meet a legal obligation, we may need to collect and use certain Personal Information. If you do not provide the information that we request, we may not be able to provide you with the requested products or services. Any of the information we collect from you may be used for the following purposes:\n\n*   Respond to inquiries and offer support\n*   Improve user experience\n*   Run and operate the Mobile Application and Services\n\nProcessing your Personal Information depends on how you interact with the Mobile Application and Services, where you are located in the world and if one of the following applies: (i) you have given your consent for one or more specific purposes; this, however, does not apply, whenever the processing of Personal Information is subject to California Consumer Privacy Act or European data protection law; (ii) provision of information is necessary for the performance of an agreement with you and/or for any pre-contractual obligations thereof; (iii) processing is necessary for compliance with a legal obligation to which you are subject; (iv) processing is related to a task that is carried out in the public interest or in the exercise of official authority vested in us; (v) processing is necessary for the purposes of the legitimate interests pursued by us or by a third party.\n\nNote that under some legislations we may be allowed to process information until you object to such processing (by opting out), without having to rely on consent or any other of the following legal bases below. In any case, we will be happy to clarify the specific legal basis that applies to the processing, and in particular whether the provision of Personal Information is a statutory or contractual requirement, or a requirement necessary to enter into a contract.\n\n## Disclosure of information\n\nDepending on the requested Services or as necessary to complete any transaction or provide any service you have requested, we may share your information with your consent with our trusted third parties that work with us, any other affiliates and subsidiaries we rely upon to assist in the operation of the Mobile Application and Services available to you. We do not share Personal Information with unaffiliated third parties. These service providers are not authorized to use or disclose your information except as necessary to perform services on our behalf or comply with legal requirements. We may share your Personal Information for these purposes only with third parties whose privacy policies are consistent with ours or who agree to abide by our policies with respect to Personal Information. These third parties are given Personal Information they need only in order to perform their designated functions, and we do not authorize them to use or disclose Personal Information for their own marketing or other purposes.\n\n## Retention of information\n\nWe will retain and use your Personal Information for the period necessary to comply with our legal obligations, resolve disputes, and enforce our agreements unless a longer retention period is required or permitted by law. We may use any aggregated data derived from or incorporating your Personal Information after you update or delete it, but not in a manner that would identify you personally. Once the retention period expires, Personal Information shall be deleted. Therefore, the right to access, the right to erasure, the right to rectification and the right to data portability cannot be enforced after the expiration of the retention period.\n\n## Transfer of information\n\nDepending on your location, data transfers may involve transferring and storing your information in a country other than your own. You are entitled to learn about the legal basis of information transfers to a country outside the European Union or to any international organization governed by public international law or set up by two or more countries, such as the UN, and about the security measures taken by us to safeguard your information. If any such transfer takes place, you can find out more by checking the relevant sections of this Policy or inquire with us using the information provided in the contact section.\n\n## The rights of users\n\nYou may exercise certain rights regarding your information processed by us. In particular, you have the right to do the following: (i) you have the right to withdraw consent where you have previously given your consent to the processing of your information; (ii) you have the right to object to the processing of your information if the processing is carried out on a legal basis other than consent; (iii) you have the right to learn if information is being processed by us, obtain disclosure regarding certain aspects of the processing and obtain a copy of the information undergoing processing; (iv) you have the right to verify the accuracy of your information and ask for it to be updated or corrected; (v) you have the right, under certain circumstances, to restrict the processing of your information, in which case, we will not process your information for any purpose other than storing it; (vi) you have the right, under certain circumstances, to obtain the erasure of your Personal Information from us; (vii) you have the right to receive your information in a structured, commonly used and machine readable format and, if technically feasible, to have it transmitted to another controller without any hindrance. This provision is applicable provided that your information is processed by automated means and that the processing is based on your consent, on a contract which you are part of or on pre-contractual obligations thereof.\n\n## The right to object to processing\n\nWhere Personal Information is processed for the public interest, in the exercise of an official authority vested in us or for the purposes of the legitimate interests pursued by us, you may object to such processing by providing a ground related to your particular situation to justify the objection.\n\n## Data protection rights under GDPR\n\nIf you are a resident of the European Economic Area (EEA), you have certain data protection rights and the Operator aims to take reasonable steps to allow you to correct, amend, delete, or limit the use of your Personal Information. If you wish to be informed what Personal Information we hold about you and if you want it to be removed from our systems, please contact us. In certain circumstances, you have the following data protection rights:\n\n*   You have the right to request access to your Personal Information that we store and have the ability to access your Personal Information.\n*   You have the right to request that we correct any Personal Information you believe is inaccurate. You also have the right to request us to complete the Personal Information you believe is incomplete.\n*   You have the right to request the erase your Personal Information under certain conditions of this Policy.\n*   You have the right to object to our processing of your Personal Information.\n*   You have the right to seek restrictions on the processing of your Personal Information. When you restrict the processing of your Personal Information, we may store it but will not process it further.\n*   You have the right to be provided with a copy of the information we have on you in a structured, machine-readable and commonly used format.\n*   You also have the right to withdraw your consent at any time where the Operator relied on your consent to process your Personal Information.\n\nYou have the right to complain to a Data Protection Authority about our collection and use of your Personal Information. For more information, please contact your local data protection authority in the European Economic Area (EEA).\n\n## California privacy rights\n\nIn addition to the rights as explained in this Policy, California residents who provide Personal Information (as defined in the statute) to obtain products or services for personal, family, or household use are entitled to request and obtain from us, once a calendar year, information about the Personal Information we shared, if any, with other businesses for marketing uses. If applicable, this information would include the categories of Personal Information and the names and addresses of those businesses with which we shared such personal information for the immediately prior calendar year (e.g., requests made in the current year will receive information about the prior year). To obtain this information please contact us.\n\n## How to exercise these rights\n\nAny requests to exercise your rights can be directed to the Operator through the contact details provided in this document. Please note that we may ask you to verify your identity before responding to such requests. Your request must provide sufficient information that allows us to verify that you are the person you are claiming to be or that you are the authorized representative of such person. You must include sufficient details to allow us to properly understand the request and respond to it. We cannot respond to your request or provide you with Personal Information unless we first verify your identity or authority to make such a request and confirm that the Personal Information relates to you.\n\n## Privacy of children\n\nWe do not knowingly collect any Personal Information from children. We encourage all children to never submit any Personal Information through the Mobile Application and Services. We encourage parents and legal guardians to monitor their children's Internet usage and to help enforce this Policy by instructing their children never to provide Personal Information through the Mobile Application and Services without their permission. If you have reason to believe that a child has provided Personal Information to us through the Mobile Application and Services, please contact us. You must also be at least 16 years of age to consent to the processing of your Personal Information in your country (in some countries we may allow your parent or guardian to do so on your behalf).\n\n## Links to other resources\n\nThe Mobile Application and Services contain links to other resources that are not owned or controlled by us. Please be aware that we are not responsible for the privacy practices of such other resources or third parties. We encourage you to be aware when you leave the Mobile Application and Services and to read the privacy statements of each and every resource that may collect Personal Information.\n\n## Information security\n\nWe secure information you provide on computer servers in a controlled, secure environment, protected from unauthorized access, use, or disclosure. We maintain reasonable administrative, technical, and physical safeguards in an effort to protect against unauthorized access, use, modification, and disclosure of Personal Information in its control and custody. However, no data transmission over the Internet or wireless network can be guaranteed. Therefore, while we strive to protect your Personal Information, you acknowledge that (i) there are security and privacy limitations of the Internet which are beyond our control; (ii) the security, integrity, and privacy of any and all information and data exchanged between you and the Mobile Application and Services cannot be guaranteed; and (iii) any such information and data may be viewed or tampered with in transit by a third party, despite best efforts.\n\n## Data breach\n\nIn the event we become aware that the security of the Mobile Application and Services has been compromised or users Personal Information has been disclosed to unrelated third parties as a result of external activity, including, but not limited to, security attacks or fraud, we reserve the right to take reasonably appropriate measures, including, but not limited to, investigation and reporting, as well as notification to and cooperation with law enforcement authorities. In the event of a data breach, we will make reasonable efforts to notify affected individuals if we believe that there is a reasonable risk of harm to the user as a result of the breach or if notice is otherwise required by law. When we do, we will post a notice in the Mobile Application.\n\n## Changes and amendments\n\nWe reserve the right to modify this Policy or its terms relating to the Mobile Application and Services from time to time in our discretion and will notify you of any material changes to the way in which we treat Personal Information. When we do, we will revise the updated date at the bottom of this page. We may also provide notice to you in other ways in our discretion, such as through contact information you have provided. Any updated version of this Policy will be effective immediately upon the posting of the revised Policy unless otherwise specified. Your continued use of the Mobile Application and Services after the effective date of the revised Policy (or such other act specified at that time) will constitute your consent to those changes. However, we will not, without your consent, use your Personal Information in a manner materially different than what was stated at the time your Personal Information was collected.\n\n## Acceptance of this policy\n\nYou acknowledge that you have read this Policy and agree to all its terms and conditions. By accessing and using the Mobile Application and Services you agree to be bound by this Policy. If you do not agree to abide by the terms of this Policy, you are not authorized to access or use the Mobile Application and Services. <!-- This privacy policy was created with the [privacy policy generator](https://www.websitepolicies.com/privacy-policy-generator). -->\n\n## Contacting us\n\nIf you would like to contact us to understand more about this Policy or wish to contact us concerning any matter relating to individual rights and your Personal Information, you may send an email to privacy@tbog.rocks.\n\nThis document was last updated on April 8, 2021\n"
  },
  {
    "path": "README.md",
    "content": "# TinyBit Launcher\n\n[<img src=\"https://github.com/TBog/TBLauncher/workflows/Android%20CI/badge.svg\"\n      alt=\"Android CI\"\n      height=\"22\"/>](https://github.com/TBog/TBLauncher/actions/)\n[<img src=\"https://app.codacy.com/project/badge/Grade/367f62e2ae68488fbee63cf2dfe267db\"\n      alt=\"Codacy Badge\"\n      height=\"22\"/>](https://www.codacy.com/gh/TBog/TBLauncher/dashboard?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=TBog/TBLauncher&amp;utm_campaign=Badge_Grade)\n[<img src=\"https://img.shields.io/github/v/release/TBog/TBLauncher.svg?logo=github&label=GitHub\"\n      alt=\"GitHub Releases\"\n      height=\"22\"/>](https://github.com/TBog/TBLauncher/releases)\n[<img src=\"https://img.shields.io/f-droid/v/rocks.tbog.tblauncher.svg?logo=f-droid&label=F-Droid\"\n      alt=\"F-Droid Releases\"\n      height=\"22\"/>](https://f-droid.org/packages/rocks.tbog.tblauncher/)\n[<img src=\"https://img.shields.io/endpoint?label=Play%20Store&style=flat&cacheSeconds=65536&url=https%3A%2F%2Fplay.cuzi.workers.dev%2Fplay%3Fi%3Drocks.tbog.tblauncher%26l%3DGoogle%2520Play%26m%3D%24version\"\n      alt=\"Playstore\"\n      height=\"22\"/>](https://play.google.com/store/apps/details?id=rocks.tbog.tblauncher)\n\n## This is _the_ launcher used and developed by TBog\n\nMotives for developing ~~a new~~ my own launcher:\n- Clean main screen to enjoy the wallpaper \n- Fast access to apps by searching \n- Icon Pack compatible\n- Ability to customize colors and behaviors \n\n### How it looks like\n\n| Homescreen | Search `tbl` | Edit QuickList | Launcher settings | Customize any icon |\n| :---: | :---: | :---: | :---: | :---: |\n| [![Homescreen](https://imgur.com/Idkhx5v.png)](https://imgur.com/Idkhx5v) | ![Search `tbl`](https://raw.githubusercontent.com/TBog/TBLauncher/master/fastlane/metadata/android/en-US/images/phoneScreenshots/2_en-US.jpeg) | ![Edit QuickList](https://raw.githubusercontent.com/TBog/TBLauncher/master/fastlane/metadata/android/en-US/images/phoneScreenshots/4_en-US.jpeg) | [![Settings](https://imgur.com/J8EslbJ.png)](https://imgur.com/J8EslbJ) | [![Customize any icon](https://imgur.com/jxvRmzV.png)](https://imgur.com/jxvRmzV) |\n\n### Where one can get it\n\n[<img src=\"https://fdroid.gitlab.io/artwork/badge/get-it-on.png\"\n     alt=\"Get it on F-Droid\"\n     height=\"80\">](https://f-droid.org/packages/rocks.tbog.tblauncher/)\n[<img src=\"https://play.google.com/intl/en_us/badges/images/generic/en-play-badge.png\"\n     alt=\"Get it on Google Play\"\n     height=\"80\">](https://play.google.com/store/apps/details?id=rocks.tbog.tblauncher)\n[<img src=\"https://i.ibb.co/q0mdc4Z/get-it-on-github.png\"\n     alt=\"Get it on Github\"\n     height=\"80\">](https://github.com/TBog/TBLauncher/releases)\n\n### Translation\n\n[<img src=\"https://hosted.weblate.org/widgets/tblauncher/-/multi-auto.svg\"\n      alt=\"Translation status\" />](https://hosted.weblate.org/engage/tblauncher/)\n\n<footer>\n      <hr>\n      <p>The initial idea and code came from <a href=\"https://github.com/Neamar/KISS\" style=\"display: inline\">KISS</a></p>\n      <p>TinyBit Launcher icon (2022)<br>Copyright 2022 <a href=\"https://www.onnno.nl/\" style=\"display: inline\">Onno van den Dungen</a><br><a href=\"https://creativecommons.org/licenses/by-sa/4.0/\" style=\"display: inline\">Licensed under CC BY-SA 4.0</a></p>\n      <p>Some SVG icons came from <a href=\"https://www.flaticon.com/\" title=\"Flaticon\">www.flaticon.com</a> and were made by\n            <ul>\n                  <li><a href=\"https://www.freepik.com\" title=\"Freepik\">Freepik</a></li>\n                  <li><a href=\"https://www.flaticon.com/authors/swifticons\" title=\"Swifticons\">Swifticons</a></li>\n                  <li><a href=\"https://www.flaticon.com/authors/eucalyp\" title=\"Eucalyp\">Eucalyp</a></li>\n                  <li><a href=\"https://www.flaticon.com/authors/monkik\" title=\"monkik\">monkik</a></li>\n                  <li><a href=\"https://www.flaticon.com/authors/prettycons\" title=\"prettycons\">prettycons</a></li>\n            </ul>\n      </p>\n      <p>The SVG files were first optimized with <a href=\"https://github.com/jakearchibald/svgomg/\">SVGOMG!</a> then imported with Android Studio</p>\n</footer>\n"
  },
  {
    "path": "_config.yml",
    "content": "description: Android launcher with icon pack support, search to launch apps and contacts, color and behaviour customizable\nshow_downloads: true\ntheme: jekyll-theme-hacker\ngithub:\n  apk_url: https://github.com/TBog/TBLauncher/releases/latest/download/app-release.apk\n"
  },
  {
    "path": "_layouts/default.html",
    "content": "<!DOCTYPE html>\n<html lang=\"{{ site.lang | default: \"en-US\" }}\">\n  <head>\n    <meta charset='utf-8'>\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <link rel=\"stylesheet\" href=\"{{ '/assets/css/style.css?v=' | append: site.github.build_revision | relative_url }}\">\n    {% include head-custom.html %}\n\n{% seo %}\n  </head>\n\n  <body>\n\n    <header>\n      <div class=\"container\">\n        <a id=\"a-title\" href=\"{{ '/' | relative_url }}\">\n          <h1>{{ site.title | default: site.github.repository_name }}</h1>\n        </a>\n        <h2>{{ site.description | default: site.github.project_tagline }}</h2>\n\n        <section id=\"downloads\">\n          {% if site.show_downloads %}\n            <a href=\"{{ site.github.zip_url }}\" class=\"btn\">Download sources as .zip</a>\n            <a href=\"{{ site.github.tar_url }}\" class=\"btn\">Download sources as .tar.gz</a>\n            <a href=\"{{ site.github.apk_url }}\" class=\"btn\">Download release apk</a>\n          {% endif %}\n          <a href=\"{{ site.github.repository_url }}\" class=\"btn btn-github\"><span class=\"icon\"></span>View project on GitHub</a>\n        </section>\n      </div>\n    </header>\n\n    <div class=\"container\">\n      <section id=\"main_content\">\n        {{ content }}\n      </section>\n    </div>\n  </body>\n</html>\n"
  },
  {
    "path": "_layouts/simple.html",
    "content": "<!DOCTYPE html>\n<html lang=\"{{ site.lang | default: \"en-US\" }}\">\n  <head>\n    <meta charset='utf-8'>\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <link rel=\"stylesheet\" href=\"{{ '/assets/css/style.css?v=' | append: site.github.build_revision | relative_url }}\">\n    {% include head-custom.html %}\n\n{% seo %}\n  </head>\n\n  <body>\n    <header>\n      <div class=\"container\">\n        <h1>{{ page.title | default: site.github.repository_name }}</h1>\n      </div>\n    </header>\n    <div class=\"container\">\n      <section id=\"main_content\">\n        {{ content }}\n      </section>\n    </div>\n  </body>\n</html>\n\n"
  },
  {
    "path": "app/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "app/build.gradle",
    "content": "apply plugin: 'com.android.application'\napply plugin: 'kotlin-android'\napply plugin: 'com.getkeepsafe.dexcount'\napply plugin: 'com.dipien.byebyejetifier'\n\nandroid {\n    namespace 'rocks.tbog.tblauncher'\n    compileSdk 34\n\n    defaultConfig {\n        applicationId \"rocks.tbog.tblauncher\"\n        minSdk 19\n        targetSdk 33\n        versionCode 43\n        versionName \"7.5\"\n        vectorDrawables.useSupportLibrary = true\n        multiDexEnabled true\n    }\n\n    buildTypes {\n        release {\n            minifyEnabled true\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n            resValue \"string\", \"app_name_dynamic\", \"@string/app_name\"\n        }\n        debug {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n            applicationIdSuffix '.debug'\n            versionNameSuffix '-dbg'\n            resValue \"string\", \"app_name_dynamic\", \"@string/app_name_debug\"\n        }\n    }\n    flavorDimensions = [\"store\"]\n    productFlavors {\n        playstore {\n            dimension \"store\"\n            buildConfigField 'boolean', 'SHOW_PRIVACY_POLICY', 'true'\n            buildConfigField 'boolean', 'SHOW_RATE_APP', 'true'\n        }\n        fdroid {\n            dimension \"store\"\n            buildConfigField 'boolean', 'SHOW_PRIVACY_POLICY', 'false'\n            buildConfigField 'boolean', 'SHOW_RATE_APP', 'true'\n        }\n        github {\n            dimension \"store\"\n            buildConfigField 'boolean', 'SHOW_PRIVACY_POLICY', 'false'\n            buildConfigField 'boolean', 'SHOW_RATE_APP', 'false'\n        }\n    }\n    compileOptions {\n        coreLibraryDesugaringEnabled true\n        sourceCompatibility = JavaVersion.VERSION_17\n        targetCompatibility = JavaVersion.VERSION_17\n    }\n    kotlinOptions {\n        jvmTarget = JavaVersion.VERSION_17.toString()\n    }\n    buildFeatures {\n        viewBinding true\n        buildConfig = true\n    }\n    lint {\n        // disable quantity translation errors\n        disable 'ImpliedQuantity'\n    }\n}\n\nconfigurations {\n    compileClasspath {\n        resolutionStrategy.force 'com.github.yalantis:ucrop:2.2.8'\n    }\n}\n\ndependencies {\n    implementation fileTree(dir: 'libs', include: ['*.jar'])\n\n    //noinspection KtxExtensionAvailable\n    implementation(\"androidx.preference:preference:$preference_version\") {\n        exclude group: 'androidx.lifecycle', module: 'lifecycle-viewmodel'\n        exclude group: 'androidx.lifecycle', module: 'lifecycle-viewmodel-ktx'\n    }\n    implementation \"ch.acra:acra-mail:$acra_version\"\n    implementation \"ch.acra:acra-dialog:$acra_version\"\n    implementation \"ch.acra:acra-core:$acra_version\"\n    implementation \"androidx.asynclayoutinflater:asynclayoutinflater:1.0.0\"\n    implementation 'androidx.annotation:annotation:1.8.0'\n    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'\n    implementation 'androidx.core:core-ktx:1.13.1'\n    implementation \"androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version\"\n    implementation \"androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version\"\n    implementation 'com.google.android.material:material:1.12.0'\n    implementation('com.github.dhaval2404:imagepicker:2.1', {\n        exclude group: 'com.squareup.okhttp3'\n    })\n    implementation \"androidx.multidex:multidex:$multidex_version\"\n    // 2.0.0 not supported, as \"Unsupported desugared library configuration version, please upgrade the D8/R8 compiler.\" Android Gradle Plugin would need to be 7.4.0-alpha10\n    coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'\n}\nrepositories {\n    mavenCentral()\n}\n\n//configurations.all {\n//    resolutionStrategy.eachDependency { DependencyResolveDetails details ->\n//        def requested = details.requested\n//        if (requested.group == 'org.jetbrains.kotlin'\n//                && (requested.name == 'kotlin-reflect'\n//                || requested.name.startsWith('kotlin-stdlib'))\n//        ) {\n//            details.useVersion kotlin_version\n//        }\n//    }\n//}\n\n//allprojects {\n//    tasks.withType(JavaCompile) {\n//        options.compilerArgs << \"-Xlint:unchecked\" << \"-Xlint:deprecation\"\n//    }\n//}\n\n//tasks.whenTaskAdded { task ->\n//    if (task.name == 'assembleDebug') {\n//        task.dependsOn lint\n//        task.mustRunAfter lint\n//    }\n//}\n"
  },
  {
    "path": "app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\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\n# Allow the access modifiers of classes and class members to be modified, while optimizing.\n-allowaccessmodification\n\n-dontobfuscate\n\n## If we are obfuscating we need to keep the dataprovider class names\n#-keepnames class rocks.tbog.tblauncher.dataprovider.*\n#\n## If we are obfuscating check rocks/tbog/tblauncher/utils/EdgeGlowHelper.java\n#-keepnames class android.**\n#-keepclassmembernames class android.** {\n#    private <fields>;\n#}\n#-keepnames class androidx.**\n#-keepclassmembernames class androidx.** {\n#    private <fields>;\n#}\n\n# Need to keep constructor of worker\n-keepclassmembers class * extends rocks.tbog.tblauncher.WorkAsync.AsyncTask { <init>(...); }\n# Keep constructor of ViewHolder\n-keepclassmembers class * extends rocks.tbog.tblauncher.utils.ViewHolderAdapter$ViewHolder { <init>(...); }\n\n# We don't use okhttp3 from com.github.dhaval2404:imagepicker so don't warn that it's missing\n-dontwarn okhttp3.**\n-dontwarn okio.BufferedSource\n-dontwarn okio.Okio\n-dontwarn okio.Sink\n\n# From https://github.com/Yalantis/uCrop\n-dontwarn com.yalantis.ucrop**\n-keep class com.yalantis.ucrop** { *; }\n-keep interface com.yalantis.ucrop** { *; }\n\n# ACRA\n-keepattributes *Annotation*\n-dontwarn javax.annotation.processing.AbstractProcessor\n-dontwarn javax.annotation.processing.SupportedOptions\n-keep class javax.annotation.processing.** { *; }\n-keep interface javax.annotation.processing.** { *; }\n"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <!-- Self explanatory -->\n    <uses-permission android:name=\"android.permission.READ_CONTACTS\" />\n    <!-- To call a phone number directly without displaying the dialer -->\n    <uses-permission android:name=\"android.permission.CALL_PHONE\" />\n    <!-- Ability to uninstall an app from KISS -->\n    <uses-permission android:name=\"android.permission.REQUEST_DELETE_PACKAGES\" />\n    <!-- Ability to open the notification bar -->\n    <uses-permission android:name=\"android.permission.EXPAND_STATUS_BAR\" />\n    <uses-permission\n        android:name=\"android.permission.QUERY_ALL_PACKAGES\"\n        tools:ignore=\"QueryAllPackagesPermission\" />\n\n    <uses-feature\n        android:name=\"android.hardware.telephony\"\n        android:required=\"false\" />\n    <uses-feature\n        android:name=\"android.hardware.bluetooth\"\n        android:required=\"false\" />\n    <uses-feature\n        android:name=\"android.hardware.wifi\"\n        android:required=\"false\" />\n    <uses-feature\n        android:name=\"android.hardware.nfc\"\n        android:required=\"false\" />\n\n    <!-- more info on queries here: https://medium.com/androiddevelopers/package-visibility-in-android-11-cc857f221cd9 -->\n    <queries>\n        <!-- for exporting the settings -->\n        <intent>\n            <action android:name=\"android.intent.action.SEND\" />\n            <data android:mimeType=\"text/xml\" />\n        </intent>\n    </queries>\n\n    <application\n        android:name=\".TBApplication\"\n        android:allowBackup=\"true\"\n        android:fullBackupContent=\"@xml/backup_descriptor\"\n        android:dataExtractionRules=\"@xml/data_extraction_rules\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name_dynamic\"\n        android:roundIcon=\"@mipmap/ic_launcher_round\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/SettingsDialogTheme.Default\">\n        <activity\n            android:name=\".TBLauncherActivity\"\n            android:clearTaskOnLaunch=\"true\"\n            android:configChanges=\"orientation|keyboardHidden|screenSize\"\n            android:excludeFromRecents=\"true\"\n            android:exported=\"true\"\n            android:launchMode=\"singleTask\"\n            android:resizeableActivity=\"true\"\n            android:screenOrientation=\"user\"\n            android:stateNotNeeded=\"true\"\n            android:taskAffinity=\".TBLauncherActivity\"\n            android:theme=\"@style/AppThemeTransparent\"\n            android:windowSoftInputMode=\"stateAlwaysHidden|adjustResize\"\n            tools:ignore=\"UnusedAttribute\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n                <category android:name=\"android.intent.category.HOME\" />\n                <category android:name=\"android.intent.category.DEFAULT\" />\n            </intent-filter>\n            <intent-filter>\n                <action android:name=\"android.intent.action.SEND\" />\n                <category android:name=\"android.intent.category.DEFAULT\" />\n                <data android:mimeType=\"text/plain\" />\n            </intent-filter>\n        </activity>\n        <activity\n            android:name=\".SettingsActivity\"\n            android:allowTaskReparenting=\"true\"\n            android:exported=\"true\"\n            android:label=\"@string/menu_popup_launcher_settings\"\n            android:launchMode=\"singleTask\"\n            android:parentActivityName=\".TBLauncherActivity\"\n            android:screenOrientation=\"user\"\n            android:taskAffinity=\".SettingsActivity\"\n            android:theme=\"@style/SettingsTheme\">\n            <!-- Parent activity meta-data to support API level 4+ -->\n            <meta-data\n                android:name=\"android.support.PARENT_ACTIVITY\"\n                android:value=\".TBLauncherActivity\" />\n            <intent-filter>\n                <action android:name=\"android.intent.action.APPLICATION_PREFERENCES\" />\n                <action android:name=\"com.sec.android.intent.action.SEC_APPLICATION_SETTINGS\" />\n                <category android:name=\"android.intent.category.PREFERENCE\" />\n                <category android:name=\"android.intent.category.DEFAULT\" />\n            </intent-filter>\n        </activity>\n        <activity\n            android:name=\".widgets.PickAppWidgetActivity\"\n            android:exported=\"false\"\n            android:launchMode=\"singleTop\"\n            android:parentActivityName=\".TBLauncherActivity\"\n            android:screenOrientation=\"portrait\"\n            android:theme=\"@style/NoTitleDialogTheme\">\n            <meta-data\n                android:name=\"android.support.PARENT_ACTIVITY\"\n                android:value=\".TBLauncherActivity\" />\n        </activity>\n\n        <activity\n            android:name=\".PinShortcutConfirm\"\n            android:allowTaskReparenting=\"true\"\n            android:excludeFromRecents=\"true\"\n            android:exported=\"false\"\n            android:launchMode=\"singleInstance\"\n            android:noHistory=\"true\"\n            android:screenOrientation=\"user\"\n            android:taskAffinity=\".TBLauncherActivity\"\n            android:theme=\"@style/TitleDialogTheme\"\n            tools:ignore=\"NewApi\">\n            <intent-filter>\n                <action android:name=\"android.content.pm.action.CONFIRM_PIN_SHORTCUT\" />\n            </intent-filter>\n        </activity>\n\n        <activity\n            android:name=\".DummyLauncherActivity\"\n            android:enabled=\"false\"\n            android:exported=\"true\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.HOME\" />\n                <category android:name=\"android.intent.category.DEFAULT\" />\n            </intent-filter>\n        </activity>\n\n        <meta-data\n            android:name=\"android.max_aspect\"\n            android:value=\"3\" />\n\n        <service android:name=\".dataprovider.AppProvider\" />\n        <service android:name=\".dataprovider.ContactsProvider\" />\n        <service android:name=\".dataprovider.ShortcutsProvider\" />\n\n        <provider\n            android:name=\"androidx.core.content.FileProvider\"\n            android:authorities=\"${applicationId}.provider\"\n            android:exported=\"false\"\n            android:grantUriPermissions=\"true\">\n\n            <meta-data\n                android:name=\"android.support.FILE_PROVIDER_PATHS\"\n                android:resource=\"@xml/file_paths\" />\n        </provider>\n\n        <receiver\n            android:name=\".DeviceAdmin\"\n            android:exported=\"true\"\n            android:permission=\"android.permission.BIND_DEVICE_ADMIN\">\n            <meta-data\n                android:name=\"android.app.device_admin\"\n                android:resource=\"@xml/policies\" />\n            <intent-filter>\n                <action android:name=\"android.app.action.DEVICE_ADMIN_ENABLED\" />\n                <action android:name=\"android.app.action.DEVICE_ADMIN_DISABLED\" />\n            </intent-filter>\n        </receiver>\n    </application>\n\n</manifest>"
  },
  {
    "path": "app/src/main/java/net/mm2d/color/chooser/ColorChooserDialog.kt",
    "content": "/*\n * Copyright (c) 2018 大前良介 (OHMAE Ryosuke)\n *\n * This software is released under the MIT License.\n * http://opensource.org/licenses/MIT\n */\n\npackage net.mm2d.color.chooser\n\nimport android.app.Dialog\nimport android.content.DialogInterface\nimport android.graphics.Color\nimport android.os.Bundle\nimport androidx.annotation.ColorInt\nimport androidx.appcompat.app.AlertDialog\nimport androidx.core.os.bundleOf\nimport androidx.fragment.app.DialogFragment\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.FragmentActivity\nimport androidx.fragment.app.FragmentManager\nimport androidx.lifecycle.LifecycleOwner\nimport rocks.tbog.tblauncher.databinding.Mm2dCcColorChooserBinding\n\n/**\n * Color chooser dialog\n */\nobject ColorChooserDialog {\n    private const val KEY_REQUEST_KEY = \"KEY_REQUEST_KEY\"\n    const val KEY_INITIAL_COLOR = \"KEY_INITIAL_COLOR\"\n    private const val KEY_WITH_ALPHA = \"KEY_WITH_ALPHA\"\n    const val KEY_INITIAL_TAB = \"KEY_INITIAL_TAB\"\n    private const val RESULT_KEY_COLOR = \"RESULT_KEY_COLOR\"\n    private const val RESULT_KEY_CANCEL = \"RESULT_KEY_CANCEL\"\n    private const val TAG = \"ColorChooserDialog\"\n    const val TAB_PALETTE: Int = 0\n    const val TAB_HSV: Int = 1\n    const val TAB_RGB: Int = 2\n\n    /**\n     * Register result listener.\n     *\n     * Call at the timing of onCreate of activity.\n     *\n     * @param activity Caller fragment activity\n     * @param requestKey Request Key, pass the same value to the `show`\n     * @param onSelect Listener receiving the result\n     * @param onCancel Listener receiving a cancel event\n     */\n    fun registerListener(\n        activity: FragmentActivity,\n        requestKey: String,\n        onSelect: (color: Int) -> Unit,\n        onCancel: (() -> Unit)? = null,\n    ) {\n        registerListener(\n            activity.supportFragmentManager,\n            requestKey,\n            activity,\n            onSelect,\n            onCancel,\n        )\n    }\n\n    /**\n     * Register result listener.\n     *\n     * Call at the timing of onViewCreated of fragment.\n     *\n     * @param fragment Caller fragment\n     * @param requestKey Request Key, pass the same value to the `show`\n     * @param onSelect Listener receiving the result\n     * @param onCancel Listener receiving a cancel event\n     */\n    fun registerListener(\n        fragment: Fragment,\n        requestKey: String,\n        onSelect: (color: Int) -> Unit,\n        onCancel: (() -> Unit)? = null,\n    ) {\n        registerListener(\n            fragment.childFragmentManager,\n            requestKey,\n            fragment.viewLifecycleOwner,\n            onSelect,\n            onCancel,\n        )\n    }\n\n    private fun registerListener(\n        manager: FragmentManager,\n        requestKey: String,\n        lifecycleOwner: LifecycleOwner,\n        onSelect: (color: Int) -> Unit,\n        onCancel: (() -> Unit)?,\n    ) {\n        manager.setFragmentResultListener(requestKey, lifecycleOwner) { _, result ->\n            if (result.getBoolean(RESULT_KEY_CANCEL)) {\n                onCancel?.invoke()\n            } else {\n                onSelect.invoke(result.getInt(RESULT_KEY_COLOR))\n            }\n        }\n    }\n\n    /**\n     * Show dialog.\n     *\n     * @param activity FragmentActivity\n     * @param requestKey Request Key used for registration with registerListener\n     * @param initialColor initial color\n     * @param withAlpha if true, alpha section is enabled\n     * @param initialTab initial tab, TAB_PALETTE/TAB_HSV/TAB_RGB\n     */\n    fun show(\n        activity: FragmentActivity,\n        requestKey: String,\n        @ColorInt initialColor: Int = Color.WHITE,\n        withAlpha: Boolean = false,\n        initialTab: Int = TAB_PALETTE\n    ) {\n        show(\n            activity.supportFragmentManager,\n            bundleOf(\n                KEY_REQUEST_KEY to requestKey,\n                KEY_INITIAL_COLOR to initialColor,\n                KEY_WITH_ALPHA to withAlpha,\n                KEY_INITIAL_TAB to initialTab,\n            )\n        )\n    }\n\n    /**\n     * Show dialog.\n     *\n     * @param fragment Fragment\n     * @param requestKey Request Key used for registration with registerListener\n     * @param initialColor initial color\n     * @param withAlpha if true, alpha section is enabled\n     * @param initialTab initial tab, TAB_PALETTE/TAB_HSV/TAB_RGB\n     */\n    fun show(\n        fragment: Fragment,\n        requestKey: String,\n        @ColorInt initialColor: Int = Color.WHITE,\n        withAlpha: Boolean = false,\n        initialTab: Int = TAB_PALETTE\n    ) {\n        show(\n            fragment.childFragmentManager,\n            bundleOf(\n                KEY_REQUEST_KEY to requestKey,\n                KEY_INITIAL_COLOR to initialColor,\n                KEY_WITH_ALPHA to withAlpha,\n                KEY_INITIAL_TAB to initialTab,\n            )\n        )\n    }\n\n    private fun show(manager: FragmentManager, arguments: Bundle) {\n        if (manager.findFragmentByTag(TAG) != null) return\n        if (manager.isStateSaved) return\n        ColorChooserDialogImpl().also {\n            it.arguments = arguments\n        }.show(manager, TAG)\n    }\n\n    internal class ColorChooserDialogImpl : DialogFragment() {\n        private lateinit var colorChooserView: ColorChooserView\n\n        override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {\n            val activity = requireActivity()\n            colorChooserView =\n                Mm2dCcColorChooserBinding.inflate(activity.layoutInflater).root\n\n            if (savedInstanceState != null) {\n                val tab = savedInstanceState.getInt(KEY_INITIAL_TAB, 0)\n                colorChooserView.setCurrentItem(tab)\n                val color = savedInstanceState.getInt(KEY_INITIAL_COLOR, 0)\n                colorChooserView.init(color)\n            } else {\n                val arguments = requireArguments()\n                val tab = arguments.getInt(KEY_INITIAL_TAB, 0)\n                colorChooserView.setCurrentItem(tab)\n                val color = arguments.getInt(KEY_INITIAL_COLOR, 0)\n                colorChooserView.init(color)\n            }\n            colorChooserView.setWithAlpha(requireArguments().getBoolean(KEY_WITH_ALPHA))\n            return AlertDialog.Builder(activity)\n                .setView(colorChooserView)\n                .setPositiveButton(\"OK\") { _, _ ->\n                    notifySelect()\n                }\n                .setNegativeButton(\"Cancel\") { dialog, _ ->\n                    dialog.cancel()\n                }\n                .create()\n        }\n\n        override fun onSaveInstanceState(outState: Bundle) {\n            super.onSaveInstanceState(outState)\n            outState.putInt(KEY_INITIAL_TAB, colorChooserView.getCurrentItem())\n            outState.putInt(KEY_INITIAL_COLOR, colorChooserView.color)\n        }\n\n        override fun onCancel(dialog: DialogInterface) {\n            val key = requireArguments().getString(KEY_REQUEST_KEY) ?: return\n            parentFragmentManager.setFragmentResult(\n                key, bundleOf(RESULT_KEY_CANCEL to true)\n            )\n        }\n\n        private fun notifySelect() {\n            val key = requireArguments().getString(KEY_REQUEST_KEY) ?: return\n            parentFragmentManager.setFragmentResult(\n                key, bundleOf(\n                    RESULT_KEY_CANCEL to false,\n                    RESULT_KEY_COLOR to colorChooserView.color,\n                )\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/net/mm2d/color/chooser/ColorChooserView.kt",
    "content": "/*\n * Copyright (c) 2018 大前良介 (OHMAE Ryosuke)\n *\n * This software is released under the MIT License.\n * http://opensource.org/licenses/MIT\n */\n\npackage net.mm2d.color.chooser\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.View\nimport androidx.constraintlayout.widget.ConstraintLayout\nimport androidx.core.graphics.alpha\nimport androidx.core.view.doOnLayout\nimport androidx.lifecycle.MutableLiveData\nimport com.google.android.material.tabs.TabLayoutMediator\nimport rocks.tbog.tblauncher.databinding.Mm2dCcViewDialogBinding\nimport net.mm2d.color.chooser.util.toOpacity\n\ninternal class ColorChooserView\n@JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null,\n    defStyleAttr: Int = 0\n) : ConstraintLayout(context, attrs, defStyleAttr), ColorLiveDataOwner {\n    private val colorLiveData: MutableLiveData<Int> = MutableLiveData()\n    private val binding: Mm2dCcViewDialogBinding =\n        Mm2dCcViewDialogBinding.inflate(LayoutInflater.from(context), this)\n    val color: Int\n        get() = binding.controlView.color\n\n    fun init(color: Int) {\n        colorLiveData.value = color.toOpacity()\n        binding.controlView.setAlpha(color.alpha)\n        val pageTitles: List<String> = listOf(\"palette\", \"hsv\", \"rgb\")\n        val pageViews: List<View> = listOf(\n            PaletteView(context), HsvView(context), SliderView(context),\n        )\n        binding.viewPager.adapter = ViewPagerAdapter(pageViews)\n        TabLayoutMediator(binding.tabLayout, binding.viewPager) { tab, position ->\n            tab.text = pageTitles[position]\n        }.attach()\n    }\n\n    fun setCurrentItem(position: Int) {\n        binding.viewPager.doOnLayout {\n            binding.viewPager.post {\n                binding.viewPager.setCurrentItem(position, false)\n            }\n        }\n    }\n\n    fun getCurrentItem(): Int = binding.viewPager.currentItem\n\n    fun setWithAlpha(withAlpha: Boolean) {\n        binding.controlView.setWithAlpha(withAlpha)\n    }\n\n    override fun getColorLiveData(): MutableLiveData<Int> = colorLiveData\n}\n"
  },
  {
    "path": "app/src/main/java/net/mm2d/color/chooser/ColorLiveDataOwner.kt",
    "content": "package net.mm2d.color.chooser\n\nimport android.view.View\nimport androidx.lifecycle.MutableLiveData\n\ninternal interface ColorLiveDataOwner {\n    fun getColorLiveData(): MutableLiveData<Int>\n}\n\ninternal fun View.findColorLiveDataOwner(): ColorLiveDataOwner? {\n    if (this is ColorLiveDataOwner) return this\n    val parent = parent\n    return if (parent !is View) null else parent.findColorLiveDataOwner()\n}\n"
  },
  {
    "path": "app/src/main/java/net/mm2d/color/chooser/ColorObserverDelegate.kt",
    "content": "package net.mm2d.color.chooser\n\nimport android.view.View\nimport androidx.lifecycle.MutableLiveData\nimport androidx.lifecycle.Observer\nimport androidx.lifecycle.distinctUntilChanged\n\ninternal class ColorObserverDelegate<T>(\n    private val target: T\n) where T : View,\n        T : Observer<Int> {\n    private var colorLiveData: MutableLiveData<Int>? = null\n\n    fun onAttachedToWindow() {\n        val owner = target.findColorLiveDataOwner()\n            ?: throw IllegalStateException(\"parent is not ColorLiveDataOwner\")\n        val liveData = owner.getColorLiveData()\n        liveData.distinctUntilChanged()\n            .observeForever(target)\n        colorLiveData = liveData\n    }\n\n    fun onDetachedFromWindow() {\n        colorLiveData?.removeObserver(target)\n        colorLiveData = null\n    }\n\n    fun post(color: Int) {\n        colorLiveData?.postValue(color)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/net/mm2d/color/chooser/ControlView.kt",
    "content": "/*\n * Copyright (c) 2018 大前良介 (OHMAE Ryosuke)\n *\n * This software is released under the MIT License.\n * http://opensource.org/licenses/MIT\n */\n\npackage net.mm2d.color.chooser\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.content.res.ColorStateList\nimport android.graphics.Color\nimport android.text.*\nimport android.text.InputFilter.LengthFilter\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport androidx.constraintlayout.widget.ConstraintLayout\nimport androidx.core.graphics.alpha\nimport androidx.core.view.ViewCompat\nimport androidx.core.view.isVisible\nimport androidx.lifecycle.Observer\nimport net.mm2d.color.chooser.util.resolveColor\nimport net.mm2d.color.chooser.util.setAlpha\nimport net.mm2d.color.chooser.util.toOpacity\nimport com.google.android.material.R\nimport rocks.tbog.tblauncher.databinding.Mm2dCcViewControlBinding\nimport java.util.*\n\ninternal class ControlView\n@JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null,\n    defStyleAttr: Int = 0\n) : ConstraintLayout(context, attrs, defStyleAttr), Observer<Int> {\n    private val delegate = ColorObserverDelegate(this)\n    private val normalTint =\n        ColorStateList.valueOf(context.resolveColor(R.attr.colorAccent, Color.BLUE))\n    private val errorTint =\n        ColorStateList.valueOf(context.resolveColor(R.attr.colorError, Color.RED))\n    private var changeHexTextByUser = true\n    private var hasAlpha: Boolean = true\n    private val rgbFilter = arrayOf(HexadecimalFilter(), LengthFilter(6))\n    private val argbFilter = arrayOf(HexadecimalFilter(), LengthFilter(8))\n    private val binding: Mm2dCcViewControlBinding =\n        Mm2dCcViewControlBinding.inflate(LayoutInflater.from(context), this)\n    var color: Int = Color.BLACK\n        private set\n\n    init {\n        binding.colorPreview.setColor(color)\n        binding.seekAlpha.setValue(color.alpha)\n        binding.seekAlpha.onValueChanged = { value, fromUser ->\n            binding.textAlpha.text = value.toString()\n            if (fromUser) {\n                setAlpha(value)\n            }\n        }\n        binding.editHex.filters = argbFilter\n        binding.editHex.addTextChangedListener(object : TextWatcher {\n            override fun afterTextChanged(s: Editable?) = Unit\n            override fun beforeTextChanged(s: CharSequence?, start: Int, c: Int, a: Int) = Unit\n            override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {\n                if (!changeHexTextByUser) {\n                    return\n                }\n                if (s.isNullOrEmpty()) {\n                    setError()\n                    return\n                }\n                try {\n                    color = Color.parseColor(\"#$s\")\n                    clearError()\n                    binding.colorPreview.setColor(color)\n                    binding.seekAlpha.setValue(color.alpha)\n                    delegate.post(color.toOpacity())\n                } catch (e: IllegalArgumentException) {\n                    setError()\n                }\n            }\n        })\n    }\n\n    override fun onAttachedToWindow() {\n        super.onAttachedToWindow()\n        delegate.onAttachedToWindow()\n    }\n\n    override fun onDetachedFromWindow() {\n        super.onDetachedFromWindow()\n        delegate.onDetachedFromWindow()\n    }\n\n    fun setAlpha(alpha: Int) {\n        binding.seekAlpha.setValue(alpha)\n        color = color.setAlpha(alpha)\n        binding.colorPreview.setColor(color)\n        setColorToHexText()\n    }\n\n    fun setWithAlpha(withAlpha: Boolean) {\n        hasAlpha = withAlpha\n        binding.seekAlpha.isVisible = withAlpha\n        binding.textAlpha.isVisible = withAlpha\n        if (withAlpha) {\n            binding.editHex.filters = argbFilter\n        } else {\n            binding.editHex.filters = rgbFilter\n            setAlpha(0xff)\n        }\n    }\n\n    private fun setError() {\n        ViewCompat.setBackgroundTintList(binding.editHex, errorTint)\n    }\n\n    private fun clearError() {\n        ViewCompat.setBackgroundTintList(binding.editHex, normalTint)\n    }\n\n    override fun onChanged(value: Int) {\n        if (this.color.toOpacity() == value) return\n        this.color = value.setAlpha(binding.seekAlpha.value)\n        binding.colorPreview.setColor(this.color)\n        setColorToHexText()\n        binding.seekAlpha.setMaxColor(value)\n    }\n\n    @SuppressLint(\"SetTextI18n\")\n    private fun setColorToHexText() {\n        changeHexTextByUser = false\n        if (hasAlpha) {\n            binding.editHex.setText(\"%08X\".format(color))\n        } else {\n            binding.editHex.setText(\"%06X\".format(color and 0xffffff))\n        }\n        clearError()\n        changeHexTextByUser = true\n    }\n\n    private class HexadecimalFilter : InputFilter {\n        override fun filter(\n            source: CharSequence?, start: Int, end: Int, dest: Spanned?, dstart: Int, dend: Int\n        ): CharSequence? {\n            val converted = source.toString()\n                .replace(\"[^0-9a-fA-F]\".toRegex(), \"\")\n                .uppercase(Locale.ENGLISH)\n            if (source.toString() == converted) return null\n            if (source !is Spanned) return converted\n            return SpannableString(converted).also {\n                TextUtils.copySpansFrom(source, 0, converted.length, null, it, 0)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/net/mm2d/color/chooser/HsvView.kt",
    "content": "/*\n * Copyright (c) 2018 大前良介 (OHMAE Ryosuke)\n *\n * This software is released under the MIT License.\n * http://opensource.org/licenses/MIT\n */\n\npackage net.mm2d.color.chooser\n\nimport android.content.Context\nimport android.graphics.Color\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport androidx.constraintlayout.widget.ConstraintLayout\nimport androidx.lifecycle.Observer\nimport rocks.tbog.tblauncher.databinding.Mm2dCcViewHsvBinding\nimport net.mm2d.color.chooser.util.ColorUtils\n\ninternal class HsvView\n@JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null,\n    defStyleAttr: Int = 0\n) : ConstraintLayout(context, attrs, defStyleAttr), Observer<Int> {\n    private val delegate = ColorObserverDelegate(this)\n    private var color: Int = Color.BLACK\n    private val binding: Mm2dCcViewHsvBinding =\n        Mm2dCcViewHsvBinding.inflate(LayoutInflater.from(context), this)\n\n    init {\n        binding.svView.onColorChanged = {\n            color = it\n            delegate.post(color)\n        }\n        binding.hueView.onHueChanged = {\n            color = ColorUtils.hsvToColor(it, binding.svView.saturation, binding.svView.value)\n            binding.svView.setHue(it)\n            delegate.post(color)\n        }\n    }\n\n    override fun onAttachedToWindow() {\n        super.onAttachedToWindow()\n        delegate.onAttachedToWindow()\n    }\n\n    override fun onDetachedFromWindow() {\n        super.onDetachedFromWindow()\n        delegate.onDetachedFromWindow()\n    }\n\n    override fun onChanged(value: Int) {\n        if (this.color == value) return\n        this.color = value\n        binding.svView.setColor(value)\n        binding.hueView.setColor(value)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/net/mm2d/color/chooser/PaletteView.kt",
    "content": "/*\n * Copyright (c) 2018 大前良介 (OHMAE Ryosuke)\n *\n * This software is released under the MIT License.\n * http://opensource.org/licenses/MIT\n */\n\npackage net.mm2d.color.chooser\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.content.res.Resources\nimport android.content.res.TypedArray\nimport android.graphics.Color\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.annotation.ArrayRes\nimport androidx.core.content.res.getColorOrThrow\nimport androidx.core.content.res.getResourceIdOrThrow\nimport androidx.core.content.res.use\nimport androidx.core.view.children\nimport androidx.lifecycle.Observer\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport net.mm2d.color.chooser.element.PaletteCell\nimport net.mm2d.color.chooser.util.getPixels\nimport rocks.tbog.tblauncher.R\nimport java.lang.ref.SoftReference\nimport kotlin.collections.forEach as kForEach\n\ninternal class PaletteView\n@JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null,\n    defStyleAttr: Int = 0\n) : RecyclerView(context, attrs, defStyleAttr), Observer<Int> {\n    private val delegate = ColorObserverDelegate(this)\n    private val cellHeight = getPixels(R.dimen.mm2d_cc_palette_cell_height)\n    private val cellAdapter = CellAdapter(context)\n    private val linearLayoutManager = LinearLayoutManager(context)\n\n    init {\n        val padding = resources.getDimensionPixelSize(R.dimen.mm2d_cc_palette_padding)\n        setPadding(0, padding, 0, padding)\n        clipToPadding = false\n        setHasFixedSize(true)\n        overScrollMode = View.OVER_SCROLL_NEVER\n        itemAnimator = null\n        layoutManager = linearLayoutManager\n        adapter = cellAdapter\n        isVerticalFadingEdgeEnabled = true\n        setFadingEdgeLength(padding)\n        cellAdapter.onColorChanged = {\n            delegate.post(it)\n        }\n    }\n\n    override fun onAttachedToWindow() {\n        super.onAttachedToWindow()\n        delegate.onAttachedToWindow()\n    }\n\n    override fun onDetachedFromWindow() {\n        super.onDetachedFromWindow()\n        delegate.onDetachedFromWindow()\n    }\n\n    override fun isPaddingOffsetRequired(): Boolean = true\n    override fun getTopPaddingOffset(): Int = -paddingTop\n    override fun getBottomPaddingOffset(): Int = paddingBottom\n\n    override fun onChanged(value: Int) {\n        cellAdapter.setColor(value)\n    }\n\n    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {\n        super.onSizeChanged(w, h, oldw, oldh)\n        if (oldh == 0 && h > 0) {\n            linearLayoutManager.scrollToPositionWithOffset(cellAdapter.index, (h - cellHeight) / 2)\n        }\n    }\n\n    private class CellAdapter(context: Context) : Adapter<CellHolder>() {\n        private val inflater: LayoutInflater = LayoutInflater.from(context)\n        private val list: List<IntArray> = createPalette(context)\n        private var color: Int = 0\n        var onColorChanged: ((color: Int) -> Unit)? = null\n        var index: Int = -1\n            private set\n\n        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CellHolder =\n            CellHolder(inflater.inflate(R.layout.mm2d_cc_item_palette, parent, false))\n                .also { holder -> holder.onColorChanged = { onColorChanged?.invoke(it) } }\n\n        override fun onBindViewHolder(holder: CellHolder, position: Int) =\n            holder.apply(list[position], color)\n\n        override fun getItemCount(): Int = list.size\n\n        fun setColor(newColor: Int) {\n            if (color == newColor) return\n            color = newColor\n            val newIndex = list.indexOfFirst { it.contains(newColor) }\n            val lastIndex = index\n            index = newIndex\n            if (lastIndex >= 0) notifyItemChanged(lastIndex)\n            if (newIndex >= 0) notifyItemChanged(newIndex)\n        }\n    }\n\n    private class CellHolder(itemView: View) : ViewHolder(itemView) {\n        private val viewList: List<PaletteCell> = (itemView as ViewGroup).children\n            .map { it as PaletteCell }\n            .toList()\n        var onColorChanged: ((color: Int) -> Unit)? = null\n\n        init {\n            viewList.kForEach {\n                it.setOnClickListener(::performOnColorChanged)\n            }\n        }\n\n        private fun performOnColorChanged(view: View) {\n            onColorChanged?.invoke(view.tag as? Int ?: return)\n        }\n\n        fun apply(colors: IntArray, selected: Int) {\n            viewList.withIndex().kForEach { (i, view) ->\n                val color = if (i < colors.size) colors[i] else Color.TRANSPARENT\n                view.tag = color\n                view.setColor(color)\n                view.checked = color == selected\n            }\n        }\n    }\n\n    companion object {\n        private var cache: SoftReference<List<IntArray>> = SoftReference<List<IntArray>>(null)\n\n        @SuppressLint(\"Recycle\")\n        private fun <R> Resources.useTypedArray(@ArrayRes id: Int, block: TypedArray.() -> R): R =\n            obtainTypedArray(id).use { it.block() }\n\n        private fun createPalette(context: Context): List<IntArray> {\n            cache.get()?.let { return it }\n            val resources = context.resources\n            return resources.useTypedArray(R.array.material_colors) {\n                (0 until length()).map { resources.readColorArray(getResourceIdOrThrow(it)) }\n            }.also {\n                cache = SoftReference(it)\n            }\n        }\n\n        private fun Resources.readColorArray(id: Int): IntArray =\n            useTypedArray(id) { IntArray(length()) { getColorOrThrow(it) } }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/net/mm2d/color/chooser/SliderView.kt",
    "content": "/*\n * Copyright (c) 2018 大前良介 (OHMAE Ryosuke)\n *\n * This software is released under the MIT License.\n * http://opensource.org/licenses/MIT\n */\n\npackage net.mm2d.color.chooser\n\nimport android.content.Context\nimport android.graphics.Color\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport androidx.constraintlayout.widget.ConstraintLayout\nimport androidx.lifecycle.Observer\nimport rocks.tbog.tblauncher.databinding.Mm2dCcViewSliderBinding\n\ninternal class SliderView\n@JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null,\n    defStyleAttr: Int = 0\n) : ConstraintLayout(context, attrs, defStyleAttr), Observer<Int> {\n    private val delegate = ColorObserverDelegate(this)\n    private val binding: Mm2dCcViewSliderBinding =\n        Mm2dCcViewSliderBinding.inflate(LayoutInflater.from(context), this)\n\n    init {\n        binding.seekRed.onValueChanged = { value, fromUser ->\n            binding.textRed.text = value.toString()\n            updateBySeekBar(fromUser)\n        }\n        binding.seekGreen.onValueChanged = { value, fromUser ->\n            binding.textGreen.text = value.toString()\n            updateBySeekBar(fromUser)\n        }\n        binding.seekBlue.onValueChanged = { value, fromUser ->\n            binding.textBlue.text = value.toString()\n            updateBySeekBar(fromUser)\n        }\n    }\n\n    override fun onAttachedToWindow() {\n        super.onAttachedToWindow()\n        delegate.onAttachedToWindow()\n    }\n\n    override fun onDetachedFromWindow() {\n        super.onDetachedFromWindow()\n        delegate.onDetachedFromWindow()\n    }\n\n    override fun onChanged(value: Int) {\n        binding.seekRed.setValue(Color.red(value))\n        binding.seekGreen.setValue(Color.green(value))\n        binding.seekBlue.setValue(Color.blue(value))\n    }\n\n    private fun updateBySeekBar(fromUser: Boolean) {\n        if (!fromUser) return\n        val color = Color.rgb(\n            binding.seekRed.value,\n            binding.seekGreen.value,\n            binding.seekBlue.value\n        )\n        delegate.post(color)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/net/mm2d/color/chooser/ViewPagerAdapter.kt",
    "content": "/*\n * Copyright (c) 2018 大前良介 (OHMAE Ryosuke)\n *\n * This software is released under the MIT License.\n * http://opensource.org/licenses/MIT\n */\n\npackage net.mm2d.color.chooser\n\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.core.view.ViewCompat\nimport androidx.recyclerview.widget.RecyclerView\nimport net.mm2d.color.chooser.ViewPagerAdapter.ViewHolder\n\ninternal class ViewPagerAdapter(\n    pageViews: List<View>\n) : RecyclerView.Adapter<ViewHolder>() {\n    private val pageViews = pageViews.toList().onEach {\n        it.id = ViewCompat.generateViewId()\n        it.layoutParams = RecyclerView.LayoutParams(\n            ViewGroup.LayoutParams.MATCH_PARENT,\n            ViewGroup.LayoutParams.MATCH_PARENT\n        )\n    }\n\n    class ViewHolder(val view: View) : RecyclerView.ViewHolder(view)\n\n    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder =\n        ViewHolder(pageViews[viewType])\n\n    override fun onBindViewHolder(holder: ViewHolder, position: Int) = Unit\n    override fun getItemViewType(position: Int): Int = position\n    override fun getItemCount(): Int = pageViews.size\n}\n"
  },
  {
    "path": "app/src/main/java/net/mm2d/color/chooser/element/ColorSliderView.kt",
    "content": "/*\n * Copyright (c) 2019 大前良介 (OHMAE Ryosuke)\n *\n * This software is released under the MIT License.\n * http://opensource.org/licenses/MIT\n */\n\npackage net.mm2d.color.chooser.element\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.graphics.*\nimport android.graphics.Paint.Style\nimport android.util.AttributeSet\nimport android.view.MotionEvent\nimport android.view.View\nimport androidx.core.content.withStyledAttributes\nimport net.mm2d.color.chooser.util.*\nimport rocks.tbog.tblauncher.R\nimport kotlin.math.max\n\ninternal class ColorSliderView\n@JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null,\n    defStyleAttr: Int = 0\n) : View(context, attrs, defStyleAttr) {\n    private val paint = Paint().also {\n        it.isAntiAlias = true\n    }\n    private val sampleRadius = getDimension(R.dimen.mm2d_cc_sample_radius)\n    private val sampleFrameRadius = sampleRadius + getDimension(R.dimen.mm2d_cc_sample_frame)\n    private val sampleShadowRadius =\n        sampleFrameRadius + getDimension(R.dimen.mm2d_cc_sample_shadow)\n    private val frameLineWidth = getDimension(R.dimen.mm2d_cc_sample_frame)\n    private val shadowLineWidth = getDimension(R.dimen.mm2d_cc_sample_shadow)\n    private val requestPaddingH = max(getPixels(R.dimen.mm2d_cc_panel_margin), sampleShadowRadius.toInt())\n    private val requestPaddingV = max(getPixels(R.dimen.mm2d_cc_panel_margin), (frameLineWidth * 2 + shadowLineWidth).toInt())\n    private val requestWidth = getPixels(R.dimen.mm2d_cc_slider_width) + requestPaddingH * 2\n    private val requestHeight = getPixels(R.dimen.mm2d_cc_slider_height) + requestPaddingV * 2\n    private val gradationRect = Rect(0, 0, RANGE, 1)\n    private val targetRect = Rect()\n    private val colorSampleFrame = getColor(R.color.mm2d_cc_sample_frame)\n    private val colorSampleShadow = getColor(R.color.mm2d_cc_sample_shadow)\n    private var checker: Bitmap? = null\n    private var floatValue: Float = 0f\n    private var maxColor: Int = Color.WHITE\n    private var gradation: Bitmap\n    private var baseColor: Int = Color.BLACK\n    private var alphaMode: Boolean = true\n    var onValueChanged: ((value: Int, fromUser: Boolean) -> Unit)? = null\n    val value: Int\n        get() = (floatValue * MAX).toInt()\n\n    init {\n        context.withStyledAttributes(attrs, R.styleable.ColorSliderView) {\n            maxColor = getColor(R.styleable.ColorSliderView_maxColor, Color.WHITE)\n            baseColor = getColor(R.styleable.ColorSliderView_baseColor, Color.BLACK)\n            alphaMode = getBoolean(R.styleable.ColorSliderView_alphaMode, true)\n        }\n        gradation = createGradation(maxColor)\n        updateChecker()\n    }\n\n    fun setMaxColor(maxColor: Int) {\n        this.maxColor = maxColor.toOpacity()\n        gradation = createGradation(this.maxColor)\n        invalidate()\n    }\n\n    fun setValue(value: Int) {\n        floatValue = (value / MAX.toFloat()).coerceIn(0f, 1f)\n        onValueChanged?.invoke(value, false)\n        invalidate()\n    }\n\n    private fun updateChecker() {\n        checker = if (alphaMode) {\n            createChecker(\n                getPixels(R.dimen.mm2d_cc_checker_size),\n                getPixels(R.dimen.mm2d_cc_slider_height),\n                getColor(R.color.mm2d_cc_checker_light),\n                getColor(R.color.mm2d_cc_checker_dark)\n            )\n        } else {\n            null\n        }\n    }\n\n    @SuppressLint(\"ClickableViewAccessibility\")\n    override fun onTouchEvent(event: MotionEvent): Boolean {\n        if (event.action == MotionEvent.ACTION_DOWN) {\n            parent.requestDisallowInterceptTouchEvent(true)\n        }\n        floatValue = ((event.x - targetRect.left) / targetRect.width().toFloat()).coerceIn(0f, 1f)\n        onValueChanged?.invoke(value, true)\n        invalidate()\n        return true\n    }\n\n    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {\n        targetRect.set(\n            paddingLeft + requestPaddingH,\n            paddingTop + requestPaddingV,\n            width - paddingRight - requestPaddingH,\n            height - paddingBottom - requestPaddingV\n        )\n    }\n\n    override fun onDraw(canvas: Canvas) {\n        paint.style = Style.STROKE\n        paint.color = colorSampleShadow\n        paint.strokeWidth = shadowLineWidth\n        val shadow = frameLineWidth + shadowLineWidth / 2\n        canvas.drawRectWithOffset(targetRect, shadow, paint)\n        paint.color = colorSampleFrame\n        paint.strokeWidth = frameLineWidth\n        val frame = frameLineWidth / 2\n        canvas.drawRectWithOffset(targetRect, frame, paint)\n        paint.style = Style.FILL\n        if (alphaMode) {\n            val checker = checker ?: return\n            canvas.save()\n            canvas.clipRect(targetRect)\n            val top = targetRect.top.toFloat()\n            for (left in targetRect.left until targetRect.right step checker.width) {\n                canvas.drawBitmap(checker, left.toFloat(), top, paint)\n            }\n            canvas.restore()\n        } else {\n            paint.color = baseColor\n            canvas.drawRect(targetRect, paint)\n        }\n        canvas.drawBitmap(gradation, gradationRect, targetRect, paint)\n        val x = floatValue * targetRect.width() + targetRect.left\n        val y = targetRect.centerY().toFloat()\n        paint.color = colorSampleShadow\n        canvas.drawCircle(x, y, sampleShadowRadius, paint)\n        paint.color = colorSampleFrame\n        canvas.drawCircle(x, y, sampleFrameRadius, paint)\n        paint.color = baseColor\n        canvas.drawCircle(x, y, sampleRadius, paint)\n        paint.color = maxColor.setAlpha(value)\n        canvas.drawCircle(x, y, sampleRadius, paint)\n    }\n\n    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {\n        setMeasuredDimension(\n            getDefaultSize(\n                maxOf(requestWidth + paddingLeft + paddingRight, suggestedMinimumWidth),\n                widthMeasureSpec\n            ),\n            resolveSizeAndState(\n                maxOf(requestHeight + paddingTop + paddingBottom, suggestedMinimumHeight),\n                heightMeasureSpec,\n                MeasureSpec.UNSPECIFIED\n            )\n        )\n    }\n\n    companion object {\n        private const val MAX = 255\n        private const val RANGE = 256\n\n        private fun createGradation(color: Int): Bitmap {\n            val pixels = IntArray(RANGE) { color.setAlpha(it) }\n            return Bitmap.createBitmap(pixels, RANGE, 1, Bitmap.Config.ARGB_8888)\n        }\n\n        private fun createChecker(step: Int, height: Int, color1: Int, color2: Int): Bitmap {\n            val width = step * 4\n            val pixels = IntArray(width * height)\n            for (y in 0 until height) {\n                for (x in 0 until width) {\n                    pixels[x + y * width] = if ((x / step + y / step) % 2 == 0) color1 else color2\n                }\n            }\n            return Bitmap.createBitmap(pixels, width, height, Bitmap.Config.ARGB_8888)\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/net/mm2d/color/chooser/element/HueView.kt",
    "content": "/*\n * Copyright (c) 2018 大前良介 (OHMAE Ryosuke)\n *\n * This software is released under the MIT License.\n * http://opensource.org/licenses/MIT\n */\n\npackage net.mm2d.color.chooser.element\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.graphics.*\nimport android.util.AttributeSet\nimport android.view.MotionEvent\nimport android.view.View\nimport androidx.annotation.ColorInt\nimport net.mm2d.color.chooser.util.ColorUtils\nimport net.mm2d.color.chooser.util.getColor\nimport net.mm2d.color.chooser.util.getDimension\nimport net.mm2d.color.chooser.util.getPixels\nimport rocks.tbog.tblauncher.R\nimport kotlin.math.max\n\ninternal class HueView\n@JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null,\n    defStyleAttr: Int = 0\n) : View(context, attrs, defStyleAttr) {\n    @ColorInt\n    private var color: Int = Color.RED\n    private val paint = Paint()\n    private val bitmap: Bitmap = createMaskBitmap()\n    private val sampleRadius = getDimension(R.dimen.mm2d_cc_sample_radius)\n    private val sampleFrameRadius =\n        sampleRadius + getDimension(R.dimen.mm2d_cc_sample_frame)\n    private val sampleShadowRadius =\n        sampleFrameRadius + getDimension(R.dimen.mm2d_cc_sample_shadow)\n    private val requestPaddingH = getPixels(R.dimen.mm2d_cc_panel_margin)\n    private val requestPaddingV = max(getPixels(R.dimen.mm2d_cc_panel_margin), sampleShadowRadius.toInt())\n    private val requestWidth = getPixels(R.dimen.mm2d_cc_hue_width) + requestPaddingH * 2\n    private val requestHeight = getPixels(R.dimen.mm2d_cc_hsv_size) + requestPaddingV * 2\n    private val bitmapRect = Rect(0, 0, 1, RANGE)\n    private val targetRect = Rect()\n    private var hue: Float = 0f\n    private val colorSampleFrame = getColor(R.color.mm2d_cc_sample_frame)\n    private val colorSampleShadow = getColor(R.color.mm2d_cc_sample_shadow)\n    var onHueChanged: ((hue: Float) -> Unit)? = null\n\n    fun setColor(@ColorInt color: Int) {\n        updateHue(ColorUtils.hue(color))\n    }\n\n    private fun updateHue(h: Float, fromUser: Boolean = false) {\n        if (hue == h) return\n        hue = h\n        color = ColorUtils.hsvToColor(hue, 1f, 1f)\n        invalidate()\n        if (fromUser) {\n            onHueChanged?.invoke(hue)\n        }\n    }\n\n    @SuppressLint(\"ClickableViewAccessibility\")\n    override fun onTouchEvent(event: MotionEvent): Boolean {\n        updateHue(((event.y - targetRect.top) / targetRect.height()).coerceIn(0f, 1f), true)\n        return true\n    }\n\n    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {\n        targetRect.set(\n            paddingLeft + requestPaddingH,\n            paddingTop + requestPaddingV,\n            width - paddingRight - requestPaddingH,\n            height - paddingBottom - requestPaddingV\n        )\n    }\n\n    override fun onDraw(canvas: Canvas) {\n        canvas.drawBitmap(bitmap, bitmapRect, targetRect, paint)\n        val x = targetRect.centerX().toFloat()\n        val y = hue * targetRect.height() + targetRect.top\n        paint.color = colorSampleShadow\n        canvas.drawCircle(x, y, sampleShadowRadius, paint)\n        paint.color = colorSampleFrame\n        canvas.drawCircle(x, y, sampleFrameRadius, paint)\n        paint.color = color\n        canvas.drawCircle(x, y, sampleRadius, paint)\n    }\n\n    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {\n        setMeasuredDimension(\n            resolveSizeAndState(\n                max(requestWidth + paddingLeft + paddingRight, suggestedMinimumWidth),\n                widthMeasureSpec,\n                MeasureSpec.UNSPECIFIED\n            ),\n            resolveSizeAndState(\n                max(requestHeight + paddingTop + paddingBottom, suggestedMinimumHeight),\n                heightMeasureSpec,\n                MeasureSpec.UNSPECIFIED\n            )\n        )\n    }\n\n    companion object {\n        private const val RANGE = 360\n\n        private fun createMaskBitmap(): Bitmap {\n            val pixels = IntArray(RANGE) { ColorUtils.hsvToColor(it.toFloat() / RANGE, 1f, 1f) }\n            return Bitmap.createBitmap(pixels, 1, RANGE, Bitmap.Config.ARGB_8888)\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/net/mm2d/color/chooser/element/PaletteCell.kt",
    "content": "/*\n * Copyright (c) 2018 大前良介 (OHMAE Ryosuke)\n *\n * This software is released under the MIT License.\n * http://opensource.org/licenses/MIT\n */\n\npackage net.mm2d.color.chooser.element\n\nimport android.content.Context\nimport android.graphics.Canvas\nimport android.graphics.Color\nimport android.graphics.Paint\nimport android.graphics.Paint.Style\nimport android.graphics.drawable.Drawable\nimport android.util.AttributeSet\nimport android.view.View\nimport androidx.appcompat.content.res.AppCompatResources\nimport androidx.core.graphics.drawable.DrawableCompat\nimport rocks.tbog.tblauncher.R.drawable\nimport net.mm2d.color.chooser.util.ColorUtils\nimport kotlin.math.min\n\ninternal class PaletteCell @JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null,\n    defStyleAttr: Int = 0\n) : View(context, attrs, defStyleAttr) {\n    private val icon: Drawable = loadIcon(context)\n    private var color: Int = Color.TRANSPARENT\n    private val paint: Paint = Paint().also {\n        it.style = Style.FILL_AND_STROKE\n    }\n    var checked: Boolean = false\n\n    fun setColor(color: Int) {\n        this.color = color\n        paint.color = color\n        isEnabled = color != Color.TRANSPARENT\n        invalidate()\n    }\n\n    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {\n        val size = min(min(width, height), icon.intrinsicWidth)\n        icon.setBounds((w - size) / 2, (h - size) / 2, (w + size) / 2, (h + size) / 2)\n    }\n\n    override fun onDraw(canvas: Canvas) {\n        if (color == Color.TRANSPARENT) return\n        canvas.drawColor(color)\n        if (checked) {\n            DrawableCompat.setTint(icon, selectForeground(color))\n            icon.draw(canvas)\n        }\n    }\n\n    companion object {\n        private var icon: Drawable? = null\n\n        private fun loadIcon(context: Context): Drawable =\n            icon ?: loadIconInner(context).also { icon = it }\n\n        private fun loadIconInner(context: Context): Drawable =\n            AppCompatResources.getDrawable(context, drawable.mm2d_cc_ic_check)!!.wrap()\n\n        private fun Drawable.wrap(): Drawable = DrawableCompat.wrap(this)\n\n        fun selectForeground(background: Int): Int =\n            if (ColorUtils.shouldUseWhiteForeground(background)) Color.WHITE else Color.BLACK\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/net/mm2d/color/chooser/element/PreviewView.kt",
    "content": "/*\n * Copyright (c) 2019 大前良介 (OHMAE Ryosuke)\n *\n * This software is released under the MIT License.\n * http://opensource.org/licenses/MIT\n */\n\npackage net.mm2d.color.chooser.element\n\nimport android.content.Context\nimport android.graphics.*\nimport android.graphics.Paint.Style\nimport android.util.AttributeSet\nimport android.view.View\nimport rocks.tbog.tblauncher.R\nimport net.mm2d.color.chooser.util.drawRectWithOffset\nimport net.mm2d.color.chooser.util.getColor\nimport net.mm2d.color.chooser.util.getDimension\nimport net.mm2d.color.chooser.util.getPixels\nimport kotlin.math.max\n\ninternal class PreviewView\n@JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null,\n    defStyleAttr: Int = 0\n) : View(context, attrs, defStyleAttr) {\n    private val paint = Paint().also {\n        it.isAntiAlias = true\n    }\n    private val requestWidth = getPixels(R.dimen.mm2d_cc_preview_width)\n    private val requestHeight = getPixels(R.dimen.mm2d_cc_preview_height)\n    private val frameLineWidth = getDimension(R.dimen.mm2d_cc_sample_frame)\n    private val shadowLineWidth = getDimension(R.dimen.mm2d_cc_sample_shadow)\n    private val colorSampleFrame = getColor(R.color.mm2d_cc_sample_frame)\n    private val colorSampleShadow = getColor(R.color.mm2d_cc_sample_shadow)\n    private val checkerRect = Rect()\n    private val targetRect = Rect()\n    private val checkerSize = getPixels(R.dimen.mm2d_cc_checker_size)\n    private val colorCheckerLight = getColor(R.color.mm2d_cc_checker_light)\n    private val colorCheckerDark = getColor(R.color.mm2d_cc_checker_dark)\n    private var checker: Bitmap? = null\n    var color: Int = Color.BLACK\n        private set\n\n    override fun onDraw(canvas: Canvas) {\n        paint.style = Style.STROKE\n        paint.color = colorSampleShadow\n        paint.strokeWidth = shadowLineWidth\n        val shadow = frameLineWidth + shadowLineWidth / 2\n        canvas.drawRectWithOffset(targetRect, shadow, paint)\n        paint.color = colorSampleFrame\n        paint.strokeWidth = frameLineWidth\n        val frame = frameLineWidth / 2\n        canvas.drawRectWithOffset(targetRect, frame, paint)\n        val checker = checker ?: return\n        canvas.drawBitmap(checker, checkerRect, targetRect, paint)\n        paint.style = Style.FILL\n        paint.color = color\n        canvas.drawRect(targetRect, paint)\n    }\n\n    fun setColor(color: Int) {\n        this.color = color\n        invalidate()\n    }\n\n    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {\n        val border = (frameLineWidth + shadowLineWidth).toInt()\n        targetRect.set(\n            paddingLeft + border,\n            paddingTop + border,\n            width - paddingRight - border,\n            height - paddingBottom - border\n        )\n        checkerRect.set(0, 0, targetRect.width(), targetRect.height())\n        checker = createChecker(\n            checkerSize,\n            checkerRect.width(),\n            checkerRect.height(),\n            colorCheckerLight,\n            colorCheckerDark\n        )\n    }\n\n    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {\n        setMeasuredDimension(\n            resolveSizeAndState(\n                max(requestWidth, suggestedMinimumWidth),\n                widthMeasureSpec,\n                MeasureSpec.UNSPECIFIED\n            ),\n            resolveSizeAndState(\n                max(requestHeight, suggestedMinimumHeight),\n                heightMeasureSpec,\n                MeasureSpec.UNSPECIFIED\n            )\n        )\n    }\n\n    companion object {\n        private fun createChecker(\n            step: Int,\n            width: Int,\n            height: Int,\n            color1: Int,\n            color2: Int\n        ): Bitmap {\n            val pixels = IntArray(width * height)\n            for (y in 0 until height) {\n                for (x in 0 until width) {\n                    pixels[x + y * width] = if ((x / step + y / step) % 2 == 0) color1 else color2\n                }\n            }\n            return Bitmap.createBitmap(pixels, width, height, Bitmap.Config.ARGB_8888)\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/net/mm2d/color/chooser/element/SvView.kt",
    "content": "/*\n * Copyright (c) 2018 大前良介 (OHMAE Ryosuke)\n *\n * This software is released under the MIT License.\n * http://opensource.org/licenses/MIT\n */\n\npackage net.mm2d.color.chooser.element\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.graphics.*\nimport android.util.AttributeSet\nimport android.view.MotionEvent\nimport android.view.View\nimport androidx.annotation.ColorInt\nimport net.mm2d.color.chooser.util.ColorUtils\nimport net.mm2d.color.chooser.util.getColor\nimport net.mm2d.color.chooser.util.getDimension\nimport net.mm2d.color.chooser.util.getPixels\nimport rocks.tbog.tblauncher.R\nimport kotlin.math.abs\nimport kotlin.math.max\nimport kotlin.math.min\n\ninternal class SvView\n@JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null,\n    defStyleAttr: Int = 0\n) : View(context, attrs, defStyleAttr) {\n    @ColorInt\n    private var color: Int = Color.BLACK\n    private var maxColor: Int = Color.RED\n    private var maskBitmap: Bitmap? = null\n    private val paint = Paint().also { it.isAntiAlias = true }\n    private val sampleRadius = getDimension(R.dimen.mm2d_cc_sample_radius)\n    private val sampleFrameRadius = sampleRadius + getDimension(R.dimen.mm2d_cc_sample_frame)\n    private val sampleShadowRadius =\n        sampleFrameRadius + getDimension(R.dimen.mm2d_cc_sample_shadow)\n    private val requestPadding = max(getPixels(R.dimen.mm2d_cc_panel_margin), sampleShadowRadius.toInt())\n    private val requestWidth = getPixels(R.dimen.mm2d_cc_hsv_size) + requestPadding * 2\n    private val requestHeight = getPixels(R.dimen.mm2d_cc_hsv_size) + requestPadding * 2\n    private val maskRect = Rect(0, 0, TONE_SIZE, TONE_SIZE)\n    private val targetRect = Rect()\n    private var hue: Float = 0f\n    private val colorSampleFrame = getColor(R.color.mm2d_cc_sample_frame)\n    private val colorSampleShadow = getColor(R.color.mm2d_cc_sample_shadow)\n    private val hsvCache = FloatArray(3)\n    var saturation: Float = 0f\n        private set\n    var value: Float = 0f\n        private set\n    var onColorChanged: ((color: Int) -> Unit)? = null\n\n    init {\n        Thread {\n            maskBitmap = createMaskBitmap()\n            postInvalidate()\n        }.start()\n    }\n\n    fun setColor(@ColorInt color: Int) {\n        this.color = color\n        ColorUtils.colorToHsv(color, hsvCache)\n        updateHue(hsvCache[0])\n        updateSv(hsvCache[1], hsvCache[2])\n    }\n\n    fun setHue(h: Float) {\n        color = ColorUtils.hsvToColor(h, saturation, value)\n        updateHue(h)\n    }\n\n    private fun updateHue(h: Float) {\n        if (hue == h) {\n            return\n        }\n        hue = h\n        maxColor = ColorUtils.hsvToColor(hue, 1f, 1f)\n        invalidate()\n    }\n\n    private fun updateSv(s: Float, v: Float, fromUser: Boolean = false) {\n        if (saturation == s && value == v) {\n            return\n        }\n        saturation = s\n        value = v\n        invalidate()\n        if (fromUser) {\n            onColorChanged?.invoke(color)\n        }\n    }\n\n    @SuppressLint(\"ClickableViewAccessibility\")\n    override fun onTouchEvent(event: MotionEvent): Boolean {\n        if (event.action == MotionEvent.ACTION_DOWN) {\n            parent.requestDisallowInterceptTouchEvent(true)\n        }\n        val s = ((event.x - targetRect.left) / targetRect.width()).coerceIn(0f, 1f)\n        val v = ((targetRect.bottom - event.y) / targetRect.height()).coerceIn(0f, 1f)\n        color = ColorUtils.hsvToColor(hue, s, v)\n        updateSv(s, v, true)\n        return true\n    }\n\n    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {\n        targetRect.set(\n            paddingLeft + requestPadding,\n            paddingTop + requestPadding,\n            width - paddingRight - requestPadding,\n            height - paddingBottom - requestPadding\n        )\n    }\n\n    override fun onDraw(canvas: Canvas) {\n        val mask = maskBitmap ?: return\n        paint.color = maxColor\n        canvas.drawRect(targetRect, paint)\n        canvas.drawBitmap(mask, maskRect, targetRect, paint)\n        val x = saturation * targetRect.width() + targetRect.left\n        val y = (1f - value) * targetRect.height() + targetRect.top\n        paint.color = colorSampleShadow\n        canvas.drawCircle(x, y, sampleShadowRadius, paint)\n        paint.color = colorSampleFrame\n        canvas.drawCircle(x, y, sampleFrameRadius, paint)\n        paint.color = color\n        canvas.drawCircle(x, y, sampleRadius, paint)\n    }\n\n    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {\n        val paddingHorizontal = paddingLeft + paddingRight\n        val paddingVertical = paddingTop + paddingBottom\n        val resizeWidth = MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY\n        val resizeHeight = MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY\n\n        if (!resizeWidth && !resizeHeight) {\n            setMeasuredDimension(\n                resolveSizeAndState(\n                    max(requestWidth + paddingHorizontal, suggestedMinimumWidth),\n                    widthMeasureSpec,\n                    MeasureSpec.UNSPECIFIED\n                ),\n                resolveSizeAndState(\n                    max(requestHeight + paddingVertical, suggestedMinimumHeight),\n                    heightMeasureSpec,\n                    MeasureSpec.UNSPECIFIED\n                )\n            )\n            return\n        }\n\n        var widthSize = resolveAdjustedSize(requestWidth + paddingHorizontal, widthMeasureSpec)\n        var heightSize = resolveAdjustedSize(requestHeight + paddingVertical, heightMeasureSpec)\n        val actualAspect =\n            (widthSize - paddingHorizontal).toFloat() / (heightSize - paddingVertical)\n        if (abs(actualAspect - 1f) < 0.0000001) {\n            setMeasuredDimension(widthSize, heightSize)\n            return\n        }\n        if (resizeWidth) {\n            val newWidth = heightSize - paddingVertical + paddingHorizontal\n            if (!resizeHeight) {\n                widthSize = resolveAdjustedSize(newWidth, widthMeasureSpec)\n            }\n            if (newWidth <= widthSize) {\n                widthSize = newWidth\n                setMeasuredDimension(widthSize, heightSize)\n                return\n            }\n        }\n        if (resizeHeight) {\n            val newHeight = widthSize - paddingHorizontal + paddingVertical\n            if (!resizeWidth) {\n                heightSize = resolveAdjustedSize(newHeight, heightMeasureSpec)\n            }\n            if (newHeight <= heightSize) {\n                heightSize = newHeight\n            }\n        }\n        setMeasuredDimension(widthSize, heightSize)\n    }\n\n    private fun resolveAdjustedSize(desiredSize: Int, measureSpec: Int): Int {\n        val specMode = MeasureSpec.getMode(measureSpec)\n        val specSize = MeasureSpec.getSize(measureSpec)\n        return when (specMode) {\n            MeasureSpec.UNSPECIFIED -> desiredSize\n            MeasureSpec.AT_MOST -> min(desiredSize, specSize)\n            MeasureSpec.EXACTLY -> specSize\n            else -> desiredSize\n        }\n    }\n\n    companion object {\n        private const val TONE_MAX = 255f\n        private const val TONE_SIZE = 256\n\n        private fun createMaskBitmap(): Bitmap {\n            val pixels = IntArray(TONE_SIZE * TONE_SIZE)\n            for (y in 0 until TONE_SIZE) {\n                for (x in 0 until TONE_SIZE) {\n                    pixels[x + y * TONE_SIZE] =\n                        ColorUtils.svToMask(x / TONE_MAX, (TONE_MAX - y) / TONE_MAX)\n                }\n            }\n            return Bitmap.createBitmap(pixels, TONE_SIZE, TONE_SIZE, Bitmap.Config.ARGB_8888)\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/net/mm2d/color/chooser/util/AttrExtentions.kt",
    "content": "/*\n * Copyright (c) 2019 大前良介 (OHMAE Ryosuke)\n *\n * This software is released under the MIT License.\n * http://opensource.org/licenses/MIT\n */\n\npackage net.mm2d.color.chooser.util\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport androidx.annotation.AttrRes\nimport androidx.annotation.ColorInt\nimport androidx.annotation.StyleRes\nimport androidx.core.content.res.use\n\n@ColorInt\ninternal fun Context.resolveColor(\n    @AttrRes attr: Int,\n    @ColorInt defaultColor: Int\n): Int = resolveColor(0, attr, defaultColor)\n\n@SuppressLint(\"Recycle\")\n@ColorInt\ninternal fun Context.resolveColor(\n    @StyleRes style: Int,\n    @AttrRes attr: Int,\n    @ColorInt defaultColor: Int\n): Int = obtainStyledAttributes(style, intArrayOf(attr))\n    .use { it.getColor(0, defaultColor) }\n"
  },
  {
    "path": "app/src/main/java/net/mm2d/color/chooser/util/CanvasExtensions.kt",
    "content": "/*\n * Copyright (c) 2020 大前良介 (OHMAE Ryosuke)\n *\n * This software is released under the MIT License.\n * http://opensource.org/licenses/MIT\n */\n\npackage net.mm2d.color.chooser.util\n\nimport android.graphics.Canvas\nimport android.graphics.Paint\nimport android.graphics.Rect\n\ninternal fun Canvas.drawRectWithOffset(rect: Rect, offset: Float, paint: Paint) =\n    drawRect(\n        rect.left - offset,\n        rect.top - offset,\n        rect.right + offset,\n        rect.bottom + offset,\n        paint\n    )\n"
  },
  {
    "path": "app/src/main/java/net/mm2d/color/chooser/util/ColorUtils.kt",
    "content": "/*\n * Copyright (c) 2018 大前良介 (OHMAE Ryosuke)\n *\n * This software is released under the MIT License.\n * http://opensource.org/licenses/MIT\n */\n\npackage net.mm2d.color.chooser.util\n\nimport androidx.core.graphics.blue\nimport androidx.core.graphics.green\nimport androidx.core.graphics.red\nimport kotlin.math.pow\n\n/**\n * HSVやRGBの色空間表現を扱う上でのメソッド\n */\ninternal object ColorUtils {\n    /**\n     * Convert given HSV [0.0f, 1.0f] to color\n     *\n     * @param h Hue\n     * @param s Saturation\n     * @param v Value\n     * @return color\n     */\n    fun hsvToColor(h: Float, s: Float, v: Float): Int {\n        if (s <= 0f) return toColor(v, v, v)\n        val hue = h * 6f // [0.0f, 6.0f]\n        val i = hue.toInt() // hueの整数部\n        val d = hue - i // hueの小数部\n        var r = v\n        var g = v\n        var b = v\n        when (i) {\n            0 -> { // h:[0.0f, 1.0f)\n                g *= 1f - s * (1f - d)\n                b *= 1f - s\n            }\n            1 -> { // h:[1.0f, 2.0f)\n                r *= 1f - s * d\n                b *= 1f - s\n            }\n            2 -> { // h:[2.0f, 3.0f)\n                r *= 1f - s\n                b *= 1f - s * (1f - d)\n            }\n            3 -> { // h:[3.0f, 4.0f)\n                r *= 1f - s\n                g *= 1f - s * d\n            }\n            4 -> { // h:[4.0f, 5.0f)\n                r *= 1f - s * (1f - d)\n                g *= 1f - s\n            }\n            5 -> { // h:[5.0f, 6.0f)\n                g *= 1f - s\n                b *= 1f - s * d\n            }\n            else -> {\n                g *= 1f - s * (1f - d)\n                b *= 1f - s\n            }\n        }\n        return toColor(r, g, b)\n    }\n\n    /**\n     * Convert given SV [0.0f, 1.0f] to monochrome + alpha mask\n     *\n     * @param s Saturation\n     * @param v Value\n     * @return pixel value of mask\n     */\n    fun svToMask(s: Float, v: Float): Int {\n        val a = 1f - (s * v)\n        val g = if (a == 0f) 0f else (v * (1f - s) / a).coerceIn(0f, 1f)\n        return toColor(a, g, g, g)\n    }\n\n    /**\n     * Convert given color to HSV [0.0f, 1.0f] array\n     *\n     * @param color color\n     * @param outHsv hsv buffer if not specify or null, allocate new array\n     * @return hsv array\n     */\n    fun colorToHsv(color: Int, outHsv: FloatArray? = null): FloatArray {\n        val r = color.red / 255f\n        val g = color.green / 255f\n        val b = color.blue / 255f\n        val max = max(r, g, b)\n        val min = min(r, g, b)\n        val hsv = outHsv ?: FloatArray(3)\n        hsv[0] = hue(r, g, b, max, min)\n        hsv[1] = saturation(max, min)\n        hsv[2] = max\n        return hsv\n    }\n\n    /**\n     * Calculate hue value\n     *\n     * @param color color\n     * @return hue\n     */\n    fun hue(color: Int): Float {\n        val r = color.red / 255f\n        val g = color.green / 255f\n        val b = color.blue / 255f\n        val max = max(r, g, b)\n        val min = min(r, g, b)\n        return hue(r, g, b, max, min)\n    }\n\n    private fun max(v1: Float, v2: Float, v3: Float): Float =\n        maxOf(maxOf(v1, v2), v3)\n\n    private fun min(v1: Float, v2: Float, v3: Float): Float =\n        minOf(minOf(v1, v2), v3)\n\n    private fun hue(r: Float, g: Float, b: Float, max: Float, min: Float): Float {\n        val range = max - min\n        if (range == 0f) return 0f\n        val hue = when (max) {\n            r -> ((g - b) / range).let { if (it < 0f) it + 6f else it }\n            g -> (b - r) / range + 2f\n            else -> (r - g) / range + 4f\n        }\n        return (hue / 6f).coerceIn(0f, 1f)\n    }\n\n    private fun saturation(max: Float, min: Float): Float =\n        if (max != 0.0f) (max - min) / max else 0f\n\n    /**\n     * Convert [0.0f, 1.0f] ARGB value to color\n     *\n     * @param r Red value\n     * @param g Green value\n     * @param b Blue value\n     * @return color\n     */\n    private fun toColor(r: Float, g: Float, b: Float): Int =\n        toColor(r.to8bit(), g.to8bit(), b.to8bit())\n\n    /**\n     * Convert [0, 255] RGB value to color\n     *\n     * @param r Red value\n     * @param g Green value\n     * @param b Blue value\n     * @return color\n     */\n    private fun toColor(r: Int, g: Int, b: Int): Int =\n        (0xff shl 24) or (0xff and r shl 16) or (0xff and g shl 8) or (0xff and b)\n\n    /**\n     * Convert [0.0f, 1.0f] ARGB value to color\n     *\n     * @param a Alpha value\n     * @param r Red value\n     * @param g Green value\n     * @param b Blue value\n     * @return color\n     */\n    private fun toColor(a: Float, r: Float, g: Float, b: Float): Int =\n        toColor(\n            a.to8bit(),\n            r.to8bit(),\n            g.to8bit(),\n            b.to8bit()\n        )\n\n    /**\n     * Convert [0, 255] ARGB value to color\n     *\n     * @param a Alpha value\n     * @param r Red value\n     * @param g Green value\n     * @param b Blue value\n     * @return color\n     */\n    private fun toColor(a: Int, r: Int, g: Int, b: Int): Int =\n        (0xff and a shl 24) or (0xff and r shl 16) or (0xff and g shl 8) or (0xff and b)\n\n    /**\n     * Calculate luminance based on ITU-R BT.709 and sRGB\n     *\n     * https://www.w3.org/TR/WCAG20/#relativeluminancedef\n     *\n     * @param r Red ratio\n     * @param g Green ratio\n     * @param b Blue ratio\n     * @return luminance\n     */\n    fun luminance(r: Float, g: Float, b: Float): Float =\n        r * 0.2126f + g * 0.7152f + b * 0.0722f\n\n    /**\n     * Minimum contrast for large text based on W3C guideline\n     *\n     * https://www.w3.org/TR/WCAG20/#visual-audio-contrast-contrast\n     */\n    private const val MINIMUM_CONTRAST_FOR_LARGE_TEXT = 3f\n\n    /**\n     * Determine whether sufficient contrast can be secured with white foreground.\n     *\n     * @param color\n     * @return if true, should use white foreground, else avoid white foreground\n     */\n    fun shouldUseWhiteForeground(color: Int): Boolean =\n        color.contrastWithWhite() > MINIMUM_CONTRAST_FOR_LARGE_TEXT\n}\n\n/**\n * Overwrite alpha value of color\n *\n * @receiver color\n * @param alpha Alpha\n * @return alpha applied color\n */\ninternal fun Int.setAlpha(alpha: Int): Int = this and 0xffffff or (alpha shl 24)\n\n/**\n * Overwrite alpha value to completely opaque\n */\ninternal fun Int.toOpacity(): Int = setAlpha(0xff)\n\n/**\n * Convert [0, 255] to [0.0f, 1.0f]\n *\n * @receiver [0, 255]\n * @return [0.0f, 1.0f]\n */\ninternal fun Int.toRatio(): Float = this / 255f\n\n/**\n * Convert [0.0f, 1.0f] to [0, 255]\n *\n * @receiver [0.0f, 1.0f]\n * @return [0, 255]\n */\ninternal fun Float.to8bit(): Int = (this * 255f + 0.5f).toInt().coerceIn(0, 255)\n\n/**\n * Normalize value of primary color luminance to calculate sRGB luminance of color\n *\n * https://www.w3.org/TR/WCAG20/#relativeluminancedef\n *\n * @receiver primary color luminance\n * @return normalized luminance\n */\ninternal fun Float.normalizeForSrgb(): Float =\n    if (this < 0.03928f) this / 12.92f else ((this + 0.055) / 1.055).pow(2.4).toFloat()\n\n/**\n * Normalize value of primary color luminance to calculate sRGB luminance of color\n *\n * https://www.w3.org/TR/WCAG20/#relativeluminancedef\n *\n * @receiver primary color luminance\n * @return normalized luminance\n */\ninternal fun Int.normalizeForSrgb(): Float = toRatio().normalizeForSrgb()\n\n/**\n * Calculate sRGB luminance of color\n *\n * @receiver color\n * @return sRGB luminance\n */\ninternal fun Int.relativeLuminance(): Float {\n    return ColorUtils.luminance(\n        red.normalizeForSrgb(),\n        green.normalizeForSrgb(),\n        blue.normalizeForSrgb()\n    )\n}\n\n/**\n * Calculate contrast between given color and pure white (#ffffff)\n *\n * @receiver color\n * @return contrast [1, 21]\n */\ninternal fun Int.contrastWithWhite(): Float {\n    return 1.05f / (relativeLuminance() + 0.05f)\n}\n"
  },
  {
    "path": "app/src/main/java/net/mm2d/color/chooser/util/ResourceExtensions.kt",
    "content": "/*\n * Copyright (c) 2020 大前良介 (OHMAE Ryosuke)\n *\n * This software is released under the MIT License.\n * http://opensource.org/licenses/MIT\n */\n\npackage net.mm2d.color.chooser.util\n\nimport android.content.Context\nimport android.util.TypedValue\nimport android.view.View\nimport androidx.annotation.ColorInt\nimport androidx.annotation.ColorRes\nimport androidx.annotation.DimenRes\nimport androidx.annotation.Dimension\nimport androidx.core.content.ContextCompat\n\n@ColorInt\ninternal fun View.getColor(@ColorRes id: Int): Int =\n    ContextCompat.getColor(context, id)\n\n@Dimension\ninternal fun View.getDimension(@DimenRes id: Int): Float =\n    resources.getDimension(id)\n\n@Dimension\ninternal fun View.getPixels(@DimenRes id: Int): Int =\n    resources.getDimensionPixelSize(id)\n\n@Dimension\ninternal fun Int.toPixelsAsDp(context: Context): Int =\n    TypedValue.complexToDimensionPixelSize(this, context.resources.displayMetrics)\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/Behaviour.java",
    "content": "package rocks.tbog.tblauncher;\n\nimport static rocks.tbog.tblauncher.entry.EntryItem.LAUNCHED_FROM_GESTURE;\n\nimport android.animation.Animator;\nimport android.animation.AnimatorListenerAdapter;\nimport android.app.Activity;\nimport android.content.ActivityNotFoundException;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.SharedPreferences;\nimport android.content.pm.ActivityInfo;\nimport android.content.pm.LauncherApps;\nimport android.graphics.drawable.Animatable;\nimport android.graphics.drawable.Drawable;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.os.Message;\nimport android.provider.Settings;\nimport android.text.Editable;\nimport android.text.TextUtils;\nimport android.text.TextWatcher;\nimport android.util.Log;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.Window;\nimport android.view.animation.AccelerateInterpolator;\nimport android.view.animation.DecelerateInterpolator;\nimport android.view.animation.LinearInterpolator;\nimport android.widget.EditText;\nimport android.widget.ImageView;\nimport android.widget.TextView;\nimport android.widget.Toast;\n\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.IdRes;\nimport androidx.annotation.LayoutRes;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.StringRes;\nimport androidx.appcompat.app.ActionBar;\nimport androidx.lifecycle.Lifecycle;\nimport androidx.preference.PreferenceManager;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport java.lang.reflect.Constructor;\nimport java.text.DateFormat;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Date;\nimport java.util.List;\nimport java.util.Random;\nimport java.util.Set;\n\nimport rocks.tbog.tblauncher.customicon.ButtonHelper;\nimport rocks.tbog.tblauncher.customicon.IconSelectDialog;\nimport rocks.tbog.tblauncher.dataprovider.IProvider;\nimport rocks.tbog.tblauncher.dataprovider.TagsProvider;\nimport rocks.tbog.tblauncher.entry.ActionEntry;\nimport rocks.tbog.tblauncher.entry.AppEntry;\nimport rocks.tbog.tblauncher.entry.DialContactEntry;\nimport rocks.tbog.tblauncher.entry.EntryItem;\nimport rocks.tbog.tblauncher.entry.EntryWithTags;\nimport rocks.tbog.tblauncher.entry.SearchEntry;\nimport rocks.tbog.tblauncher.entry.ShortcutEntry;\nimport rocks.tbog.tblauncher.entry.StaticEntry;\nimport rocks.tbog.tblauncher.handler.DataHandler;\nimport rocks.tbog.tblauncher.quicklist.EditQuickListDialog;\nimport rocks.tbog.tblauncher.result.CustomRecycleLayoutManager;\nimport rocks.tbog.tblauncher.result.RecycleAdapter;\nimport rocks.tbog.tblauncher.result.RecycleScrollListener;\nimport rocks.tbog.tblauncher.result.ResultHelper;\nimport rocks.tbog.tblauncher.result.ResultItemDecoration;\nimport rocks.tbog.tblauncher.searcher.ISearchActivity;\nimport rocks.tbog.tblauncher.searcher.QuerySearcher;\nimport rocks.tbog.tblauncher.searcher.Searcher;\nimport rocks.tbog.tblauncher.shortcut.ShortcutUtil;\nimport rocks.tbog.tblauncher.ui.DialogFragment;\nimport rocks.tbog.tblauncher.ui.KeyboardHandler;\nimport rocks.tbog.tblauncher.ui.LinearAdapter;\nimport rocks.tbog.tblauncher.ui.ListPopup;\nimport rocks.tbog.tblauncher.ui.RecyclerList;\nimport rocks.tbog.tblauncher.ui.ViewStubPreview;\nimport rocks.tbog.tblauncher.ui.WindowInsetsHelper;\nimport rocks.tbog.tblauncher.ui.dialog.TagsManagerDialog;\nimport rocks.tbog.tblauncher.utils.KeyboardToggleHelper;\nimport rocks.tbog.tblauncher.utils.KeyboardTriggerBehaviour;\nimport rocks.tbog.tblauncher.utils.PrefCache;\nimport rocks.tbog.tblauncher.utils.SystemUiVisibility;\nimport rocks.tbog.tblauncher.utils.UISizes;\nimport rocks.tbog.tblauncher.utils.UITheme;\nimport rocks.tbog.tblauncher.utils.UserHandleCompat;\nimport rocks.tbog.tblauncher.utils.Utilities;\n\n/**\n * Behaviour of the launcher, when are stuff hidden, animation, user interaction responses\n */\npublic class Behaviour implements ISearchActivity {\n\n    public static final int LAUNCH_DELAY = 100;\n    static final String DIALOG_CUSTOM_ICON = \"custom_icon_dialog\";\n    static final String DIALOG_EDIT_TAGS = \"edit_tags_dialog\";\n    static final String DIALOG_EDIT_QUICK_LIST = \"edit_quick_list_dialog\";\n    static final String DIALOG_TAGS_MANAGER = \"tags_manager_dialog\";\n    private static final int UI_ANIMATION_DELAY = 300;\n    // time to wait for the keyboard to show up\n    private static final int KEYBOARD_ANIMATION_DELAY = 100;\n    private static final int UI_ANIMATION_DURATION = 200;\n    private static final String TAG = Behaviour.class.getSimpleName();\n    private TBLauncherActivity mTBLauncherActivity = null;\n    private DialogFragment<?> mFragmentDialog = null;\n    private View mResultLayout;\n    private RecyclerList mResultList;\n    private RecycleAdapter mResultAdapter;\n    private EditText mSearchEditText;\n    private View mSearchBarContainer;\n    private View mWidgetContainer;\n    private View mClearButton;\n    private View mMenuButton;\n    private TextView mLauncherTime = null;\n    private final Runnable mUpdateTime = new Runnable() {\n        @Override\n        public void run() {\n            if (mLauncherTime == null ||\n                !mLauncherTime.isAttachedToWindow() ||\n                !mTBLauncherActivity.getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED))\n                return;\n            Date date = new Date();\n            mLauncherTime.setText(DateFormat.getDateTimeInstance().format(date));\n            long delay = 1000 - date.getTime() % 1000;\n            mLauncherTime.postDelayed(mUpdateTime, delay);\n        }\n    };\n    private boolean mLaunchMostRelevantResult = false;\n    private final TextWatcher mSearchTextWatcher = new TextWatcher() {\n        @NonNull\n        String lastText = \"\";\n\n        public void afterTextChanged(Editable s) {\n            //Log.i(TAG, \"afterTextChanged `\" + s + \"`\");\n            // left-trim text.\n            final int length = s.length();\n            int spaceEnd = 0;\n            while (spaceEnd < length && s.charAt(spaceEnd) == ' ')\n                spaceEnd += 1;\n            if (spaceEnd > 0) {\n                // delete and wait for the next call to afterTextChanged generated by the delete\n                s.delete(0, spaceEnd);\n            } else {\n                String text = s.toString();\n                if (lastText.equals(text) || mLaunchMostRelevantResult)\n                    return;\n                if (TextUtils.isEmpty(text))\n                    clearAdapter();\n                else\n                    updateSearchRecords(false, text);\n                updateClearButton();\n            }\n        }\n\n        public void beforeTextChanged(CharSequence s, int start, int count, int after) {\n            lastText = (s != null) ? s.toString() : \"\";\n        }\n\n        public void onTextChanged(CharSequence s, int start, int before, int count) {\n            // do nothing\n        }\n    };\n    private ImageView mLauncherButton;\n    private View mDecorView;\n    private Handler mHandler;\n    private final Runnable mHidePart2Runnable = new Runnable() {\n        @Override\n        public void run() {\n//            if (TBApplication.state().isKeyboardVisible()) {\n//                // if keyboard is visible, the notification bar is also visible\n//                return;\n//            }\n\n            // Delayed hide UI elements\n            ActionBar actionBar = mTBLauncherActivity != null ? mTBLauncherActivity.getSupportActionBar() : null;\n            if (actionBar != null) {\n                actionBar.hide();\n            }\n            SystemUiVisibility.setFullscreen(mDecorView);\n            //mTBLauncherActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);\n        }\n    };\n    private final Runnable mShowPart2Runnable = new Runnable() {\n        @Override\n        public void run() {\n            // Delayed display of UI elements\n            ActionBar actionBar = mTBLauncherActivity != null ? mTBLauncherActivity.getSupportActionBar() : null;\n            if (actionBar != null) {\n                actionBar.show();\n            }\n            SystemUiVisibility.clearFullscreen(mDecorView);\n            //mTBLauncherActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);\n        }\n    };\n    private final Runnable mShowKeyboardRunnable = () -> {\n        if (WindowInsetsHelper.isKeyboardVisible(mSearchEditText))\n            this.mKeyboardHandler.mRequestOpen = false;\n        else\n            this.mKeyboardHandler.showKeyboard();\n    };\n    private final Runnable mOnKeyboardClosedByUser = () -> {\n        Log.i(TAG, \"on keyboard closed by user\");\n        if (dismissPopup())\n            return;\n        LauncherState state = TBApplication.state();\n        if (LauncherState.Desktop.SEARCH == state.getDesktop()) {\n            if (PrefCache.linkCloseKeyboardToBackButton(this.mPref))\n                onBackPressed();\n        }\n        if (LauncherState.Desktop.SEARCH == state.getDesktop()) {\n            if (state.isKeyboardHidden() && PrefCache.modeSearchFullscreen(this.mPref))\n                enableFullscreen(0);\n        }\n    };\n    private View mNotificationBackground;\n    private KeyboardToggleHelper mKeyboardHandler = null;\n    private RecycleScrollListener mRecycleScrollListener;\n    private SharedPreferences mPref;\n\n    private static void launchIntent(@NonNull Behaviour behaviour, @NonNull View view, @NonNull Intent intent) {\n        behaviour.beforeLaunchOccurred();\n        view.postDelayed(() -> {\n            Activity activity = Utilities.getActivity(view);\n            if (activity == null)\n                return;\n            Utilities.setIntentSourceBounds(intent, view);\n            Bundle startActivityOptions = Utilities.makeStartActivityOptions(view);\n            try {\n                activity.startActivity(intent, startActivityOptions);\n            } catch (ActivityNotFoundException ignored) {\n                return;\n            }\n            behaviour.afterLaunchOccurred();\n        }, LAUNCH_DELAY);\n    }\n\n    private void initResultLayout() {\n        mResultLayout = inflateViewStub(R.id.resultLayout);\n\n        mResultList = mResultLayout.findViewById(R.id.resultList);\n        if (mResultList == null)\n            throw new IllegalStateException(\"mResultList==null\");\n\n        mRecycleScrollListener = new RecycleScrollListener(new KeyboardHandler() {\n            @Override\n            public void showKeyboard() {\n                mKeyboardHandler.showKeyboard();\n            }\n\n            @Override\n            public void hideKeyboard() {\n                mKeyboardHandler.hideKeyboard();\n                mKeyboardHandler.mHiddenByScrolling = true;\n            }\n        });\n\n        mResultAdapter = new RecycleAdapter(getContext(), new ArrayList<>());\n\n        mResultList.setHasFixedSize(true);\n        mResultList.setAdapter(mResultAdapter);\n        mResultList.addOnScrollListener(mRecycleScrollListener);\n//        mResultList.addOnLayoutChangeListener(recycleScrollListener);\n\n        int vertical = getContext().getResources().getDimensionPixelSize(R.dimen.result_margin_vertical);\n        mResultList.addItemDecoration(new ResultItemDecoration(0, vertical, true));\n\n        setListLayout();\n    }\n\n    private void initSearchBarContainer() {\n        int layout = PrefCache.getSearchBarLayout(mPref);\n        if (PrefCache.searchBarAtBottom(mPref)) {\n            mSearchBarContainer = inflateViewStub(R.id.stubSearchBottom, layout);\n        } else {\n            mSearchBarContainer = inflateViewStub(R.id.stubSearchTop, layout);\n        }\n        if (mSearchBarContainer == null)\n            throw new IllegalStateException(\"mSearchBarContainer==null\");\n\n        mLauncherButton = mSearchBarContainer.findViewById(R.id.launcherButton);\n        mSearchEditText = mSearchBarContainer.findViewById(R.id.launcherSearch);\n        mClearButton = mSearchBarContainer.findViewById(R.id.clearButton);\n        mMenuButton = mSearchBarContainer.findViewById(R.id.menuButton);\n\n        // when pill search bar expanded, show keyboard\n        mTBLauncherActivity.customizeUI.setExpandedSearchPillListener(this::showKeyboard);\n    }\n\n    private void initLauncherButtons() {\n        final ListPopup buttonMenu;\n        if (PrefCache.getSearchBarLayout(mPref) == R.layout.search_pill)\n            buttonMenu = getButtonPopup(getContext(), ButtonHelper.BTN_ID_LAUNCHER_PILL, R.drawable.launcher_pill);\n        else\n            buttonMenu = getButtonPopup(getContext(), ButtonHelper.BTN_ID_LAUNCHER_WHITE, R.drawable.launcher_white);\n\n        mLauncherButton.setOnClickListener((v) -> executeButtonAction(\"button-launcher\"));\n        mLauncherButton.setOnLongClickListener((v) -> ButtonHelper.showButtonPopup(v, buttonMenu));\n\n        // menu button / 3 dot button actions\n        mMenuButton.setOnClickListener(v -> {\n            Context ctx = v.getContext();\n            ListPopup menu = getMenuPopup(ctx);\n            registerPopup(menu);\n            menu.showCenter(v);\n        });\n        mMenuButton.setOnLongClickListener(v -> {\n            Context ctx = v.getContext();\n            ListPopup menu = getMenuPopup(ctx);\n\n            // check if menu contains elements and if yes show it\n            if (!menu.getAdapter().isEmpty()) {\n                registerPopup(menu);\n                menu.show(v, 0f);\n                return true;\n            }\n\n            return false;\n        });\n\n        // clear button actions\n        mClearButton.setOnClickListener(v -> clearSearch());\n        mClearButton.setOnLongClickListener(v -> {\n            clearSearch();\n\n            Context ctx = v.getContext();\n            ListPopup menu = getMenuPopup(ctx);\n\n            // check if menu contains elements and if yes show it\n            if (!menu.getAdapter().isEmpty()) {\n                registerPopup(menu);\n                menu.show(v);\n                return true;\n            }\n\n            return false;\n        });\n    }\n\n    private void setSearchHint() {\n        Set<String> selectedHints = mPref.getStringSet(\"selected-search-hints\", null);\n        if (selectedHints != null && !selectedHints.isEmpty()) {\n            int random = new Random().nextInt(selectedHints.size());\n            for (String selectedHint : selectedHints) {\n                if (--random < 0) {\n                    mSearchEditText.setHint(selectedHint);\n                    break;\n                }\n            }\n        }\n    }\n\n    private void initLauncherSearchEditText() {\n        setSearchHint();\n\n        mSearchEditText.setTextIsSelectable(false);\n        mSearchEditText.addTextChangedListener(mSearchTextWatcher);\n\n        // On validate, launch first record\n        mSearchEditText.setOnEditorActionListener((view, actionId, event) -> {\n            // Return true if you have consumed the action, else false.\n\n            // if keyboard close action issued\n            if (actionId == android.R.id.closeButton) {\n                LauncherState state = TBApplication.state();\n                // Fix for #238\n                state.syncKeyboardVisibility(view);\n                if (state.isKeyboardHidden()) {\n                    Log.i(TAG, \"Keyboard - closeButton while keyboard hidden\");\n                    return false;\n                }\n                if (state.isSearchBarVisible() && PrefCache.linkKeyboardAndSearchBar(mPref)) {\n                    // consume action to avoid closing the keyboard\n                    Log.i(TAG, \"Keyboard - closeButton - linkKeyboardAndSearchBar\");\n                    return true;\n                }\n                // close the keyboard\n                Log.i(TAG, \"Keyboard - closeButton - close\");\n                return false;\n            }\n\n            // launch most relevant result\n            if (TBApplication.hasSearchTask(getContext())) {\n                mLaunchMostRelevantResult = true;\n                return true;\n            } else {\n                final int mostRelevantIdx = mResultList.getAdapterFirstItemIdx();\n                if (mostRelevantIdx >= 0 && mostRelevantIdx < mResultAdapter.getItemCount()) {\n                    RecyclerView.ViewHolder holder = mResultList.findViewHolderForAdapterPosition(mostRelevantIdx);\n                    mResultAdapter.onClick(mostRelevantIdx, holder != null ? holder.itemView : view);\n                    return true;\n                }\n            }\n            return false;\n        });\n    }\n\n    private KeyboardToggleHelper newKeyboardHandler() {\n        return new KeyboardToggleHelper(mSearchEditText) {\n            @Override\n            public void showKeyboard() {\n                LauncherState state = TBApplication.state();\n                if (TBApplication.activityInvalid(mTBLauncherActivity)) {\n                    Log.e(TAG, \"[activityInvalid] showKeyboard\");\n                    return;\n                }\n\n                if (state.isSearchBarVisible() && PrefCache.modeSearchFullscreen(mPref)) {\n                    showSystemBars();\n                    disableFullscreen();\n                }\n\n                Log.i(TAG, \"Keyboard - SHOW\");\n                removeCallback(mOnKeyboardClosedByUser);\n                dismissPopup();\n\n                mSearchEditText.requestFocus();\n\n                super.showKeyboard();\n            }\n\n            @Override\n            public void hideKeyboard() {\n                if (TBApplication.activityInvalid(mTBLauncherActivity)) {\n                    Log.e(TAG, \"[activityInvalid] hideKeyboard\");\n                    return;\n                }\n                if (TBApplication.state().isSearchBarVisible() && PrefCache.modeSearchFullscreen(mPref)) {\n                    //hideSystemBars();\n                    enableFullscreen(0);\n                }\n\n                Log.i(TAG, \"Keyboard - HIDE\");\n                dismissPopup();\n\n                View focus = mTBLauncherActivity.getCurrentFocus();\n                if (focus != null)\n                    focus.clearFocus();\n                mSearchEditText.clearFocus();\n\n                super.hideKeyboard();\n            }\n        };\n    }\n\n    private void removeCallback(@NonNull Runnable callback) {\n        mHandler.removeCallbacks(callback);\n    }\n\n    private void postDelayedCallbackOnce(@NonNull Runnable callback, long delayMillis) {\n        mHandler.removeCallbacks(callback);\n        mHandler.postDelayed(callback, delayMillis);\n    }\n\n    private void postDelayedRunnableOnce(@NonNull Runnable runnable, @NonNull Object token, long delayMillis) {\n        mHandler.removeCallbacksAndMessages(token);\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {\n            mHandler.postDelayed(runnable, token, delayMillis);\n        } else {\n            Message msg = Message.obtain(mHandler, runnable);\n            msg.obj = token;\n            mHandler.sendMessageDelayed(msg, delayMillis);\n        }\n    }\n\n    public void onCreateActivity(TBLauncherActivity tbLauncherActivity) {\n        mHandler = new Handler(tbLauncherActivity.getMainLooper());\n        mTBLauncherActivity = tbLauncherActivity;\n        mPref = PreferenceManager.getDefaultSharedPreferences(mTBLauncherActivity);\n        Window window = mTBLauncherActivity.getWindow();\n        mDecorView = window.getDecorView();\n        KeyboardTriggerBehaviour keyboardListener = new KeyboardTriggerBehaviour(mTBLauncherActivity);\n        keyboardListener.observe(mTBLauncherActivity, status -> {\n            LauncherState state = TBApplication.state();\n            if (status == KeyboardTriggerBehaviour.Status.CLOSED) {\n                //-->> keyboard CLOSED event <<--//\n\n                boolean keyboardClosedByUser = true;\n                if (state.getSearchBarVisibility() == LauncherState.AnimatedVisibility.ANIM_TO_VISIBLE) {\n                    Log.i(TAG, \"keyboard closed - app start\");\n                    // don't call onKeyboardClosed() when we start the app\n                    keyboardClosedByUser = false;\n                } else if (mKeyboardHandler != null && mKeyboardHandler.mHiddenByScrolling) {\n                    Log.i(TAG, \"keyboard closed - scrolling results\");\n                    // keyboard closed because the result list was scrolled\n                    keyboardClosedByUser = false;\n                } else if (isFragmentDialogVisible()) {\n                    Log.i(TAG, \"keyboard closed - fragment dialog\");\n                    // don't send keyboard close event while we have a dialog open\n                    keyboardClosedByUser = false;\n                } else if (mKeyboardHandler != null && mKeyboardHandler.mLaunchedApp) {\n                    Log.i(TAG, \"keyboard closed - launched app\");\n                    // don't send keyboard close event after start intent\n                    keyboardClosedByUser = false;\n                }\n\n                if (keyboardClosedByUser) {\n                    if (mKeyboardHandler != null && mKeyboardHandler.mRequestOpen) {\n                        Log.i(TAG, \"keyboard closed - while mRequestOpen true\");\n                        mKeyboardHandler.mRequestOpen = false;\n                    } else {\n                        Log.i(TAG, \"keyboard closed - user\");\n                        // delay keyboard closed event to make sure the keyboard is not just glitching\n                        postDelayedCallbackOnce(mOnKeyboardClosedByUser, UI_ANIMATION_DURATION);\n                    }\n                }\n\n                // collapse search pill\n                if (state.isSearchBarVisible()) {\n                    int duration = 0;\n                    if (mPref.getBoolean(\"search-bar-animation\", true))\n                        duration = UI_ANIMATION_DURATION;\n                    mTBLauncherActivity.customizeUI.collapseSearchPill(duration);\n                }\n            } else {\n                //-->> keyboard OPEN event <<--//\n\n                if (mKeyboardHandler != null) {\n                    // request to open fulfilled\n                    mKeyboardHandler.mRequestOpen = false;\n                    // reset HiddenByScrolling flag when keyboard opens\n                    mKeyboardHandler.mHiddenByScrolling = false;\n                }\n\n                // don't call the keyboard closed event if keyboard opened\n                removeCallback(mOnKeyboardClosedByUser);\n\n                // expand search pill\n                if (state.isSearchBarVisible()) {\n                    int duration = 0;\n                    if (mPref.getBoolean(\"search-bar-animation\", true))\n                        duration = UI_ANIMATION_DURATION;\n                    mTBLauncherActivity.customizeUI.expandSearchPill(duration);\n                    mSearchEditText.requestFocus();\n                }\n            }\n        });\n\n        initResultLayout();\n        initSearchBarContainer();\n\n        // KeyboardHandler needs SearchEditText initialized\n        mKeyboardHandler = newKeyboardHandler();\n\n        mNotificationBackground = findViewById(R.id.notificationBackground);\n        mWidgetContainer = findViewById(R.id.widgetContainer);\n\n        initLauncherButtons();\n        initLauncherSearchEditText();\n    }\n\n    public void onStart() {\n        // don't let the close keyboard event trigger\n        mKeyboardHandler.mLaunchedApp = true;\n\n        String initialDesktop = mPref.getString(\"initial-desktop\", null);\n        if (\"none\".equals(initialDesktop)) {\n            if (TBApplication.state().getDesktop() != null) {\n                return;\n            }\n            Log.d(TAG, \"desktop is null\");\n        }\n        if (executeAction(initialDesktop, null))\n            return;\n        switchToDesktop(LauncherState.Desktop.EMPTY);\n    }\n\n    private ListPopup getMenuPopup(Context ctx) {\n        LinearAdapter adapter = new LinearAdapter();\n        ListPopup menu = ListPopup.create(ctx, adapter);\n\n        adapter.add(new LinearAdapter.ItemTitle(ctx, R.string.menu_popup_title));\n        adapter.add(new LinearAdapter.Item(ctx, R.string.change_wallpaper));\n        adapter.add(new LinearAdapter.Item(ctx, R.string.menu_widget_add));\n        if (TBApplication.widgetManager(ctx).widgetCount() > 0)\n            adapter.add(new LinearAdapter.Item(ctx, R.string.menu_widget_remove));\n        adapter.add(new LinearAdapter.ItemTitle(ctx, R.string.menu_popup_title_settings));\n        adapter.add(new LinearAdapter.Item(ctx, R.string.menu_popup_launcher_settings));\n        adapter.add(new LinearAdapter.Item(ctx, R.string.menu_popup_tags_manager));\n        adapter.add(new LinearAdapter.Item(ctx, R.string.menu_popup_tags_menu));\n        adapter.add(new LinearAdapter.Item(ctx, R.string.menu_popup_android_settings));\n\n        menu.setOnItemClickListener((a, v, pos) -> {\n            LinearAdapter.MenuItem item = ((LinearAdapter) a).getItem(pos);\n            @StringRes int stringId = 0;\n            if (item instanceof LinearAdapter.Item) {\n                stringId = ((LinearAdapter.Item) a.getItem(pos)).stringId;\n            }\n            Context c = mTBLauncherActivity;\n            if (stringId == R.string.menu_popup_tags_manager) {\n                launchTagsManagerDialog(mTBLauncherActivity);\n            } else if (stringId == R.string.menu_popup_tags_menu) {\n                executeAction(\"showTagsMenu\", \"button-menu\");\n            } else if (stringId == R.string.menu_popup_launcher_settings) {\n                Intent intent = new Intent(mClearButton.getContext(), SettingsActivity.class);\n                launchIntent(this, mClearButton, intent);\n            } else if (stringId == R.string.change_wallpaper) {\n                Intent intent = new Intent(Intent.ACTION_SET_WALLPAPER);\n                intent = Intent.createChooser(intent, c.getString(R.string.change_wallpaper));\n                launchIntent(this, mClearButton, intent);\n            } else if (stringId == R.string.menu_widget_add) {\n                TBApplication.widgetManager(c).showSelectWidget(mTBLauncherActivity);\n            } else if (stringId == R.string.menu_widget_remove) {\n                TBApplication.widgetManager(c).showRemoveWidgetPopup();\n            } else if (stringId == R.string.menu_popup_android_settings) {\n                Intent intent = new Intent(Settings.ACTION_SETTINGS);\n                launchIntent(this, mClearButton, intent);\n            }\n        });\n\n        return menu;\n    }\n\n    public void launchIntent(@NonNull View view, @NonNull Intent intent) {\n        launchIntent(this, view, intent);\n    }\n\n    @SuppressWarnings(\"TypeParameterUnusedInFormals\")\n    private <T extends View> T findViewById(@IdRes int id) {\n        return mTBLauncherActivity.findViewById(id);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    private <T extends View> T inflateViewStub(@IdRes int id) {\n        View stub = mTBLauncherActivity.findViewById(id);\n        return (T) ViewStubPreview.inflateStub(stub);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    private <T extends View> T inflateViewStub(@IdRes int id, @LayoutRes int layoutRes) {\n        View stub = mTBLauncherActivity.findViewById(id);\n        return (T) ViewStubPreview.inflateStub(stub, layoutRes);\n    }\n\n    private void updateClearButton() {\n        if (mSearchEditText.getText().length() > 0 || TBApplication.state().isResultListVisible()) {\n            mClearButton.setVisibility(View.VISIBLE);\n            mMenuButton.setVisibility(View.INVISIBLE);\n        } else {\n            mClearButton.setVisibility(View.INVISIBLE);\n            mMenuButton.setVisibility(View.VISIBLE);\n        }\n    }\n\n    public void switchToDesktop(@NonNull LauncherState.Desktop mode) {\n        // get current mode\n        @Nullable\n        LauncherState.Desktop currentMode = TBApplication.state().getDesktop();\n        Log.d(TAG, \"desktop changed \" + currentMode + \" -> \" + mode);\n        if (mode.equals(currentMode)) {\n            // no change, maybe refresh?\n            if (TBApplication.state().isResultListVisible() && mResultAdapter.getItemCount() == 0)\n                showDesktop(mode);\n            return;\n        }\n\n        // hide current mode\n        if (currentMode != null) {\n            switch (currentMode) {\n                case SEARCH:\n                    resetTask();\n                    hideSearchBar();\n                    break;\n                case WIDGET:\n                    hideWidgets();\n                    break;\n                case EMPTY:\n                default:\n                    break;\n            }\n        }\n\n        // show next mode\n        showDesktop(mode);\n    }\n\n    private void showDesktop(LauncherState.Desktop mode) {\n        if (TBApplication.activityInvalid(mTBLauncherActivity)) {\n            Log.e(TAG, \"[activityInvalid] showDesktop \" + mode);\n            return;\n        }\n        TBApplication.state().setDesktop(mode);\n        switch (mode) {\n            case SEARCH:\n                // show the SearchBar\n                showSearchBar();\n\n                // hide/show result list\n                final String openResult = PrefCache.modeSearchOpenResult(mPref);\n                if (\"none\".equals(openResult)) {\n                    // hide result\n                    hideResultList(false);\n                } else {\n                    // try to execute the action\n                    postDelayedRunnableOnce(() ->\n                            TBApplication.dataHandler(getContext()).runAfterLoadOver(() -> {\n                                LauncherState state = TBApplication.state();\n                                if (!state.isResultListVisible() && state.getDesktop() == LauncherState.Desktop.SEARCH)\n                                    executeAction(openResult, \"dm-search-open-result\");\n                            }),\n                        mLauncherButton,\n                        KEYBOARD_ANIMATION_DELAY);\n                }\n\n                // hide/show the QuickList\n                TBApplication.quickList(getContext()).updateVisibility();\n\n                // enable/disable fullscreen (status and navigation bar)\n                if (TBApplication.state().isKeyboardHidden()\n                    && PrefCache.modeSearchFullscreen(mPref))\n                    enableFullscreen(UI_ANIMATION_DELAY);\n                else\n                    disableFullscreen();\n                break;\n            case WIDGET:\n                // show widgets\n                showWidgets();\n                // hide/show the QuickList\n                TBApplication.quickList(getContext()).updateVisibility();\n                // enable/disable fullscreen (status and navigation bar)\n                if (PrefCache.modeWidgetFullscreen(mPref))\n                    enableFullscreen(UI_ANIMATION_DELAY);\n                else\n                    disableFullscreen();\n                break;\n            case EMPTY:\n            default:\n                // hide/show the QuickList\n                TBApplication.quickList(getContext()).updateVisibility();\n                // enable/disable fullscreen (status and navigation bar)\n                if (PrefCache.modeEmptyFullscreen(mPref))\n                    enableFullscreen(UI_ANIMATION_DELAY);\n                else\n                    disableFullscreen();\n                break;\n        }\n    }\n\n    /**\n     * Hide status and notification bar\n     *\n     * @param startDelay milliseconds of delay\n     */\n    private void enableFullscreen(int startDelay) {\n        boolean animate = !SystemUiVisibility.isFullscreenSet(mDecorView) || TBApplication.state().isNotificationBarVisible();\n\n        Log.i(TAG, \"enableFullscreen delay=\" + startDelay + \" anim=\" + animate);\n\n        // Schedule a runnable to remove the status and navigation bar after a delay\n        removeCallback(mShowPart2Runnable);\n        postDelayedCallbackOnce(mHidePart2Runnable, startDelay);\n\n        // hide notification background\n        final int statusHeight = UISizes.getStatusBarSize(getContext());\n        if (TBApplication.state().getNotificationBarVisibility() != LauncherState.AnimatedVisibility.ANIM_TO_HIDDEN)\n            mNotificationBackground.animate().cancel();\n        if (animate) {\n            mNotificationBackground.animate()\n                .translationY(-statusHeight)\n                .setStartDelay(startDelay)\n                .setDuration(UI_ANIMATION_DURATION)\n                .setInterpolator(new AccelerateInterpolator())\n                .setListener(new AnimatorListenerAdapter() {\n                    @Override\n                    public void onAnimationStart(Animator animation) {\n                        TBApplication.state().setNotificationBar(LauncherState.AnimatedVisibility.ANIM_TO_HIDDEN);\n                    }\n\n                    @Override\n                    public void onAnimationEnd(Animator animation) {\n                        TBApplication.state().setNotificationBar(LauncherState.AnimatedVisibility.HIDDEN);\n                    }\n                })\n                .start();\n        } else {\n            TBApplication.state().setNotificationBar(LauncherState.AnimatedVisibility.HIDDEN);\n            mNotificationBackground.setTranslationY(-statusHeight);\n        }\n    }\n\n    /**\n     * Show status and notification bar\n     */\n    private void disableFullscreen() {\n        boolean animate = SystemUiVisibility.isFullscreenSet(mDecorView) || !TBApplication.state().isNotificationBarVisible();\n\n        // Schedule a runnable to display UI elements after a delay\n        removeCallback(mHidePart2Runnable);\n        postDelayedCallbackOnce(mShowPart2Runnable, UI_ANIMATION_DELAY);\n\n        // show notification background\n        final int statusHeight = UISizes.getStatusBarSize(getContext());\n        if (!TBApplication.state().isNotificationBarVisible())\n            mNotificationBackground.setTranslationY(-statusHeight);\n        if (TBApplication.state().getNotificationBarVisibility() != LauncherState.AnimatedVisibility.ANIM_TO_VISIBLE)\n            mNotificationBackground.animate().cancel();\n        if (animate) {\n            mNotificationBackground.animate()\n                .translationY(0f)\n                .setStartDelay(0)\n                .setDuration(UI_ANIMATION_DURATION)\n                .setInterpolator(new LinearInterpolator())\n                .setListener(new AnimatorListenerAdapter() {\n                    @Override\n                    public void onAnimationStart(Animator animation) {\n                        TBApplication.state().setNotificationBar(LauncherState.AnimatedVisibility.ANIM_TO_VISIBLE);\n                    }\n\n                    @Override\n                    public void onAnimationEnd(Animator animation) {\n                        TBApplication.state().setNotificationBar(LauncherState.AnimatedVisibility.VISIBLE);\n                    }\n                })\n                .start();\n        } else {\n            TBApplication.state().setNotificationBar(LauncherState.AnimatedVisibility.VISIBLE);\n            mNotificationBackground.setTranslationY(0f);\n        }\n    }\n\n    private void showSearchBar() {\n        mSearchEditText.setEnabled(true);\n        setSearchHint();\n        UITheme.applySearchBarTextShadow(mSearchEditText);\n\n        if (TBApplication.state().getSearchBarVisibility() != LauncherState.AnimatedVisibility.ANIM_TO_VISIBLE)\n            mSearchBarContainer.animate().cancel();\n        mSearchBarContainer.setVisibility(View.VISIBLE);\n        mSearchBarContainer.animate()\n            .setStartDelay(0)\n            .alpha(1f)\n            .translationY(0f)\n            .setDuration(UI_ANIMATION_DURATION)\n            .setInterpolator(new DecelerateInterpolator())\n            .setListener(new AnimatorListenerAdapter() {\n                @Override\n                public void onAnimationStart(Animator animation) {\n                    TBApplication.state().setSearchBar(LauncherState.AnimatedVisibility.ANIM_TO_VISIBLE);\n                }\n\n                @Override\n                public void onAnimationEnd(Animator animation) {\n                    LauncherState state = TBApplication.state();\n                    state.setSearchBar(LauncherState.AnimatedVisibility.VISIBLE);\n                    if (PrefCache.linkKeyboardAndSearchBar(mPref))\n                        showKeyboard();\n                    else {\n                        // sync keyboard state\n                        state.syncKeyboardVisibility(mSearchEditText);\n                    }\n                }\n            })\n            .start();\n\n        mSearchEditText.requestFocus();\n    }\n\n    private void hideWidgets() {\n        TBApplication.state().setWidgetScreen(LauncherState.AnimatedVisibility.HIDDEN);\n        mWidgetContainer.setVisibility(View.GONE);\n    }\n\n    private void hideSearchBar() {\n        boolean animate = !TBApplication.state().isSearchBarVisible();\n        hideSearchBar(animate);\n    }\n\n    private void hideSearchBar(boolean animate) {\n        clearSearchText();\n        clearAdapter();\n\n        if (mSearchBarContainer.getVisibility() == View.VISIBLE) {\n            final float translationY;\n            if (PrefCache.searchBarAtBottom(mPref))\n                translationY = mSearchBarContainer.getHeight() * 2f;\n            else\n                translationY = mSearchBarContainer.getHeight() * -2f;\n\n            if (TBApplication.state().getSearchBarVisibility() != LauncherState.AnimatedVisibility.ANIM_TO_HIDDEN)\n                mSearchBarContainer.animate().cancel();\n\n            if (animate) {\n                mSearchBarContainer.setTranslationY(0f);\n                mSearchBarContainer.animate()\n                    .alpha(0f)\n                    .translationY(translationY)\n                    .setDuration(UI_ANIMATION_DURATION)\n                    .setInterpolator(new AccelerateInterpolator())\n                    .setListener(new AnimatorListenerAdapter() {\n                        @Override\n                        public void onAnimationStart(Animator animation) {\n                            TBApplication.state().setSearchBar(LauncherState.AnimatedVisibility.ANIM_TO_HIDDEN);\n                        }\n\n                        @Override\n                        public void onAnimationEnd(Animator animation) {\n                            TBApplication.state().setSearchBar(LauncherState.AnimatedVisibility.HIDDEN);\n                            mSearchBarContainer.setVisibility(View.GONE);\n                        }\n                    })\n                    .start();\n            } else {\n                TBApplication.state().setSearchBar(LauncherState.AnimatedVisibility.HIDDEN);\n                mSearchBarContainer.setAlpha(0f);\n                mSearchBarContainer.setTranslationY(translationY);\n                mSearchBarContainer.setVisibility(View.GONE);\n            }\n        } else {\n            Log.d(TAG, \"mSearchBarContainer not VISIBLE, setting state to HIDDEN\");\n            TBApplication.state().setResultList(LauncherState.AnimatedVisibility.HIDDEN);\n        }\n\n        if (PrefCache.linkKeyboardAndSearchBar(mPref))\n            hideKeyboard();\n        // disabling mSearchEditText will most probably also close the keyboard\n        mSearchEditText.setEnabled(false);\n    }\n\n    private void showWidgets() {\n        boolean animate = !TBApplication.state().isWidgetScreenVisible();\n        showWidgets(animate);\n    }\n\n    private void showWidgets(boolean animate) {\n        if (TBApplication.state().getWidgetScreenVisibility() != LauncherState.AnimatedVisibility.ANIM_TO_VISIBLE)\n            mSearchBarContainer.animate().cancel();\n\n        mWidgetContainer.setVisibility(View.VISIBLE);\n        if (animate) {\n            mWidgetContainer.setAlpha(0f);\n            mWidgetContainer.animate()\n                .setStartDelay(UI_ANIMATION_DURATION)\n                .alpha(1f)\n                .setDuration(UI_ANIMATION_DURATION)\n                .setInterpolator(new DecelerateInterpolator())\n                .setListener(new AnimatorListenerAdapter() {\n                    @Override\n                    public void onAnimationStart(Animator animation) {\n                        TBApplication.state().setWidgetScreen(LauncherState.AnimatedVisibility.ANIM_TO_VISIBLE);\n                    }\n\n                    @Override\n                    public void onAnimationEnd(Animator animation) {\n                        TBApplication.state().setWidgetScreen(LauncherState.AnimatedVisibility.VISIBLE);\n                    }\n                })\n                .start();\n        } else {\n            mWidgetContainer.animate().cancel();\n            TBApplication.state().setWidgetScreen(LauncherState.AnimatedVisibility.VISIBLE);\n            mWidgetContainer.setAlpha(1f);\n        }\n        hideResultList(animate);\n    }\n\n    public void showKeyboard() {\n        mKeyboardHandler.mRequestOpen = true;\n\n        mKeyboardHandler.showKeyboard();\n\n        // UI_ANIMATION_DURATION should be the exact time the full-screen animation ends\n        postDelayedCallbackOnce(mShowKeyboardRunnable, UI_ANIMATION_DELAY);\n    }\n\n    public void hideKeyboard() {\n        mKeyboardHandler.mRequestOpen = false;\n\n        removeCallback(mShowKeyboardRunnable);\n        mKeyboardHandler.hideKeyboard();\n    }\n\n    @Override\n    public void displayLoader(boolean running) {\n        if (mLauncherButton == null)\n            return;\n\n        Drawable loadingDrawable = mLauncherButton.getDrawable();\n        if (loadingDrawable instanceof Animatable) {\n            if (running)\n                ((Animatable) loadingDrawable).start();\n            else\n                ((Animatable) loadingDrawable).stop();\n        }\n    }\n\n    @NonNull\n    @Override\n    public Context getContext() {\n        return mTBLauncherActivity;\n    }\n\n    @Override\n    public void resetTask() {\n        TBApplication.resetTask(getContext());\n    }\n\n    @Override\n    public void clearAdapter() {\n        mResultAdapter.clear();\n        TBApplication.quickList(getContext()).adapterCleared();\n\n        if (TBApplication.state().isResultListVisible())\n            hideResultList(true);\n        updateClearButton();\n    }\n\n    public boolean showProviderEntries(@Nullable IProvider<?> provider) {\n        return showProviderEntries(provider, null);\n    }\n\n    public boolean showProviderEntries(@Nullable IProvider<?> provider, @Nullable java.util.Comparator<? super EntryItem> comparator) {\n        if (TBApplication.state().getDesktop() != LauncherState.Desktop.SEARCH) {\n            // TODO: switchToDesktop might show the result list, we may need to prevent this as an optimization\n            switchToDesktop(LauncherState.Desktop.SEARCH);\n            clearAdapter();\n        }\n\n        List<? extends EntryItem> entries = provider != null ? provider.getPojos() : null;\n        if (entries != null && entries.size() > 0) {\n            // reset relevance. This is normally done by a Searcher.\n            for (EntryItem entry : entries)\n                entry.resetResultInfo();\n\n//            // copy list in order to change it\n//            entries = new ArrayList<>(entries);\n//            // remove actions and filters from the result list\n//            for (Iterator<? extends EntryItem> iterator = entries.iterator(); iterator.hasNext(); ) {\n//                EntryItem entry = iterator.next();\n//                if (entry instanceof FilterEntry)\n//                    iterator.remove();\n//            }\n\n            if (comparator != null) {\n                // copy list in order to change it\n                entries = new ArrayList<>(entries);\n                //TODO: do we need this on another thread?\n                Collections.sort(entries, comparator);\n            }\n\n            updateAdapter(entries, false);\n            return true;\n        }\n\n        return false;\n    }\n\n    @Override\n    public void updateAdapter(@NonNull List<? extends EntryItem> results, boolean isRefresh) {\n        Log.d(TAG, \"updateAdapter \" + results.size() + \" result(s); isRefresh=\" + isRefresh);\n\n        if (!isFragmentDialogVisible()) {\n            LauncherState state = TBApplication.state();\n            if (!state.isResultListVisible() && state.getDesktop() == LauncherState.Desktop.SEARCH)\n                showResultList(false);\n        }\n        mResultAdapter.updateItems(results);\n\n        if (!isRefresh) {\n            // Make sure the first item is visible when we search\n            mResultList.scrollToFirstItem();\n        }\n\n        mTBLauncherActivity.quickList.adapterUpdated();\n        mClearButton.setVisibility(View.VISIBLE);\n        mMenuButton.setVisibility(View.INVISIBLE);\n\n        if (mLaunchMostRelevantResult) {\n            mLaunchMostRelevantResult = false;\n\n            // get any view\n            View view = mResultList.getLayoutManager() != null ? mResultList.getLayoutManager().getChildAt(0) : null;\n            final int mostRelevantIdx = mResultList.getAdapterFirstItemIdx();\n            // try to get view of the most relevant item from adapter\n            if (mostRelevantIdx >= 0 && mostRelevantIdx < mResultAdapter.getItemCount()) {\n                RecyclerView.ViewHolder holder = mResultList.findViewHolderForAdapterPosition(mostRelevantIdx);\n                if (holder != null)\n                    view = holder.itemView;\n            }\n            if (view != null)\n                mResultAdapter.onClick(mostRelevantIdx, view);\n        }\n    }\n\n    @Override\n    public void removeResult(@NonNull EntryItem result) {\n        // Do not reset scroll, we want the remaining items to still be in view\n        mResultAdapter.removeItem(result);\n    }\n\n    @Override\n    public void filterResults(String text) {\n        mResultAdapter.getFilter().filter(text);\n    }\n\n    public void handleRemoveApp(String packageName) {\n        int count = mResultAdapter.getItemCount();\n        for (int idx = count - 1; idx >= 0; idx -= 1) {\n            EntryItem entryItem = mResultAdapter.getItem(idx);\n            if (entryItem.id.contains(packageName))\n                removeResult(entryItem);\n        }\n    }\n\n    public void runSearcher(@NonNull String query, @NonNull Class<? extends Searcher> searcherClass) {\n        if (TBApplication.state().getDesktop() != LauncherState.Desktop.SEARCH) {\n            // TODO: switchToDesktop might show the result list, we may need to prevent this as an optimization\n            switchToDesktop(LauncherState.Desktop.SEARCH);\n            clearAdapter();\n        }\n\n        clearSearchText();\n        Searcher searcher = null;\n        try {\n            Constructor<? extends Searcher> constructor = searcherClass.getConstructor(ISearchActivity.class, String.class);\n            searcher = constructor.newInstance(this, query);\n        } catch (ReflectiveOperationException e) {\n            Log.e(TAG, \"new <? extends Searcher>\", e);\n        }\n        if (searcher != null)\n            updateSearchRecords(false, searcher);\n    }\n\n    public void clearSearchText() {\n        if (mSearchEditText == null)\n            return;\n\n        mSearchEditText.removeTextChangedListener(mSearchTextWatcher);\n        mSearchEditText.setText(\"\");\n        mSearchEditText.addTextChangedListener(mSearchTextWatcher);\n    }\n\n    public void clearSearch() {\n        clearSearchText();\n        clearAdapter();\n        updateClearButton();\n    }\n\n    public void refreshSearchRecords() {\n        if (mResultList != null) {\n            mResultList.getRecycledViewPool().clear();\n        }\n        if (mResultAdapter != null) {\n            mResultAdapter.setGridLayout(getContext(), isGridLayout());\n            mResultAdapter.refresh();\n        }\n    }\n\n    public void refreshSearchRecord(EntryItem entry) {\n        mResultAdapter.notifyItemChanged(entry);\n    }\n\n    private void showResultList(boolean animate) {\n        Log.d(TAG, \"showResultList (anim \" + animate + \")\");\n        if (TBApplication.state().getResultListVisibility() != LauncherState.AnimatedVisibility.ANIM_TO_VISIBLE)\n            mResultLayout.animate().cancel();\n\n        mResultLayout.setVisibility(View.VISIBLE);\n        if (animate) {\n            TBApplication.state().setResultList(LauncherState.AnimatedVisibility.ANIM_TO_VISIBLE);\n            mResultLayout.setAlpha(0f);\n            mResultLayout.animate()\n                .alpha(1f)\n                .setDuration(UI_ANIMATION_DURATION)\n                .setListener(new AnimatorListenerAdapter() {\n                    @Override\n                    public void onAnimationEnd(Animator animation) {\n                        TBApplication.state().setResultList(LauncherState.AnimatedVisibility.VISIBLE);\n                    }\n                })\n                .start();\n        } else {\n            TBApplication.state().setResultList(LauncherState.AnimatedVisibility.VISIBLE);\n            mResultLayout.setAlpha(1f);\n        }\n    }\n\n    private void hideResultList(boolean animate) {\n        Log.d(TAG, \"hideResultList (anim \" + animate + \")\");\n        if (TBApplication.state().getResultListVisibility() != LauncherState.AnimatedVisibility.ANIM_TO_HIDDEN)\n            mResultLayout.animate().cancel();\n        if (mResultLayout.getVisibility() != View.VISIBLE) {\n            Log.d(TAG, \"mResultLayout not VISIBLE, setting state to HIDDEN\");\n            TBApplication.state().setResultList(LauncherState.AnimatedVisibility.HIDDEN);\n            return;\n        }\n        if (animate) {\n            TBApplication.state().setResultList(LauncherState.AnimatedVisibility.ANIM_TO_HIDDEN);\n            mResultLayout.animate()\n                .alpha(0f)\n                .setDuration(UI_ANIMATION_DURATION)\n                .setListener(new AnimatorListenerAdapter() {\n                    @Override\n                    public void onAnimationEnd(Animator animation) {\n                        TBApplication.state().setResultList(LauncherState.AnimatedVisibility.HIDDEN);\n                        Log.d(TAG, \"mResultLayout set INVISIBLE\");\n                        mResultLayout.setVisibility(View.INVISIBLE);\n                    }\n                })\n                .start();\n        } else {\n            TBApplication.state().setResultList(LauncherState.AnimatedVisibility.HIDDEN);\n            Log.d(TAG, \"mResultLayout set INVISIBLE\");\n            mResultLayout.setVisibility(View.INVISIBLE);\n        }\n    }\n\n    public void updateSearchRecords() {\n        Editable searchText = mSearchEditText.getText();\n        if (searchText.length() > 0) {\n            String text = searchText.toString();\n            updateSearchRecords(true, text);\n        } else {\n            refreshSearchRecords();\n        }\n    }\n\n    /**\n     * This function gets called on query changes.\n     * It will ask all the providers for data\n     * This function is not called for non search-related changes! Have a look at onDataSetChanged() if that's what you're looking for :)\n     *\n     * @param isRefresh whether the query is refreshing the existing result, or is a completely new query\n     * @param query     the query on which to search\n     */\n    private void updateSearchRecords(boolean isRefresh, @NonNull String query) {\n        updateSearchRecords(isRefresh, new QuerySearcher(this, query));\n    }\n\n    private void updateSearchRecords(boolean isRefresh, @NonNull Searcher searcher) {\n        searcher.setRefresh(isRefresh);\n\n        resetTask();\n        dismissPopup();\n\n        TBApplication.runTask(getContext(), searcher);\n        boolean animate = !TBApplication.state().isResultListVisible();\n        showResultList(animate);\n    }\n\n    public void beforeLaunchOccurred() {\n        RecycleScrollListener.setListLayoutHeight(mResultList, mResultList.getHeight());\n\n        // don't call the keyboard closed event\n        mKeyboardHandler.mLaunchedApp = true;\n\n        hideKeyboard();\n    }\n\n    public void afterLaunchOccurred() {\n        postDelayedRunnableOnce(() -> {\n            RecycleScrollListener.setListLayoutHeight(mResultList, ViewGroup.LayoutParams.MATCH_PARENT);\n            if (PrefCache.clearSearchAfterLaunch(mPref)) {\n                // We selected an item on the list, now we can cleanup the filter:\n                if (mSearchEditText.getText().length() > 0) {\n                    mSearchEditText.setText(\"\");\n                } else if (TBApplication.state().isResultListVisible()) {\n                    clearAdapter();\n                }\n            }\n            if (PrefCache.showWidgetScreenAfterLaunch(mPref)) {\n                // show widgets when we return to the launcher\n                switchToDesktop(LauncherState.Desktop.WIDGET);\n            }\n        }, mSearchEditText, UI_ANIMATION_DELAY);\n    }\n\n    public void showContextMenu() {\n        mMenuButton.performClick();\n    }\n\n    public void setListLayout() {\n        // update adapter draw flags\n        mResultAdapter.setGridLayout(getContext(), false);\n\n        // get layout manager\n        RecyclerView.LayoutManager layoutManager = mResultList.getLayoutManager();\n        if (!(layoutManager instanceof CustomRecycleLayoutManager)) {\n            mResultList.setLayoutManager(layoutManager = new CustomRecycleLayoutManager());\n            ((CustomRecycleLayoutManager) layoutManager).setOverScrollListener(mRecycleScrollListener);\n        }\n\n        CustomRecycleLayoutManager lm = (CustomRecycleLayoutManager) layoutManager;\n        lm.setBottomToTop(PrefCache.firstAtBottom(mPref));\n        lm.setColumns(1, false);\n    }\n\n    public void setGridLayout() {\n        setGridLayout(3);\n    }\n\n    public void setGridLayout(int columnCount) {\n        // update adapter draw flags\n        mResultAdapter.setGridLayout(getContext(), true);\n\n        // get layout manager\n        RecyclerView.LayoutManager layoutManager = mResultList.getLayoutManager();\n        if (!(layoutManager instanceof CustomRecycleLayoutManager)) {\n            mResultList.setLayoutManager(layoutManager = new CustomRecycleLayoutManager());\n            ((CustomRecycleLayoutManager) layoutManager).setOverScrollListener(mRecycleScrollListener);\n        }\n\n        CustomRecycleLayoutManager lm = (CustomRecycleLayoutManager) layoutManager;\n        lm.setBottomToTop(PrefCache.firstAtBottom(mPref));\n        lm.setRightToLeft(PrefCache.rightToLeft(mPref));\n        lm.setColumns(columnCount, false);\n    }\n\n    public boolean isGridLayout() {\n        RecyclerView.LayoutManager layoutManager = mResultList.getLayoutManager();\n        if (layoutManager instanceof CustomRecycleLayoutManager)\n            return ((CustomRecycleLayoutManager) layoutManager).getColumnCount() > 1;\n        return false;\n    }\n\n    /**\n     * Handle the back button press. Returns true if action handled.\n     *\n     * @return returns true if action handled\n     */\n    public boolean onBackPressed() {\n        if (closeFragmentDialog())\n            return true;\n\n        Log.i(TAG, \"onBackPressed query=\" + mSearchEditText.getText());\n        mSearchEditText.setText(\"\");\n\n        LauncherState.Desktop desktop = TBApplication.state().getDesktop();\n        if (desktop != null) {\n            switch (desktop) {\n                case SEARCH:\n                    executeButtonAction(\"dm-search-back\");\n                    break;\n                case WIDGET:\n                    executeButtonAction(\"dm-widget-back\");\n                    break;\n                case EMPTY:\n                default:\n                    executeButtonAction(\"dm-empty-back\");\n                    break;\n            }\n        }\n\n        // Calling super.onBackPressed() will quit the launcher, only do this if this is not the user's default home.\n        // Action not handled (return false) if not the default launcher.\n        return TBApplication.isDefaultLauncher(mTBLauncherActivity);\n    }\n\n    @NonNull\n    public static ListPopup getButtonPopup(@NonNull Context ctx, @NonNull String buttonId, @DrawableRes int defaultButtonIcon) {\n        LinearAdapter adapter = new LinearAdapter();\n        adapter.add(new LinearAdapter.ItemTitle(ctx, R.string.popup_title_customize));\n        adapter.add(new LinearAdapter.Item(ctx, R.string.menu_custom_icon));\n\n        return ListPopup.create(ctx, adapter).setOnItemClickListener((a, view, pos) -> {\n            LinearAdapter.MenuItem menuItem = ((LinearAdapter) a).getItem(pos);\n            @StringRes int id = 0;\n            if (menuItem instanceof LinearAdapter.Item) {\n                id = ((LinearAdapter.Item) a.getItem(pos)).stringId;\n            }\n            if (id == R.string.menu_custom_icon) {\n                TBApplication.behaviour(ctx).launchCustomIconDialog(buttonId, defaultButtonIcon, () -> {\n                    // refresh search bar preferences to reload the icon\n                    TBApplication.ui(ctx).refreshSearchBar();\n                });\n            }\n        });\n    }\n\n    @NonNull\n    public static IconSelectDialog getCustomIconDialog(@NonNull Context ctx, boolean hideResultList) {\n        IconSelectDialog dialog = new IconSelectDialog();\n        //openFragmentDialog(dialog, DIALOG_CUSTOM_ICON);\n        if (hideResultList) {\n            // If results are visible\n            if (TBApplication.state().isResultListVisible()) {\n                final Behaviour behaviour = TBApplication.behaviour(ctx);\n                behaviour.mResultLayout.setVisibility(View.INVISIBLE);\n                // OnDismiss: We restore mResultLayout visibility\n                dialog.setOnDismissListener(dlg -> behaviour.mResultLayout.setVisibility(View.VISIBLE));\n            }\n        }\n\n        //dialog.show(mTBLauncherActivity.getSupportFragmentManager(), DIALOG_CUSTOM_ICON);\n        return dialog;\n    }\n\n    public void launchCustomIconDialog(AppEntry appEntry) {\n        IconSelectDialog dialog = getCustomIconDialog(getContext(), false);\n        dialog\n            .putArgString(\"componentName\", appEntry.getUserComponentName())\n            .putArgLong(\"customIcon\", appEntry.getCustomIcon())\n            .putArgString(\"entryName\", appEntry.getName());\n\n        dialog.setOnConfirmListener(drawable -> {\n            TBApplication app = TBApplication.getApplication(getContext());\n            if (drawable == null)\n                app.iconsHandler().restoreDefaultIcon(appEntry);\n            else\n                app.iconsHandler().changeIcon(appEntry, drawable);\n            // force a result refresh to update the icon in the view\n            refreshSearchRecord(appEntry);\n            mTBLauncherActivity.queueDockReload();\n        });\n        showDialog(dialog, DIALOG_CUSTOM_ICON);\n    }\n\n    public void launchCustomIconDialog(ShortcutEntry shortcutEntry) {\n        IconSelectDialog dialog = getCustomIconDialog(getContext(), true);\n        dialog\n            .putArgString(\"packageName\", shortcutEntry.packageName)\n            .putArgString(\"shortcutData\", shortcutEntry.shortcutData)\n            .putArgString(\"shortcutId\", shortcutEntry.id);\n\n        dialog.setOnConfirmListener(drawable -> {\n            final TBApplication app = TBApplication.getApplication(mTBLauncherActivity);\n            if (drawable == null)\n                app.iconsHandler().restoreDefaultIcon(shortcutEntry);\n            else\n                app.iconsHandler().changeIcon(shortcutEntry, drawable);\n            // force a result refresh to update the icon in the view\n            refreshSearchRecord(shortcutEntry);\n            mTBLauncherActivity.queueDockReload();\n        });\n        showDialog(dialog, DIALOG_CUSTOM_ICON);\n    }\n\n    public void launchCustomIconDialog(@NonNull StaticEntry staticEntry) {\n        launchCustomIconDialog(staticEntry, null);\n    }\n\n    public void launchCustomIconDialog(@NonNull StaticEntry staticEntry, @Nullable Runnable afterConfirmation) {\n        IconSelectDialog dialog = getCustomIconDialog(getContext(), true);\n        dialog.putArgString(\"entryId\", staticEntry.id);\n\n        dialog.setOnConfirmListener(drawable -> {\n            final TBApplication app = TBApplication.getApplication(mTBLauncherActivity);\n            if (drawable == null)\n                app.iconsHandler().restoreDefaultIcon(staticEntry);\n            else\n                app.iconsHandler().changeIcon(staticEntry, drawable);\n            // force a result refresh to update the icon in the view\n            refreshSearchRecord(staticEntry);\n            mTBLauncherActivity.queueDockReload();\n            if (afterConfirmation != null)\n                afterConfirmation.run();\n        });\n        showDialog(dialog, DIALOG_CUSTOM_ICON);\n    }\n\n    public void launchCustomIconDialog(@NonNull SearchEntry searchEntry, @Nullable Runnable afterConfirmation) {\n        IconSelectDialog dialog = getCustomIconDialog(getContext(), true);\n        dialog\n            .putArgString(\"searchEntryId\", searchEntry.id)\n            .putArgString(\"searchName\", searchEntry.getName());\n\n        dialog.setOnConfirmListener(drawable -> {\n            final TBApplication app = TBApplication.getApplication(mTBLauncherActivity);\n            if (drawable == null)\n                app.iconsHandler().restoreDefaultIcon(searchEntry);\n            else\n                app.iconsHandler().changeIcon(searchEntry, drawable);\n            // force a result refresh to update the icon in the view\n            refreshSearchRecord(searchEntry);\n            mTBLauncherActivity.queueDockReload();\n            if (afterConfirmation != null)\n                afterConfirmation.run();\n        });\n        showDialog(dialog, DIALOG_CUSTOM_ICON);\n    }\n\n    /**\n     * Change the icon for the \"Dial\" contact\n     *\n     * @param dialEntry entry that currently holds the \"Dial\" icon\n     */\n    public void launchCustomIconDialog(@NonNull DialContactEntry dialEntry) {\n        IconSelectDialog dialog = getCustomIconDialog(getContext(), true);\n        dialog\n            .putArgString(\"contactEntryId\", dialEntry.id)\n            .putArgString(\"contactName\", dialEntry.getName());\n\n        dialog.setOnConfirmListener(drawable -> {\n            final TBApplication app = TBApplication.getApplication(getContext());\n            if (drawable == null)\n                app.iconsHandler().restoreDefaultIcon(dialEntry);\n            else\n                app.iconsHandler().changeIcon(dialEntry, drawable);\n            // force a result refresh to update the icon in the view\n            refreshSearchRecord(dialEntry);\n            mTBLauncherActivity.queueDockReload();\n        });\n        showDialog(dialog, DIALOG_CUSTOM_ICON);\n    }\n\n\n    public void launchCustomIconDialog(@NonNull String buttonId, int defaultButtonIcon, @Nullable Runnable afterConfirmation) {\n        IconSelectDialog dialog = getCustomIconDialog(getContext(), true);\n        dialog\n            .putArgString(\"buttonId\", buttonId)\n            .putArgInt(\"defaultIcon\", defaultButtonIcon);\n\n        dialog.setOnConfirmListener(drawable -> {\n            var iconsHandler = TBApplication.iconsHandler(getContext());\n            if (drawable == null)\n                iconsHandler.restoreDefaultIcon(buttonId);\n            else\n                iconsHandler.changeIcon(buttonId, drawable);\n            if (afterConfirmation != null)\n                afterConfirmation.run();\n        });\n        showDialog(dialog, DIALOG_CUSTOM_ICON);\n    }\n\n    public void launchEditTagsDialog(EntryWithTags entry) {\n        EditTagsDialog dialog = new EditTagsDialog();\n        openFragmentDialog(dialog, DIALOG_EDIT_TAGS);\n\n        // set args\n        {\n            Bundle args = new Bundle();\n            args.putString(\"entryId\", entry.id);\n            args.putString(\"entryName\", entry.getName());\n            dialog.setArguments(args);\n        }\n\n        dialog.setOnConfirmListener(newTags -> {\n            TBApplication.tagsHandler(getContext()).setTags(entry, newTags);\n            refreshSearchRecord(entry);\n        });\n\n        dialog.show(mTBLauncherActivity.getSupportFragmentManager(), DIALOG_EDIT_TAGS);\n    }\n\n    public void launchEditQuickListDialog(Context context) {\n        showDialog(context, new EditQuickListDialog(), DIALOG_EDIT_QUICK_LIST);\n    }\n\n    public void launchTagsManagerDialog(Context context) {\n        showDialog(context, new TagsManagerDialog(), DIALOG_TAGS_MANAGER);\n    }\n\n    private boolean isFragmentDialogVisible() {\n        return mFragmentDialog != null && mFragmentDialog.isVisible();\n    }\n\n    /**\n     * Keep track of the last dialog. Use context to find a SupportFragmentManager\n     *\n     * @param context to get the FragmentActivity from\n     * @param dialog  to open\n     * @param tag     name to keep track of\n     */\n    public static void showDialog(Context context, DialogFragment<?> dialog, String tag) {\n        if (TBApplication.activityInvalid(context)) {\n            Log.e(TAG, \"[activityInvalid] showDialog \" + tag);\n            return;\n        }\n        TBApplication.behaviour(context).showDialog(dialog, tag);\n    }\n\n    private void showDialog(@NonNull DialogFragment<?> dialog, @Nullable String tag) {\n        openFragmentDialog(dialog, tag);\n        dialog.show(mTBLauncherActivity.getSupportFragmentManager(), tag);\n    }\n\n    private void openFragmentDialog(DialogFragment<?> dialog, @Nullable String tag) {\n        closeFragmentDialog(tag);\n        mFragmentDialog = dialog;\n    }\n\n    public boolean closeFragmentDialog() {\n        return closeFragmentDialog(null);\n    }\n\n    private boolean closeFragmentDialog(@Nullable String tag) {\n        if (mFragmentDialog != null && mFragmentDialog.isVisible()) {\n            if (tag != null && tag.equals(mFragmentDialog.getTag())) {\n                mFragmentDialog.dismiss();\n                return true;\n            } else if (tag == null) {\n                mFragmentDialog.dismiss();\n                mFragmentDialog = null;\n                return true;\n            }\n        }\n        mFragmentDialog = null;\n        return false;\n    }\n\n    private void registerPopup(ListPopup menu) {\n        TBApplication.getApplication(getContext()).registerPopup(menu);\n    }\n\n    private boolean dismissPopup() {\n        return TBApplication.getApplication(getContext()).dismissPopup();\n    }\n\n    public void onResume() {\n        Log.i(TAG, \"onResume\");\n        mKeyboardHandler.mLaunchedApp = false;\n\n        LauncherState.Desktop desktop = TBApplication.state().getDesktop();\n        showDesktop(desktop);\n\n        mLauncherTime = null;\n        if (PrefCache.searchBarHasTimer(mPref)) {\n            mLauncherTime = mSearchBarContainer.findViewById(R.id.launcherTime);\n            UITheme.applySearchBarTextShadow(mLauncherTime);\n            mLauncherTime.post(mUpdateTime);\n        }\n    }\n\n    public void onNewIntent() {\n        if (mTBLauncherActivity == null)\n            return;\n        LauncherState state = TBApplication.state();\n        Log.i(TAG, \"onNewIntent desktop=\" + state.getDesktop());\n\n        closeFragmentDialog();\n\n        Intent intent = mTBLauncherActivity.getIntent();\n        if (intent != null) {\n            final String action = intent.getAction();\n            if (LauncherApps.ACTION_CONFIRM_PIN_SHORTCUT.equals(action)) {\n                // Save single shortcut via a pin request\n                ShortcutUtil.addShortcut(mTBLauncherActivity, intent);\n                return;\n            }\n            // Pasting shared text from Sharesheet via intent-filter into kiss search bar\n            if (Intent.ACTION_SEND.equals(action) && \"text/plain\".equals(intent.getType())) {\n                String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);\n                // making sure the shared text is not an empty string\n                if (sharedText != null && sharedText.trim().length() > 0) {\n                    mSearchEditText.setText(sharedText);\n                    return;\n                } else {\n                    //Toast.makeText(this, R.string.shared_text_empty, Toast.LENGTH_SHORT).show();\n                }\n            }\n        }\n\n        executeButtonAction(\"button-home\");\n    }\n\n    public void onWindowFocusChanged(boolean hasFocus) {\n        Log.i(TAG, \"onWindowFocusChanged \" + hasFocus);\n        LauncherState state = TBApplication.state();\n        if (hasFocus) {\n            if (mKeyboardHandler.mLaunchedApp) {\n                Log.d(TAG, \"skip focus change; LaunchedApp = true\");\n            } else {\n                if (state.getDesktop() == LauncherState.Desktop.SEARCH) {\n                    if (state.isSearchBarVisible() && PrefCache.linkKeyboardAndSearchBar(mPref)) {\n                        Log.d(TAG, \"SearchBarVisible and linkKeyboardAndSearchBar\");\n                        showKeyboard();\n                    } else {\n                        //TODO: find why keyboard gets hidden after onWindowFocusChanged\n                        Log.d(TAG, \"state().isKeyboardHidden=\" + TBApplication.state().isKeyboardHidden() + \" mRequestOpen=\" + mKeyboardHandler.mRequestOpen);\n                        if (mKeyboardHandler.mRequestOpen) {\n                            showKeyboard();\n                        }\n                    }\n                    if (TBApplication.state().isResultListVisible() && mResultAdapter.getItemCount() == 0)\n                        showDesktop(TBApplication.state().getDesktop());\n                    else if (TBApplication.state().isResultListVisible()) {\n                        showResultList(false);\n                    } else\n                        hideResultList(true);\n                } else {\n                    if (state.getDesktop() == LauncherState.Desktop.WIDGET) {\n                        hideKeyboard();\n                    }\n                }\n            }\n        }\n    }\n\n    public static void setActivityOrientation(@NonNull Activity act, @NonNull SharedPreferences pref) {\n        if (pref.getBoolean(\"lock-portrait\", true)) {\n            if (pref.getBoolean(\"sensor-orientation\", true))\n                act.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT);\n            else\n                act.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT);\n        } else {\n            if (pref.getBoolean(\"sensor-orientation\", true))\n                act.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);\n            else\n                act.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_USER);\n        }\n    }\n\n    private boolean launchStaticEntry(@NonNull String entryId) {\n        Context ctx = getContext();\n        DataHandler dataHandler = TBApplication.dataHandler(ctx);\n        EntryItem item = dataHandler.getPojo(entryId);\n        if (item == null) {\n            item = TagsProvider.newTagEntryCheckId(entryId);\n        }\n        if (item instanceof StaticEntry) {\n            if (TBApplication.state().getDesktop() != LauncherState.Desktop.SEARCH) {\n                // TODO: switchToDesktop might show the result list, we may need to prevent this as an optimization\n                switchToDesktop(LauncherState.Desktop.SEARCH);\n                clearAdapter();\n            }\n            // make sure the QuickList will not toggle off\n            TBApplication.quickList(ctx).adapterCleared();\n            item.doLaunch(mLauncherButton, LAUNCHED_FROM_GESTURE);\n            return true;\n        } else {\n            Toast.makeText(ctx, ctx.getString(R.string.entry_not_found, entryId), Toast.LENGTH_SHORT).show();\n        }\n        return false;\n    }\n\n    private boolean launchActionEntry(@NonNull String action) {\n        return launchStaticEntry(ActionEntry.SCHEME + action);\n    }\n\n    private boolean launchAppEntry(@NonNull String userComponentName) {\n        Context ctx = getContext();\n        UserHandleCompat user = UserHandleCompat.fromComponentName(ctx, userComponentName);\n        ComponentName component = UserHandleCompat.unflattenComponentName(userComponentName);\n        String appId = AppEntry.generateAppId(component, user);\n        EntryItem item = TBApplication.dataHandler(ctx).getPojo(appId);\n        if (item instanceof AppEntry) {\n            ResultHelper.launch(mLauncherButton, item, LAUNCHED_FROM_GESTURE);\n            return true;\n        }\n        Toast.makeText(ctx, ctx.getString(R.string.application_not_found, appId), Toast.LENGTH_SHORT).show();\n        return false;\n    }\n\n    private boolean launchEntryById(@NonNull String entryId) {\n        Context ctx = getContext();\n        DataHandler dataHandler = TBApplication.dataHandler(ctx);\n        EntryItem item = dataHandler.getPojo(entryId);\n        if (item == null) {\n            Toast.makeText(ctx, ctx.getString(R.string.entry_not_found, entryId), Toast.LENGTH_SHORT).show();\n            return false;\n        }\n        item.doLaunch(mLauncherButton, LAUNCHED_FROM_GESTURE);\n        return true;\n    }\n\n    private void executeButtonAction(@Nullable String button) {\n        if (mPref != null)\n            executeAction(mPref.getString(button, null), button);\n    }\n\n    private boolean executeGestureAction(@Nullable String gesture) {\n        if (mPref != null)\n            return executeAction(mPref.getString(gesture, null), gesture);\n        return false;\n    }\n\n    private boolean executeAction(@Nullable String action, @Nullable String source) {\n        if (action == null)\n            return false;\n        if (TBApplication.activityInvalid(mTBLauncherActivity)) {\n            Log.e(TAG, \"[activityInvalid] executeAction \" + action);\n            // only do stuff if we are the current activity\n            return false;\n        }\n        Log.d(TAG, \"executeAction( action=\" + action + \" source=\" + source + \" )\");\n        switch (action) {\n            case \"lockScreen\":\n                if (DeviceAdmin.isAdminActive(mTBLauncherActivity)) {\n                    DeviceAdmin.lockScreen(mTBLauncherActivity);\n                } else {\n                    Toast.makeText(getContext(), R.string.device_admin_required, Toast.LENGTH_SHORT).show();\n                }\n                return true;\n            case \"expandNotificationsPanel\":\n                Utilities.expandNotificationsPanel(mTBLauncherActivity);\n                return true;\n            case \"expandSettingsPanel\":\n                Utilities.expandSettingsPanel(mTBLauncherActivity);\n                return true;\n            case \"showSearchBar\":\n                switchToDesktop(LauncherState.Desktop.SEARCH);\n                return true;\n            case \"showSearchBarAndKeyboard\":\n                switchToDesktop(LauncherState.Desktop.SEARCH);\n                showKeyboard();\n                return true;\n            case \"showWidgets\":\n                switchToDesktop(LauncherState.Desktop.WIDGET);\n                return true;\n            case \"showWidgetsCenter\":\n                switchToDesktop(LauncherState.Desktop.WIDGET);\n                mTBLauncherActivity.liveWallpaper.resetPosition();\n                return true;\n            case \"showEmpty\":\n                switchToDesktop(LauncherState.Desktop.EMPTY);\n                return true;\n            case \"toggleSearchAndWidget\":\n                if (TBApplication.state().getDesktop() == LauncherState.Desktop.SEARCH)\n                    switchToDesktop(LauncherState.Desktop.WIDGET);\n                else\n                    switchToDesktop(LauncherState.Desktop.SEARCH);\n                return true;\n            case \"toggleSearchWidgetEmpty\": {\n                final LauncherState.Desktop desktop = TBApplication.state().getDesktop();\n                if (desktop == LauncherState.Desktop.SEARCH)\n                    switchToDesktop(LauncherState.Desktop.WIDGET);\n                else if (desktop == LauncherState.Desktop.WIDGET)\n                    switchToDesktop(LauncherState.Desktop.EMPTY);\n                else\n                    switchToDesktop(LauncherState.Desktop.SEARCH);\n                return true;\n            }\n            case \"reloadProviders\":\n                TBApplication.dataHandler(getContext()).reloadProviders();\n                return true;\n            case \"showAllAppsAZ\":\n                return launchActionEntry(\"show/apps/byName\");\n            case \"toggleGrid\":\n                return launchActionEntry(\"toggle/grid\");\n            case \"showAllAppsZA\":\n                return launchActionEntry(\"show/apps/byNameReversed\");\n            case \"showContactsAZ\":\n                return launchActionEntry(\"show/contacts/byName\");\n            case \"showContactsZA\":\n                return launchActionEntry(\"show/contacts/byNameReversed\");\n            case \"showShortcutsAZ\":\n                return launchActionEntry(\"show/shortcuts/byName\");\n            case \"showShortcutsZA\":\n                return launchActionEntry(\"show/shortcuts/byNameReversed\");\n            case \"showFavorites\":\n                return launchActionEntry(\"show/favorites/byName\");\n            case \"showHistoryByRecency\":\n                return launchActionEntry(\"show/history/recency\");\n            case \"showHistoryByFrequency\":\n                return launchActionEntry(\"show/history/frequency\");\n            case \"showHistoryByFrecency\":\n                return launchActionEntry(\"show/history/frecency\");\n            case \"showHistoryByAdaptive\":\n                return launchActionEntry(\"show/history/adaptive\");\n            case \"showUntagged\":\n                return launchActionEntry(\"show/untagged\");\n            case \"showTagsList\":\n                return launchActionEntry(\"show/tags/list\");\n            case \"showTagsListReversed\":\n                return launchActionEntry(\"show/tags/listReversed\");\n            case \"showTagsMenu\": {\n                View anchor = null;\n                if (\"button-launcher\".equals(source))\n                    anchor = mLauncherButton;\n                Context ctx = mLauncherButton.getContext();\n                ListPopup menu = TBApplication.tagsHandler(ctx).getTagsMenu(ctx);\n                registerPopup(menu);\n                if (anchor != null)\n                    menu.show(anchor);\n                else\n                    menu.showCenter(mLauncherButton);\n\n            }\n            return true;\n            case \"runApp\": {\n                String runApp = mPref.getString(source + \"-app-to-run\", null);\n                if (runApp != null)\n                    return launchAppEntry(runApp);\n                break;\n            }\n            case \"runShortcut\": {\n                String runApp = mPref.getString(source + \"-shortcut-to-run\", null);\n                if (runApp != null)\n                    return launchEntryById(runApp);\n                break;\n            }\n            case \"showEntry\": {\n                String entryToShow = mPref.getString(source + \"-entry-to-show\", null);\n                if (entryToShow != null)\n                    return launchStaticEntry(entryToShow);\n                break;\n            }\n            default:\n                // do nothing\n                break;\n        }\n        return false;\n    }\n\n    public boolean onFlingDownLeft() {\n        return executeGestureAction(\"gesture-fling-down-left\");\n    }\n\n    public boolean onFlingDownRight() {\n        return executeGestureAction(\"gesture-fling-down-right\");\n    }\n\n    public boolean onFlingUp() {\n        return executeGestureAction(\"gesture-fling-up\");\n    }\n\n    public boolean onFlingLeft() {\n        return executeGestureAction(\"gesture-fling-left\");\n    }\n\n    public boolean onFlingRight() {\n        return executeGestureAction(\"gesture-fling-right\");\n    }\n\n    public boolean onClick() {\n        return executeGestureAction(\"gesture-click\");\n    }\n\n    public boolean hasDoubleClick() {\n        if (mPref == null)\n            return false;\n        String action = mPref.getString(\"gesture-double-click\", null);\n        return action != null && !action.isEmpty() && !action.equals(\"none\");\n    }\n\n    public boolean onDoubleClick() {\n        return executeGestureAction(\"gesture-double-click\");\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/CustomizeUI.java",
    "content": "package rocks.tbog.tblauncher;\n\nimport static rocks.tbog.tblauncher.customicon.ButtonHelper.BTN_ID_LAUNCHER_PILL;\nimport static rocks.tbog.tblauncher.customicon.ButtonHelper.BTN_ID_LAUNCHER_WHITE;\n\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.content.res.ColorStateList;\nimport android.content.res.Resources;\nimport android.content.res.TypedArray;\nimport android.graphics.Color;\nimport android.graphics.drawable.ColorDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.graphics.drawable.GradientDrawable;\nimport android.graphics.drawable.RippleDrawable;\nimport android.graphics.drawable.StateListDrawable;\nimport android.os.Build;\nimport android.provider.Settings;\nimport android.text.InputType;\nimport android.util.TypedValue;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.AbsListView;\nimport android.widget.EditText;\nimport android.widget.ImageView;\n\nimport androidx.annotation.IdRes;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.constraintlayout.motion.widget.MotionLayout;\nimport androidx.core.content.res.ResourcesCompat;\nimport androidx.core.view.ViewCompat;\nimport androidx.core.view.WindowInsetsControllerCompat;\nimport androidx.preference.PreferenceManager;\n\nimport rocks.tbog.tblauncher.drawable.LoadingDrawable;\nimport rocks.tbog.tblauncher.result.ResultViewHelper;\nimport rocks.tbog.tblauncher.ui.RecyclerList;\nimport rocks.tbog.tblauncher.ui.SearchEditText;\nimport rocks.tbog.tblauncher.utils.EdgeGlowHelper;\nimport rocks.tbog.tblauncher.utils.PrefCache;\nimport rocks.tbog.tblauncher.utils.UIColors;\nimport rocks.tbog.tblauncher.utils.UISizes;\nimport rocks.tbog.tblauncher.utils.Utilities;\n\npublic class CustomizeUI {\n    private TBLauncherActivity mTBLauncherActivity;\n    private SharedPreferences mPref = null;\n    private ImageView mNotificationBackground;\n    private ViewGroup mSearchBarContainer;\n    private SearchEditText mSearchBar;\n    private ImageView mLauncherButton;\n    private ImageView mMenuButton;\n    private ImageView mClearButton;\n    private WindowInsetsControllerCompat mWindowHelper;\n\n    /**\n     * InputType that behaves as if the consuming IME is a standard-obeying\n     * soft-keyboard\n     * <p>\n     * *Auto Complete* means \"we're handling auto-completion ourselves\". Then\n     * we ignore whatever the IME thinks we should display.\n     */\n    private final static int INPUT_TYPE_STANDARD = InputType.TYPE_CLASS_TEXT\n        | InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE\n        | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;\n    /**\n     * InputType that behaves as if the consuming IME is SwiftKey\n     * <p>\n     * *Visible Password* fields will break many non-Latin IMEs and may show\n     * unexpected behaviour in numerous ways. (#454, #517)\n     */\n    private final static int INPUT_TYPE_WORKAROUND = InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD\n        | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT;\n\n    private final View.OnLayoutChangeListener updateResultFadeOut = new UpdateResultFadeOut();\n    private final MotionTransitionListener mSearchBarTransition = new MotionTransitionListener();\n\n    private static final class UpdateResultFadeOut implements View.OnLayoutChangeListener {\n        @Override\n        public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {\n            SharedPreferences pref = TBApplication.getApplication(v.getContext()).preferences();\n            boolean fadeOut = PrefCache.getResultFadeOut(pref);\n            if (!fadeOut) {\n                v.removeOnLayoutChangeListener(this);\n                return;\n            }\n            int oldHeight = oldBottom - oldTop;\n            if (oldHeight != v.getHeight()) {\n                int backgroundColor = UIColors.getResultListBackground(pref);\n                if (!setResultListGradientFade(v, backgroundColor))\n                    v.removeOnLayoutChangeListener(this);\n            }\n        }\n    }\n\n    public static final class MotionTransitionListener implements MotionLayout.TransitionListener {\n        private Runnable transitionToEndListener = null;\n\n        public enum TransitionType {STARTED, CHANGE, COMPLETED, TRIGGER}\n\n        public void setTransitionToEndListener(Runnable listener) {\n            transitionToEndListener = listener;\n        }\n\n        @Override\n        public void onTransitionStarted(MotionLayout motionLayout, int startId, int endId) {\n            // do nothing\n        }\n\n        @Override\n        public void onTransitionChange(MotionLayout motionLayout, int startId, int endId, float progress) {\n            // do nothing\n        }\n\n        @Override\n        public void onTransitionCompleted(MotionLayout motionLayout, int currentId) {\n            if (transitionToEndListener != null && motionLayout.getEndState() == currentId)\n                transitionToEndListener.run();\n        }\n\n        @Override\n        public void onTransitionTrigger(MotionLayout motionLayout, int triggerId, boolean positive, float progress) {\n            // do nothing\n        }\n    }\n\n    @SuppressWarnings(\"TypeParameterUnusedInFormals\")\n    private <T extends View> T findViewById(@IdRes int id) {\n        return mTBLauncherActivity.findViewById(id);\n    }\n\n    public void onCreateActivity(TBLauncherActivity tbLauncherActivity) {\n        mTBLauncherActivity = tbLauncherActivity;\n        mPref = PreferenceManager.getDefaultSharedPreferences(tbLauncherActivity);\n\n        mNotificationBackground = findViewById(R.id.notificationBackground);\n        mSearchBarContainer = findViewById(R.id.searchBarContainer);\n        mSearchBar = mSearchBarContainer.findViewById(R.id.launcherSearch);\n        mLauncherButton = mSearchBarContainer.findViewById(R.id.launcherButton);\n        mMenuButton = mSearchBarContainer.findViewById(R.id.menuButton);\n        mClearButton = mSearchBarContainer.findViewById(R.id.clearButton);\n\n        if (mSearchBarContainer instanceof MotionLayout)\n            ((MotionLayout) mSearchBarContainer).addTransitionListener(mSearchBarTransition);\n\n        mWindowHelper = ViewCompat.getWindowInsetsController(mSearchBarContainer);\n    }\n\n    public void onStart() {\n        refreshSearchBar();\n        View resultLayout = findViewById(R.id.resultLayout);\n        setResultListPref(resultLayout, true);\n        resultLayout.addOnLayoutChangeListener(updateResultFadeOut);\n        updateResultFadeOut.onLayoutChange(resultLayout,\n            resultLayout.getLeft(), resultLayout.getTop(), resultLayout.getRight(), resultLayout.getBottom(),\n            0, 0, 0, 0);\n        adjustInputType(mSearchBar);\n        setNotificationBarColor();\n        setNavigationBarColor();\n    }\n\n    private void setNotificationBarColor() {\n        int argb = UIColors.getColor(mPref, \"notification-bar-argb\");\n        boolean gradient = mPref.getBoolean(\"notification-bar-gradient\", true);\n\n        if (gradient) {\n            int size = UISizes.getStatusBarSize(getContext());\n            ViewGroup.LayoutParams params = mNotificationBackground.getLayoutParams();\n            if (params != null) {\n                params.height = size;\n                mNotificationBackground.setLayoutParams(params);\n            }\n            Utilities.setColorFilterMultiply(mNotificationBackground, argb);\n            UIColors.setStatusBarColor(mTBLauncherActivity, 0x00000000);\n        } else {\n            mNotificationBackground.setVisibility(View.GONE);\n            UIColors.setStatusBarColor(mTBLauncherActivity, argb);\n        }\n\n        // Notification drawer icon color\n        mWindowHelper.setAppearanceLightStatusBars(mPref.getBoolean(\"black-notification-icons\", false));\n    }\n\n    private void setNavigationBarColor() {\n        int argb = UIColors.getColor(mPref, \"navigation-bar-argb\");\n        UIColors.setNavigationBarColor(mTBLauncherActivity, argb, UIColors.setAlpha(argb, 0xFF));\n\n        // Navigation bar icon color\n        mWindowHelper.setAppearanceLightNavigationBars(UIColors.isColorLight(argb));\n    }\n\n    public void refreshSearchBar() {\n        if (PrefCache.getSearchBarLayout(mPref) == R.layout.search_pill)\n            setSearchPillPref();\n        else\n            setSearchBarPref();\n    }\n\n    private void setSearchBarPref() {\n        final Context ctx = getContext();\n        final Resources resources = mSearchBarContainer.getResources();\n\n        // size\n        int barHeight = mPref.getInt(\"search-bar-height\", 0);\n        if (barHeight <= 1)\n            barHeight = resources.getInteger(R.integer.default_search_bar_height);\n        barHeight = UISizes.dp2px(ctx, barHeight);\n        int textSize = mPref.getInt(\"search-bar-text-size\", 0);\n        if (textSize <= 1)\n            textSize = resources.getInteger(R.integer.default_size_text);\n\n        // layout height and margins\n        {\n            ViewGroup.LayoutParams params = mSearchBarContainer.getLayoutParams();\n            if (params instanceof ViewGroup.MarginLayoutParams) {\n                params.height = barHeight;\n                int hMargin = UISizes.getSearchBarMarginHorizontal(ctx);\n                int vMargin = UISizes.getSearchBarMarginVertical(ctx);\n                ((ViewGroup.MarginLayoutParams)params).setMargins(hMargin, vMargin, hMargin, vMargin);\n                mSearchBarContainer.setLayoutParams(params);\n            } else {\n                throw new IllegalStateException(\"mSearchBarContainer has the wrong layout params\");\n            }\n        }\n\n        // text size\n        {\n            mSearchBar.setTextSize(TypedValue.COMPLEX_UNIT_SP, textSize);\n        }\n\n        final int searchBarRipple = UIColors.setAlpha(UIColors.getColor(mPref, \"search-bar-ripple-color\"), 0xFF);\n        final int searchIconColor = UIColors.setAlpha(UIColors.getColor(mPref, \"search-bar-icon-color\"), 0xFF);\n        final int argbBackground = UIColors.getColor(mPref, \"search-bar-argb\");\n\n        // text color\n        {\n            int searchTextCursor = UIColors.getColor(mPref, \"search-bar-cursor-argb\");\n            int searchTextHighlight = UIColors.setAlpha(searchTextCursor, 0x7F);\n            int searchTextColor = UIColors.getSearchTextColor(ctx);\n            int searchHintColor = UIColors.setAlpha(searchTextColor, 0xBB);\n\n            mSearchBar.setTextColor(searchTextColor);\n            mSearchBar.setHintTextColor(searchHintColor);\n            // set color for selection background\n            mSearchBar.setHighlightColor(searchTextHighlight);\n\n            Utilities.setTextCursorColor(mSearchBar, searchTextCursor);\n            Utilities.setTextSelectHandleColor(mSearchBar, searchBarRipple);\n        }\n\n        // icon\n        ResultViewHelper.setButtonIconAsync(mLauncherButton, BTN_ID_LAUNCHER_WHITE, context -> {\n            Drawable drawable = new LoadingDrawable();\n            Utilities.setColorFilterMultiply(drawable, searchIconColor);\n            return drawable;\n        });\n\n        // icon color\n        {\n            Utilities.setColorFilterMultiply(mMenuButton, searchIconColor);\n            Utilities.setColorFilterMultiply(mClearButton, searchIconColor);\n            mLauncherButton.setBackground(getSelectorDrawable(mLauncherButton, searchBarRipple, true));\n            mMenuButton.setBackground(getSelectorDrawable(mMenuButton, searchBarRipple, true));\n            mClearButton.setBackground(getSelectorDrawable(mClearButton, searchBarRipple, true));\n        }\n\n        // set background\n        boolean isGradient = mPref.getBoolean(\"search-bar-gradient\", true);\n        int cornerRadius = UISizes.getSearchBarRadius(ctx);\n        if (isGradient || cornerRadius > 0) {\n            GradientDrawable drawable;\n            if (isGradient) {\n                final GradientDrawable.Orientation orientation;\n                if (PrefCache.searchBarAtBottom(mPref))\n                    orientation = GradientDrawable.Orientation.TOP_BOTTOM;\n                else\n                    orientation = GradientDrawable.Orientation.BOTTOM_TOP;\n                int alpha = Color.alpha(argbBackground);\n                int c1 = UIColors.setAlpha(argbBackground, 0);\n                int c2 = UIColors.setAlpha(argbBackground, alpha * 3 / 4);\n                int c3 = UIColors.setAlpha(argbBackground, alpha);\n                drawable = new GradientDrawable(orientation, new int[]{c1, c2, c3});\n            } else {\n                drawable = new GradientDrawable();\n                drawable.setColor(argbBackground);\n            }\n            drawable.setCornerRadius(cornerRadius);\n            mSearchBarContainer.setBackground(drawable);\n        } else {\n            mSearchBarContainer.setBackground(new ColorDrawable(argbBackground));\n        }\n    }\n\n    private void setSearchPillPref() {\n        final Context ctx = getContext();\n        final Resources resources = mSearchBarContainer.getResources();\n\n        // size\n        int barHeight = mPref.getInt(\"search-bar-height\", 0);\n        if (barHeight <= 1)\n            barHeight = resources.getInteger(R.integer.default_search_bar_height);\n        barHeight = UISizes.dp2px(ctx, barHeight);\n        int textSize = mPref.getInt(\"search-bar-text-size\", 0);\n        if (textSize <= 1)\n            textSize = resources.getInteger(R.integer.default_size_text);\n\n        // layout height and margins\n        {\n            ViewGroup.LayoutParams params = mSearchBarContainer.getLayoutParams();\n            if (params instanceof ViewGroup.MarginLayoutParams) {\n                params.height = barHeight;\n                int hMargin = UISizes.getSearchBarMarginHorizontal(ctx);\n                int vMargin = UISizes.getSearchBarMarginVertical(ctx);\n                // left must be touching the margin to look good\n                ((ViewGroup.MarginLayoutParams) params).setMargins(0, vMargin, hMargin, vMargin);\n                mSearchBarContainer.setLayoutParams(params);\n            } else {\n                throw new IllegalStateException(\"mSearchBarContainer has the wrong layout params\");\n            }\n        }\n\n        // text size\n        {\n            mSearchBar.setTextSize(TypedValue.COMPLEX_UNIT_SP, textSize);\n        }\n\n        final int searchBarRipple = UIColors.setAlpha(UIColors.getColor(mPref, \"search-bar-ripple-color\"), 0xFF);\n        final int searchIconColor = UIColors.setAlpha(UIColors.getColor(mPref, \"search-bar-icon-color\"), 0xFF);\n        final int argbBackground = UIColors.getColor(mPref, \"search-bar-argb\");\n\n        // text color\n        {\n            int searchTextCursor = UIColors.getColor(mPref, \"search-bar-cursor-argb\");\n            int searchTextHighlight = UIColors.setAlpha(searchTextCursor, 0x7F);\n            int searchTextColor = UIColors.getSearchTextColor(ctx);\n            int searchHintColor = UIColors.setAlpha(searchTextColor, 0xBB);\n\n            mSearchBar.setTextColor(searchTextColor);\n            mSearchBar.setHintTextColor(searchHintColor);\n            // set color for selection background\n            mSearchBar.setHighlightColor(searchTextHighlight);\n\n            Utilities.setTextCursorColor(mSearchBar, searchTextCursor);\n            Utilities.setTextSelectHandleColor(mSearchBar, searchBarRipple);\n        }\n\n        // set icon\n        {\n            ResultViewHelper.setButtonIconAsync(mLauncherButton, BTN_ID_LAUNCHER_PILL, context -> {\n                Drawable drawable = ResourcesCompat.getDrawable(context.getResources(), R.drawable.launcher_pill, null);\n                Utilities.setColorFilterMultiply(drawable, searchIconColor);\n                return drawable;\n            });\n        }\n\n        // icon color\n        {\n            Utilities.setColorFilterMultiply(mMenuButton, searchIconColor);\n            Utilities.setColorFilterMultiply(mClearButton, searchIconColor);\n            mLauncherButton.setBackground(getSelectorDrawable(mLauncherButton, searchBarRipple, true));\n            mMenuButton.setBackground(getSelectorDrawable(mMenuButton, searchBarRipple, true));\n            mClearButton.setBackground(getSelectorDrawable(mClearButton, searchBarRipple, true));\n        }\n\n        // set text bar background\n        boolean isGradient = mPref.getBoolean(\"search-bar-gradient\", true);\n        if (isGradient) {\n            GradientDrawable drawable;\n            final GradientDrawable.Orientation orientation;\n            orientation = GradientDrawable.Orientation.LEFT_RIGHT;\n            int alpha = Color.alpha(argbBackground);\n            int c1 = UIColors.setAlpha(argbBackground, 0);\n            int c2 = UIColors.setAlpha(argbBackground, alpha * 3 / 4);\n            int c3 = UIColors.setAlpha(argbBackground, alpha);\n            drawable = new GradientDrawable(orientation, new int[]{c1, c2, c3});\n            mSearchBar.setBackground(drawable);\n        } else {\n            mSearchBar.setBackground(new ColorDrawable(argbBackground));\n        }\n\n        // set color for the pill background\n        ImageView bkgBehindButton = mSearchBarContainer.findViewById(R.id.bkgBehindButton);\n        if (bkgBehindButton != null)\n            Utilities.setColorFilterMultiply(bkgBehindButton, argbBackground);\n\n        // set menu button background\n        int cornerRadius = UISizes.getSearchBarRadius(ctx);\n        if (isGradient || cornerRadius > 0) {\n            GradientDrawable drawable;\n            if (isGradient) {\n                final GradientDrawable.Orientation orientation;\n                orientation = GradientDrawable.Orientation.RIGHT_LEFT;\n                int alpha = Color.alpha(argbBackground);\n                int c1 = UIColors.setAlpha(argbBackground, 0);\n                int c2 = UIColors.setAlpha(argbBackground, alpha * 3 / 4);\n                int c3 = UIColors.setAlpha(argbBackground, alpha);\n                drawable = new GradientDrawable(orientation, new int[]{c1, c2, c3});\n            } else {\n                drawable = new GradientDrawable();\n                drawable.setColor(argbBackground);\n            }\n            drawable.setCornerRadius(cornerRadius);\n            mMenuButton.setBackground(drawable);\n            mClearButton.setBackground(drawable);\n        } else {\n            mMenuButton.setBackground(new ColorDrawable(argbBackground));\n            mClearButton.setBackground(new ColorDrawable(argbBackground));\n        }\n\n        // set margin between the pill and menu button\n        {\n            ViewGroup.LayoutParams params = mLauncherButton.getLayoutParams();\n            if (params instanceof ViewGroup.MarginLayoutParams) {\n                int hMargin = UISizes.getSearchBarMarginHorizontal(ctx);\n                // left must be touching the margin to look good\n                ((ViewGroup.MarginLayoutParams) params).setMargins(0, 0, hMargin, 0);\n                mLauncherButton.setLayoutParams(params);\n            } else {\n                throw new IllegalStateException(\"mMenuButton has the wrong layout params\");\n            }\n        }\n    }\n\n    public static void setResultListPref(View resultLayout) {\n        setResultListPref(resultLayout, false);\n    }\n\n    private static boolean setResultListGradientFade(@NonNull View resultLayout, int backgroundColor) {\n        Drawable bg = resultLayout.getBackground();\n        if (bg instanceof GradientDrawable) {\n            GradientDrawable drawable = (GradientDrawable) bg;\n            drawable.setGradientType(GradientDrawable.LINEAR_GRADIENT);\n            drawable.setOrientation(GradientDrawable.Orientation.TOP_BOTTOM);\n\n            int color = backgroundColor & 0x00ffffff;\n            int alpha = Color.alpha(backgroundColor);\n            int c1 = UIColors.setAlpha(color, 0);\n            int c2 = UIColors.setAlpha(color, alpha * 3 / 4);\n            int c3 = UIColors.setAlpha(color, alpha);\n            // compute fade percentage of height\n            float p = UISizes.getResultIconSize(resultLayout.getContext()) * .5f / resultLayout.getHeight();\n            // if height is too small, fade only on 66% of the height (33% fade in and 33% fade out)\n            p = Math.min(p, .33f);\n            return Utilities.setGradientDrawableColors(drawable,\n                new int[]{c1, c2, c3, c3, c2, c1},\n                new float[]{0f, p * .5f, p, 1f - p, 1f - (p * .5f), 1f});\n        }\n        return false;\n    }\n\n    public static void setResultListPref(View resultLayout, boolean setMargin) {\n        Context ctx = resultLayout.getContext();\n        SharedPreferences pref = TBApplication.getApplication(ctx).preferences();\n\n        if (setMargin) {\n            ViewGroup.LayoutParams params = resultLayout.getLayoutParams();\n            if (params instanceof ViewGroup.MarginLayoutParams) {\n                final var margin = UISizes.getResultListMargin(ctx);\n                ((ViewGroup.MarginLayoutParams) params).setMargins(margin.left, margin.top, margin.right, margin.bottom);\n            }\n        }\n\n        boolean fadeOut = PrefCache.getResultFadeOut(pref);\n        int backgroundColor = UIColors.getResultListBackground(pref);\n        int cornerRadius = UISizes.getResultListRadius(ctx);\n        if (cornerRadius > 0 || fadeOut) {\n            final GradientDrawable drawable = new GradientDrawable();\n            drawable.setCornerRadius(cornerRadius);\n            resultLayout.setBackground(drawable);\n            if (fadeOut)\n                setResultListGradientFade(resultLayout, backgroundColor);\n            else\n                drawable.setColor(backgroundColor);\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n                // clip list content to rounded corners\n                resultLayout.setClipToOutline(true);\n            }\n        } else {\n            resultLayout.setBackgroundColor(backgroundColor);\n        }\n\n        int overscrollColor = UIColors.getResultListRipple(ctx);\n        overscrollColor = UIColors.setAlpha(overscrollColor, 0x7F);\n        if (resultLayout instanceof AbsListView) {\n            setListViewSelectorPref((AbsListView) resultLayout, true);\n            setListViewScrollbarPref(resultLayout);\n            EdgeGlowHelper.setEdgeGlowColor((AbsListView) resultLayout, overscrollColor);\n            if (setMargin)\n                setFadingEdge(resultLayout, fadeOut);\n        } else {\n            View list = resultLayout.findViewById(R.id.resultList);\n            if (list instanceof AbsListView) {\n                setListViewSelectorPref((AbsListView) list, false);\n                setListViewScrollbarPref(list);\n                EdgeGlowHelper.setEdgeGlowColor((AbsListView) list, overscrollColor);\n            } else if (list instanceof RecyclerList) {\n                setListViewScrollbarPref(list);\n                EdgeGlowHelper.setEdgeGlowColor((RecyclerList) list, overscrollColor);\n            }\n            if (setMargin)\n                setFadingEdge(list, fadeOut);\n        }\n    }\n\n    private static void setFadingEdge(@Nullable View view, boolean enabled) {\n        if (view == null)\n            return;\n        if (enabled)\n            view.setFadingEdgeLength(UISizes.getResultIconSize(view.getContext()));\n        view.setVerticalFadingEdgeEnabled(enabled);\n    }\n\n    private void adjustInputType(EditText searchEditText) {\n        int currentInputType = searchEditText.getInputType();\n        int requiredInputType;\n\n        if (isSuggestionsEnabled()) {\n            requiredInputType = InputType.TYPE_CLASS_TEXT;\n        } else {\n            if (isNonCompliantKeyboard()) {\n                requiredInputType = INPUT_TYPE_WORKAROUND;\n            } else {\n                requiredInputType = INPUT_TYPE_STANDARD;\n            }\n        }\n\n        if (currentInputType != requiredInputType) {\n            searchEditText.setInputType(requiredInputType);\n        }\n    }\n\n    public static void setListViewSelectorPref(AbsListView listView, boolean borderless) {\n        int touchColor = UIColors.getResultListRipple(listView.getContext());\n        Drawable selector = getSelectorDrawable(listView, touchColor, borderless);\n        listView.setSelector(selector);\n    }\n\n    public static Drawable getSelectorDrawable(View view, int color, boolean borderless) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            Drawable mask = borderless ? null : new ColorDrawable(Color.WHITE);\n            Drawable content = borderless ? null : view.getBackground();\n            return new RippleDrawable(ColorStateList.valueOf(color), content, mask);\n        } else {\n            ColorDrawable stateColor = new ColorDrawable(color);\n\n            StateListDrawable stateListDrawable = new StateListDrawable();\n            stateListDrawable.addState(new int[]{android.R.attr.state_selected}, stateColor);\n            stateListDrawable.addState(new int[]{android.R.attr.state_focused}, stateColor);\n            stateListDrawable.addState(new int[]{android.R.attr.state_pressed}, stateColor);\n            stateListDrawable.addState(new int[]{}, new ColorDrawable(Color.TRANSPARENT));\n            stateListDrawable.setEnterFadeDuration(300);\n            stateListDrawable.setExitFadeDuration(100);\n            return stateListDrawable;\n        }\n    }\n\n    public static void setListViewScrollbarPref(View listView) {\n        int color = UIColors.getResultListRipple(listView.getContext());\n        setListViewScrollbarPref(listView, UIColors.setAlpha(color, 0x7F));\n    }\n\n    public static void setListViewScrollbarPref(View listView, int color) {\n        GradientDrawable drawable = new GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT, new int[]{color, color});\n        drawable.setCornerRadius(UISizes.dp2px(listView.getContext(), 3));\n        drawable.setSize(UISizes.dp2px(listView.getContext(), 4), drawable.getIntrinsicHeight());\n\n        Utilities.setVerticalScrollbarThumbDrawable(listView, drawable);\n    }\n\n    public Context getContext() {\n        return mTBLauncherActivity;\n    }\n\n    @NonNull\n    public static Drawable getPopupBackgroundDrawable(@NonNull Context ctx) {\n        int border = UISizes.dp2px(ctx, 1);\n        int radius = UISizes.getPopupCornerRadius(ctx);\n\n        GradientDrawable gradient = new GradientDrawable();\n        gradient.setCornerRadius(radius);\n        gradient.setStroke(border, UIColors.getPopupBorderColor(ctx));\n        gradient.setColor(UIColors.getPopupBackgroundColor(ctx));\n\n        return gradient;\n    }\n\n    public static Drawable getDialogButtonBarBackgroundDrawable(@NonNull Resources.Theme theme) {\n        TypedValue typedValue = new TypedValue();\n        if (theme.resolveAttribute(android.R.attr.buttonBarStyle, typedValue, true)) {\n            TypedArray a = theme.obtainStyledAttributes(typedValue.resourceId, new int[]{android.R.attr.background});\n            Drawable background = a.getDrawable(0);\n            a.recycle();\n            return background;\n        }\n\n        return null;\n    }\n\n    /**\n     * Should we force the keyboard not to display suggestions?\n     * (swiftkey is broken, see https://github.com/Neamar/KISS/issues/44)\n     * (same for flesky: https://github.com/Neamar/KISS/issues/1263)\n     */\n    private boolean isNonCompliantKeyboard() {\n        String currentKeyboard = Settings.Secure.getString(mTBLauncherActivity.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD).toLowerCase();\n        return currentKeyboard.contains(\"swiftkey\") || currentKeyboard.contains(\"flesky\") || currentKeyboard.endsWith(\".latinime\");\n    }\n\n    /**\n     * Should the keyboard autocomplete and suggest options\n     */\n    private boolean isSuggestionsEnabled() {\n        return mPref.getBoolean(\"enable-suggestions-keyboard\", false);\n    }\n\n    public void expandSearchPill(int duration) {\n        if (mSearchBarContainer instanceof MotionLayout) {\n            ((MotionLayout) mSearchBarContainer).setTransitionDuration(duration);\n            ((MotionLayout) mSearchBarContainer).transitionToEnd();\n        }\n    }\n\n    public void collapseSearchPill(int duration) {\n        if (mSearchBarContainer instanceof MotionLayout) {\n            ((MotionLayout) mSearchBarContainer).setTransitionDuration(duration);\n            ((MotionLayout) mSearchBarContainer).transitionToStart();\n        }\n    }\n\n    public void setExpandedSearchPillListener(Runnable listener) {\n        mSearchBarTransition.setTransitionToEndListener(listener);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/DeviceAdmin.java",
    "content": "package rocks.tbog.tblauncher;\n\nimport android.app.admin.DeviceAdminReceiver;\nimport android.app.admin.DevicePolicyManager;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.SharedPreferences;\n\nimport androidx.annotation.NonNull;\nimport androidx.preference.PreferenceManager;\n\npublic class DeviceAdmin extends DeviceAdminReceiver {\n\n    @Override\n    public void onEnabled(@NonNull Context context, @NonNull Intent intent) {\n        super.onEnabled(context, intent);\n        SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n        pref.edit().putBoolean(\"device-admin\", true).apply();\n    }\n\n    @Override\n    public void onDisabled(@NonNull Context context, @NonNull Intent intent) {\n        super.onDisabled(context, intent);\n        SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n        pref.edit().putBoolean(\"device-admin\", false).apply();\n    }\n\n    @NonNull\n    public static ComponentName getAdminComponent(@NonNull Context context) {\n        return new ComponentName(context, DeviceAdmin.class);\n    }\n\n    public static boolean isAdminActive(@NonNull Context context) {\n        Object service = context.getSystemService(Context.DEVICE_POLICY_SERVICE);\n        if (service instanceof DevicePolicyManager) {\n            DevicePolicyManager dpm = (DevicePolicyManager) service;\n            return dpm.isAdminActive(getAdminComponent(context));\n        }\n        return false;\n    }\n\n    public static void removeActiveAdmin(@NonNull Context context) {\n        Object service = context.getSystemService(Context.DEVICE_POLICY_SERVICE);\n        if (service instanceof DevicePolicyManager) {\n            DevicePolicyManager dpm = (DevicePolicyManager) service;\n            dpm.removeActiveAdmin(getAdminComponent(context));\n        }\n    }\n\n    public static void lockScreen(@NonNull Context context) {\n        Object service = context.getSystemService(Context.DEVICE_POLICY_SERVICE);\n        if (service instanceof DevicePolicyManager) {\n            DevicePolicyManager dpm = (DevicePolicyManager) service;\n            dpm.lockNow();\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/DrawableCache.java",
    "content": "package rocks.tbog.tblauncher;\n\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.graphics.drawable.Drawable;\nimport android.util.Log;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.collection.LruCache;\n\nimport java.util.Calendar;\nimport java.util.Collection;\n\npublic class DrawableCache {\n    private static final String TAG = \"DrawCache\";\n    private boolean mEnabled = true;\n    private final LruCache<String, DrawableInfo> mCache = new LruCache<>(16);\n\n    public void setSize(int maxSize) {\n        mCache.resize(maxSize);\n    }\n\n    public void setCalendar(String cacheId) {\n        synchronized (mCache) {\n            DrawableInfo info = mCache.get(cacheId);\n            if (info != null)\n                info.setToday();\n        }\n    }\n\n    @Nullable\n    public Drawable getCachedDrawable(@NonNull String cacheId) {\n        synchronized (mCache) {\n            DrawableInfo info = mCache.get(cacheId);\n            if (info != null) {\n                if (info.isToday())\n                    return info.drawable;\n                mCache.remove(cacheId);\n            }\n        }\n        return null;\n    }\n\n    public void cacheDrawable(@NonNull String cacheId, @Nullable Drawable drawable) {\n        synchronized (mCache) {\n            if (drawable == null) {\n                mCache.remove(cacheId);\n                return;\n            }\n            if (!mEnabled)\n                return;\n            DrawableInfo info = new DrawableInfo(drawable);\n            mCache.put(cacheId, info);\n        }\n    }\n\n    public void clearCache() {\n        synchronized (mCache) {\n            mCache.evictAll();\n        }\n    }\n\n    public void onPrefChanged(Context ctx, SharedPreferences pref) {\n        boolean enabled = pref.getBoolean(\"cache-drawable\", true);\n        if (enabled != mEnabled) {\n            mEnabled = enabled;\n            clearCache();\n        }\n        boolean halfSize = pref.getBoolean(\"cache-half-apps\", true);\n        Collection<?> apps = TBApplication.appsHandler(ctx).getAllApps();\n        int size = apps.size();\n        size = size < 16 ? 16 : halfSize ? (size / 2) : (size * 115 / 100);\n        Log.i(TAG, \"Cache size: \" + size);\n        synchronized (mCache) {\n            mCache.resize(size);\n        }\n    }\n\n    public static class DrawableInfo {\n        public final Drawable drawable;\n        public int dayOfMonth = 0;\n\n        public DrawableInfo(Drawable drawable) {\n            this.drawable = drawable;\n        }\n\n        /**\n         * Set day for cached drawable. This is a number indicating the day of the month.\n         * The first day of the month has value 1.\n         */\n        public void setToday() {\n            dayOfMonth = Calendar.getInstance().get(Calendar.DAY_OF_MONTH);\n        }\n\n        public boolean isToday() {\n            if (dayOfMonth == 0)\n                return true;\n            return dayOfMonth == Calendar.getInstance().get(Calendar.DAY_OF_MONTH);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/DummyLauncherActivity.java",
    "content": "package rocks.tbog.tblauncher;\n\nimport android.app.Activity;\n\npublic class DummyLauncherActivity extends Activity {\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/EditTagsDialog.java",
    "content": "package rocks.tbog.tblauncher;\n\nimport android.app.Dialog;\nimport android.content.Context;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.text.Editable;\nimport android.text.TextWatcher;\nimport android.util.Log;\nimport android.view.KeyEvent;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.inputmethod.EditorInfo;\nimport android.view.inputmethod.InputMethodManager;\nimport android.widget.ArrayAdapter;\nimport android.widget.AutoCompleteTextView;\nimport android.widget.BaseAdapter;\nimport android.widget.GridView;\nimport android.widget.ImageView;\nimport android.widget.TextView;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.collection.ArraySet;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Set;\n\nimport rocks.tbog.tblauncher.entry.EntryItem;\nimport rocks.tbog.tblauncher.handler.TagsHandler;\nimport rocks.tbog.tblauncher.ui.DialogFragment;\nimport rocks.tbog.tblauncher.ui.DialogWrapper;\n\npublic class EditTagsDialog extends DialogFragment<Set<String>> {\n\n    private static final String TAG = EditTagsDialog.class.getSimpleName();\n    private final ArraySet<String> mTagList = new ArraySet<>();\n    private TagsAdapter mAdapter;\n    private AutoCompleteTextView mNewTag;\n\n    @Override\n    protected int layoutRes() {\n        return R.layout.dialog_edit_tags;\n    }\n\n    @Nullable\n    @Override\n    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {\n        Context context = requireDialog().getContext();\n\n        setupDefaultButtonOkCancel(context);\n        Bundle args = getArguments() != null ? getArguments() : new Bundle();\n\n        // make sure we use the dialog context\n        LayoutInflater dialogInflater = inflater.cloneInContext(context);\n        ViewGroup root = (ViewGroup) super.onCreateView(dialogInflater, container, savedInstanceState);\n        assert root != null;\n\n        // make a layout for the entry we are changing\n        String entryId = args.getString(\"entryId\", \"\");\n        TBApplication app = TBApplication.getApplication(context);\n        EntryItem entry = app.getDataHandler().getPojo(entryId);\n        ViewGroup wrapper = root.findViewById(R.id.previewWrapper);\n        if (wrapper == null)\n            wrapper = root;\n        if (entry != null) {\n            int drawFlags = EntryItem.FLAG_DRAW_LIST | EntryItem.FLAG_DRAW_NAME | EntryItem.FLAG_DRAW_ICON;\n            View entryView = dialogInflater.inflate(entry.getResultLayout(drawFlags), wrapper, false);\n            entryView.setId(R.id.iconPreview);\n            wrapper.addView(entryView, 0);\n            CustomizeUI.setResultListPref(entryView);\n        }\n\n        return root;\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n\n        Context context = view.getContext();\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            view.setClipToOutline(true);\n        }\n\n        Bundle args = getArguments() != null ? getArguments() : new Bundle();\n        String entryId = args.getString(\"entryId\", \"\");\n        String entryName = args.getString(\"entryName\", \"\");\n\n        // show the app we are changing\n        EntryItem entry = TBApplication.getApplication(context).getDataHandler().getPojo(entryId);\n        if (entry == null) {\n            dismiss();\n            return;\n        }\n        int drawFlags = EntryItem.FLAG_DRAW_LIST | EntryItem.FLAG_DRAW_NAME | EntryItem.FLAG_DRAW_ICON;\n        entry.displayResult(view.findViewById(R.id.iconPreview), drawFlags);\n\n        // prepare the grid with all the tags\n        mAdapter = new TagsAdapter(mTagList);\n        GridView gridView = view.findViewById(R.id.grid);\n        gridView.setAdapter(mAdapter);\n\n        mAdapter.setOnItemClickListener((adapter, v, position) -> removeTag(adapter.getItem(position)));\n\n        // initialize new tag EditView\n        mNewTag = view.findViewById(R.id.newTag);\n        mNewTag.addTextChangedListener(new TextWatcher() {\n            public void afterTextChanged(Editable s) {\n                // Auto left-trim text.\n                if (s.length() > 0 && s.charAt(0) == ' ')\n                    s.delete(0, 1);\n            }\n\n            public void beforeTextChanged(CharSequence s, int start, int count, int after) {\n            }\n\n            public void onTextChanged(CharSequence s, int start, int before, int count) {\n            }\n        });\n        mNewTag.setOnEditorActionListener((v, actionId, event) -> {\n            if (event == null) {\n                if (actionId != EditorInfo.IME_ACTION_NONE) {\n                    String tag = mNewTag.getText().toString();\n                    if (tag.isEmpty()) {\n                        onConfirm(mTagList);\n                        dismiss();\n                        return true;\n                    }\n                    addTag(tag);\n                    return true;\n                }\n            } else if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) {\n                if (event.getAction() == KeyEvent.ACTION_UP) {\n                    String tag = mNewTag.getText().toString();\n                    addTag(tag);\n                }\n                return true;\n            }\n            return false;\n        });\n        // set the auto complete list\n        {\n            List<String> allTags = new ArrayList<>(TBApplication.tagsHandler(context).getValidTags());\n            Collections.sort(allTags);\n            mNewTag.setAdapter(new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, allTags));\n        }\n\n        // initialize add tag button\n        ImageView addTag = view.findViewById(R.id.addTag);\n        addTag.setOnClickListener(v ->\n        {\n            String tag = mNewTag.getText().toString();\n            addTag(tag);\n        });\n    }\n\n    @Override\n    public void onButtonClick(@NonNull Button button) {\n        if (button == Button.POSITIVE) {\n            String tag = mNewTag.getText().toString();\n            addTag(tag);\n            onConfirm(mTagList);\n        }\n        super.onButtonClick(button);\n    }\n\n    @Override\n    public void onStart() {\n        super.onStart();\n        Dialog dialog = getDialog();\n        if (dialog instanceof DialogWrapper) {\n            ((DialogWrapper) dialog).setOnWindowFocusChanged((dlg, hasFocus) -> {\n                if (hasFocus) {\n                    dlg.setOnWindowFocusChanged(null);\n                    showKeyboard(dlg, mNewTag);\n                }\n            });\n        }\n    }\n\n    private static void showKeyboard(@NonNull Dialog dialog, @NonNull TextView textView) {\n        Log.i(TAG, \"Keyboard - SHOW\");\n        textView.requestFocus();\n\n        InputMethodManager mgr = (InputMethodManager) dialog.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);\n        assert mgr != null;\n        mgr.showSoftInput(textView, InputMethodManager.SHOW_IMPLICIT);\n    }\n\n    private void addTag(String tag) {\n        tag = tag.trim();\n        if (tag.length() == 0)\n            return;\n        mTagList.add(tag);\n        mAdapter.notifyDataSetChanged();\n        mNewTag.setText(\"\");\n    }\n\n    private void removeTag(String tag) {\n        mTagList.remove(tag);\n        mAdapter.notifyDataSetChanged();\n    }\n\n    @Override\n    public void onActivityCreated(@Nullable Bundle savedInstanceState) {\n        super.onActivityCreated(savedInstanceState);\n\n        Context context = getActivity();\n        assert context != null;\n\n        Bundle args = getArguments() != null ? getArguments() : new Bundle();\n        String entryId = args.getString(\"entryId\", \"\");\n\n        TagsHandler tagsHandler = TBApplication.tagsHandler(context);\n        mTagList.clear();\n        mTagList.addAll(tagsHandler.getTags(entryId));\n\n        mAdapter.notifyDataSetChanged();\n    }\n\n    static class TagsAdapter extends BaseAdapter {\n        private final ArraySet<String> mTags;\n        private OnItemClickListener mOnItemClickListener = null;\n\n        public interface OnItemClickListener {\n            void onItemClick(TagsAdapter adapter, View view, int position);\n        }\n\n        TagsAdapter(@NonNull ArraySet<String> tags) {\n            mTags = tags;\n        }\n\n        void setOnItemClickListener(OnItemClickListener listener) {\n            mOnItemClickListener = listener;\n        }\n\n        @Override\n        public int getCount() {\n            return mTags.size();\n        }\n\n        @Override\n        public String getItem(int position) {\n            return mTags.valueAt(position);\n        }\n\n        @Override\n        public long getItemId(int position) {\n            return getItem(position).hashCode();\n        }\n\n        @Override\n        public View getView(int position, View convertView, ViewGroup parent) {\n            final View view;\n            if (convertView == null) {\n                view = LayoutInflater.from(parent.getContext()).inflate(R.layout.edit_tag_item, parent, false);\n            } else {\n                view = convertView;\n            }\n            ViewHolder holder = view.getTag() instanceof ViewHolder ? (ViewHolder) view.getTag() : new ViewHolder(view);\n\n            String content = getItem(position);\n            holder.setContent(content);\n            holder.buttonView.setOnClickListener(v -> {\n                if (mOnItemClickListener != null)\n                    mOnItemClickListener.onItemClick(TagsAdapter.this, v, position);\n            });\n\n            return view;\n        }\n\n        static class ViewHolder {\n            TextView textView;\n            View buttonView;\n\n            ViewHolder(View itemView) {\n                itemView.setTag(this);\n                textView = itemView.findViewById(android.R.id.text1);\n                buttonView = itemView.findViewById(android.R.id.button1);\n            }\n\n            public void setContent(CharSequence content) {\n                textView.setText(content);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/LauncherState.java",
    "content": "package rocks.tbog.tblauncher;\n\nimport android.view.View;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport rocks.tbog.tblauncher.ui.WindowInsetsHelper;\n\npublic class LauncherState {\n    public enum AnimatedVisibility {\n        HIDDEN,\n        ANIM_TO_HIDDEN,\n        ANIM_TO_VISIBLE,\n        VISIBLE,\n    }\n\n    public enum Desktop {\n        SEARCH,\n        WIDGET,\n        EMPTY,\n    }\n\n    private AnimatedVisibility quickList = AnimatedVisibility.HIDDEN;\n    private AnimatedVisibility searchBar = AnimatedVisibility.HIDDEN;\n    private AnimatedVisibility resultList = AnimatedVisibility.HIDDEN;\n    private AnimatedVisibility notificationBar = AnimatedVisibility.HIDDEN;\n    private AnimatedVisibility widgetScreen = AnimatedVisibility.HIDDEN;\n    private AnimatedVisibility clearScreen = AnimatedVisibility.HIDDEN;\n    private AnimatedVisibility keyboard = AnimatedVisibility.HIDDEN;\n\n    private Desktop desktop = null;\n\n    private static boolean isVisible(AnimatedVisibility state) {\n        return state == AnimatedVisibility.ANIM_TO_VISIBLE ||\n            state == AnimatedVisibility.VISIBLE;\n    }\n\n    public boolean isQuickListVisible() {\n        return isVisible(quickList);\n    }\n\n    public boolean isSearchBarVisible() {\n        return isVisible(searchBar);\n    }\n\n    public boolean isResultListVisible() {\n        return isVisible(resultList);\n    }\n\n    public boolean isNotificationBarVisible() {\n        return isVisible(notificationBar);\n    }\n\n    public boolean isWidgetScreenVisible() {\n        return isVisible(widgetScreen);\n    }\n\n    public boolean isClearScreenVisible() {\n        return isVisible(clearScreen);\n    }\n\n    public boolean isKeyboardHidden() {\n        return !isVisible(keyboard);\n    }\n\n    public void syncKeyboardVisibility(View anyView) {\n        if (WindowInsetsHelper.isKeyboardVisible(anyView))\n            setKeyboard(AnimatedVisibility.VISIBLE);\n        else\n            setKeyboard(AnimatedVisibility.HIDDEN);\n    }\n\n    @Nullable\n    public Desktop getDesktop() {\n        return desktop;\n    }\n\n    public void setNotificationBar(@NonNull AnimatedVisibility state) {\n        notificationBar = state;\n    }\n\n    public void setSearchBar(@NonNull AnimatedVisibility state) {\n        searchBar = state;\n    }\n\n    public void setResultList(@NonNull AnimatedVisibility state) {\n        resultList = state;\n    }\n\n    public void setQuickList(@NonNull AnimatedVisibility state) {\n        quickList = state;\n    }\n\n    public void setWidgetScreen(@NonNull AnimatedVisibility state) {\n        widgetScreen = state;\n    }\n\n    public void setKeyboard(@NonNull AnimatedVisibility state) {\n        keyboard = state;\n    }\n\n    public void setDesktop(@NonNull Desktop mode) {\n        desktop = mode;\n    }\n\n    @NonNull\n    public AnimatedVisibility getSearchBarVisibility() {\n        return searchBar;\n    }\n\n    @NonNull\n    public AnimatedVisibility getResultListVisibility() {\n        return resultList;\n    }\n\n    @NonNull\n    public AnimatedVisibility getNotificationBarVisibility() {\n        return notificationBar;\n    }\n    \n    @NonNull\n    public AnimatedVisibility getWidgetScreenVisibility() { return widgetScreen; }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/LiveWallpaper.java",
    "content": "package rocks.tbog.tblauncher;\n\nimport android.app.WallpaperManager;\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.graphics.Point;\nimport android.graphics.PointF;\nimport android.graphics.Rect;\nimport android.os.Build;\nimport android.util.Log;\nimport android.view.GestureDetector;\nimport android.view.Gravity;\nimport android.view.HapticFeedbackConstants;\nimport android.view.MotionEvent;\nimport android.view.VelocityTracker;\nimport android.view.View;\nimport android.view.ViewConfiguration;\nimport android.view.WindowMetrics;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.preference.PreferenceManager;\n\nimport java.util.Locale;\n\nimport rocks.tbog.tblauncher.ui.ListPopup;\nimport rocks.tbog.tblauncher.utils.GestureDetectorHelper;\nimport rocks.tbog.tblauncher.utils.UISizes;\n\npublic class LiveWallpaper {\n    private static final String TAG = \"LWP\";\n    private static final int FLING_DELTA_ANGLE = 33;\n    private static final int GD_TOUCH_SLOP_DP = 16;\n    private TBLauncherActivity mTBLauncherActivity = null;\n    private WallpaperManager mWallpaperManager;\n    private final Point mWindowSize = new Point(1, 1);\n    private View mContentView;\n    private final PointF mFirstTouchOffset = new PointF();\n    private final PointF mFirstTouchPos = new PointF();\n    private final PointF mLastTouchPos = new PointF();\n    private final PointF mWallpaperOffset = new PointF(.5f, .5f);\n    private WallpaperSnapAnim mSnapAnimation;\n    private VelocityTracker mVelocityTracker;\n\n    public static int SCREEN_COUNT_HORIZONTAL = Integer.parseInt(\"3\");\n    public static int SCREEN_COUNT_VERTICAL = Integer.parseInt(\"1\"); // not tested with values != 1\n\n    private boolean lwpScrollPages = true;\n    private boolean lwpTouch = true;\n    private boolean lwpDrag = false;\n    private boolean wpDragAnimate = true;\n    private boolean wpReturnCenter = true;\n    private boolean wpStickToSides = false;\n\n    private GestureDetector gestureDetector = null;\n    private final GestureDetector.SimpleOnGestureListener onGestureListener = new GestureDetector.SimpleOnGestureListener() {\n        @Override\n        public void onLongPress(@NonNull MotionEvent e) {\n            if (!TBApplication.state().isWidgetScreenVisible())\n                return;\n            View view = mTBLauncherActivity.findViewById(R.id.root_layout);\n            onLongClick(view);\n        }\n\n        @Override\n        public boolean onFling(@Nullable MotionEvent e1, @NonNull MotionEvent e2, float velocityX, float velocityY) {\n            long deltaTimeMs = (e1 != null) ? (e2.getEventTime() - e1.getEventTime()) : 0;\n            if (deltaTimeMs > ViewConfiguration.getDoubleTapTimeout())\n                return false;\n            View view = mTBLauncherActivity.findViewById(R.id.root_layout);\n            float xMove = velocityX;\n            float yMove = velocityY;\n            if (e1 != null) {\n                xMove = e1.getRawX() - e2.getRawX();\n                yMove = e1.getRawY() - e2.getRawY();\n            }\n            return LiveWallpaper.this.onFling(view, xMove, yMove, velocityX, velocityY);\n        }\n\n        @Override\n        public boolean onDoubleTapEvent(@NonNull MotionEvent e) {\n            if (e.getActionMasked() == MotionEvent.ACTION_UP) {\n                View view = mTBLauncherActivity.findViewById(R.id.root_layout);\n                return onDoubleClick(view);\n            }\n            return false;\n        }\n\n        @Override\n        public boolean onSingleTapUp(@NonNull MotionEvent e) {\n            // if we have a double tap listener, wait for onSingleTapConfirmed\n            if (mTBLauncherActivity.behaviour.hasDoubleClick())\n                return true;\n            View view = mTBLauncherActivity.findViewById(R.id.root_layout);\n            return onClick(view);\n        }\n\n        @Override\n        public boolean onSingleTapConfirmed(MotionEvent e) {\n            // if we have both a double tap and click, handle click here\n            if (mTBLauncherActivity.behaviour.hasDoubleClick()) {\n                View view = mTBLauncherActivity.findViewById(R.id.root_layout);\n                return onClick(view);\n            }\n            return false;\n        }\n    };\n\n    LiveWallpaper() {\n//        TypedValue typedValue = new TypedValue();\n//        mainActivity.getTheme().resolveAttribute(android.R.attr.windowShowWallpaper, typedValue, true);\n//        TypedArray a = mainActivity.obtainStyledAttributes(typedValue.resourceId, new int[]{android.R.attr.windowShowWallpaper});\n//        wallpaperIsVisible = a.getBoolean(0, true);\n//        a.recycle();\n    }\n\n    @NonNull\n    public PointF getWallpaperOffset() {\n        return mWallpaperOffset;\n    }\n\n    @NonNull\n    public Point getWindowSize() {\n        return mWindowSize;\n    }\n\n    public void scroll(MotionEvent e1, MotionEvent e2) {\n        cacheWindowSize();\n        mFirstTouchPos.set(e1.getRawX(), e1.getRawY());\n        mLastTouchPos.set(e2.getRawX(), e2.getRawY());\n        float xMove = (mFirstTouchPos.x - mLastTouchPos.x) / mWindowSize.x;\n        float yMove = (mFirstTouchPos.y - mLastTouchPos.y) / mWindowSize.y;\n        float offsetX = mFirstTouchOffset.x + xMove * 1.01f;\n        float offsetY = mFirstTouchOffset.y + yMove * 1.01f;\n        updateWallpaperOffset(offsetX, offsetY);\n    }\n\n    private int prefGetInt(@NonNull SharedPreferences prefs, @NonNull String key, int defaultValue) {\n        String value = prefs.getString(key, null);\n        if (value != null) {\n            try {\n                return Integer.parseInt(value);\n            } catch (NumberFormatException ignored) {\n            }\n        }\n        return defaultValue;\n    }\n\n    public void onCreateActivity(TBLauncherActivity mainActivity) {\n        mTBLauncherActivity = mainActivity;\n\n        // load preferences\n        {\n            SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mainActivity);\n            lwpScrollPages = prefs.getBoolean(\"lwp-scroll-pages\", true);\n            lwpTouch = prefs.getBoolean(\"lwp-touch\", true);\n            lwpDrag = prefs.getBoolean(\"lwp-drag\", false);\n            wpDragAnimate = prefs.getBoolean(\"wp-drag-animate\", false);\n            wpReturnCenter = prefs.getBoolean(\"wp-animate-center\", true);\n            wpStickToSides = prefs.getBoolean(\"wp-animate-sides\", false);\n            SCREEN_COUNT_VERTICAL = prefGetInt(prefs, \"lwp-page-count-vertical\", SCREEN_COUNT_VERTICAL);\n            SCREEN_COUNT_HORIZONTAL = prefGetInt(prefs, \"lwp-page-count-horizontal\", SCREEN_COUNT_HORIZONTAL);\n        }\n\n        mWallpaperManager = (WallpaperManager) mainActivity.getSystemService(Context.WALLPAPER_SERVICE);\n        assert mWallpaperManager != null;\n\n        // set mContentView before we call updateWallpaperOffset\n        mContentView = mainActivity.findViewById(android.R.id.content);\n        resetPageCount();\n\n        mSnapAnimation = new WallpaperSnapAnim(this);\n        mVelocityTracker = null;\n        View root = mainActivity.findViewById(R.id.root_layout);\n        root.setOnTouchListener(this::onRootTouch);\n\n        gestureDetector = new GestureDetector(mainActivity, onGestureListener);\n        gestureDetector.setIsLongpressEnabled(true);\n        GestureDetectorHelper.setGestureDetectorTouchSlop(gestureDetector, UISizes.dp2px(mainActivity, GD_TOUCH_SLOP_DP));\n    }\n\n    public void resetPosition() {\n        resetPageCount();\n    }\n\n    private void resetPageCount() {\n        Log.i(TAG, \"resetPageCount \" + SCREEN_COUNT_HORIZONTAL + \"x\" + SCREEN_COUNT_VERTICAL);\n\n        float xStep = (SCREEN_COUNT_HORIZONTAL > 1) ? (1.f / (SCREEN_COUNT_HORIZONTAL - 1)) : 0.f;\n        float yStep = (SCREEN_COUNT_VERTICAL > 1) ? (1.f / (SCREEN_COUNT_VERTICAL - 1)) : 0.f;\n        mWallpaperManager.setWallpaperOffsetSteps(xStep, yStep);\n\n        if (isPreferenceLWPScrollPages()) {\n            mTBLauncherActivity.widgetManager.setPageCount(SCREEN_COUNT_HORIZONTAL, SCREEN_COUNT_VERTICAL);\n        }\n\n        int centerScreenX = SCREEN_COUNT_HORIZONTAL / 2;\n        int centerScreenY = SCREEN_COUNT_VERTICAL / 2;\n        updateWallpaperOffset(centerScreenX * xStep, centerScreenY * yStep);\n    }\n\n    private static boolean onClick(View view) {\n        if (!view.isAttachedToWindow())\n            return false;\n        return TBApplication.behaviour(view.getContext()).onClick();\n    }\n\n    private static boolean onDoubleClick(View view) {\n        if (!view.isAttachedToWindow())\n            return false;\n        return TBApplication.behaviour(view.getContext()).onDoubleClick();\n    }\n\n    private static int computeAngle(float x, float y) {\n        return (int) (.5 + Math.toDegrees(Math.atan2(y, x)));\n    }\n\n    private boolean onFling(View view, float xMove, float yMove, float xVel, float yVel) {\n        if (!view.isAttachedToWindow())\n            return false;\n        final Behaviour behaviour = mTBLauncherActivity.behaviour;\n\n        final int angle;\n//        if (-minMovement < xMove && xMove < minMovement && -minMovement < yMove && yMove < minMovement) {\n//            // too little movement, use velocity\n//            angle = computeAngle(xVel, yVel);\n//        } else {\n        angle = computeAngle(xMove, yMove);\n//        }\n\n        // fling upwards\n        if ((90 + FLING_DELTA_ANGLE) > angle && angle > (90 - FLING_DELTA_ANGLE)) {\n            Log.d(TAG, String.format(Locale.US, \"Angle=%d - fling upward\", angle));\n            return behaviour.onFlingUp();\n        }\n        // fling downwards\n        else if ((90 + FLING_DELTA_ANGLE) > -angle && -angle > (90 - FLING_DELTA_ANGLE)) {\n            Log.d(TAG, String.format(Locale.US, \"Angle=%d - fling downward\", angle));\n            final int posX = (int) mFirstTouchPos.x;\n            if (posX < (mWindowSize.x / 2))\n                return behaviour.onFlingDownLeft();\n            else\n                return behaviour.onFlingDownRight();\n        }\n        // fling left\n        else if (FLING_DELTA_ANGLE > angle && angle > -FLING_DELTA_ANGLE) {\n            Log.d(TAG, String.format(Locale.US, \"Angle=%d - fling left\", angle));\n            return behaviour.onFlingLeft();\n        }\n        // fling right\n        else if ((180 - FLING_DELTA_ANGLE) < angle || angle < (-180 + FLING_DELTA_ANGLE)) {\n            Log.d(TAG, String.format(Locale.US, \"Angle=%d - fling right\", angle));\n            return behaviour.onFlingRight();\n        }\n        Log.d(TAG, String.format(Locale.US, \"Angle=%d - fling direction uncertain\", angle));\n        return false;\n    }\n\n    private void onLongClick(View view) {\n        if (!view.isAttachedToWindow()) {\n            return;\n        }\n        view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);\n        ListPopup menu = mTBLauncherActivity.widgetManager.getConfigPopup(mTBLauncherActivity);\n        TBApplication.getApplication(mTBLauncherActivity).registerPopup(menu);\n        int x = (int) (mLastTouchPos.x + .5f);\n        int y = (int) (mLastTouchPos.y + .5f);\n        menu.showAtLocation(view, Gravity.START | Gravity.TOP, x, y);\n    }\n\n    private void cacheWindowSize() {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {\n            WindowMetrics windowMetrics = mTBLauncherActivity.getWindowManager().getCurrentWindowMetrics();\n            //Insets insets = windowMetrics.getWindowInsets().getInsetsIgnoringVisibility(WindowInsets.Type.systemBars());\n            Rect windowBound = windowMetrics.getBounds();\n            int width = windowBound.width();// - insets.left - insets.right;\n            int height = windowBound.height();// - insets.top - insets.bottom;\n            mWindowSize.set(width, height);\n        } else {\n            mTBLauncherActivity.getWindowManager()\n                .getDefaultDisplay()\n                .getSize(mWindowSize);\n        }\n    }\n\n    private boolean initializeSnapAnimation() {\n        return mSnapAnimation.init(mVelocityTracker);\n    }\n\n    boolean onRootTouch(View view, MotionEvent event) {\n        if (!view.isAttachedToWindow()) {\n            return false;\n        }\n\n        Log.d(TAG, \"onRootTouch\\r\\n\" + event);\n        int actionMasked = event.getActionMasked();\n        boolean eventConsumed = false;\n\n        switch (actionMasked) {\n            case MotionEvent.ACTION_DOWN: {\n                mFirstTouchPos.set(event.getRawX(), event.getRawY());\n                mLastTouchPos.set(mFirstTouchPos);\n                mFirstTouchOffset.set(mWallpaperOffset);\n                cacheWindowSize();\n                if (isScrollEnabled()) {\n                    mContentView.clearAnimation();\n                }\n                if (mVelocityTracker != null)\n                    mVelocityTracker.recycle();\n                mVelocityTracker = VelocityTracker.obtain();\n                mVelocityTracker.addMovement(event);\n                //send touch event to the LWP\n                if (isPreferenceLWPTouch())\n                    sendTouchEvent(view, event);\n                eventConsumed = true;\n                break;\n            }\n            case MotionEvent.ACTION_MOVE: {\n                mLastTouchPos.set(event.getRawX(), event.getRawY());\n                float xMove = (mFirstTouchPos.x - mLastTouchPos.x) / mWindowSize.x;\n                float yMove = (mFirstTouchPos.y - mLastTouchPos.y) / mWindowSize.y;\n                if (mVelocityTracker != null)\n                    mVelocityTracker.addMovement(event);\n\n                if (isScrollEnabled()) {\n                    float offsetX = mFirstTouchOffset.x + xMove * 1.01f;\n                    float offsetY = mFirstTouchOffset.y + yMove * 1.01f;\n                    updateWallpaperOffset(offsetX, offsetY);\n                }\n\n                //send move/drag event to the LWP\n                if (isPreferenceLWPDrag())\n                    sendTouchEvent(view, event);\n                if (isScrollEnabled())\n                    eventConsumed = true;\n                break;\n            }\n            case MotionEvent.ACTION_UP: {\n                // was this a click?\n                float xMove = (mFirstTouchPos.x - mLastTouchPos.x) / mWindowSize.x;\n                float yMove = (mFirstTouchPos.y - mLastTouchPos.y) / mWindowSize.y;\n                if (mVelocityTracker == null) {\n                    Log.d(TAG, String.format(Locale.US, \"Move=(%.3f, %.3f)\", xMove, yMove));\n                } else {\n                    mVelocityTracker.addMovement(event);\n                    mVelocityTracker.computeCurrentVelocity(1000 / 30); // 1000 provides px per second\n                    float xVel = mVelocityTracker.getXVelocity();// / mWindowSize.x;\n                    float yVel = mVelocityTracker.getYVelocity();// / mWindowSize.y;\n                    Log.d(TAG, String.format(Locale.US, \"Velocity=(%.3f, %.3f)\\u2248%d\\u00b0 Move=(%.3f, %.3f)\\u2248%d\\u00b0\", xVel, yVel, computeAngle(xVel, yVel), xMove, yMove, computeAngle(xMove, yMove)));\n                    // snap position if needed\n                    if (isScrollEnabled() && initializeSnapAnimation())\n                        mContentView.startAnimation(mSnapAnimation);\n                }\n            }\n            // fallthrough\n            case MotionEvent.ACTION_CANCEL:\n                if (isScrollEnabled()) {\n                    if (mVelocityTracker != null) {\n                        mVelocityTracker.addMovement(event);\n\n                        mVelocityTracker.computeCurrentVelocity(1000 / 30); // 1000 provides px per second\n                        if (initializeSnapAnimation())\n                            mContentView.startAnimation(mSnapAnimation);\n\n                        mVelocityTracker.recycle();\n                        mVelocityTracker = null;\n                    } else {\n                        if (initializeSnapAnimation())\n                            mContentView.startAnimation(mSnapAnimation);\n                    }\n                    eventConsumed = true;\n                }\n                break;\n        }\n\n        eventConsumed = gestureDetector.onTouchEvent(event) || eventConsumed;\n        Log.d(TAG, \"onRootTouch event \" + (eventConsumed ? \"\" : \"NOT \") + \"consumed\");\n        return eventConsumed;\n    }\n\n    public Context getContext() {\n        return mTBLauncherActivity;\n    }\n\n    public void onPrefChanged(SharedPreferences prefs, String key) {\n        switch (key) {\n            case \"lwp-scroll-pages\":\n                lwpScrollPages = prefs.getBoolean(\"lwp-scroll-pages\", true);\n                break;\n            case \"lwp-touch\":\n                lwpTouch = prefs.getBoolean(\"lwp-touch\", true);\n                break;\n            case \"lwp-drag\":\n                lwpDrag = prefs.getBoolean(\"lwp-drag\", false);\n                break;\n            case \"wp-drag-animate\":\n                wpDragAnimate = prefs.getBoolean(\"wp-drag-animate\", false);\n                break;\n            case \"wp-animate-center\":\n                wpReturnCenter = prefs.getBoolean(\"wp-animate-center\", true);\n                break;\n            case \"wp-animate-sides\":\n                wpStickToSides = prefs.getBoolean(\"wp-animate-sides\", false);\n                break;\n            case \"lwp-page-count-vertical\": {\n                int count = prefGetInt(prefs, \"lwp-page-count-vertical\", SCREEN_COUNT_VERTICAL);\n                if (SCREEN_COUNT_VERTICAL != count) {\n                    SCREEN_COUNT_VERTICAL = count;\n                    resetPageCount();\n                }\n                break;\n            }\n            case \"lwp-page-count-horizontal\": {\n                int count = prefGetInt(prefs, \"lwp-page-count-horizontal\", SCREEN_COUNT_HORIZONTAL);\n                if (SCREEN_COUNT_HORIZONTAL != count) {\n                    SCREEN_COUNT_HORIZONTAL = count;\n                    resetPageCount();\n                }\n                break;\n            }\n        }\n    }\n\n    private boolean isScrollEnabled() {\n        return lwpScrollPages || wpDragAnimate;\n    }\n\n    private boolean isPreferenceLWPScrollPages() {\n        return lwpScrollPages;\n    }\n\n    private boolean isPreferenceLWPTouch() {\n        return lwpTouch;\n    }\n\n    private boolean isPreferenceLWPDrag() {\n        return lwpDrag;\n    }\n\n    public boolean isPreferenceWPDragAnimate() {\n        return wpDragAnimate;\n    }\n\n    public boolean isPreferenceWPReturnCenter() {\n        return wpReturnCenter;\n    }\n\n    public boolean isPreferenceWPStickToSides() {\n        return wpStickToSides;\n    }\n\n    private android.os.IBinder getWindowToken() {\n        return mContentView != null && mContentView.isAttachedToWindow() ? mContentView.getWindowToken() : null;\n    }\n\n    public void updateWallpaperOffset(float offsetX, float offsetY) {\n        offsetX = Math.max(0.f, Math.min(1.f, offsetX));\n        offsetY = Math.max(0.f, Math.min(1.f, offsetY));\n        mWallpaperOffset.set(offsetX, offsetY);\n        if (isPreferenceLWPScrollPages()) {\n            mTBLauncherActivity.widgetManager.scroll(offsetX, offsetY);\n        }\n        if (isPreferenceWPDragAnimate()) {\n            android.os.IBinder iBinder = getWindowToken();\n            if (iBinder != null) {\n                mWallpaperManager.setWallpaperOffsets(iBinder, offsetX, offsetY);\n            }\n        }\n    }\n\n    private void sendTouchEvent(int x, int y, int index) {\n        android.os.IBinder iBinder = getWindowToken();\n        if (iBinder != null) {\n            String command = index == 0 ? WallpaperManager.COMMAND_TAP : WallpaperManager.COMMAND_SECONDARY_TAP;\n            try {\n                mWallpaperManager.sendWallpaperCommand(iBinder, command, x, y, 0, null);\n            } catch (Exception e) {\n                Log.e(TAG, \"sendTouchEvent (\" + x + \",\" + y + \") idx=\" + index, e);\n            }\n        }\n    }\n\n    private void sendTouchEvent(View view, MotionEvent event) {\n        int pointerCount = event.getPointerCount();\n        int[] viewOffset = {0, 0};\n        // this will not account for a rotated view\n        view.getLocationOnScreen(viewOffset);\n\n        // get index of first finger\n        int pointerIndex = event.findPointerIndex(0);\n        if (pointerIndex >= 0 && pointerIndex < pointerCount) {\n            sendTouchEvent((int) event.getX(pointerIndex) + viewOffset[0], (int) event.getY(pointerIndex) + viewOffset[1], pointerIndex);\n        }\n\n        // get index of second finger\n        pointerIndex = event.findPointerIndex(1);\n        if (pointerIndex >= 0 && pointerIndex < pointerCount) {\n            sendTouchEvent((int) event.getX(pointerIndex) + viewOffset[0], (int) event.getY(pointerIndex) + viewOffset[1], pointerIndex);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/MimeTypeCache.java",
    "content": "package rocks.tbog.tblauncher;\n\nimport android.accounts.AccountManager;\nimport android.accounts.AuthenticatorDescription;\nimport android.annotation.SuppressLint;\nimport android.content.ComponentName;\nimport android.content.ContentResolver;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.SyncAdapterType;\nimport android.content.pm.PackageManager;\nimport android.content.pm.ResolveInfo;\nimport android.content.pm.ServiceInfo;\nimport android.content.res.XmlResourceParser;\nimport android.provider.ContactsContract;\nimport android.util.Log;\n\nimport androidx.annotation.NonNull;\nimport androidx.collection.ArraySet;\n\nimport org.xmlpull.v1.XmlPullParser;\nimport org.xmlpull.v1.XmlPullParserException;\n\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport rocks.tbog.tblauncher.utils.MimeTypeUtils;\nimport rocks.tbog.tblauncher.utils.PackageManagerUtils;\nimport rocks.tbog.tblauncher.utils.Timer;\nimport rocks.tbog.tblauncher.utils.Utilities;\n\npublic class MimeTypeCache {\n\n    private static final String CONTACTS_DATA_KIND = \"ContactsDataKind\";\n    private static final String CONTACT_ATTR_MIME_TYPE = \"mimeType\";\n    private static final String CONTACT_ATTR_DETAIL_COLUMN = \"detailColumn\";\n\n    private static final String[] METADATA_CONTACTS_NAMES = new String[]{\n        \"android.provider.ALTERNATE_CONTACTS_STRUCTURE\",\n        \"android.provider.CONTACTS_STRUCTURE\"\n    };\n    private static final String TAG = \"MTCache\";\n\n    // Cached componentName\n    private final Map<String, ComponentName> componentNames = new HashMap<>();\n    // Cached label\n    private final Map<String, String> labels = new HashMap<>();\n    // Cached detail columns\n    private Map<String, String> mDetailColumnsCache = null;\n\n    public synchronized void clearCache() {\n        this.componentNames.clear();\n        this.labels.clear();\n        this.mDetailColumnsCache = null;\n    }\n\n    /**\n     * @param context  so we can get the label\n     * @param mimeType to look for\n     * @return label for best matching app by mimetype\n     */\n    public String getLabel(Context context, String mimeType) {\n        if (labels.containsKey(mimeType)) {\n            return labels.get(mimeType);\n        }\n\n        final Intent intent = MimeTypeUtils.getIntentByMimeType(mimeType, -1, \"\");\n        String label = PackageManagerUtils.getLabel(context, intent);\n        labels.put(mimeType, label);\n        return label;\n    }\n\n    public ComponentName getComponentName(Context context, String mimeType) {\n        if (componentNames.containsKey(mimeType)) {\n            return componentNames.get(mimeType);\n        }\n\n        final Intent intent = MimeTypeUtils.getIntentByMimeType(mimeType, -1, \"\");\n        ComponentName componentName = PackageManagerUtils.getComponentName(context, intent);\n        this.componentNames.put(mimeType, componentName);\n\n        return componentName;\n    }\n\n    /**\n     * @param context to get the Account system service and PackageManager\n     * @return all mime types and related data columns from contact sync adapters\n     */\n    public Map<String, String> fetchDetailColumns(Context context) {\n        synchronized (this) {\n            if (mDetailColumnsCache != null)\n                return mDetailColumnsCache;\n        }\n        Timer timer = Timer.startNano();\n\n        HashMap<String, String> detailColumns = new HashMap<>();\n        // add data columns for known mime types\n        detailColumns.put(ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE, ContactsContract.CommonDataKinds.Email.ADDRESS);\n        detailColumns.put(ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE, ContactsContract.CommonDataKinds.Phone.NUMBER);\n\n        final Set<String> contactSyncableTypes = new HashSet<>();\n\n        SyncAdapterType[] syncAdapterTypes = ContentResolver.getSyncAdapterTypes();\n        for (SyncAdapterType type : syncAdapterTypes) {\n            if (type.authority.equals(ContactsContract.AUTHORITY)) {\n                contactSyncableTypes.add(type.accountType);\n            }\n        }\n\n        AuthenticatorDescription[] authenticatorDescriptions = ((AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE)).getAuthenticatorTypes();\n        for (AuthenticatorDescription auth : authenticatorDescriptions) {\n            if (contactSyncableTypes.contains(auth.type)) {\n                XmlResourceParser parser = loadContactsXml(context, auth.packageName);\n                if (parser != null) {\n                    try {\n                        while (parser.next() != XmlPullParser.END_DOCUMENT) {\n                            if (CONTACTS_DATA_KIND.equals(parser.getName())) {\n                                String foundMimeType = null;\n                                String foundDetailColumn = null;\n                                int attributeCount = parser.getAttributeCount();\n                                for (int i = 0; i < attributeCount; i++) {\n                                    String attr = parser.getAttributeName(i);\n                                    String value = parser.getAttributeValue(i);\n                                    if (CONTACT_ATTR_MIME_TYPE.equals(attr)) {\n                                        foundMimeType = value;\n                                    } else if (CONTACT_ATTR_DETAIL_COLUMN.equals(attr)) {\n                                        foundDetailColumn = value;\n                                    }\n                                }\n                                if (foundMimeType != null) {\n                                    detailColumns.put(foundMimeType, foundDetailColumn);\n                                }\n                            }\n                        }\n                    } catch (IOException | XmlPullParserException e) {\n                        Log.w(TAG, \"type=\" + auth.type + \" package=\" + auth.packageName, e);\n                    }\n                }\n            }\n        }\n\n        Log.i(\"time\", timer + \" to fetch detail data columns\");\n        synchronized (this) {\n            return mDetailColumnsCache = detailColumns;\n        }\n    }\n\n    /**\n     * Loads contact description from other sync providers, search for ContactsAccountType or ContactsSource\n     * detailed description can be found here https://developer.android.com/guide/topics/providers/contacts-provider\n     *\n     * @param context     to get the PackageManager\n     * @param packageName AuthenticatorDescription.packageName\n     * @return XmlResourceParser for contacts.xml, null if nothing found\n     */\n    @SuppressLint(\"WrongConstant\")\n    public XmlResourceParser loadContactsXml(Context context, String packageName) {\n        final PackageManager pm = context.getPackageManager();\n        final Intent intent = new Intent(\"android.content.SyncAdapter\").setPackage(packageName);\n        final List<ResolveInfo> intentServices = pm.queryIntentServices(intent,\n            PackageManager.GET_META_DATA | PackageManager.GET_SERVICES);\n\n        if (intentServices != null) {\n            for (final ResolveInfo resolveInfo : intentServices) {\n                final ServiceInfo serviceInfo = resolveInfo.serviceInfo;\n                if (serviceInfo == null) {\n                    continue;\n                }\n                for (String metadataName : METADATA_CONTACTS_NAMES) {\n                    final XmlResourceParser parser = serviceInfo.loadXmlMetaData(\n                        pm, metadataName);\n                    if (parser != null) {\n                        return parser;\n                    }\n                }\n            }\n        }\n        return null;\n    }\n\n    /**\n     * @param context\n     * @param mimeType\n     * @return related detail data column for mime type\n     */\n    public String getDetailColumn(Context context, String mimeType) {\n        Map<String, String> detailColumns = fetchDetailColumns(context);\n        return detailColumns.get(mimeType);\n    }\n\n    private static String greatestCommonPrefix(@NonNull String a, @NonNull String b) {\n        int minLength = Math.min(a.length(), b.length());\n        for (int i = 0; i < minLength; i++) {\n            if (a.charAt(i) != b.charAt(i)) {\n                return a.substring(0, i);\n            }\n        }\n        return a.substring(0, minLength);\n    }\n\n    /**\n     * Generates unique labels for given mime types, appends mimeType itself if an app supports multiple mime types\n     *\n     * @param context\n     * @param mimeTypes\n     * @return labels for given mime types\n     */\n    public Map<String, String> getUniqueLabels(Context context, Set<String> mimeTypes) {\n        Map<String, String> uniqueLabels = new HashMap<>(mimeTypes.size());\n\n        // get labels for mime types\n        Map<String, Set<String>> mappedMimeTypes = new HashMap<>();\n        for (String mimeType : mimeTypes) {\n            String label = getLabel(context, mimeType);\n            Set<String> mimeTypesPerLabel = mappedMimeTypes.get(label);\n            if (mimeTypesPerLabel == null) {\n                mimeTypesPerLabel = new ArraySet<>();\n                mappedMimeTypes.put(label, mimeTypesPerLabel);\n            }\n            mimeTypesPerLabel.add(mimeType);\n        }\n\n        int layoutDirection = context.getResources().getConfiguration().getLayoutDirection();\n\n        // check supported mime types and make labels unique\n        for (String mimeType : mimeTypes) {\n            String label = getLabel(context, mimeType);\n            Set<String> mimeTypesPerLabel = mappedMimeTypes.get(label);\n            if (mimeTypesPerLabel != null && mimeTypesPerLabel.size() > 1) {\n                String prefix = null;\n                for (String labelMimeType : mimeTypesPerLabel) {\n                    if (labelMimeType != null) {\n                        if (prefix == null) {\n                            prefix = labelMimeType;\n                        } else {\n                            prefix = greatestCommonPrefix(prefix, labelMimeType);\n                        }\n                    }\n                }\n                if (prefix != null) {\n                    // assume dot separated words\n                    int pos = prefix.lastIndexOf('.');\n                    if (pos == -1) {\n                        // no dot found, remove whole prefix\n                        pos = prefix.length();\n                    } else {\n                        // remove words before the dot\n                        pos += 1;\n                    }\n                    label = Utilities.appendString(label, \" \", \"(\" + mimeType.substring(pos) + \")\", layoutDirection);\n                } else {\n                    // no prefix !?\n                    label = Utilities.appendString(label, \" \", \"(\" + MimeTypeUtils.getShortMimeType(mimeType) + \")\", layoutDirection);\n                }\n            }\n            uniqueLabels.put(mimeType, label);\n        }\n\n        return uniqueLabels;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/Permission.java",
    "content": "package rocks.tbog.tblauncher;\n\nimport android.Manifest;\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.pm.PackageManager;\nimport android.os.Build;\n\nimport androidx.annotation.NonNull;\n\nimport java.lang.ref.WeakReference;\nimport java.util.ArrayList;\nimport java.util.ListIterator;\n\n\npublic class Permission {\n    public static final int PERMISSION_READ_CONTACTS = 0;\n    public static final int PERMISSION_CALL_PHONE = 1;\n    public static final int PERMISSION_READ_PHONE_STATE = 2;\n\n    private static final String[] permissions = {\n            Manifest.permission.READ_CONTACTS,\n            Manifest.permission.CALL_PHONE,\n            Manifest.permission.READ_PHONE_STATE,\n    };\n\n    // Static weak reference to the linked activity, this is sadly required\n    // to ensure classes requesting permission can access activity.requestPermission()\n    private static WeakReference<Activity> currentActivity = new WeakReference<>(null);\n\n    private static ArrayList<PermissionResultListener> permissionListeners = null;\n\n    public static boolean checkPermission(Context context, int permission) {\n        return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || context.checkSelfPermission(permissions[permission]) == PackageManager.PERMISSION_GRANTED;\n    }\n\n    public static void askPermission(int permission, PermissionResultListener listener) {\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {\n            return;\n        }\n        if (listener != null) {\n            listener.permission = permission;\n            if (permissionListeners == null) {\n                permissionListeners = new ArrayList<>();\n            }\n            permissionListeners.add(listener);\n        }\n\n        Activity activity = Permission.currentActivity.get();\n        if (activity != null) {\n            activity.requestPermissions(new String[]{permissions[permission]}, permission);\n        }\n    }\n\n    public Permission(Activity activity) {\n        // Store the latest reference to a MainActivity\n        currentActivity = new WeakReference<>(activity);\n    }\n\n    public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {\n        if (grantResults.length == 0) {\n            return;\n        }\n\n        if (permissionListeners != null) {\n            // Iterator allows to remove while iterating\n            ListIterator<PermissionResultListener> it = permissionListeners.listIterator();\n            PermissionResultListener permissionListener;\n            while (it.hasNext()) {\n                permissionListener = it.next();\n                if (permissionListener.permission == requestCode) {\n                    if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {\n                        permissionListener.onGranted();\n                    } else {\n                        permissionListener.onDenied();\n                    }\n                    it.remove();\n                }\n            }\n        }\n    }\n\n    public static class PermissionResultListener {\n        public int permission = 0;\n\n        public void onGranted() {\n        }\n\n        public void onDenied() {\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/PermissionsManager.java",
    "content": "package rocks.tbog.tblauncher;\n\nimport androidx.annotation.NonNull;\nimport androidx.appcompat.app.AppCompatActivity;\n\npublic interface PermissionsManager {\n    enum PermissionGroup {\n        Calendar,\n        Location,\n        Contacts,\n        ExternalStorage,\n        Notifications,\n        AppShortcuts,\n    }\n\n\n    void requestPermission(AppCompatActivity context, PermissionGroup permissionGroup);\n\n    /**\n     * Check if this permission is granted right now without receiving further updates\n     * about the granted state.\n     * @return true if the given permission group is fully granted\n     */\n    Boolean checkPermissionOnce(PermissionGroup permissionGroup);\n\n    void onRequestPermissionsResult(\n        int requestCode,\n        @NonNull String[] permissions,\n        @NonNull int[] grantResults\n    );\n\n    void onResume();\n\n    Boolean hasPermission(PermissionGroup permissionGroup);\n\n    /**\n     * Special function for the Notification listener to report its status.\n     * May not be called by anything else.\n     */\n    void reportNotificationListenerState(Boolean running);\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/PinShortcutConfirm.java",
    "content": "/*\n * Copyright (C) 2016 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 */\npackage rocks.tbog.tblauncher;\n\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.SharedPreferences;\nimport android.content.pm.LauncherApps;\nimport android.content.pm.ShortcutInfo;\nimport android.graphics.drawable.Drawable;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.text.Html;\nimport android.util.Log;\nimport android.view.View;\nimport android.view.View.OnClickListener;\nimport android.view.Window;\nimport android.view.WindowManager;\nimport android.widget.EditText;\nimport android.widget.ImageView;\nimport android.widget.TextView;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.RequiresApi;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.preference.PreferenceManager;\n\nimport rocks.tbog.tblauncher.db.ShortcutRecord;\nimport rocks.tbog.tblauncher.entry.EntryItem;\nimport rocks.tbog.tblauncher.entry.ShortcutEntry;\nimport rocks.tbog.tblauncher.shortcut.ShortcutUtil;\nimport rocks.tbog.tblauncher.utils.DebugInfo;\nimport rocks.tbog.tblauncher.utils.Utilities;\n\n@RequiresApi(api = Build.VERSION_CODES.O)\npublic class PinShortcutConfirm extends AppCompatActivity implements OnClickListener {\n    private static final String TAG = \"ShortcutConfirm\";\n\n    protected LauncherApps mLauncherApps;\n    private EditText mShortcutName;\n    private LauncherApps.PinItemRequest mRequest;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);\n\n        requestWindowFeature(Window.FEATURE_NO_TITLE);\n        Window window = getWindow();\n        if (window != null) {\n            window.setDimAmount(0.7f);\n            window.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);\n        }\n        setContentView(R.layout.pin_shortcut_confirm);\n\n        mLauncherApps = getSystemService(LauncherApps.class);\n        mRequest = mLauncherApps.getPinItemRequest(getIntent());\n        final ShortcutInfo shortcutInfo = mRequest.getShortcutInfo();\n        if (shortcutInfo == null) {\n            Log.e(TAG, \"No shortcut info provided\");\n            finish();\n            return;\n        }\n\n        if (prefs.getBoolean(\"pin-auto-confirm\", false)) {\n            acceptShortcut();\n            finish();\n            return;\n        }\n\n        initViews(shortcutInfo);\n    }\n\n    private void initViews(@NonNull ShortcutInfo shortcutInfo) {\n        // OK button\n        {\n            TextView button1 = findViewById(android.R.id.button1);\n            button1.setOnClickListener(this);\n            button1.setText(android.R.string.ok);\n        }\n        // Cancel button\n        {\n            TextView button2 = findViewById(android.R.id.button2);\n            button2.setOnClickListener(this);\n            button2.setText(android.R.string.cancel);\n        }\n        // Other button\n        {\n            TextView button3 = findViewById(android.R.id.button3);\n            button3.setVisibility(View.GONE);\n            View spacer = findViewById(R.id.spacer);\n            if (spacer != null)\n                spacer.setVisibility(View.GONE);\n        }\n\n        // Label\n        {\n            mShortcutName = findViewById(R.id.shortcutName);\n            String packageName = packageNameHeuristic(this, shortcutInfo);\n            String appName = ShortcutUtil.getAppNameFromPackageName(this, packageName);\n            CharSequence label = shortcutInfo.getLongLabel();\n            if (label == null)\n                label = shortcutInfo.getShortLabel();\n            if (label == null)\n                label = shortcutInfo.getPackage();\n            if (!appName.isEmpty())\n                mShortcutName.setText(getString(R.string.shortcut_with_appName, appName, label));\n            else\n                mShortcutName.setText(label);\n        }\n\n        // Description\n        if (DebugInfo.widgetAdd(this)) {\n            TextView description = findViewById(R.id.shortcutDetails);\n            ComponentName activity = shortcutInfo.getActivity();\n            String htmlString = String.format(\n                \"<h1>Shortcut details:</h1>\" +\n                    \"<b>Long label</b>: %s<br>\" +\n                    \"<b>Short label</b>: %s<br>\" +\n                    \"<b>Activity</b>: %s<br>\" +\n                    \"<b>Publisher</b>: %s<br>\" +\n                    \"<b>ID</b>: %s\",\n\n                shortcutInfo.getLongLabel(),\n                shortcutInfo.getShortLabel(),\n                activity != null ? activity.flattenToShortString() : null,\n                shortcutInfo.getPackage(), // publisher app package\n                shortcutInfo.getId()\n            );\n            description.setText(Html.fromHtml(htmlString, Html.FROM_HTML_MODE_COMPACT));\n            description.setVisibility(View.VISIBLE);\n        } else {\n            findViewById(R.id.shortcutDetails).setVisibility(View.GONE);\n        }\n\n        {\n            View view = findViewById(R.id.image);\n            TextView nameView = view.findViewById(android.R.id.text1);\n            nameView.setVisibility(View.GONE);\n            ImageView icon1 = view.findViewById(android.R.id.icon1);\n            setIconsAsync(icon1, shortcutInfo, (ctx) -> mLauncherApps.getShortcutIconDrawable(shortcutInfo, 0));\n        }\n\n        {\n            View view = findViewById(R.id.imageWithBadge);\n            TextView nameView = view.findViewById(android.R.id.text1);\n            nameView.setVisibility(View.GONE);\n            ImageView icon1 = view.findViewById(android.R.id.icon1);\n            setIconsAsync(icon1, shortcutInfo, (ctx) -> mLauncherApps.getShortcutBadgedIconDrawable(shortcutInfo, 0));\n        }\n    }\n\n\n    private static void setIconsAsync(ImageView icon, ShortcutInfo shortcutInfo, Utilities.GetDrawable getIcon) {\n        new Utilities.AsyncSetDrawable(icon) {\n            Drawable appDrawable;\n\n            @Override\n            protected Drawable getDrawable(Context context) {\n                appDrawable = ShortcutEntry.getAppDrawable(context, shortcutInfo.getId(), shortcutInfo.getPackage(), shortcutInfo, true);\n                return getIcon.getDrawable(context);\n            }\n\n            @Override\n            protected void onPostExecute(Drawable drawable) {\n                ImageView icon1 = (ImageView) weakView.get();\n                super.onPostExecute(drawable);\n                if (icon1 != null) {\n                    int drawFlags = EntryItem.FLAG_DRAW_ICON | EntryItem.FLAG_DRAW_ICON_BADGE;\n                    ShortcutEntry.setIcons(drawFlags, icon1, drawable, appDrawable);\n                }\n            }\n        }.execute();\n    }\n\n    @NonNull\n    public static String packageNameHeuristic(@NonNull Context context, @NonNull ShortcutInfo shortcutInfo) {\n        Intent intent = shortcutInfo.getIntent();\n        ComponentName activity = intent != null ? intent.getComponent() : null;\n        String packageName;\n        if (activity == null) {\n            // try to parse the ID to get the package name\n            String id = shortcutInfo.getId();\n            int schemePos = id.indexOf(\"://\");\n            int startPos = schemePos >= 0 ? schemePos + 3 : 0;\n            int endPos = id.indexOf(\"/\", startPos);\n            if (endPos == -1)\n                endPos = id.indexOf(\"#\", startPos);\n            if (endPos == -1)\n                endPos = id.length();\n            packageName = id.substring(startPos, endPos);\n            String appName = ShortcutUtil.getAppNameFromPackageName(context, packageName);\n            if (appName.isEmpty())\n                packageName = null;\n            if (packageName == null) {\n                if (shortcutInfo.getActivity() != null)\n                    packageName = shortcutInfo.getActivity().getPackageName();\n                else\n                    packageName = shortcutInfo.getPackage();\n            }\n        } else {\n            packageName = activity.getPackageName();\n        }\n        return packageName;\n    }\n\n    @Override\n    public void onClick(View v) {\n        switch (v.getId()) {\n            case android.R.id.button1:\n                acceptShortcut();\n                finish();\n                break;\n\n            case android.R.id.button2:\n                finish();\n                break;\n        }\n    }\n\n    private void acceptShortcut() {\n        final ShortcutInfo shortcutInfo = mRequest.getShortcutInfo();\n        if (shortcutInfo == null) {\n            Log.e(TAG, \"shortcut info is null\");\n            return;\n        }\n        final boolean result = mRequest.accept();\n        Log.i(TAG, \"Accept returned: \" + result);\n        ShortcutRecord record = ShortcutUtil.createShortcutRecord(this, shortcutInfo, false);\n        if (record != null) {\n            if (mShortcutName.getText().length() > 0)\n                record.displayName = mShortcutName.getText().toString();\n            TBApplication.getApplication(this).getDataHandler().addShortcut(record);\n        }\n        mRequest = null;\n    }\n}"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/SettingsActivity.java",
    "content": "package rocks.tbog.tblauncher;\n\nimport android.annotation.SuppressLint;\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.SharedPreferences;\nimport android.graphics.PorterDuff;\nimport android.graphics.PorterDuffColorFilter;\nimport android.graphics.drawable.Drawable;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.text.Spannable;\nimport android.text.SpannableString;\nimport android.text.Spanned;\nimport android.text.style.ForegroundColorSpan;\nimport android.util.Log;\nimport android.util.Pair;\nimport android.view.Menu;\nimport android.view.MenuItem;\nimport android.view.View;\nimport android.widget.Toast;\n\nimport androidx.annotation.LayoutRes;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.appcompat.app.ActionBar;\nimport androidx.appcompat.app.AlertDialog;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.appcompat.content.res.AppCompatResources;\nimport androidx.collection.ArraySet;\nimport androidx.core.graphics.drawable.DrawableCompat;\nimport androidx.fragment.app.DialogFragment;\nimport androidx.fragment.app.Fragment;\nimport androidx.fragment.app.FragmentManager;\nimport androidx.fragment.app.FragmentTransaction;\nimport androidx.preference.ListPreference;\nimport androidx.preference.MultiSelectListPreference;\nimport androidx.preference.Preference;\nimport androidx.preference.PreferenceFragmentCompat;\nimport androidx.preference.PreferenceGroup;\nimport androidx.preference.PreferenceManager;\nimport androidx.preference.PreferenceScreen;\nimport androidx.preference.SwitchPreference;\n\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport rocks.tbog.tblauncher.dataprovider.ShortcutsProvider;\nimport rocks.tbog.tblauncher.dataprovider.TagsProvider;\nimport rocks.tbog.tblauncher.db.ExportedData;\nimport rocks.tbog.tblauncher.db.XmlImport;\nimport rocks.tbog.tblauncher.drawable.SizeWrappedDrawable;\nimport rocks.tbog.tblauncher.entry.AppEntry;\nimport rocks.tbog.tblauncher.entry.EntryItem;\nimport rocks.tbog.tblauncher.entry.StaticEntry;\nimport rocks.tbog.tblauncher.entry.TagEntry;\nimport rocks.tbog.tblauncher.handler.IconsHandler;\nimport rocks.tbog.tblauncher.preference.BaseListPreferenceDialog;\nimport rocks.tbog.tblauncher.preference.BaseMultiSelectListPreferenceDialog;\nimport rocks.tbog.tblauncher.preference.ConfirmDialog;\nimport rocks.tbog.tblauncher.preference.ContentLoadHelper;\nimport rocks.tbog.tblauncher.preference.CustomDialogPreference;\nimport rocks.tbog.tblauncher.preference.EditSearchEnginesPreferenceDialog;\nimport rocks.tbog.tblauncher.preference.EditSearchHintPreferenceDialog;\nimport rocks.tbog.tblauncher.preference.IconListPreferenceDialog;\nimport rocks.tbog.tblauncher.preference.MarginDialog;\nimport rocks.tbog.tblauncher.preference.OrderListPreferenceDialog;\nimport rocks.tbog.tblauncher.preference.PreferenceColorDialog;\nimport rocks.tbog.tblauncher.preference.QuickListPreferenceDialog;\nimport rocks.tbog.tblauncher.preference.ShadowDialog;\nimport rocks.tbog.tblauncher.preference.SliderDialog;\nimport rocks.tbog.tblauncher.preference.TagOrderListPreferenceDialog;\nimport rocks.tbog.tblauncher.ui.dialog.PleaseWaitDialog;\nimport rocks.tbog.tblauncher.utils.FileUtils;\nimport rocks.tbog.tblauncher.utils.MimeTypeUtils;\nimport rocks.tbog.tblauncher.utils.PrefCache;\nimport rocks.tbog.tblauncher.utils.PrefOrderedListHelper;\nimport rocks.tbog.tblauncher.utils.SystemUiVisibility;\nimport rocks.tbog.tblauncher.utils.UIColors;\nimport rocks.tbog.tblauncher.utils.UISizes;\nimport rocks.tbog.tblauncher.utils.UITheme;\nimport rocks.tbog.tblauncher.utils.Utilities;\n\npublic class SettingsActivity extends AppCompatActivity implements PreferenceFragmentCompat.OnPreferenceStartScreenCallback/*, PreferenceFragmentCompat.OnPreferenceStartFragmentCallback*/ {\n\n    private final static String INTENT_EXTRA_BACK_STACK_TAGS = \"backStackTagList\";\n\n    private final static ArraySet<String> PREF_THAT_REQUIRE_LAYOUT_UPDATE = new ArraySet<>(Arrays.asList(\n        \"result-list-argb\", \"result-ripple-color\", \"result-list-radius\", \"result-list-row-height\",\n        \"notification-bar-argb\", \"notification-bar-gradient\", \"black-notification-icons\",\n        \"navigation-bar-argb\",\n        \"search-bar-height\", \"search-bar-text-size\", \"search-bar-radius\", \"search-bar-gradient\", \"search-bar-at-bottom\",\n        \"search-bar-argb\", \"search-bar-text-color\", \"search-bar-icon-color\",\n        \"search-bar-ripple-color\", \"search-bar-cursor-argb\", \"enable-suggestions-keyboard\",\n        \"lock-portrait\", \"sensor-orientation\",\n        \"search-bar-layout\", \"quick-list-position\"\n    ));\n    private final static ArraySet<String> PREF_LISTS_WITH_DEPENDENCY = new ArraySet<>(Arrays.asList(\n        \"gesture-click\",\n        \"gesture-double-click\",\n        \"gesture-fling-down-left\",\n        \"gesture-fling-down-right\",\n        \"gesture-fling-up\",\n        \"gesture-fling-left\",\n        \"gesture-fling-right\",\n        \"button-launcher\",\n        \"button-home\",\n        \"dm-empty-back\",\n        \"dm-search-back\",\n        \"dm-widget-back\",\n        \"dm-search-open-result\"\n    ));\n\n    private static final int FILE_SELECT_XML_SET = 63;\n    private static final int FILE_SELECT_XML_OVERWRITE = 62;\n    private static final int FILE_SELECT_XML_APPEND = 61;\n    public static final int ENABLE_DEVICE_ADMIN = 60;\n    private static final String TAG = \"SettAct\";\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        int theme = UITheme.getSettingsTheme(this);\n        if (theme != UITheme.ID_NULL)\n            setTheme(theme);\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_settings);\n\n        if (savedInstanceState == null) {\n            // Create the fragment only when the activity is created for the first time.\n            // ie. not after orientation changes\n            Fragment fragment = getSupportFragmentManager().findFragmentByTag(SettingsFragment.FRAGMENT_TAG);\n            if (fragment == null) {\n                fragment = new SettingsFragment();\n            }\n\n            getSupportFragmentManager()\n                .beginTransaction()\n                .replace(R.id.settings_container, fragment, SettingsFragment.FRAGMENT_TAG)\n                .commit();\n\n            restoreBackStack();\n        }\n\n        ActionBar actionBar = getSupportActionBar();\n        if (actionBar != null) {\n            actionBar.setDisplayHomeAsUpEnabled(true);\n        }\n    }\n\n    private void restoreBackStack() {\n        Intent intent = getIntent();\n        if (intent == null)\n            return;\n        ArrayList<String> backStackEntryList = intent.getStringArrayListExtra(INTENT_EXTRA_BACK_STACK_TAGS);\n        if (backStackEntryList != null)\n            for (String key : backStackEntryList)\n                if (key != null)\n                    addToBackStack(key);\n    }\n\n    private void addToBackStack(@NonNull String key) {\n        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();\n        SettingsFragment fragment = new SettingsFragment();\n        Bundle args = new Bundle();\n        args.putString(PreferenceFragmentCompat.ARG_PREFERENCE_ROOT, key);\n        fragment.setArguments(args);\n        ft.replace(R.id.settings_container, fragment, key);\n        ft.addToBackStack(key);\n        ft.commit();\n    }\n\n    @Override\n    public boolean onCreateOptionsMenu(Menu menu) {\n        String[] themeNames = getResources().getStringArray(R.array.settingsThemeEntries);\n        for (String name : themeNames)\n            menu.add(name);\n        return true;\n    }\n\n    @SuppressLint(\"ApplySharedPref\")\n    @Override\n    public boolean onOptionsItemSelected(MenuItem item) {\n        if (item.getTitle() != null) {\n            String itemName = item.getTitle().toString();\n\n            String[] themeNames = getResources().getStringArray(R.array.settingsThemeEntries);\n            String[] themeValues = getResources().getStringArray(R.array.settingsThemeValues);\n\n            for (int themeIdx = 0; themeIdx < themeNames.length; themeIdx++) {\n                String name = themeNames[themeIdx];\n                if (itemName.equals(name)) {\n                    SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);\n                    sharedPreferences.edit().putString(\"settings-theme\", themeValues[themeIdx]).commit();\n                    restart();\n                    return true;\n                }\n            }\n        }\n        return super.onOptionsItemSelected(item);\n    }\n\n    private void restart() {\n        // save backstack\n        FragmentManager fm = getSupportFragmentManager();\n        int backStackEntryCount = fm.getBackStackEntryCount();\n        ArrayList<String> backStackTags = null;\n        if (backStackEntryCount > 0) {\n            backStackTags = new ArrayList<>(backStackEntryCount);\n            for (int idx = 0; idx < backStackEntryCount; idx += 1) {\n                FragmentManager.BackStackEntry entry = fm.getBackStackEntryAt(idx);\n                String tag = entry.getName();\n                backStackTags.add(tag);\n            }\n        }\n\n        // close current activity\n        finish();\n\n        // start new activity\n        Intent activityIntent = new Intent(this, getClass());\n        if (backStackTags != null) {\n            // remember the back stack pages so we can restore them\n            activityIntent.putStringArrayListExtra(INTENT_EXTRA_BACK_STACK_TAGS, backStackTags);\n        }\n        startActivity(activityIntent);\n\n        // set transition animation\n        overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);\n    }\n\n    @Override\n    protected void onTitleChanged(CharSequence title, int color) {\n        super.onTitleChanged(title, color);\n        ActionBar actionBar = getSupportActionBar();\n        if (actionBar != null) {\n            if (color != 0 && !(title instanceof Spannable)) {\n                SpannableString ss = new SpannableString(title);\n                ss.setSpan(new ForegroundColorSpan(color), 0, title.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);\n                actionBar.setTitle(ss);\n            } else {\n                actionBar.setTitle(title);\n            }\n        }\n    }\n\n    @Override\n    public boolean onSupportNavigateUp() {\n        if (getSupportFragmentManager().popBackStackImmediate()) {\n            final int count = getSupportFragmentManager().getBackStackEntryCount();\n            CharSequence title = null;\n            if (count > 0) {\n                String tag = getSupportFragmentManager().getBackStackEntryAt(count - 1).getName();\n                if (tag != null) {\n                    Fragment fragment = getSupportFragmentManager().findFragmentByTag(SettingsFragment.FRAGMENT_TAG);\n                    if (fragment instanceof SettingsFragment) {\n                        Preference preference = ((SettingsFragment) fragment).findPreference(tag);\n                        if (preference != null)\n                            title = preference.getTitle();\n                    }\n                }\n            }\n            if (title != null)\n                setTitle(title);\n            else\n                setTitle(R.string.menu_popup_launcher_settings);\n            return true;\n        }\n        return super.onSupportNavigateUp();\n    }\n\n    @Override\n    public boolean onPreferenceStartScreen(PreferenceFragmentCompat caller, PreferenceScreen preferenceScreen) {\n        final String key = preferenceScreen.getKey();\n        addToBackStack(key);\n        return true;\n    }\n\n    @Override\n    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {\n        super.onActivityResult(requestCode, resultCode, data);\n        Log.d(TAG, \"onActivityResult request=\" + requestCode + \" result=\" + resultCode);\n        if (requestCode == ENABLE_DEVICE_ADMIN) {\n            if (resultCode != RESULT_OK) {\n                Toast.makeText(this, \"Failed!\", Toast.LENGTH_SHORT).show();\n            }\n        } else if (resultCode == RESULT_OK) {\n            ExportedData.Method method = null;\n            switch (requestCode) {\n                case FILE_SELECT_XML_APPEND:\n                    method = ExportedData.Method.APPEND;\n                    break;\n                case FILE_SELECT_XML_OVERWRITE:\n                    method = ExportedData.Method.OVERWRITE;\n                    break;\n                case FILE_SELECT_XML_SET:\n                    method = ExportedData.Method.SET;\n                    break;\n            }\n            if (method != null) {\n                Uri uri = data != null ? data.getData() : null;\n                File importedFile = FileUtils.copyFile(this, uri, \"imported.xml\");\n                if (importedFile != null) {\n                    PleaseWaitDialog dialog = new PleaseWaitDialog();\n                    // set args\n                    {\n                        Bundle args = new Bundle();\n                        //args.putString(PleaseWaitDialog.ARG_TITLE, getString(R.string.import_dialog_title));\n                        args.putString(PleaseWaitDialog.ARG_DESCRIPTION, getString(R.string.import_dialog_description));\n                        dialog.setArguments(args);\n                    }\n                    final ExportedData.Method importMethod = method;\n                    dialog.setWork(() -> {\n                        Activity activity = Utilities.getActivity(dialog.getContext());\n                        if (activity != null) {\n                            if (!XmlImport.settingsXml(activity, importedFile, importMethod)) {\n                                Toast.makeText(activity, R.string.error_fail_import, Toast.LENGTH_LONG).show();\n                                dialog.dismiss();\n                            }\n                        }\n                        dialog.onWorkFinished();\n                    });\n                    dialog.show(getSupportFragmentManager(), \"load_imported\");\n                } else {\n                    Toast.makeText(this, R.string.error_fail_import, Toast.LENGTH_LONG).show();\n                }\n            }\n        }\n    }\n\n    public static class SettingsFragment extends PreferenceFragmentCompat implements SharedPreferences.OnSharedPreferenceChangeListener {\n        private static final String FRAGMENT_TAG = SettingsFragment.class.getName();\n        private static final String DIALOG_FRAGMENT_TAG = \"androidx.preference.PreferenceFragment.DIALOG\";\n        private static final String TAG = \"Settings\";\n\n        private static Pair<CharSequence[], CharSequence[]> AppToRunListContent = null;\n        private static Pair<CharSequence[], CharSequence[]> ShortcutToRunListContent = null;\n        private static Pair<CharSequence[], CharSequence[]> EntryToShowListContent = null;\n        private static ContentLoadHelper.OrderedMultiSelectListData TagsMenuContent = null;\n        private static ContentLoadHelper.OrderedMultiSelectListData ResultPopupContent = null;\n        private static Pair<CharSequence[], CharSequence[]> MimeTypeListContent = null;\n\n        public SettingsFragment() {\n            super();\n        }\n\n        @Override\n        public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {\n            if (rootKey != null && rootKey.startsWith(\"feature-\"))\n                setPreferencesFromResource(R.xml.preference_features, rootKey);\n            else\n                setPreferencesFromResource(R.xml.preferences, rootKey);\n\n            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {\n                removePreference(\"black-notification-icons\");\n            }\n            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {\n                removePreference(\"pin-auto-confirm\");\n            }\n            if (!BuildConfig.SHOW_RATE_APP) {\n                removePreference(\"rate-app\");\n            }\n            if (!BuildConfig.SHOW_PRIVACY_POLICY) {\n                removePreference(\"privacy-policy\");\n            }\n            if (!BuildConfig.DEBUG) {\n                removePreference(\"crash-app\");\n            }\n            // set app name and version\n            {\n                Preference appVer = findPreference(\"app-version\");\n                if (appVer != null) {\n                    var version = appVer.getContext().getString(R.string.app_version, BuildConfig.VERSION_NAME);\n                    var appName = appVer.getContext().getText(R.string.app_name);\n                    String appStore;\n                    switch (BuildConfig.FLAVOR) {\n                        case \"playstore\":\n                            appStore = \"Google Play\";\n                            break;\n                        case \"fdroid\":\n                            appStore = \"F-Droid\";\n                            break;\n                        case \"github\":\n                            appStore = \"GitHub\";\n                            break;\n                        default:\n                            throw new IllegalStateException(\"Undefined flavor\");\n                    }\n                    var summary = appVer.getContext().getString(R.string.app_version_summary, appName, appStore);\n                    appVer.setTitle(version);\n                    appVer.setSummary(summary);\n\n                    // add link to the launcher webpage if app not installed from a store\n                    if (!BuildConfig.SHOW_RATE_APP) {\n                        appVer.setEnabled(true);\n                    }\n                }\n            }\n\n            final Activity activity = requireActivity();\n\n            // set activity title as the preference screen title\n            activity.setTitle(getPreferenceScreen().getTitle());\n\n            ActionBar actionBar = ((SettingsActivity) activity).getSupportActionBar();\n            if (actionBar != null) {\n                // we can change the theme from the options menu\n                removePreference(\"settings-theme\");\n            }\n\n            setupButtonActions(activity);\n\n            final Context context = requireContext();\n\n            tintPreferenceIcons(getPreferenceScreen(), UIColors.getThemeColor(context, com.google.android.material.R.attr.colorAccent));\n\n            SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);\n\n            // quick-list\n            {\n                Preference pref = findPreference(\"quick-list-enabled\");\n                // if we don't have the toggle in this screen we need to apply dependency by hand\n                if (pref == null) {\n                    // only show the category if we use the quick list\n                    Preference section = findPreference(\"quick-list-section\");\n                    if (section != null)\n                        section.setVisible(sharedPreferences.getBoolean(\"quick-list-enabled\", true));\n                }\n            }\n\n            onCreateAsyncLoad(context, sharedPreferences, savedInstanceState);\n        }\n\n        private void setupButtonActions(@NonNull Activity activity) {\n            // import settings\n            {\n                Preference pref = findPreference(\"import-settings-set\");\n                if (pref != null)\n                    pref.setOnPreferenceClickListener(preference -> {\n                        FileUtils.chooseSettingsFile(activity, FILE_SELECT_XML_SET);\n                        return true;\n                    });\n                pref = findPreference(\"import-settings-overwrite\");\n                if (pref != null)\n                    pref.setOnPreferenceClickListener(preference -> {\n                        FileUtils.chooseSettingsFile(activity, FILE_SELECT_XML_OVERWRITE);\n                        return true;\n                    });\n                pref = findPreference(\"import-settings-append\");\n                if (pref != null)\n                    pref.setOnPreferenceClickListener(preference -> {\n                        FileUtils.chooseSettingsFile(activity, FILE_SELECT_XML_APPEND);\n                        return true;\n                    });\n            }\n        }\n\n        private void onCreateAsyncLoad(@NonNull Context context, @NonNull SharedPreferences sharedPreferences, @Nullable Bundle savedInstanceState) {\n            if (savedInstanceState == null) {\n                initAppToRunLists(context, sharedPreferences);\n                initShortcutToRunLists(context, sharedPreferences);\n                initEntryToShowLists(context, sharedPreferences);\n                initTagsMenuList(context, sharedPreferences);\n                initResultPopupList(context, sharedPreferences);\n                initMimeTypes(context);\n            } else {\n                synchronized (SettingsFragment.class) {\n                    if (AppToRunListContent == null)\n                        AppToRunListContent = generateAppToRunListContent(context);\n                    if (ShortcutToRunListContent == null)\n                        ShortcutToRunListContent = generateShortcutToRunListContent(context);\n                    if (EntryToShowListContent == null)\n                        EntryToShowListContent = generateEntryToShowListContent(context);\n                    if (TagsMenuContent == null)\n                        TagsMenuContent = ContentLoadHelper.generateTagsMenuContent(context, sharedPreferences);\n                    if (ResultPopupContent == null)\n                        ResultPopupContent = ContentLoadHelper.generateResultPopupContent(context, sharedPreferences);\n                    if (MimeTypeListContent == null)\n                        MimeTypeListContent = generateMimeTypeListContent(context);\n\n                    for (String gesturePref : PREF_LISTS_WITH_DEPENDENCY) {\n                        updateAppToRunList(sharedPreferences, gesturePref);\n                        updateShortcutToRunList(sharedPreferences, gesturePref);\n                        updateEntryToShowList(sharedPreferences, gesturePref);\n                    }\n                    TagsMenuContent.setMultiListValues(findPreference(\"tags-menu-list\"));\n                    TagsMenuContent.setOrderedListValues(findPreference(\"tags-menu-order\"));\n                    ResultPopupContent.setOrderedListValues(findPreference(\"result-popup-order\"));\n                    ContentLoadHelper.setMultiListValues(findPreference(\"selected-contact-mime-types\"), MimeTypeListContent, null);\n                }\n            }\n\n            final ListPreference iconsPack = findPreference(\"icons-pack\");\n            if (iconsPack != null) {\n                iconsPack.setEnabled(false);\n\n                if (savedInstanceState == null) {\n                    // Run asynchronously to open settings fast\n                    Utilities.runAsync(getLifecycle(),\n                        t -> SettingsFragment.this.setListPreferenceIconsPacksData(iconsPack),\n                        t -> iconsPack.setEnabled(true));\n                } else {\n                    // Run synchronously to ensure preferences can be restored from state\n                    SettingsFragment.this.setListPreferenceIconsPacksData(iconsPack);\n                    iconsPack.setEnabled(true);\n                }\n            }\n        }\n\n        private void initAppToRunLists(@NonNull Context context, @NonNull SharedPreferences sharedPreferences) {\n            final Runnable updateLists = () -> {\n                for (String gesturePref : PREF_LISTS_WITH_DEPENDENCY)\n                    updateAppToRunList(sharedPreferences, gesturePref);\n            };\n            if (AppToRunListContent == null) {\n                Utilities.runAsync(getLifecycle(), t -> {\n                    Pair<CharSequence[], CharSequence[]> content = generateAppToRunListContent(context);\n                    synchronized (SettingsFragment.class) {\n                        if (AppToRunListContent == null)\n                            AppToRunListContent = content;\n                    }\n                }, t -> updateLists.run());\n            } else {\n                updateLists.run();\n            }\n        }\n\n        private void initShortcutToRunLists(@NonNull Context context, @NonNull SharedPreferences sharedPreferences) {\n            final Runnable updateLists = () -> {\n                for (String gesturePref : PREF_LISTS_WITH_DEPENDENCY)\n                    updateShortcutToRunList(sharedPreferences, gesturePref);\n            };\n            if (ShortcutToRunListContent == null) {\n                Utilities.runAsync(getLifecycle(), t -> {\n                    Pair<CharSequence[], CharSequence[]> content = generateShortcutToRunListContent(context);\n                    synchronized (SettingsFragment.this) {\n                        if (ShortcutToRunListContent == null)\n                            ShortcutToRunListContent = content;\n                    }\n                }, t -> updateLists.run());\n            } else {\n                updateLists.run();\n            }\n        }\n\n        private void initEntryToShowLists(@NonNull Context context, @NonNull SharedPreferences sharedPreferences) {\n            final Runnable updateLists = () -> {\n                for (String gesturePref : PREF_LISTS_WITH_DEPENDENCY)\n                    updateEntryToShowList(sharedPreferences, gesturePref);\n            };\n            if (EntryToShowListContent == null) {\n                Utilities.runAsync(getLifecycle(), t -> {\n                    Pair<CharSequence[], CharSequence[]> content = generateEntryToShowListContent(context);\n                    synchronized (SettingsFragment.class) {\n                        if (EntryToShowListContent == null)\n                            EntryToShowListContent = content;\n                    }\n                }, t -> updateLists.run());\n            } else {\n                updateLists.run();\n            }\n        }\n\n        private void initTagsMenuList(@NonNull Context context, @NonNull SharedPreferences sharedPreferences) {\n            final Runnable setTagsMenuValues = () -> {\n                synchronized (SettingsFragment.class) {\n                    if (TagsMenuContent != null) {\n                        TagsMenuContent.setMultiListValues(findPreference(\"tags-menu-list\"));\n                        TagsMenuContent.setOrderedListValues(findPreference(\"tags-menu-order\"));\n                    }\n                }\n            };\n\n            if (TagsMenuContent == null) {\n                Utilities.runAsync(getLifecycle(), t -> {\n                    ContentLoadHelper.OrderedMultiSelectListData content = ContentLoadHelper.generateTagsMenuContent(context, sharedPreferences);\n                    synchronized (SettingsFragment.class) {\n                        if (TagsMenuContent == null) {\n                            TagsMenuContent = content;\n                        }\n                    }\n                }, t -> setTagsMenuValues.run());\n            } else {\n                setTagsMenuValues.run();\n            }\n        }\n\n        private void initResultPopupList(@NonNull Context context, @NonNull SharedPreferences sharedPreferences) {\n            final Runnable setResultPopupValues = () -> {\n                synchronized (SettingsFragment.class) {\n                    if (ResultPopupContent != null)\n                        ResultPopupContent.setOrderedListValues(findPreference(\"result-popup-order\"));\n                }\n            };\n\n            if (ResultPopupContent == null) {\n                Utilities.runAsync(getLifecycle(), t -> {\n                    ContentLoadHelper.OrderedMultiSelectListData content = ContentLoadHelper.generateResultPopupContent(context, sharedPreferences);\n                    synchronized (SettingsFragment.class) {\n                        if (ResultPopupContent == null) {\n                            ResultPopupContent = content;\n                        }\n                    }\n                }, t -> setResultPopupValues.run());\n            } else {\n                setResultPopupValues.run();\n            }\n        }\n\n        private void initMimeTypes(@NonNull Context context) {\n            // get all supported mime types\n            final Runnable setMimeTypeValues = () -> {\n                synchronized (SettingsFragment.class) {\n                    if (MimeTypeListContent != null)\n                        ContentLoadHelper.setMultiListValues(findPreference(\"selected-contact-mime-types\"), MimeTypeListContent, null);\n                }\n            };\n\n            if (MimeTypeListContent == null) {\n                Utilities.runAsync(getLifecycle(), t -> {\n                    Pair<CharSequence[], CharSequence[]> content = generateMimeTypeListContent(context);\n                    synchronized (SettingsFragment.class) {\n                        if (MimeTypeListContent == null)\n                            MimeTypeListContent = content;\n                    }\n                }, t -> setMimeTypeValues.run());\n            } else {\n                setMimeTypeValues.run();\n            }\n        }\n\n        private void tintPreferenceIcons(Preference preference, int color) {\n            Drawable icon = preference.getIcon();\n            if (icon != null) {\n                // workaround to set drawable size\n                {\n                    int size = UISizes.getResultIconSize(preference.getContext());\n                    icon = new SizeWrappedDrawable(icon, size);\n                }\n                icon.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY));\n                preference.setIcon(icon);\n            }\n            if (preference instanceof PreferenceGroup) {\n                PreferenceGroup group = ((PreferenceGroup) preference);\n                for (int i = 0; i < group.getPreferenceCount(); i++) {\n                    tintPreferenceIcons(group.getPreference(i), color);\n                }\n            }\n        }\n\n        private void removePreference(String key) {\n            Preference pref = findPreference(key);\n            if (pref != null && pref.getParent() != null)\n                pref.getParent().removePreference(pref);\n        }\n\n        private void setListPreferenceIconsPacksData(ListPreference lp) {\n            Context context = getContext();\n            if (context == null)\n                return;\n            IconsHandler iph = TBApplication.getApplication(context).iconsHandler();\n\n            CharSequence[] entries = new CharSequence[iph.getIconPackNames().size() + 1];\n            CharSequence[] entryValues = new CharSequence[iph.getIconPackNames().size() + 1];\n\n            int i = 0;\n            entries[0] = this.getString(R.string.icons_pack_default_name);\n            entryValues[0] = \"default\";\n            for (String packageIconsPack : iph.getIconPackNames().keySet()) {\n                entries[++i] = iph.getIconPackNames().get(packageIconsPack);\n                entryValues[i] = packageIconsPack;\n            }\n\n            lp.setEntries(entries);\n            lp.setDefaultValue(\"default\");\n            lp.setEntryValues(entryValues);\n        }\n\n        @Override\n        public void onResume() {\n            super.onResume();\n            SharedPreferences sharedPreferences = getPreferenceScreen().getSharedPreferences();\n            sharedPreferences.registerOnSharedPreferenceChangeListener(this);\n\n            applyNotificationBarColor(sharedPreferences, requireContext());\n        }\n\n        @Override\n        public void onPause() {\n            super.onPause();\n            getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);\n        }\n\n        @Override\n        public void onDestroy() {\n            super.onDestroy();\n            synchronized (SettingsFragment.class) {\n                AppToRunListContent = null;\n                ShortcutToRunListContent = null;\n                EntryToShowListContent = null;\n                TagsMenuContent = null;\n                ResultPopupContent = null;\n            }\n        }\n\n        @Override\n        public void onDisplayPreferenceDialog(@NonNull Preference preference) {\n            String key = preference.getKey();\n            // Try if the preference is one of our custom Preferences\n            DialogFragment dialogFragment;\n            if (preference instanceof CustomDialogPreference) {\n                // Create a new instance of CustomDialog with the key of the related Preference\n                Log.d(TAG, \"onDisplayPreferenceDialog \" + key);\n                switch (key) {\n                    case \"quick-list-content\":\n                        dialogFragment = QuickListPreferenceDialog.newInstance(key);\n                        break;\n                    case \"reset-search-engines\":\n                    case \"edit-search-engines\":\n                    case \"add-search-engine\":\n                        dialogFragment = EditSearchEnginesPreferenceDialog.newInstance(key);\n                        break;\n                    case \"reset-search-hint\":\n                    case \"edit-search-hint\":\n                    case \"add-search-hint\":\n                        dialogFragment = EditSearchHintPreferenceDialog.newInstance(key);\n                        break;\n                    default:\n                        dialogFragment = null;\n                }\n                if (dialogFragment == null) {\n                    @LayoutRes\n                    int dialogLayout = ((CustomDialogPreference) preference).getDialogLayoutResource();\n                    if (dialogLayout == 0) {\n                        if (key.endsWith(\"-color\") || key.endsWith(\"-argb\"))\n                            dialogFragment = PreferenceColorDialog.newInstance(key);\n                    } else if (R.layout.pref_slider == dialogLayout) {\n                        dialogFragment = SliderDialog.newInstance(key);\n                    } else if (R.layout.pref_shadow == dialogLayout) {\n                        dialogFragment = ShadowDialog.newInstance(key);\n                    } else if (R.layout.pref_margin_offset == dialogLayout) {\n                        dialogFragment = MarginDialog.newInstance(key);\n                    } else if (R.layout.pref_confirm == dialogLayout) {\n                        dialogFragment = ConfirmDialog.newInstance(key);\n                    }\n                }\n                if (dialogFragment == null)\n                    throw new IllegalArgumentException(\"CustomDialogPreference \\\"\" + key + \"\\\" has no dialog defined\");\n            } else if (preference instanceof ListPreference) {\n                switch (key) {\n                    case \"adaptive-shape\":\n                    case \"contacts-shape\":\n                    case \"shortcut-shape\":\n                    case \"icons-pack\":\n                        dialogFragment = IconListPreferenceDialog.newInstance(key);\n                        break;\n                    default:\n                        dialogFragment = BaseListPreferenceDialog.newInstance(key);\n                        break;\n                }\n            } else if (preference instanceof MultiSelectListPreference) {\n                if (\"tags-menu-order\".equals(key)) {\n                    dialogFragment = TagOrderListPreferenceDialog.newInstance(key);\n                } else if (\"result-popup-order\".equals(key)) {\n                    dialogFragment = OrderListPreferenceDialog.newInstance(key);\n                } else {\n                    dialogFragment = BaseMultiSelectListPreferenceDialog.newInstance(key);\n                }\n            } else {\n                Log.i(TAG, \"Preference \\\"\" + key + \"\\\" has no custom dialog defined\");\n                dialogFragment = null;\n            }\n\n            // If it was one of our custom Preferences, show its dialog\n            if (dialogFragment != null) {\n                final FragmentManager fm = this.getParentFragmentManager();\n                // check if dialog is already showing\n                if (fm.findFragmentByTag(DIALOG_FRAGMENT_TAG) != null) {\n                    return;\n                }\n                dialogFragment.setTargetFragment(this, 0);\n                dialogFragment.show(fm, DIALOG_FRAGMENT_TAG);\n            }\n            // Could not be handled here. Try with the super method.\n            else {\n                super.onDisplayPreferenceDialog(preference);\n            }\n        }\n\n        private static Pair<CharSequence[], CharSequence[]> generateAppToRunListContent(@NonNull Context context) {\n            List<AppEntry> appEntryList = TBApplication.appsHandler(context).getApplications();\n            Collections.sort(appEntryList, AppEntry.NAME_COMPARATOR);\n            final int appCount = appEntryList.size();\n            CharSequence[] entries = new CharSequence[appCount];\n            CharSequence[] entryValues = new CharSequence[appCount];\n            for (int idx = 0; idx < appCount; idx++) {\n                AppEntry appEntry = appEntryList.get(idx);\n                entries[idx] = appEntry.getName();\n                entryValues[idx] = appEntry.getUserComponentName();\n            }\n            return new Pair<>(entries, entryValues);\n        }\n\n        private static Pair<CharSequence[], CharSequence[]> generateShortcutToRunListContent(@NonNull Context context) {\n            ShortcutsProvider shortcutsProvider = TBApplication.dataHandler(context).getShortcutsProvider();\n            List<? extends EntryItem> shortcutList = shortcutsProvider == null ? null : shortcutsProvider.getPojos();\n            if (shortcutList == null)\n                return new Pair<>(new CharSequence[0], new CharSequence[0]);\n            // copy list in order to sort it\n            shortcutList = new ArrayList<>(shortcutList);\n            Collections.sort(shortcutList, EntryItem.NAME_COMPARATOR);\n            final int entryCount = shortcutList.size();\n            CharSequence[] entries = new CharSequence[entryCount];\n            CharSequence[] entryValues = new CharSequence[entryCount];\n            for (int idx = 0; idx < entryCount; idx++) {\n                EntryItem shortcutEntry = shortcutList.get(idx);\n                entries[idx] = shortcutEntry.getName();\n                entryValues[idx] = shortcutEntry.id;\n            }\n            return new Pair<>(entries, entryValues);\n        }\n\n        private static Pair<CharSequence[], CharSequence[]> generateEntryToShowListContent(@NonNull Context context) {\n            final List<StaticEntry> tagList;\n\n            final TBApplication app = TBApplication.getApplication(context);\n            final TagsProvider tagsProvider = app.getDataHandler().getTagsProvider();\n            if (tagsProvider != null) {\n                ArrayList<String> tagNames = new ArrayList<>(app.tagsHandler().getValidTags());\n                Collections.sort(tagNames);\n                tagList = new ArrayList<>(tagNames.size());\n                for (String tagName : tagNames) {\n                    TagEntry tagEntry = tagsProvider.getTagEntry(tagName);\n                    tagList.add(tagEntry);\n                }\n            } else {\n                tagList = Collections.emptyList();\n            }\n\n            final CharSequence[] entries;\n            final CharSequence[] entryValues;\n            if (tagList.isEmpty()) {\n                entries = new CharSequence[]{context.getString(R.string.no_tags)};\n                entryValues = new CharSequence[]{\"\"};\n            } else {\n                return ContentLoadHelper.generateStaticEntryList(context, tagList);\n            }\n            return new Pair<>(entries, entryValues);\n        }\n\n        private static Pair<CharSequence[], CharSequence[]> generateMimeTypeListContent(@NonNull Context context) {\n            Set<String> supportedMimeTypes = MimeTypeUtils.getSupportedMimeTypes(context);\n            Map<String, String> labels = TBApplication.mimeTypeCache(context).getUniqueLabels(context, supportedMimeTypes);\n\n            String[] mimeTypes = labels.keySet().toArray(new String[0]);\n            Arrays.sort(mimeTypes);\n\n            CharSequence[] mimeLabels = new CharSequence[mimeTypes.length];\n            for (int index = 0; index < mimeTypes.length; index += 1) {\n                mimeLabels[index] = labels.get(mimeTypes[index]);\n            }\n            return new Pair<>(mimeTypes, mimeLabels);\n        }\n\n        private void updateListPrefDependency(@NonNull String dependOnKey, @Nullable String dependOnValue, @NonNull String enableValue, @NonNull String listKey, @Nullable Pair<CharSequence[], CharSequence[]> listContent) {\n            Preference prefEntryToRun = findPreference(listKey);\n            if (prefEntryToRun instanceof ListPreference) {\n                synchronized (SettingsFragment.class) {\n                    if (listContent != null) {\n                        CharSequence[] entries = listContent.first;\n                        CharSequence[] entryValues = listContent.second;\n                        ((ListPreference) prefEntryToRun).setEntries(entries);\n                        ((ListPreference) prefEntryToRun).setEntryValues(entryValues);\n                        prefEntryToRun.setVisible(enableValue.equals(dependOnValue));\n                        return;\n                    }\n                }\n            }\n            if (prefEntryToRun == null) {\n                // the ListPreference for selecting an app is missing. Remove the option to run an app.\n                Preference pref = findPreference(dependOnKey);\n                if (pref instanceof ListPreference) {\n                    removeEntryValueFromListPreference(enableValue, (ListPreference) pref);\n                }\n            } else {\n                Log.w(TAG, \"ListPreference `\" + listKey + \"` can't be updated\");\n                prefEntryToRun.setVisible(false);\n            }\n        }\n\n        private void updateAppToRunList(@NonNull SharedPreferences sharedPreferences, String key) {\n            updateListPrefDependency(key, sharedPreferences.getString(key, null), \"runApp\", key + \"-app-to-run\", AppToRunListContent);\n        }\n\n        private void updateShortcutToRunList(@NonNull SharedPreferences sharedPreferences, String key) {\n            updateListPrefDependency(key, sharedPreferences.getString(key, null), \"runShortcut\", key + \"-shortcut-to-run\", ShortcutToRunListContent);\n        }\n\n        private void updateEntryToShowList(@NonNull SharedPreferences sharedPreferences, String key) {\n            updateListPrefDependency(key, sharedPreferences.getString(key, null), \"showEntry\", key + \"-entry-to-show\", EntryToShowListContent);\n        }\n\n        private static void removeEntryValueFromListPreference(@NonNull String entryValueToRemove, ListPreference listPref) {\n            CharSequence[] entryValues = listPref.getEntryValues();\n            int indexToRemove = -1;\n            for (int idx = 0, entryValuesLength = entryValues.length; idx < entryValuesLength; idx++) {\n                CharSequence entryValue = entryValues[idx];\n                if (entryValueToRemove.contentEquals(entryValue)) {\n                    indexToRemove = idx;\n                    break;\n                }\n            }\n            if (indexToRemove == -1)\n                return;\n            CharSequence[] entries = listPref.getEntries();\n            final int size = entries.length;\n            final int newSize = size - 1;\n            CharSequence[] newEntries = new CharSequence[newSize];\n            CharSequence[] newEntryValues = new CharSequence[newSize];\n            if (indexToRemove > 0) {\n                System.arraycopy(entries, 0, newEntries, 0, indexToRemove);\n                System.arraycopy(entryValues, 0, newEntryValues, 0, indexToRemove);\n            }\n            if (indexToRemove < newSize) {\n                System.arraycopy(entries, indexToRemove + 1, newEntries, indexToRemove, newSize - indexToRemove);\n                System.arraycopy(entryValues, indexToRemove + 1, newEntryValues, indexToRemove, newSize - indexToRemove);\n            }\n            listPref.setEntries(newEntries);\n            listPref.setEntryValues(newEntryValues);\n        }\n\n        @Override\n        public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {\n            SettingsActivity activity = (SettingsActivity) getActivity();\n            if (activity == null || key == null)\n                return;\n\n            if (PREF_LISTS_WITH_DEPENDENCY.contains(key)) {\n                updateAppToRunList(sharedPreferences, key);\n                updateShortcutToRunList(sharedPreferences, key);\n                updateEntryToShowList(sharedPreferences, key);\n            }\n\n            // rebind and relayout all visible views because I can't find how to rebind only the current view\n            getListView().getAdapter().notifyDataSetChanged();\n\n            SettingsActivity.onSharedPreferenceChanged(activity, sharedPreferences, key);\n\n            synchronized (SettingsFragment.class) {\n                if (TagsMenuContent != null) {\n                    if (\"tags-menu-list\".equals(key) || \"tags-menu-order\".equals(key)) {\n                        TagsMenuContent.reloadOrderedValues(sharedPreferences, this, \"tags-menu-order\");\n                    } else if (\"result-popup-order\".equals(key)) {\n                        ResultPopupContent.reloadOrderedValues(sharedPreferences, this, \"result-popup-order\");\n                    }\n                }\n            }\n        }\n    }\n\n    @SuppressWarnings(\"deprecation\")\n    private static void setActionBarTextColor(Activity activity, int color) {\n        ActionBar actionBar = activity instanceof AppCompatActivity\n            ? ((AppCompatActivity) activity).getSupportActionBar()\n            : null;\n        CharSequence title = actionBar != null ? actionBar.getTitle() : null;\n        if (title == null)\n            return;\n        activity.setTitleColor(color);\n\n        Drawable arrow = AppCompatResources.getDrawable(activity, R.drawable.ic_arrow_back);\n        if (arrow != null) {\n            arrow = DrawableCompat.wrap(arrow);\n            DrawableCompat.setTint(arrow, color);\n            actionBar.setHomeAsUpIndicator(arrow);\n        }\n\n        SpannableString text = new SpannableString(title);\n        ForegroundColorSpan[] spansToRemove = text.getSpans(0, text.length(), ForegroundColorSpan.class);\n        for (ForegroundColorSpan span : spansToRemove) {\n            text.removeSpan(span);\n        }\n        text.setSpan(new ForegroundColorSpan(color), 0, text.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);\n        actionBar.setTitle(text);\n    }\n\n    private static void applyNotificationBarColor(@NonNull SharedPreferences sharedPreferences, @Nullable Context context) {\n        int color = UIColors.getColor(sharedPreferences, \"notification-bar-argb\");\n        // keep the bars opaque to avoid white text on white background by mistake\n        int alpha = 0xFF;//UIColors.getAlpha(sharedPreferences, \"notification-bar-alpha\");\n        Activity activity = Utilities.getActivity(context);\n        if (activity instanceof SettingsActivity)\n            UIColors.setStatusBarColor((SettingsActivity) activity, UIColors.setAlpha(color, alpha));\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n            View view = activity != null ? activity.findViewById(android.R.id.content) : null;\n            if (view == null && activity != null)\n                view = activity.getWindow() != null ? activity.getWindow().getDecorView() : null;\n            if (view != null) {\n                if (sharedPreferences.getBoolean(\"black-notification-icons\", false)) {\n                    SystemUiVisibility.setLightStatusBar(view);\n                } else {\n                    SystemUiVisibility.clearLightStatusBar(view);\n                }\n            }\n        }\n        setActionBarTextColor(activity, UIColors.getTextContrastColor(color));\n    }\n\n    private static void applyNavigationBarColor(@NonNull SharedPreferences sharedPreferences, @Nullable Context context) {\n        int color = UIColors.getColor(sharedPreferences, \"navigation-bar-argb\");\n        Activity activity = Utilities.getActivity(context);\n        if (activity instanceof SettingsActivity)\n            UIColors.setNavigationBarColor((SettingsActivity) activity, color, color);\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            View view = activity != null ? activity.findViewById(android.R.id.content) : null;\n            if (view == null && activity != null)\n                view = activity.getWindow() != null ? activity.getWindow().getDecorView() : null;\n            if (view != null) {\n                if (UIColors.isColorLight(color)) {\n                    SystemUiVisibility.setLightNavigationBar(view);\n                } else {\n                    SystemUiVisibility.clearLightNavigationBar(view);\n                }\n            }\n        }\n    }\n\n    public static void onSharedPreferenceChanged(Context context, SharedPreferences sharedPreferences, String key) {\n        TBApplication app = TBApplication.getApplication(context);\n\n        if (PREF_THAT_REQUIRE_LAYOUT_UPDATE.contains(key))\n            app.requireLayoutUpdate();\n\n        TBLauncherActivity activity = app.launcherActivity();\n\n        if (activity != null)\n            activity.liveWallpaper.onPrefChanged(sharedPreferences, key);\n\n        switch (key) {\n            case \"notification-bar-argb\":\n            case \"black-notification-icons\":\n                applyNotificationBarColor(sharedPreferences, context);\n                break;\n            case \"navigation-bar-argb\":\n                applyNavigationBarColor(sharedPreferences, context);\n                break;\n            case \"icon-scale-red\":\n            case \"icon-scale-green\":\n            case \"icon-scale-blue\":\n            case \"icon-scale-alpha\":\n            case \"icon-hue\":\n            case \"icon-contrast\":\n            case \"icon-brightness\":\n            case \"icon-saturation\":\n            case \"icon-background-argb\":\n            case \"matrix-contacts\":\n            case \"icons-visible\":\n                TBApplication.drawableCache(context).clearCache();\n                if (activity != null)\n                    activity.refreshSearchRecords();\n                // fallthrough\n            case \"quick-list-argb\":\n            case \"quick-list-ripple-color\":\n                // static entities will change color based on luminance\n                // fallthrough\n            case \"quick-list-toggle-color\":\n                // toggle animation is also caching the color\n                if (activity != null)\n                    activity.queueDockReload();\n                // fallthrough\n            case \"result-list-argb\":\n            case \"result-ripple-color\":\n            case \"result-highlight-color\":\n            case \"result-text-color\":\n            case \"result-text2-color\":\n            case \"result-shadow-color\":\n            case \"contact-action-color\":\n                if (activity != null)\n                    activity.refreshSearchRecords();\n                // fallthrough\n            case \"search-bar-text-color\":\n            case \"search-bar-shadow-color\":\n            case \"popup-background-argb\":\n            case \"popup-border-argb\":\n            case \"popup-ripple-color\":\n            case \"popup-text-color\":\n            case \"popup-title-color\":\n            case \"popup-shadow-color\":\n                UIColors.resetCache();\n                break;\n            case \"quick-list-icon-size\":\n                if (activity != null)\n                    activity.queueDockReload();\n                // fallthrough\n            case \"result-text-size\":\n            case \"result-text2-size\":\n            case \"result-icon-size\":\n            case \"result-shadow-radius\":\n            case \"result-shadow-dx\":\n            case \"result-shadow-dy\":\n            case \"result-list-row-height\":\n                if (activity != null)\n                    activity.refreshSearchRecords();\n                // fallthrough\n            case \"tags-menu-icon-size\":\n            case \"search-bar-shadow-dx\":\n            case \"search-bar-shadow-dy\":\n            case \"search-bar-shadow-radius\":\n            case \"popup-corner-radius\":\n            case \"popup-shadow-dx\":\n            case \"popup-shadow-dy\":\n            case \"popup-shadow-radius\":\n                UISizes.resetCache();\n                break;\n            case \"result-history-size\":\n            case \"result-history-adaptive\":\n            case \"fuzzy-search-tags\":\n            case \"result-search-cap\":\n            case \"tags-menu-icons\":\n            case \"loading-icon\":\n            case \"tags-menu-untagged\":\n            case \"tags-menu-untagged-index\":\n            case \"result-popup-order\":\n                PrefCache.resetCache();\n                break;\n            case \"adaptive-shape\":\n            case \"force-adaptive\":\n            case \"force-shape\":\n            case \"icons-pack\":\n            case \"contact-pack-mask\":\n            case \"contacts-shape\":\n            case \"shortcut-pack-mask\":\n            case \"shortcut-shape\":\n            case \"shortcut-pack-badge-mask\":\n                TBApplication.iconsHandler(context).onPrefChanged(sharedPreferences);\n                TBApplication.drawableCache(context).clearCache();\n                if (activity != null)\n                    activity.queueDockReload();\n                break;\n            case \"tags-enabled\": {\n                boolean useTags = sharedPreferences.getBoolean(\"tags-enabled\", true);\n                Activity settingsActivity = Utilities.getActivity(context);\n                Fragment fragment = null;\n                if (settingsActivity instanceof SettingsActivity)\n                    fragment = ((SettingsActivity) settingsActivity).getSupportFragmentManager().findFragmentByTag(SettingsFragment.FRAGMENT_TAG);\n                SwitchPreference preference = null;\n                if (fragment instanceof SettingsFragment)\n                    preference = ((SettingsFragment) fragment).findPreference(\"fuzzy-search-tags\");\n                if (preference != null)\n                    preference.setChecked(useTags);\n                else\n                    sharedPreferences.edit().putBoolean(\"fuzzy-search-tags\", useTags).apply();\n                break;\n            }\n            case \"quick-list-enabled\":\n            case \"quick-list-text-visible\":\n            case \"quick-list-icons-visible\":\n            case \"quick-list-show-badge\":\n            case \"quick-list-columns\":\n            case \"quick-list-rows\":\n            case \"quick-list-rtl\":\n                if (activity != null)\n                    activity.queueDockReload();\n                break;\n            case \"cache-drawable\":\n            case \"cache-half-apps\":\n                TBApplication.drawableCache(context).onPrefChanged(context, sharedPreferences);\n                break;\n            case \"enable-search\":\n            case \"enable-url\":\n            case \"enable-calculator\":\n            case \"enable-dial\":\n            case \"enable-contacts\":\n            case \"selected-contact-mime-types\":\n            case \"shortcut-dynamic-in-results\":\n                TBApplication.dataHandler(context).reloadProviders();\n                break;\n            case \"root-mode\":\n                if (sharedPreferences.getBoolean(\"root-mode\", false) &&\n                    !TBApplication.rootHandler(context).isRootAvailable()) {\n                    //show error dialog\n                    new AlertDialog.Builder(context).setMessage(R.string.root_mode_error)\n                        .setPositiveButton(android.R.string.ok, (dialog, which) -> {\n                            sharedPreferences.edit().putBoolean(\"root-mode\", false).apply();\n                        }).show();\n                }\n                TBApplication.rootHandler(context).resetRootHandler(sharedPreferences);\n                break;\n            case \"tags-menu-list\":\n                PrefOrderedListHelper.syncOrderedList(sharedPreferences, \"tags-menu-list\", \"tags-menu-order\");\n                break;\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/TBApplication.java",
    "content": "package rocks.tbog.tblauncher;\n\nimport android.app.Activity;\nimport android.app.Application;\nimport android.content.ComponentCallbacks2;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.ContextWrapper;\nimport android.content.Intent;\nimport android.content.SharedPreferences;\nimport android.content.pm.PackageManager;\nimport android.content.pm.ResolveInfo;\nimport android.database.sqlite.SQLiteDatabase;\nimport android.util.Log;\nimport android.view.View;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.lifecycle.Lifecycle;\nimport androidx.preference.PreferenceManager;\n\nimport org.acra.ACRA;\nimport org.acra.config.CoreConfigurationBuilder;\nimport org.acra.config.DialogConfigurationBuilder;\nimport org.acra.config.MailSenderConfigurationBuilder;\nimport org.acra.data.StringFormat;\n\nimport java.lang.ref.WeakReference;\nimport java.util.ConcurrentModificationException;\nimport java.util.Iterator;\nimport java.util.LinkedList;\n\nimport rocks.tbog.tblauncher.handler.AppsHandler;\nimport rocks.tbog.tblauncher.handler.DataHandler;\nimport rocks.tbog.tblauncher.handler.IconsHandler;\nimport rocks.tbog.tblauncher.handler.TagsHandler;\nimport rocks.tbog.tblauncher.icons.IconPackCache;\nimport rocks.tbog.tblauncher.quicklist.QuickList;\nimport rocks.tbog.tblauncher.searcher.Searcher;\nimport rocks.tbog.tblauncher.ui.ListPopup;\nimport rocks.tbog.tblauncher.utils.PrefCache;\nimport rocks.tbog.tblauncher.utils.RootHandler;\nimport rocks.tbog.tblauncher.utils.Utilities;\nimport rocks.tbog.tblauncher.widgets.WidgetManager;\n\npublic class TBApplication extends Application {\n    private static final String TAG = \"APP\";\n\n    /**\n     * The state of certain launcher features\n     */\n    @NonNull\n    private static final LauncherState mState = new LauncherState();\n\n    private DataHandler dataHandler = null;\n    private IconsHandler iconsPackHandler = null;\n    private TagsHandler tagsHandler = null;\n    private AppsHandler appsHandler = null;\n    private SharedPreferences mSharedPreferences = null;\n    private ListPopup mPopup = null;\n\n    /**\n     * List of running launcher activities\n     */\n    private final LinkedList<WeakReference<TBLauncherActivity>> mActivities = new LinkedList<>();\n\n    /**\n     * Task launched on text change\n     */\n    private Searcher mSearchTask;\n    /**\n     * We store a number of drawables in memory for fast redraw\n     */\n    private final DrawableCache mDrawableCache = new DrawableCache();\n    /**\n     * We store a number of icon packs so we don't have to parse the XML\n     */\n    private final IconPackCache mIconPackCache = new IconPackCache();\n    /**\n     * We store a number of icon packs so we don't have to parse the XML\n     */\n    private final MimeTypeCache mMimeTypeCache = new MimeTypeCache();\n    /**\n     * Root handler - su\n     */\n    private RootHandler mRootHandler = null;\n\n    @Override\n    protected void attachBaseContext(Context base) {\n        super.attachBaseContext(base);\n        //MultiDex.install(this);\n        ACRA.init(this, new CoreConfigurationBuilder()\n            .withBuildConfigClass(BuildConfig.class)\n            .withReportFormat(StringFormat.JSON)\n            .withPluginConfigurations(new MailSenderConfigurationBuilder()\n                    .withMailTo(\"tblauncher.acra@tbog.rocks\")\n                    .withReportAsFile(false)\n                    .build()\n                , new DialogConfigurationBuilder()\n                    .withTitle(getString(R.string.crash_title))\n                    .withText(getString(R.string.crash_text))\n                    .withPositiveButtonText(getString(R.string.crash_send_email))\n                    .withResTheme(R.style.TitleDialogTheme)\n                    .build()));\n    }\n\n    @NonNull\n    public static TBApplication getApplication(@NonNull Context context) {\n        Context appContext = context.getApplicationContext();\n        if (appContext instanceof TBApplication)\n            return (TBApplication) appContext;\n        throw new IllegalStateException(\"appContext \" + appContext + \" not of type \" + TBApplication.class.getSimpleName());\n    }\n\n    @NonNull\n    private TBLauncherActivity validateActivity(@NonNull Context context) {\n        Activity activity = Utilities.getActivity(context);\n        if (activity == null)\n            throw new IllegalStateException(\"context \" + context + \" null activity\");\n        TBLauncherActivity foundActivity = null;\n        for (WeakReference<TBLauncherActivity> ref : mActivities) {\n            TBLauncherActivity launcherActivity = ref.get();\n            if (launcherActivity == activity)\n                foundActivity = launcherActivity;\n        }\n        if (foundActivity == null)\n            throw new IllegalStateException(\"activity \" + activity + \" not registered\");\n        return foundActivity;\n    }\n\n    @NonNull\n    private TBLauncherActivity getActivity() {\n        WeakReference<TBLauncherActivity> ref = mActivities.peekFirst();\n        if (ref == null)\n            throw new IllegalStateException(\"no activity registered\");\n        TBLauncherActivity launcherActivity = ref.get();\n        while (launcherActivity == null) {\n            if (!mActivities.remove(ref))\n                throw new ConcurrentModificationException();\n            ref = mActivities.peekFirst();\n            if (ref == null)\n                throw new IllegalStateException(\"all registered activities released\");\n            launcherActivity = ref.get();\n        }\n        if (launcherActivity.getLifecycle().getCurrentState().compareTo(Lifecycle.State.DESTROYED) == 0)\n            throw new IllegalStateException(\"activity destroyed\");\n        return launcherActivity;\n    }\n\n    /**\n     * There should be only one activity, but for short periods of time there can be:\n     * - none when launcher got shut down for memory reasons\n     * - two when the activity gets recreated (user pressed the \"home\" button for example)\n     *\n     * @return most recently registered launcher activity or null\n     */\n    @Nullable\n    public TBLauncherActivity launcherActivity() {\n        WeakReference<TBLauncherActivity> ref = mActivities.peekFirst();\n        TBLauncherActivity launcherActivity = ref == null ? null : ref.get();\n        if (launcherActivity != null && launcherActivity.getLifecycle().getCurrentState().compareTo(Lifecycle.State.DESTROYED) == 0)\n            return null;\n        Log.d(TAG, \"launcherActivity=\" + launcherActivity);\n        return launcherActivity;\n    }\n\n    /**\n     * Same as the getting application from context then calling launcherActivity()\n     *\n     * @param context to get application from\n     * @return most recently registered launcher activity or null\n     */\n    @Nullable\n    public static TBLauncherActivity launcherActivity(@NonNull Context context) {\n        return getApplication(context).launcherActivity();\n    }\n\n    public static boolean activityInvalid(@Nullable View view) {\n        if (view != null && view.isAttachedToWindow())\n            return activityInvalid(view.getContext());\n        return false;\n    }\n\n    public static boolean activityInvalid(@Nullable Context ctx) {\n        return !activityValid(ctx);\n    }\n\n    public static boolean activityValid(@Nullable Context context) {\n        Context ctx = context;\n        while (ctx instanceof ContextWrapper) {\n            if (ctx instanceof Activity) {\n                Activity act = (Activity) ctx;\n                if (act.isFinishing() || act.isDestroyed()) {\n                    // activity is no more\n                    return false;\n                }\n                TBApplication app = getApplication(act);\n                for (WeakReference<TBLauncherActivity> ref : app.mActivities) {\n                    TBLauncherActivity launcherActivity = ref.get();\n                    if (act.equals(launcherActivity)) {\n                        Lifecycle.State state = launcherActivity.getLifecycle().getCurrentState();\n                        return state.isAtLeast(Lifecycle.State.INITIALIZED);\n                    }\n                }\n                // activity not registered\n                return false;\n            }\n            ctx = ((ContextWrapper) ctx).getBaseContext();\n        }\n        // context is null\n        return false;\n    }\n\n    public void onCreateActivity(TBLauncherActivity activity) {\n        // clean list\n        for (Iterator<WeakReference<TBLauncherActivity>> iterator = mActivities.iterator(); iterator.hasNext(); ) {\n            WeakReference<TBLauncherActivity> ref = iterator.next();\n            TBLauncherActivity launcherActivity = ref.get();\n            if (launcherActivity == null)\n                iterator.remove();\n        }\n        // add to list\n        mActivities.push(new WeakReference<>(activity));\n        Log.d(TAG, \"activities.size=\" + mActivities.size());\n    }\n\n    @NonNull\n    public SharedPreferences preferences() {\n        return mSharedPreferences;\n    }\n\n    public static Behaviour behaviour(@NonNull Context context) {\n        TBApplication app = getApplication(context);\n        return app.validateActivity(context).behaviour;\n    }\n\n    @NonNull\n    public Behaviour behaviour() {\n        return getActivity().behaviour;\n    }\n\n    @NonNull\n    public static LiveWallpaper liveWallpaper(Context context) {\n        TBApplication app = getApplication(context);\n        return app.validateActivity(context).liveWallpaper;\n    }\n\n    public static QuickList quickList(Context context) {\n        TBApplication app = getApplication(context);\n        return app.validateActivity(context).quickList;\n    }\n\n    public static CustomizeUI ui(Context context) {\n        TBApplication app = getApplication(context);\n        return app.validateActivity(context).customizeUI;\n    }\n\n    @NonNull\n    public static WidgetManager widgetManager(Context context) {\n        TBApplication app = getApplication(context);\n        return app.validateActivity(context).widgetManager;\n    }\n\n    @NonNull\n    public static DrawableCache drawableCache(Context context) {\n        return getApplication(context).mDrawableCache;\n    }\n\n    @NonNull\n    public static IconPackCache iconPackCache(Context context) {\n        return getApplication(context).mIconPackCache;\n    }\n\n    @NonNull\n    public static MimeTypeCache mimeTypeCache(Context context) {\n        return getApplication(context).mMimeTypeCache;\n    }\n\n    @NonNull\n    public static TagsHandler tagsHandler(Context context) {\n        return getApplication(context).tagsHandler();\n    }\n\n    @NonNull\n    public static AppsHandler appsHandler(Context context) {\n        return getApplication(context).appsHandler();\n    }\n\n    @NonNull\n    public static DataHandler dataHandler(Context context) {\n        return getApplication(context).getDataHandler();\n    }\n\n    @NonNull\n    public static RootHandler rootHandler(Context context) {\n        return getApplication(context).rootHandler();\n    }\n\n    @NonNull\n    public static LauncherState state() {\n        return mState;\n    }\n\n    public static void onDestroyActivity(TBLauncherActivity activity) {\n        TBApplication app = getApplication(activity);\n        Activity popupActivity = null;\n        if (app.mPopup != null)\n            popupActivity = Utilities.getActivity(app.mPopup.getContentView());\n        if (popupActivity == null && app.dismissPopup())\n            Log.i(TAG, \"Popup dismissed in onDestroyActivity\");\n\n        for (Iterator<WeakReference<TBLauncherActivity>> iterator = app.mActivities.iterator(); iterator.hasNext(); ) {\n            WeakReference<TBLauncherActivity> ref = iterator.next();\n            TBLauncherActivity launcherActivity = ref.get();\n            if (launcherActivity == null || launcherActivity == activity) {\n                if (activity == popupActivity && app.dismissPopup())\n                    Log.i(TAG, \"Popup dismissed in onDestroyActivity \" + activity);\n                iterator.remove();\n            }\n        }\n    }\n\n    public static void runTask(Context context, Searcher task) {\n        resetTask(context);\n        getApplication(context).mSearchTask = task;\n        task.execute();\n    }\n\n    public static void resetTask(Context context) {\n        TBApplication app = getApplication(context);\n        if (app.mSearchTask != null) {\n            app.mSearchTask.cancel(true);\n            app.mSearchTask = null;\n        }\n    }\n\n    public static boolean hasSearchTask(Context context) {\n        TBApplication app = getApplication(context);\n        return app.mSearchTask != null;\n    }\n\n    public static IconsHandler iconsHandler(Context ctx) {\n        return getApplication(ctx).iconsHandler();\n    }\n\n    public static boolean isDefaultLauncher(Context context) {\n        String homePackage;\n        try {\n            Intent i = new Intent(Intent.ACTION_MAIN);\n            i.addCategory(Intent.CATEGORY_HOME);\n            PackageManager pm = context.getPackageManager();\n            final ResolveInfo mInfo = pm.resolveActivity(i, PackageManager.MATCH_DEFAULT_ONLY);\n            homePackage = mInfo == null ? \"null\" : mInfo.activityInfo.packageName;\n        } catch (Exception e) {\n            homePackage = \"unknown\";\n        }\n\n        return homePackage.equals(context.getPackageName());\n    }\n\n    public static void resetDefaultLauncherAndOpenChooser(Context context) {\n        PackageManager packageManager = context.getPackageManager();\n        ComponentName componentName = new ComponentName(context, DummyLauncherActivity.class);\n        packageManager.setComponentEnabledSetting(componentName, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);\n\n        Intent selector = new Intent(Intent.ACTION_MAIN);\n        selector.addCategory(Intent.CATEGORY_HOME);\n        selector.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n        context.startActivity(selector);\n\n        packageManager.setComponentEnabledSetting(componentName, PackageManager.COMPONENT_ENABLED_STATE_DEFAULT, PackageManager.DONT_KILL_APP);\n    }\n\n    @Override\n    public void onCreate() {\n        super.onCreate();\n        PreferenceManager.setDefaultValues(this, R.xml.preferences, true);\n        PreferenceManager.setDefaultValues(this, R.xml.preference_features, true);\n        mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);\n\n        if (PrefCache.isMigrateRequired(mSharedPreferences) && PrefCache.migratePreferences(this, mSharedPreferences)) {\n            Log.i(TAG, \"Preferences migration done.\");\n        }\n\n//        SharedPreferences.Editor editor = mSharedPreferences.edit();\n//        for (Map.Entry<String, ?> entry : mSharedPreferences.getAll().entrySet() )\n//        {\n//            if (entry.getKey().startsWith(\"gesture-\")) {\n//                Log.d(\"Pref\", entry.getKey() + \"=\" + entry.getValue());\n//                editor.putString(entry.getKey(), \"none\");\n//            }\n//        }\n//        editor.commit();\n\n        mDrawableCache.onPrefChanged(this, mSharedPreferences);\n    }\n\n    @Override\n    public void onTerminate() {\n        TBLauncherActivity launcherActivity = launcherActivity();\n        if (launcherActivity != null)\n            launcherActivity.widgetManager.stop();\n        super.onTerminate();\n    }\n\n    @NonNull\n    public TagsHandler tagsHandler() {\n        if (tagsHandler == null)\n            tagsHandler = new TagsHandler(this);\n        return tagsHandler;\n    }\n\n    @NonNull\n    public AppsHandler appsHandler() {\n        if (appsHandler == null)\n            appsHandler = new AppsHandler(this);\n        return appsHandler;\n    }\n\n    @NonNull\n    public DataHandler getDataHandler() {\n        synchronized (this) {\n            if (dataHandler == null) {\n                dataHandler = new DataHandler(this);\n            }\n        }\n        return dataHandler;\n    }\n\n    @NonNull\n    public DrawableCache drawableCache() {\n        return mDrawableCache;\n    }\n\n    public void initDataHandler() {\n        synchronized (this) {\n            if (dataHandler == null) {\n                dataHandler = new DataHandler(this);\n            }\n        }\n        if (dataHandler.fullLoadOverSent()) {\n            // Already loaded! We still need to fire the FULL_LOAD event\n            DataHandler.sendBroadcast(this, TBLauncherActivity.FULL_LOAD_OVER, TAG);\n        }\n    }\n\n    @NonNull\n    public IconsHandler iconsHandler() {\n        if (iconsPackHandler == null) {\n            iconsPackHandler = new IconsHandler(this);\n        }\n\n        return iconsPackHandler;\n    }\n\n    public void resetIconsHandler() {\n        iconsPackHandler = new IconsHandler(this);\n    }\n\n    @NonNull\n    public RootHandler rootHandler() {\n        if (mRootHandler == null)\n            mRootHandler = new RootHandler(mSharedPreferences);\n        return mRootHandler;\n    }\n\n    public void requireLayoutUpdate() {\n        for (WeakReference<TBLauncherActivity> ref : mActivities) {\n            TBLauncherActivity launcherActivity = ref.get();\n            if (launcherActivity != null)\n                launcherActivity.requireLayoutUpdate();\n        }\n    }\n\n    public void registerPopup(ListPopup popup) {\n        if (mPopup == popup)\n            return;\n        dismissPopup();\n        mPopup = popup;\n        popup.setOnDismissListener(() -> mPopup = null);\n    }\n\n    public boolean dismissPopup() {\n        if (mPopup != null) {\n            mPopup.dismiss();\n            return true;\n        }\n        return false;\n    }\n\n    @Nullable\n    public ListPopup getPopup() {\n        return mPopup;\n    }\n\n    /**\n     * Release memory when the UI becomes hidden or when system resources become low.\n     *\n     * @param level the memory-related event that was raised.\n     */\n    @Override\n    public void onTrimMemory(int level) {\n        super.onTrimMemory(level);\n\n        if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {\n            // the process had been showing a user interface, and is no longer doing so\n            mDrawableCache.clearCache();\n        }\n        if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {\n            // this is called every time the screen is off\n            SQLiteDatabase.releaseMemory();\n            mIconPackCache.clearCache(this);\n            if (mSharedPreferences.getBoolean(\"screen-off-cache-clear\", false))\n                mDrawableCache.clearCache();\n            mMimeTypeCache.clearCache();\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/TBLauncherActivity.java",
    "content": "package rocks.tbog.tblauncher;\n\nimport android.content.BroadcastReceiver;\nimport android.content.Context;\nimport android.content.ContextWrapper;\nimport android.content.Intent;\nimport android.content.IntentFilter;\nimport android.content.SharedPreferences;\nimport android.content.res.Configuration;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.view.KeyEvent;\nimport android.view.MotionEvent;\nimport android.view.View;\nimport android.view.WindowManager;\nimport android.widget.TextView;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.core.app.ActivityCompat;\nimport androidx.core.content.ContextCompat;\nimport androidx.preference.PreferenceManager;\n\nimport rocks.tbog.tblauncher.quicklist.QuickList;\nimport rocks.tbog.tblauncher.ui.ListPopup;\nimport rocks.tbog.tblauncher.utils.DebugInfo;\nimport rocks.tbog.tblauncher.utils.DeviceUtils;\nimport rocks.tbog.tblauncher.widgets.WidgetManager;\n\npublic class TBLauncherActivity extends AppCompatActivity implements ActivityCompat.OnRequestPermissionsResultCallback {\n\n    private static final String TAG = \"TBL\";\n    public static final String START_LOAD = \"rocks.tbog.provider.START_LOAD\";\n    public static final String LOAD_OVER = \"rocks.tbog.provider.LOAD_OVER\";\n    public static final String FULL_LOAD_OVER = \"rocks.tbog.provider.FULL_LOAD_OVER\";\n    public static final String INTENT_DATA = \"rocks.tbog.provider.INTENT_DATA\";\n\n    /**\n     * Receive events from providers\n     */\n    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {\n        @Override\n        public void onReceive(Context context, Intent intent) {\n            Log.d(TAG, \"BroadcastReceiver action=`\" + intent.getAction() + \"` extra=`\" + intent.getStringExtra(INTENT_DATA) + \"`\");\n            if (START_LOAD.equalsIgnoreCase(intent.getAction())) {\n                behaviour.displayLoader(true);\n            } else if (LOAD_OVER.equalsIgnoreCase(intent.getAction())) {\n                behaviour.updateSearchRecords();\n            } else if (FULL_LOAD_OVER.equalsIgnoreCase(intent.getAction())) {\n                Log.v(TAG, \"All providers are done loading.\");\n\n                TBApplication app = TBApplication.getApplication(TBLauncherActivity.this);\n                app.getDataHandler().executeAfterLoadOverTasks();\n                behaviour.displayLoader(false);\n\n                SharedPreferences prefs = app.preferences();\n                // we need to set drawable cache preferences after we load all the apps\n                app.drawableCache().onPrefChanged(TBLauncherActivity.this, prefs);\n                // make sure we load the icon pack as early as possible\n                app.iconsHandler().onPrefChanged(prefs);\n\n                // Run GC once to free all the garbage accumulated during provider initialization\n                System.gc();\n            }\n            updateTextView(debugTextView);\n        }\n    };\n\n    private Permission permissionManager;\n    private TextView debugTextView;\n\n    /**\n     * Everything that has to do with the UI behaviour\n     */\n    public final Behaviour behaviour = new Behaviour();\n    /**\n     * Manage live wallpaper interaction\n     */\n    public final LiveWallpaper liveWallpaper = new LiveWallpaper();\n    /**\n     * The dock / quick access bar\n     */\n    public final QuickList quickList = new QuickList();\n    /**\n     * Everything that has to do with the UI customization (drawables and colors)\n     */\n    public final CustomizeUI customizeUI = new CustomizeUI();\n    /**\n     * Manage widgets\n     */\n    public final WidgetManager widgetManager = new WidgetManager();\n\n    private boolean bLayoutUpdateRequired = false;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        widgetManager.start(this);\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {\n            getWindow().getAttributes().layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;\n        }\n\n        final TBApplication app = TBApplication.getApplication(this);\n        app.onCreateActivity(this);\n\n        /*\n         * Initialize preferences\n         */\n        PreferenceManager.setDefaultValues(this, R.xml.preferences, false);\n        var prefs = PreferenceManager.getDefaultSharedPreferences(this);\n        Behaviour.setActivityOrientation(this, prefs);\n\n        /*\n         * Permission Manager\n         */\n        permissionManager = new Permission(this);\n\n        /*\n         * Initialize data handler and start loading providers\n         */\n        IntentFilter intentFilter = new IntentFilter();\n        intentFilter.addAction(START_LOAD);\n        intentFilter.addAction(LOAD_OVER);\n        intentFilter.addAction(FULL_LOAD_OVER);\n\n        ActivityCompat.registerReceiver(this, mReceiver, intentFilter, ContextCompat.RECEIVER_NOT_EXPORTED);\n\n        // init DataHandler after we register the receiver\n        app.initDataHandler();\n\n        setContentView(R.layout.activity_fullscreen);\n        debugTextView = findViewById(R.id.debugText);\n\n        if (BuildConfig.DEBUG) {\n            DeviceUtils.showDeviceInfo(\"TBLauncher\", this);\n        }\n\n        Log.d(TAG, \"onCreateActivity(\" + this + \")\");\n        // call after all views are set\n        behaviour.onCreateActivity(this);\n        customizeUI.onCreateActivity(this);\n        quickList.onCreateActivity(this);\n        liveWallpaper.onCreateActivity(this);\n        widgetManager.onCreateActivity(this);\n    }\n\n    @Override\n    public boolean dispatchKeyEvent(KeyEvent event) {\n        Log.d(TAG, \"dispatchKeyEvent \" + event);\n        return super.dispatchKeyEvent(event);\n    }\n\n    @Override\n    protected void onStart() {\n        Log.d(TAG, \"onStart(\" + this + \")\");\n        super.onStart();\n\n        if (DebugInfo.providerStatus(this)) {\n            debugTextView.setVisibility(View.VISIBLE);\n        }\n\n        behaviour.onStart();\n        customizeUI.onStart();\n        quickList.onStart();\n    }\n\n    @Override\n    protected void onStop() {\n        Log.d(TAG, \"onStop(\" + this + \")\");\n        super.onStop();\n    }\n\n    @Override\n    protected void onRestart() {\n        Log.d(TAG, \"onRestart(\" + this + \")\");\n        super.onRestart();\n    }\n\n    @Override\n    protected void onDestroy() {\n        Log.d(TAG, \"onDestroy(\" + this + \")\");\n        if (behaviour.closeFragmentDialog()) {\n            Log.i(TAG, \"closed dialog from onDestroy \" + this);\n        }\n        TBApplication.onDestroyActivity(this);\n        unregisterReceiver(mReceiver);\n        widgetManager.stop();\n        super.onDestroy();\n    }\n\n    @Override\n    public void onConfigurationChanged(@NonNull Configuration newConfig) {\n        //TBApplication.behaviour(this).onConfigurationChanged(this, newConfig);\n        Log.d(TAG, \"onConfigurationChanged\" +\n            \" orientation=\" + newConfig.orientation +\n            \" keyboard=\" + newConfig.keyboard +\n            \" keyboardHidden=\" + newConfig.keyboardHidden);\n        super.onConfigurationChanged(newConfig);\n    }\n\n    public boolean isLayoutUpdateRequired() {\n        return bLayoutUpdateRequired;\n    }\n\n    public void requireLayoutUpdate(boolean require) {\n        bLayoutUpdateRequired = require;\n    }\n\n    public void requireLayoutUpdate() {\n        bLayoutUpdateRequired = true;\n    }\n\n    @Override\n    protected void onResume() {\n        Log.d(TAG, \"onResume(\" + this + \")\");\n        super.onResume();\n\n        if (isLayoutUpdateRequired()) {\n            requireLayoutUpdate(false);\n            Log.i(TAG, \"Restarting app after setting changes\");\n            // Restart current activity to refresh view, since some preferences may require using a new UI\n            //getWindow().getDecorView().post(TBLauncherActivity.this::recreate);\n            Log.d(TAG, \"finish(\" + this + \")\");\n            finish();\n            startActivity(new Intent(this, getClass()));\n            overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);\n            return;\n        }\n\n        behaviour.onResume();\n    }\n\n    @Override\n    protected void onNewIntent(Intent intent) {\n        Log.d(TAG, \"onNewIntent(\" + this + \")\");\n        setIntent(intent);\n        super.onNewIntent(intent);\n\n        // This is called when the user press Home again while already browsing MainActivity\n        // onResume() will be called right after, hiding the kissbar if any.\n        // http://developer.android.com/reference/android/app/Activity.html#onNewIntent(android.content.Intent)\n        // Animation can't happen in this method, since the activity is not resumed yet, so they'll happen in the onResume()\n        // https://github.com/Neamar/KISS/issues/569\n        behaviour.onNewIntent();\n    }\n\n    @Override\n    protected void onSaveInstanceState(@NonNull Bundle outState) {\n        Log.i(TAG, \"onSaveInstanceState \" + Integer.toHexString(outState.hashCode()) + \" \" + this);\n        super.onSaveInstanceState(outState);\n        outState.clear();\n    }\n\n    @Override\n    public boolean onKeyDown(int keycode, KeyEvent e) {\n        // For devices with a physical menu button, we still want to display *our* contextual menu\n        if (keycode == KeyEvent.KEYCODE_MENU) {\n            behaviour.showContextMenu();\n            return true;\n        }\n\n        return super.onKeyDown(keycode, e);\n    }\n\n    @Override\n    public void onBackPressed() {\n        if (TBApplication.getApplication(this).dismissPopup())\n            return;\n\n        if (behaviour.onBackPressed())\n            return;\n\n        super.onBackPressed();\n    }\n\n    @Override\n    public void onWindowFocusChanged(boolean hasFocus) {\n        super.onWindowFocusChanged(hasFocus);\n        behaviour.onWindowFocusChanged(hasFocus);\n    }\n\n    //    /**\n//     * Schedules a call to hide() in delay milliseconds, canceling any\n//     * previously scheduled calls.\n//     */\n//    private void delayedHide(int delayMillis) {\n//        mHideHandler.removeCallbacks(mHideRunnable);\n//        mHideHandler.postDelayed(mHideRunnable, delayMillis);\n//    }\n\n    public void queueDockReload() {\n        quickList.reload();\n    }\n\n    public void refreshSearchRecords() {\n        behaviour.refreshSearchRecords();\n    }\n\n    @Override\n    public boolean dispatchTouchEvent(MotionEvent event) {\n        boolean shouldDismissPopup = false;\n        ListPopup listPopup = TBApplication.getApplication(this).getPopup();\n        if (listPopup != null) {\n            int action = event.getActionMasked();\n            if (action == MotionEvent.ACTION_DOWN) {\n                // this check is not needed\n                // we'll not receive the event if it happened inside the popup\n                int x = (int) (event.getRawX() + .5f);\n                int y = (int) (event.getRawY() + .5f);\n                if (!listPopup.isInsideViewBounds(x, y))\n                    shouldDismissPopup = true;\n            }\n        }\n        if (shouldDismissPopup && TBApplication.getApplication(this).dismissPopup())\n            return true;\n        return super.dispatchTouchEvent(event);\n    }\n\n    @Override\n    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {\n        super.onRequestPermissionsResult(requestCode, permissions, grantResults);\n        permissionManager.onRequestPermissionsResult(requestCode, permissions, grantResults);\n    }\n\n    @Override\n    protected void attachBaseContext(Context newBase) {\n        super.attachBaseContext(newBase);\n\n        Context c = this;\n        while (null != c) {\n            Log.d(TAG, \"Ctx: \" + c.toString() + \" | Res: \" + c.getResources().toString());\n\n            if (c instanceof ContextWrapper)\n                c = ((ContextWrapper) c).getBaseContext();\n            else\n                c = null;\n        }\n    }\n\n    @Override\n    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {\n        if (widgetManager.onActivityResult(this, requestCode, resultCode, data))\n            return;\n        super.onActivityResult(requestCode, resultCode, data);\n    }\n\n    private void updateTextView(TextView debugTextView) {\n        if (debugTextView == null)\n            return;\n\n        StringBuilder text = new StringBuilder();\n        TBApplication app = TBApplication.getApplication(this);\n\n        app.getDataHandler().appendDebugText(text);\n\n        debugTextView.setText(text);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/TagsManager.java",
    "content": "package rocks.tbog.tblauncher;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.graphics.Paint;\nimport android.graphics.Typeface;\nimport android.graphics.drawable.Drawable;\nimport android.util.Log;\nimport android.view.View;\nimport android.view.ViewTreeObserver;\nimport android.widget.ImageView;\nimport android.widget.ListView;\nimport android.widget.TextView;\nimport android.widget.Toast;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.appcompat.content.res.AppCompatResources;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.Objects;\n\nimport rocks.tbog.tblauncher.WorkAsync.RunnableTask;\nimport rocks.tbog.tblauncher.WorkAsync.TaskRunner;\nimport rocks.tbog.tblauncher.customicon.IconSelectDialog;\nimport rocks.tbog.tblauncher.dataprovider.IProvider;\nimport rocks.tbog.tblauncher.dataprovider.TagsProvider;\nimport rocks.tbog.tblauncher.drawable.CodePointDrawable;\nimport rocks.tbog.tblauncher.drawable.DrawableUtils;\nimport rocks.tbog.tblauncher.entry.ActionEntry;\nimport rocks.tbog.tblauncher.entry.EntryItem;\nimport rocks.tbog.tblauncher.entry.StaticEntry;\nimport rocks.tbog.tblauncher.entry.TagEntry;\nimport rocks.tbog.tblauncher.handler.AppsHandler;\nimport rocks.tbog.tblauncher.handler.DataHandler;\nimport rocks.tbog.tblauncher.handler.IconsHandler;\nimport rocks.tbog.tblauncher.handler.TagsHandler;\nimport rocks.tbog.tblauncher.result.ResultViewHelper;\nimport rocks.tbog.tblauncher.utils.DialogHelper;\nimport rocks.tbog.tblauncher.utils.Utilities;\nimport rocks.tbog.tblauncher.utils.ViewHolderAdapter;\nimport rocks.tbog.tblauncher.utils.ViewHolderListAdapter;\n\npublic class TagsManager {\n    private static final String TAG = \"TagMgr\";\n\n    private final ArrayList<TagInfo> mTagList = new ArrayList<>();\n    private ListView mListView;\n    private TagsAdapter mAdapter;\n\n    public interface OnItemClickListener {\n        void onItemClickListener(@NonNull View view, @NonNull TagInfo tagInfo);\n    }\n\n    public boolean hasChangesMade() {\n        for (TagInfo tagInfo : mTagList) {\n            if (tagInfo.action != TagInfo.Action.NONE)\n                return true;\n            if (tagInfo.icon != null && tagInfo.staticEntry != null)\n                return true;\n        }\n        return false;\n    }\n\n    public void applyChanges(@NonNull Context context) {\n        TagsHandler tagsHandler = TBApplication.tagsHandler(context);\n        IconsHandler iconsHandler = TBApplication.iconsHandler(context);\n        TBLauncherActivity launcherActivity = TBApplication.launcherActivity(context);\n        boolean changesMade = false;\n        for (TagInfo tagInfo : mTagList) {\n            if (tagInfo.staticEntry instanceof ActionEntry) {\n                // can't delete actions (it's the show untagged action)\n                if (tagInfo.action == TagInfo.Action.RENAME) {\n                    TBApplication.dataHandler(context).renameStaticEntry(tagInfo.staticEntry, tagInfo.name);\n                    changesMade = true;\n                }\n            } else {\n                switch (tagInfo.action) {\n                    case RENAME:\n                        if (tagsHandler.renameTag(tagInfo.tagName, tagInfo.name))\n                            changesMade = true;\n                        break;\n                    case DELETE:\n                        if (tagInfo.staticEntry != null)\n                            iconsHandler.restoreDefaultIcon(tagInfo.staticEntry);\n                        tagInfo.icon = null;\n                        if (tagsHandler.removeTag(tagInfo.tagName))\n                            changesMade = true;\n                        break;\n                }\n            }\n            if (tagInfo.icon != null && tagInfo.staticEntry != null) {\n                iconsHandler.changeIcon(tagInfo.staticEntry, tagInfo.icon);\n                if (launcherActivity != null) {\n                    // force a result refresh to update the icon in the view\n                    launcherActivity.behaviour.refreshSearchRecord(tagInfo.staticEntry);\n                }\n            }\n        }\n        // make sure we're in sync\n        if (changesMade) {\n            if (launcherActivity != null)\n                launcherActivity.queueDockReload();\n            afterChangesMade(context);\n        }\n    }\n\n    public static void afterChangesMade(@NonNull Context context) {\n        TBApplication.drawableCache(context).clearCache();\n        DataHandler dataHandler = TBApplication.dataHandler(context);\n\n        dataHandler.reloadProviders(IProvider.LOAD_STEP_2);\n\n        RunnableTask afterProviders = TaskRunner.newTask(task -> {\n                TBApplication app = TBApplication.getApplication(context);\n                AppsHandler.setTagsForApps(app.appsHandler().getAllApps(), app.tagsHandler());\n            },\n            task -> {\n                Log.d(TAG, \"tags and fav providers should have loaded by now\");\n                TBLauncherActivity activity = TBApplication.launcherActivity(context);\n                if (activity != null) {\n                    activity.refreshSearchRecords();\n                    activity.queueDockReload();\n                }\n            });\n        DataHandler.EXECUTOR_PROVIDERS.execute(afterProviders);\n    }\n\n    public void bindView(@NonNull View view, @Nullable OnItemClickListener listener) {\n        final Context context = view.getContext();\n        mListView = view.findViewById(android.R.id.list);\n\n        if (listener != null) {\n            mListView.setOnItemClickListener((parent, v, pos, id) -> {\n                Object objItem = parent.getAdapter().getItem(pos);\n                if (objItem instanceof TagInfo) {\n                    listener.onItemClickListener(v, (TagInfo) objItem);\n                }\n            });\n        }\n\n        // prepare the grid with all the tags\n        mAdapter = new TagsAdapter(mTagList);\n\n        mAdapter.setOnRemoveListener((adapter, v, position) -> {\n            TagInfo info = adapter.getItem(position);\n            if (info.action == TagInfo.Action.DELETE) {\n                info.action = info.tagName.equals(info.name) ? TagInfo.Action.NONE : TagInfo.Action.RENAME;\n            } else {\n                info.action = TagInfo.Action.DELETE;\n            }\n            mAdapter.notifyDataSetChanged();\n        });\n\n        mAdapter.setOnRenameListener((adapter, v, position) -> {\n            TagInfo info = adapter.getItem(position);\n            launchRenameDialog(v.getContext(), info);\n        });\n\n        mAdapter.setOnEditIconListener((adapter, v, position) -> {\n            TagInfo info = adapter.getItem(position);\n            launchCustomTagIconDialog(v.getContext(), info);\n        });\n\n        mAdapter.newLoadAsyncList(() -> {\n            Activity activity = Utilities.getActivity(context);\n            if (activity == null)\n                return null;\n\n            TagsHandler tagsHandler = TBApplication.tagsHandler(activity);\n            TagsProvider tagsProvider = TBApplication.dataHandler(activity).getTagsProvider();\n            Collection<String> validTags = tagsHandler.getValidTags();\n            ArrayList<TagInfo> tags = new ArrayList<>(validTags.size() + 1);\n            for (String tagName : validTags) {\n                TagEntry tagEntry = tagsProvider != null ? tagsProvider.getTagEntry(tagName) : null;\n                TagInfo tagInfo = tagEntry != null ? new TagInfo(tagEntry) : new TagInfo(tagName);\n                tagInfo.setInfo(tagName, tagsHandler.getValidEntryIds(tagName).size());\n                tags.add(tagInfo);\n            }\n            Collections.sort(tags, Comparator.comparing(lhs -> lhs.tagName));\n\n            EntryItem untaggedEntry = TBApplication.dataHandler(context).getPojo(ActionEntry.SCHEME + \"show/untagged\");\n            if (untaggedEntry instanceof ActionEntry) {\n                TagInfo tagInfo = new TagInfo((ActionEntry) untaggedEntry);\n                tagInfo.setInfo(untaggedEntry.getName(), -1);\n                tags.add(0, tagInfo);\n            }\n\n            return tags;\n        }).execute();\n    }\n\n    private void launchRenameDialog(Context ctx, TagInfo info) {\n        DialogHelper.makeRenameDialog(ctx, info.name, (dialog, newName) -> {\n            boolean isValid = true;\n            for (TagInfo tagInfo : mTagList) {\n                if (tagInfo == info)\n                    continue;\n                if (tagInfo.tagName.equals(newName) || tagInfo.name.equals(newName)) {\n                    isValid = false;\n                    break;\n                }\n            }\n            if (!isValid) {\n                Toast.makeText(ctx, ctx.getString(R.string.invalid_rename_tag, newName), Toast.LENGTH_LONG).show();\n                return;\n            }\n\n            // Set new name\n            info.name = newName;\n            info.action = info.tagName.equals(info.name) ? TagInfo.Action.NONE : TagInfo.Action.RENAME;\n\n            mAdapter.notifyDataSetChanged();\n        })\n            .setTitle(R.string.title_rename_tag)\n            .setHint(R.string.hint_rename_tag)\n            .show();\n    }\n\n    private void launchCustomTagIconDialog(Context ctx, TagInfo info) {\n        DataHandler dh = TBApplication.dataHandler(ctx);\n        final StaticEntry staticEntry;\n        if (info.staticEntry != null) {\n            staticEntry = info.staticEntry;\n        } else {\n            TagsProvider tagsProvider = dh.getTagsProvider();\n            if (tagsProvider == null)\n                return;\n            TagEntry tagEntry = tagsProvider.getTagEntry(info.tagName);\n            // add this tag to the provider before launchCustomIconDialog, in case it isn't already\n            tagsProvider.addTagEntry(tagEntry);\n\n            staticEntry = tagEntry;\n        }\n\n        // make sure we have the tag in the provider or else IconSelectDialog will not find it\n        if (staticEntry instanceof TagEntry) {\n            TagsProvider tagsProvider = dh.getTagsProvider();\n            if (tagsProvider != null)\n                tagsProvider.addTagEntry((TagEntry) staticEntry);\n        }\n\n        IconSelectDialog dlg = Behaviour.getCustomIconDialog(ctx, false);\n        dlg.putArgString(\"entryId\", staticEntry.id);\n        dlg.setOnConfirmListener(drawable -> {\n            int pos = mTagList.indexOf(info);\n            if (pos == -1)\n                return;\n\n            // update tag info\n            if (staticEntry.hasCustomIcon() && drawable == null)\n                info.icon = staticEntry.getDefaultDrawable(ctx);\n            else\n                info.icon = drawable;\n\n            mAdapter.notifyDataSetChanged();\n        });\n        Behaviour.showDialog(ctx, dlg, Behaviour.DIALOG_CUSTOM_ICON);\n    }\n\n    public void onStart() {\n        // Set list adapter after the view inflated\n        // This is a workaround to fix listview items not having the correct width\n        mListView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {\n            @Override\n            public boolean onPreDraw() {\n                mListView.getViewTreeObserver().removeOnPreDrawListener(this);\n                mListView.setAdapter(mAdapter);\n                return false;\n            }\n        });\n        //mListView.post(() -> mListView.setAdapter(mAdapter));\n    }\n\n    static class TagsAdapter extends ViewHolderListAdapter<TagInfo, TagViewHolder> {\n        private OnItemClickListener mOnRemoveListener = null;\n        private OnItemClickListener mOnRenameListener = null;\n        private OnItemClickListener mOnEditIconListener = null;\n\n        public interface OnItemClickListener {\n            void onClick(TagsAdapter adapter, View view, int position);\n        }\n\n        TagsAdapter(@NonNull ArrayList<TagInfo> tags) {\n            super(TagViewHolder.class, R.layout.tags_manager_item, tags);\n        }\n\n        void setOnRemoveListener(OnItemClickListener listener) {\n            mOnRemoveListener = listener;\n        }\n\n        void setOnRenameListener(OnItemClickListener listener) {\n            mOnRenameListener = listener;\n        }\n\n        void setOnEditIconListener(OnItemClickListener listener) {\n            mOnEditIconListener = listener;\n        }\n\n        @Override\n        protected int getItemViewTypeLayout(int viewType) {\n            if (viewType == 1)\n                return R.layout.tags_manager_item_deleted;\n            return super.getItemViewTypeLayout(viewType);\n        }\n\n        public int getItemViewType(int position) {\n            return getItem(position).action == TagInfo.Action.DELETE ? 1 : 0;\n        }\n\n        public int getViewTypeCount() {\n            return 2;\n        }\n    }\n\n    public static class TagViewHolder extends ViewHolderAdapter.ViewHolder<TagInfo> {\n        ImageView iconView;\n        TextView text1View;\n        TextView text2View;\n        View removeBtnView;\n        View renameBtnView;\n        View changeIconBtnView;\n\n        public TagViewHolder(View itemView) {\n            super(itemView);\n            iconView = itemView.findViewById(android.R.id.icon);\n            text1View = itemView.findViewById(android.R.id.text1);\n            text2View = itemView.findViewById(android.R.id.text2);\n            removeBtnView = itemView.findViewById(android.R.id.button1);\n            renameBtnView = itemView.findViewById(android.R.id.button2);\n            changeIconBtnView = itemView.findViewById(android.R.id.button3);\n        }\n\n        @Override\n        protected void setContent(TagInfo content, int position, @NonNull ViewHolderAdapter<TagInfo, ? extends ViewHolderAdapter.ViewHolder<TagInfo>> adapter) {\n            TagsAdapter tagsAdapter = (TagsAdapter) adapter;\n\n            text1View.setText(content.name);\n            text1View.setTypeface(null, content.action == TagInfo.Action.RENAME ? Typeface.BOLD : Typeface.NORMAL);\n\n            if (content.staticEntry instanceof ActionEntry) {\n                // this is the untagged entry\n                removeBtnView.setVisibility(View.GONE);\n                Context context = text1View.getContext();\n                Drawable untagged = AppCompatResources.getDrawable(context, R.drawable.ic_untagged);\n                if (untagged != null) {\n                    int iconSize = text1View.getHeight();\n                    if (iconSize <= 0)\n                        iconSize = context.getResources().getDimensionPixelSize(R.dimen.icon_preview_size);\n                    untagged.setBounds(0, 0, iconSize, iconSize);\n                    int dir = context.getResources().getConfiguration().getLayoutDirection();\n                    CharSequence text = Utilities.addDrawableAfterString(content.name, untagged, dir);\n                    text1View.setText(text);\n                }\n            } else {\n                removeBtnView.setVisibility(View.VISIBLE);\n                removeBtnView.setOnClickListener(v -> {\n                    if (tagsAdapter.mOnRemoveListener != null)\n                        tagsAdapter.mOnRemoveListener.onClick(tagsAdapter, v, position);\n                });\n            }\n\n            if (content.action == TagInfo.Action.DELETE) {\n                text1View.setPaintFlags(text1View.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);\n                // the rest of the views are null, exit now\n                return;\n            }\n\n            int count = content.entryCount;\n            if (count >= 0) {\n                text2View.setVisibility(View.VISIBLE);\n                text2View.setText(text2View.getResources().getQuantityString(R.plurals.tag_entry_count, count, count));\n            } else {\n                // we can't have a negative count\n                text2View.setVisibility(View.GONE);\n            }\n\n            if (content.icon == null) {\n                if (content.staticEntry != null) {\n                    int drawFlags = EntryItem.FLAG_DRAW_ICON | EntryItem.FLAG_DRAW_NO_CACHE;\n                    ResultViewHelper.setIconAsync(drawFlags, content.staticEntry, iconView, StaticEntry.AsyncSetEntryIcon.class, StaticEntry.class);\n                } else {\n                    Drawable icon = new CodePointDrawable(content.name);\n                    icon = DrawableUtils.applyIconMaskShape(iconView.getContext(), icon, DrawableUtils.SHAPE_SQUIRCLE, false);\n                    iconView.setImageDrawable(icon);\n                }\n            } else {\n                iconView.setImageDrawable(content.icon);\n            }\n\n            renameBtnView.setOnClickListener(v -> {\n                if (tagsAdapter.mOnRenameListener != null)\n                    tagsAdapter.mOnRenameListener.onClick(tagsAdapter, v, position);\n            });\n\n            changeIconBtnView.setOnClickListener(v -> {\n                if (tagsAdapter.mOnEditIconListener != null)\n                    tagsAdapter.mOnEditIconListener.onClick(tagsAdapter, v, position);\n            });\n        }\n    }\n\n    public static class TagInfo {\n        public final StaticEntry staticEntry;\n        public final String tagName;\n        private String name;\n        private Drawable icon = null;\n\n        private int entryCount;\n        private Action action = Action.NONE;\n\n        public enum Action {NONE, DELETE, RENAME}\n\n        public TagInfo(String name) {\n            staticEntry = null;\n            tagName = name;\n        }\n\n        public TagInfo(StaticEntry entry) {\n            staticEntry = entry;\n            tagName = entry.getName();\n        }\n\n        public void setInfo(String name, int count) {\n            this.name = name;\n            this.entryCount = count;\n        }\n\n        @Override\n        public boolean equals(Object o) {\n            if (this == o)\n                return true;\n            if (o == null || getClass() != o.getClass())\n                return false;\n            TagInfo tagInfo = (TagInfo) o;\n            return Objects.equals(staticEntry, tagInfo.staticEntry) &&\n                Objects.equals(tagName, tagInfo.tagName) &&\n                Objects.equals(name, tagInfo.name) &&\n                Objects.equals(icon, tagInfo.icon) &&\n                action == tagInfo.action;\n        }\n\n        @Override\n        public int hashCode() {\n            return Objects.hash(staticEntry, tagName, name, icon, action);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/WallpaperSnapAnim.java",
    "content": "package rocks.tbog.tblauncher;\n\nimport android.graphics.Point;\nimport android.graphics.PointF;\nimport android.view.VelocityTracker;\nimport android.view.animation.Animation;\nimport android.view.animation.DecelerateInterpolator;\nimport android.view.animation.Transformation;\n\nimport androidx.annotation.Nullable;\n\nclass WallpaperSnapAnim extends Animation {\n    private final LiveWallpaper liveWallpaper;\n    final PointF mStartOffset = new PointF();\n    final PointF mDeltaOffset = new PointF();\n    final PointF mVelocity = new PointF();\n\n    WallpaperSnapAnim(LiveWallpaper liveWallpaper) {\n        super();\n        this.liveWallpaper = liveWallpaper;\n        setDuration(500);\n        setInterpolator(new DecelerateInterpolator());\n    }\n\n    boolean init(@Nullable VelocityTracker velocityTracker) {\n        if (velocityTracker == null) {\n            mVelocity.set(0.f, 0.f);\n        } else {\n            mVelocity.set(velocityTracker.getXVelocity(), velocityTracker.getYVelocity());\n        }\n        //Log.d(TAG, \"mVelocity=\" + String.format(Locale.US, \"%.2f\", mVelocity));\n\n        mStartOffset.set(liveWallpaper.getWallpaperOffset());\n        //Log.d(TAG, \"mStartOffset=\" + String.format(Locale.US, \"%.2f\", mStartOffset));\n        final Point windowSize = liveWallpaper.getWindowSize();\n        final float expectedPosX = -Math.min(Math.max(mVelocity.x / windowSize.x, -.5f), .5f) + mStartOffset.x;\n        final float expectedPosY = -Math.min(Math.max(mVelocity.y / windowSize.y, -.5f), .5f) + mStartOffset.y;\n        //Log.d(TAG, \"expectedPos=\" + String.format(Locale.US, \"%.2f %.2f\", expectedPosX, expectedPosY));\n\n        SnapInfo si = new SnapInfo(liveWallpaper.isPreferenceWPStickToSides(), liveWallpaper.isPreferenceWPReturnCenter());\n        si.init(expectedPosX, expectedPosY);\n        si.removeDiagonals(expectedPosX, expectedPosY);\n\n        // compute offset based on stick location\n        if (si.stickToTop)\n            mDeltaOffset.y = 0.f - mStartOffset.y;\n        else if (si.stickToBottom)\n            mDeltaOffset.y = 1.f - mStartOffset.y;\n        else if (si.stickToCenter)\n            mDeltaOffset.y = .5f - mStartOffset.y;\n\n        if (si.stickToLeft)\n            mDeltaOffset.x = 0.f - mStartOffset.x;\n        else if (si.stickToRight)\n            mDeltaOffset.x = 1.f - mStartOffset.x;\n        else if (si.stickToCenter)\n            mDeltaOffset.x = .5f - mStartOffset.x;\n\n        return si.stickToLeft || si.stickToTop || si.stickToRight || si.stickToBottom || si.stickToCenter;\n    }\n\n    @Override\n    protected void applyTransformation(float interpolatedTime, Transformation t) {\n        float offsetX = mStartOffset.x + mDeltaOffset.x * interpolatedTime;\n        float offsetY = mStartOffset.y + mDeltaOffset.y * interpolatedTime;\n        float velocityInterpolator = (float) Math.sqrt(interpolatedTime) * 3.f;\n        final Point windowSize = liveWallpaper.getWindowSize();\n        if (velocityInterpolator < 1.f) {\n            offsetX -= mVelocity.x / windowSize.x * velocityInterpolator;\n            offsetY -= mVelocity.y / windowSize.y * velocityInterpolator;\n        } else {\n            offsetX -= mVelocity.x / windowSize.x * (1.f - 0.5f * (velocityInterpolator - 1.f));\n            offsetY -= mVelocity.y / windowSize.y * (1.f - 0.5f * (velocityInterpolator - 1.f));\n        }\n        liveWallpaper.updateWallpaperOffset(offsetX, offsetY);\n    }\n\n    private static class SnapInfo {\n        public final boolean stickToSides;\n        public final boolean stickToCenter;\n\n        public boolean stickToLeft;\n        public boolean stickToTop;\n        public boolean stickToRight;\n        public boolean stickToBottom;\n\n        public SnapInfo(boolean sidesSnap, boolean centerSnap) {\n            stickToSides = sidesSnap;\n            stickToCenter = centerSnap;\n        }\n\n        public void init(float x, float y) {\n            // if we stick only to the center\n            float leftStickPercent = -1.f;\n            float topStickPercent = -1.f;\n            float rightStickPercent = 2.f;\n            float bottomStickPercent = 2.f;\n\n            if (stickToSides && stickToCenter) {\n                // if we stick to the left, right and center\n                leftStickPercent = .2f;\n                topStickPercent = .2f;\n                rightStickPercent = .8f;\n                bottomStickPercent = .8f;\n            } else if (stickToSides) {\n                // if we stick only to the center\n                leftStickPercent = .5f;\n                topStickPercent = .5f;\n                rightStickPercent = .5f;\n                bottomStickPercent = .5f;\n            }\n\n            stickToLeft = x <= leftStickPercent;\n            stickToTop = y <= topStickPercent;\n            stickToRight = x >= rightStickPercent;\n            stickToBottom = y >= bottomStickPercent;\n        }\n\n        public void removeDiagonals(float x, float y) {\n            if (stickToTop) {\n                // don't stick to the top-left or top-right corner\n                if (stickToLeft) {\n                    stickToLeft = x < y;\n                    stickToTop = !stickToLeft;\n                } else if (stickToRight) {\n                    stickToRight = (1.f - x) < y;\n                    stickToTop = !stickToRight;\n                }\n            } else if (stickToBottom) {\n                // don't stick to the bottom-left or bottom-right corner\n                if (stickToLeft) {\n                    stickToLeft = x < y;\n                    stickToBottom = !stickToLeft;\n                } else if (stickToRight) {\n                    stickToRight = (1.f - x) < y;\n                    stickToBottom = !stickToRight;\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/WorkAsync/AsyncTask.java",
    "content": "package rocks.tbog.tblauncher.WorkAsync;\n\nimport android.util.Log;\n\nimport androidx.annotation.MainThread;\nimport androidx.annotation.WorkerThread;\n\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.FutureTask;\n\npublic abstract class AsyncTask<In, Out> extends FutureTask<Out> {\n    private static final String TAG = \"AsyncT\";\n    In input = null;\n\n    protected AsyncTask() {\n        this(new BackgroundWorker<>());\n    }\n\n    private AsyncTask(BackgroundWorker<In, Out> worker) {\n        super(worker);\n        worker.task = this;\n    }\n\n    @MainThread\n    protected void onPreExecute() {\n    }\n\n    @WorkerThread\n    protected abstract Out doInBackground(In input);\n\n    @Override\n    protected void done() {\n        TaskRunner.runOnUiThread(() -> {\n            if (isCancelled())\n                onCancelled();\n            else {\n                Out result = null;\n                try {\n                    result = get();\n                } catch (ExecutionException | InterruptedException e) {\n                    Log.e(TAG, \"AsyncTask \" + AsyncTask.this, e);\n                }\n                onPostExecute(result);\n            }\n        });\n    }\n\n    @MainThread\n    protected void onPostExecute(Out output) {\n    }\n\n    @MainThread\n    protected void onCancelled() {\n    }\n\n    private static class BackgroundWorker<In, Out> implements Callable<Out> {\n        private AsyncTask<In, Out> task = null;\n\n        @Override\n        public Out call() {\n            Out output = task.doInBackground(task.input);\n            task.input = null;\n            return output;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/WorkAsync/RunnableTask.java",
    "content": "package rocks.tbog.tblauncher.WorkAsync;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.lifecycle.Lifecycle;\n\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.FutureTask;\n\npublic final class RunnableTask extends FutureTask<RunnableTask> {\n    private TaskRunner.AsyncRunnable whenDone = null;\n    private Lifecycle lifecycle = null;\n\n    public void cancel() {\n        cancel(false);\n    }\n\n    protected RunnableTask(@NonNull TaskRunner.AsyncRunnable worker, @Nullable TaskRunner.AsyncRunnable main, @Nullable Lifecycle lifecycle) {\n        this(new BackgroundWorker(worker));\n        whenDone = main;\n        this.lifecycle = lifecycle;\n    }\n\n    protected RunnableTask(@NonNull TaskRunner.AsyncRunnable worker, @Nullable TaskRunner.AsyncRunnable main) {\n        this(worker, main, null);\n    }\n\n    private RunnableTask(@NonNull BackgroundWorker background) {\n        super(background);\n        background.task = this;\n    }\n\n    @Override\n    protected void done() {\n        if (whenDone != null) {\n            TaskRunner.runOnUiThread(() -> {\n                if (lifecycle == null || lifecycle.getCurrentState().isAtLeast(Lifecycle.State.STARTED))\n                    whenDone.run(this);\n            });\n        }\n    }\n\n    private static class BackgroundWorker implements Callable<RunnableTask> {\n        private RunnableTask task = null;\n        private final TaskRunner.AsyncRunnable worker;\n\n        private BackgroundWorker(TaskRunner.AsyncRunnable worker) {\n            this.worker = worker;\n        }\n\n        @Override\n        public RunnableTask call() {\n            worker.run(task);\n            return task;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/WorkAsync/TaskRunner.java",
    "content": "package rocks.tbog.tblauncher.WorkAsync;\n\nimport android.os.Handler;\nimport android.os.Looper;\n\nimport androidx.annotation.MainThread;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.lifecycle.Lifecycle;\n\nimport java.util.concurrent.ExecutorService;\n\npublic class TaskRunner {\n    private final static Handler handler = new Handler(Looper.getMainLooper());\n\n    public interface AsyncRunnable {\n        void run(@NonNull RunnableTask task);\n    }\n\n    public static boolean runOnUiThread(Runnable runnable) {\n        return handler.post(runnable);\n    }\n\n    @NonNull\n    public static RunnableTask newTask(@NonNull Lifecycle lifecycle, @NonNull AsyncRunnable worker, @Nullable AsyncRunnable main) {\n        return new RunnableTask(worker, main, lifecycle);\n    }\n\n    @NonNull\n    public static RunnableTask newTask(@NonNull AsyncRunnable worker, @Nullable AsyncRunnable main) {\n        return new RunnableTask(worker, main);\n    }\n\n    @MainThread\n    public static <In, Out, T extends AsyncTask<In, Out>> void executeOnExecutor(@NonNull ExecutorService executor, @NonNull T task) {\n        executeOnExecutor(executor, task, null);\n    }\n\n    @MainThread\n    public static <In, Out> void executeOnExecutor(@NonNull ExecutorService executor, @NonNull AsyncTask<In, Out> task, @Nullable In input) {\n        task.onPreExecute();\n        task.input = input;\n        executor.submit(task);\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/broadcast/IncomingCallHandler.java",
    "content": "package rocks.tbog.tblauncher.broadcast;\n\nimport android.content.BroadcastReceiver;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.telephony.TelephonyManager;\nimport android.util.Log;\n\nimport rocks.tbog.tblauncher.handler.DataHandler;\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.dataprovider.ContactsProvider;\nimport rocks.tbog.tblauncher.entry.ContactEntry;\n\npublic class IncomingCallHandler extends BroadcastReceiver {\n\n    private static final String TAG = IncomingCallHandler.class.getSimpleName();\n\n    @Override\n    public void onReceive(final Context context, Intent intent) {\n        // Only handle calls received\n        if (!\"android.intent.action.PHONE_STATE\".equals(intent.getAction())) {\n            return;\n        }\n\n        try {\n            DataHandler dataHandler = TBApplication.getApplication(context).getDataHandler();\n            ContactsProvider contactsProvider = dataHandler.getContactsProvider();\n\n            // Stop if contacts are not enabled\n            if (contactsProvider == null) {\n                return;\n            }\n\n            if (TelephonyManager.EXTRA_STATE_RINGING.equals(intent.getStringExtra(TelephonyManager.EXTRA_STATE))) {\n                String phoneNumber = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER);\n\n                if (phoneNumber == null) {\n                    // Skipping (private call)\n                    return;\n                }\n\n                ContactEntry contactEntry = contactsProvider.findByPhone(phoneNumber);\n                if (contactEntry != null) {\n                    dataHandler.addToHistory(contactEntry.getHistoryId());\n                }\n            }\n        } catch (Exception e) {\n            Log.e(TAG, \"Phone Receive Error\", e);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/broadcast/LocaleChangedReceiver.java",
    "content": "package rocks.tbog.tblauncher.broadcast;\n\nimport android.content.BroadcastReceiver;\nimport android.content.Context;\nimport android.content.Intent;\n\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.dataprovider.AppProvider;\n\npublic class LocaleChangedReceiver extends BroadcastReceiver {\n\n    @Override\n    @SuppressWarnings(\"CatchAndPrintStackTrace\")\n    public void onReceive(Context ctx, Intent intent) {\n        // Only handle system broadcasts\n        if (!\"android.intent.action.LOCALE_CHANGED\".equals(intent.getAction())) {\n            return;\n        }\n\n        try {\n            // If new locale, then reset tags to load the correct aliases\n            TBApplication.tagsHandler(ctx).loadFromDB(true);\n        } catch (IllegalStateException e) {\n            // Since Android 8.1, we're not allowed to create a new service\n            // when the app is not running\n            e.printStackTrace();\n        }\n\n        // Reload application list\n        final AppProvider provider = TBApplication.getApplication(ctx).getDataHandler().getAppProvider();\n        if (provider != null) {\n            provider.reload(true);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/broadcast/PackageAddedRemovedHandler.java",
    "content": "package rocks.tbog.tblauncher.broadcast;\n\nimport android.content.BroadcastReceiver;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.util.Log;\n\nimport androidx.preference.PreferenceManager;\n\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.TBLauncherActivity;\nimport rocks.tbog.tblauncher.dataprovider.AppProvider;\nimport rocks.tbog.tblauncher.dataprovider.ShortcutsProvider;\nimport rocks.tbog.tblauncher.entry.AppEntry;\nimport rocks.tbog.tblauncher.handler.DataHandler;\nimport rocks.tbog.tblauncher.utils.UserHandleCompat;\n\n/**\n * This class gets called when an application is created or removed on the\n * system\n * <p/>\n * We then recreate our data set.\n *\n * @author dorvaryn\n */\npublic class PackageAddedRemovedHandler extends BroadcastReceiver {\n\n    public static void handleEvent(Context ctx, String action, String packageName, UserHandleCompat user, boolean replacing) {\n        DataHandler dataHandler = TBApplication.getApplication(ctx).getDataHandler();\n\n        Log.i(\"Pack\", action + \" \" + packageName + \" isCurrentUser:\" + user.isCurrentUser());\n\n        if (PreferenceManager.getDefaultSharedPreferences(ctx).getBoolean(\"enable-app-history\", true)) {\n            // Insert into history new packages (not updated ones)\n            if (Intent.ACTION_PACKAGE_ADDED.equals(action) && !replacing) {\n                // Add new package to history\n                Intent launchIntent = ctx.getPackageManager().getLaunchIntentForPackage(packageName);\n                if (launchIntent == null || launchIntent.getComponent() == null) {\n                    //for some plugin app\n                    return;\n                }\n\n                final String appId = AppEntry.generateAppId(launchIntent.getComponent(), user);\n                dataHandler.addToHistory(appId);\n            }\n        }\n\n        if (\"android.intent.action.PACKAGE_REMOVED\".equals(action) && !replacing) {\n            // Remove all installed shortcuts\n            dataHandler.removeShortcuts(packageName);\n            TBLauncherActivity launcherActivity = TBApplication.launcherActivity(ctx);\n            if (launcherActivity != null)\n                launcherActivity.behaviour.handleRemoveApp(packageName);\n//            dataHandler.removeFromExcluded(packageName);\n        }\n\n        // This may be an icon pack, reload packs\n        TBApplication.getApplication(ctx).resetIconsHandler();\n\n        // Reload application list\n        {\n            final AppProvider provider = dataHandler.getAppProvider();\n            if (provider != null)\n                provider.reload(true);\n        }\n        // Reload shortcuts list\n        {\n            final ShortcutsProvider provider = dataHandler.getShortcutsProvider();\n            if (provider != null)\n                provider.reload(true);\n        }\n    }\n\n    @Override\n    public void onReceive(Context ctx, Intent intent) {\n\n        String packageName = intent.getData() != null ? intent.getData().getSchemeSpecificPart() : null;\n\n        if (packageName == null || packageName.equalsIgnoreCase(ctx.getPackageName())) {\n            // When running locally, sending a new version of the APK immediately triggers a \"package removed\"\n            // There is no need to handle this event.\n            // Discarding it makes startup time much faster locally as apps don't have to be loaded twice.\n            return;\n        }\n\n        handleEvent(ctx,\n            intent.getAction(),\n            packageName, UserHandleCompat.CURRENT_USER,\n            intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)\n        );\n\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/calculator/Calculator.java",
    "content": "package rocks.tbog.tblauncher.calculator;\n\nimport android.os.Build;\n\nimport java.math.BigDecimal;\nimport java.math.MathContext;\nimport java.util.ArrayDeque;\n\npublic class Calculator {\n\n\tpublic static Result<BigDecimal> calculateExpression(ArrayDeque<Tokenizer.Token> expression) {\n\t\ttry {\n\t\t\treturn calculateExpressionThrowing(expression);\n\t\t} catch (ArithmeticException e) {\n\t\t\treturn Result.arithmeticalError();\n\t\t}\n\t}\n\n\t// Implements the Shunting Yard algorithm\n\t// https://en.wikipedia.org/wiki/Shunting-yard_algorithm\n\tprivate static Result<BigDecimal> calculateExpressionThrowing(ArrayDeque<Tokenizer.Token> expression)\n\t\t\tthrows ArithmeticException {\n\t\tArrayDeque<BigDecimal> stack = new ArrayDeque<>();\n\n\t\tfor (Tokenizer.Token token : expression) {\n\t\t\tBigDecimal operand2 = null;\n\t\t\tBigDecimal operand1 = null;\n\n\t\t\tswitch (token.type) {\n\t\t\t\tcase Tokenizer.Token.NUMBER_TOKEN:\n\t\t\t\t\tstack.push(token.number);\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase Tokenizer.Token.UNARY_PLUS_TOKEN:\n\t\t\t\t\tif (errorInExpression(true, stack)) {\n\t\t\t\t\t\treturn Result.syntacticalError();\n\t\t\t\t\t}\n\n\t\t\t\t\t//redundant: stack.push(stack.pop());\n\t\t\t\t\tbreak;\n\t\t\t\tcase Tokenizer.Token.UNARY_MINUS_TOKEN:\n\t\t\t\t\tif (errorInExpression(true, stack)) {\n\t\t\t\t\t\treturn Result.syntacticalError();\n\t\t\t\t\t}\n\n\t\t\t\t\tstack.push(stack.pop().negate());\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase Tokenizer.Token.SUM_TOKEN:\n\t\t\t\t\tif (errorInExpression(false, stack)) {\n\t\t\t\t\t\treturn Result.syntacticalError();\n\t\t\t\t\t}\n\n\t\t\t\t\toperand2 = stack.pop();\n\t\t\t\t\toperand1 = stack.pop();\n\t\t\t\t\tstack.push(operand1.add(operand2));\n\t\t\t\t\tbreak;\n\t\t\t\tcase Tokenizer.Token.SUBTRACT_TOKEN:\n\t\t\t\t\tif (errorInExpression(false, stack)) {\n\t\t\t\t\t\treturn Result.syntacticalError();\n\t\t\t\t\t}\n\n\t\t\t\t\toperand2 = stack.pop();\n\t\t\t\t\toperand1 = stack.pop();\n\t\t\t\t\tstack.push(operand1.subtract(operand2));\n\t\t\t\t\tbreak;\n\t\t\t\tcase Tokenizer.Token.MULTIPLY_TOKEN:\n\t\t\t\t\tif (errorInExpression(false, stack)) {\n\t\t\t\t\t\treturn Result.syntacticalError();\n\t\t\t\t\t}\n\n\t\t\t\t\toperand2 = stack.pop();\n\t\t\t\t\toperand1 = stack.pop();\n\t\t\t\t\tstack.push(operand1.multiply(operand2));\n\t\t\t\t\tbreak;\n\t\t\t\tcase Tokenizer.Token.DIVIDE_TOKEN:\n\t\t\t\t\tif (errorInExpression(false, stack)) {\n\t\t\t\t\t\treturn Result.syntacticalError();\n\t\t\t\t\t}\n\n\t\t\t\t\toperand2 = stack.pop();\n\t\t\t\t\toperand1 = stack.pop();\n\t\t\t\t\tstack.push(operand1.divide(operand2, MathContext.DECIMAL32));\n\t\t\t\t\tbreak;\n\t\t\t\tcase Tokenizer.Token.EXP_TOKEN:\n\t\t\t\t\tif (errorInExpression(false, stack)) {\n\t\t\t\t\t\treturn Result.syntacticalError();\n\t\t\t\t\t}\n\n\t\t\t\t\toperand2 = stack.pop();\n\t\t\t\t\toperand1 = stack.pop();\n\n\t\t\t\t\tdouble pow = StrictMath.pow(operand1.doubleValue(), operand2.doubleValue());\n\n\t\t\t\t\tif(!isFinite(pow)) {\n\t\t\t\t\t\tthrow new ArithmeticException(\"Not finite result: \"\n\t\t\t\t\t\t\t\t+ operand1.toString() + \"^\" + operand2.toString() + \" = \" + pow);\n\t\t\t\t\t}\n\n\t\t\t\t\tstack.push(new BigDecimal(pow));\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif(stack.size() != 1) {\n\t\t\treturn Result.syntacticalError();\n\t\t}\n\n\t\treturn Result.result(stack.pop());\n\t}\n\n\tprivate static boolean errorInExpression(boolean isUnary, final ArrayDeque<BigDecimal> stack) {\n\t\tboolean error = false;\n\t\tif(isUnary) {\n\t\t\terror = error || stack.size() < 1;\n\t\t} else {\n\t\t\terror = error || stack.size() < 2;\n\t\t}\n\t\treturn error;\n\t}\n\n\t/**\n\t * Returns {@code true} if the argument is a finite floating-point\n\t * value; returns {@code false} otherwise (for NaN and infinity\n\t * arguments).\n\t *\n\t * @param d the {@code double} value to be tested\n\t * @return {@code true} if the argument is a finite\n\t * floating-point value, {@code false} otherwise.\n\t */\n\tpublic static boolean isFinite(double d) {\n\t\tif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n\t\t    return Double.isFinite(d);\n\t\t} else {\n\t\t\treturn Math.abs(d) <= Double.MAX_VALUE;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/calculator/Result.java",
    "content": "package rocks.tbog.tblauncher.calculator;\n\nimport androidx.annotation.NonNull;\n\npublic final class Result<T> {\n\tstatic <T> Result<T> syntacticalError() {\n\t\treturn new Result<>(true);\n\t}\n\n\tstatic <T> Result<T> arithmeticalError() {\n\t\treturn new Result<>(false);\n\t}\n\n\tstatic <T> Result<T> result(@NonNull T result) {\n\t\treturn new Result<>(result);\n\t}\n\n\n\tpublic final T result;\n\tpublic final boolean syntacticalError;\n\tpublic final boolean arithmeticalError;\n\n\tprivate Result(boolean isSyntactical) {\n\t\tthis.syntacticalError = isSyntactical;\n\t\tthis.arithmeticalError = !isSyntactical;\n\t\tthis.result = null;\n\t}\n\n\tprivate Result(@NonNull T result) {\n\t\tthis.result = result;\n\t\tthis.syntacticalError = this.arithmeticalError = false;\n\t}\n}"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/calculator/ShuntingYard.java",
    "content": "package rocks.tbog.tblauncher.calculator;\n\nimport java.util.ArrayDeque;\n\npublic class ShuntingYard {\n\n\tpublic static Result<ArrayDeque<Tokenizer.Token>> infixToPostfix(ArrayDeque<Tokenizer.Token> infix) {\n\t\tArrayDeque<Tokenizer.Token> outQueue = new ArrayDeque<>();\n\t\tArrayDeque<Tokenizer.Token> operatorStack = new ArrayDeque<>();\n\n\t\tfor (Tokenizer.Token token : infix) {\n\t\t\tswitch (token.type) {\n\t\t\t\tcase Tokenizer.Token.NUMBER_TOKEN:\n\t\t\t\t\toutQueue.add(token);\n\t\t\t\t\tbreak;\n\t\t\t\tcase Tokenizer.Token.UNARY_PLUS_TOKEN:\n\t\t\t\tcase Tokenizer.Token.UNARY_MINUS_TOKEN:\n\t\t\t\tcase Tokenizer.Token.SUM_TOKEN:\n\t\t\t\tcase Tokenizer.Token.SUBTRACT_TOKEN:\n\t\t\t\tcase Tokenizer.Token.MULTIPLY_TOKEN:\n\t\t\t\tcase Tokenizer.Token.DIVIDE_TOKEN:\n\t\t\t\tcase Tokenizer.Token.EXP_TOKEN:\n\t\t\t\t\tif (operatorStack.isEmpty()) {\n\t\t\t\t\t\toperatorStack.push(token);\n\t\t\t\t\t} else {\n\t\t\t\t\t\twhile (!operatorStack.isEmpty()) {\n\t\t\t\t\t\t\tint prec1 = token.getPrecedence();\n\t\t\t\t\t\t\tint prec2 = operatorStack.peek().getPrecedence();\n\n\t\t\t\t\t\t\tif ((token.isLeftAssociative() && prec1 <= prec2) || (token.isRightAssociative() && prec1 < prec2)) {\n\t\t\t\t\t\t\t\toutQueue.add(operatorStack.pop());\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\toperatorStack.push(token);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase Tokenizer.Token.PARENTHESIS_OPEN_TOKEN:\n\t\t\t\t\toperatorStack.push(token);\n\t\t\t\t\tbreak;\n\t\t\t\tcase Tokenizer.Token.PARENTHESIS_CLOSE_TOKEN:\n\t\t\t\t\tif(operatorStack.isEmpty()) {\n\t\t\t\t\t\treturn Result.syntacticalError();\n\t\t\t\t\t}\n\n\t\t\t\t\t// until '(' on stack, pop operators.\n\t\t\t\t\twhile (operatorStack.peek().type != Tokenizer.Token.PARENTHESIS_OPEN_TOKEN) {\n\t\t\t\t\t\toutQueue.add(operatorStack.pop());\n\n\t\t\t\t\t\tif(operatorStack.isEmpty()) {\n\t\t\t\t\t\t\treturn Result.syntacticalError();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\toperatorStack.pop();\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\twhile (!operatorStack.isEmpty()) {\n\t\t\toutQueue.addLast(operatorStack.pop());\n\t\t}\n\n\t\treturn Result.result(outQueue);\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/calculator/Tokenizer.java",
    "content": "package rocks.tbog.tblauncher.calculator;\n\nimport java.math.BigDecimal;\nimport java.text.DecimalFormat;\nimport java.text.DecimalFormatSymbols;\nimport java.util.Locale;\nimport java.text.ParseException;\nimport java.util.ArrayDeque;\n\nimport androidx.annotation.NonNull;\n\npublic class Tokenizer {\n\tpublic static final class Token {\n\t\tpublic static final int SUM_TOKEN = 0;\n\t\tpublic static final int SUBTRACT_TOKEN = 1;\n\t\tpublic static final int MULTIPLY_TOKEN = 2;\n\t\tpublic static final int DIVIDE_TOKEN = 3;\n\t\tpublic static final int EXP_TOKEN = 4;\n\n\t\tpublic static final int UNARY_PLUS_TOKEN = 8;\n\t\tpublic static final int UNARY_MINUS_TOKEN = 9;\n\n\t\tpublic static final int NUMBER_TOKEN = 16;\n\t\tpublic static final int PARENTHESIS_OPEN_TOKEN = 17;\n\t\tpublic static final int PARENTHESIS_CLOSE_TOKEN = 18;\n\n\n\t\tpublic final int type;\n\t\tpublic final BigDecimal number;\n\n\t\tpublic Token(int type) {\n\t\t\tif(type != SUM_TOKEN && type != SUBTRACT_TOKEN && type != MULTIPLY_TOKEN && type != DIVIDE_TOKEN\n\t\t\t\t\t&& type != EXP_TOKEN\n\t\t\t\t\t&& type != UNARY_PLUS_TOKEN && type != UNARY_MINUS_TOKEN) {\n\t\t\t\tthrow new IllegalArgumentException(\"Wrong constructor!\");\n\t\t\t}\n\t\t\tthis.type = type;\n\t\t\tnumber = null;\n\t\t}\n\n\t\tpublic Token(@NonNull BigDecimal number) {\n\t\t\tthis.type = NUMBER_TOKEN;\n\t\t\tthis.number = number;\n\t\t}\n\n\t\tpublic Token(boolean isParenthesisOpen) {\n\t\t\tthis.type = isParenthesisOpen? PARENTHESIS_OPEN_TOKEN : PARENTHESIS_CLOSE_TOKEN;\n\t\t\tthis.number = null;\n\t\t}\n\n\t\tpublic final int getPrecedence() {\n\t\t\tswitch (type) {\n\t\t\t\tcase UNARY_PLUS_TOKEN:\n\t\t\t\tcase UNARY_MINUS_TOKEN:\n\t\t\t\t\treturn 4;\n\t\t\t\tcase SUM_TOKEN:\n\t\t\t\tcase SUBTRACT_TOKEN:\n\t\t\t\t\treturn 1;\n\t\t\t\tcase MULTIPLY_TOKEN:\n\t\t\t\tcase DIVIDE_TOKEN:\n\t\t\t\t\treturn 2;\n\t\t\t\tcase EXP_TOKEN:\n\t\t\t\t\treturn 3;\n\t\t\t\tdefault:\n\t\t\t\t\treturn -1;\n\t\t\t}\n\t\t}\n\n\t\tpublic final boolean isRightAssociative() {\n\t\t\tswitch (type) {\n\t\t\t\tcase UNARY_PLUS_TOKEN:\n\t\t\t\tcase UNARY_MINUS_TOKEN:\n\t\t\t\t\treturn true;\n\t\t\t\tcase SUM_TOKEN:\n\t\t\t\tcase SUBTRACT_TOKEN:\n\t\t\t\tcase MULTIPLY_TOKEN:\n\t\t\t\tcase DIVIDE_TOKEN:\n\t\t\t\tcase EXP_TOKEN:\n\t\t\t\t\treturn false;\n\t\t\t\tdefault:\n\t\t\t\t\tthrow new IllegalStateException();\n\t\t\t}\n\t\t}\n\n\t\tpublic final boolean isLeftAssociative() {\n\t\t\treturn !isRightAssociative();\n\t\t}\n\t}\n\n\tpublic static Result<ArrayDeque<Token>> tokenize(String expression) {\n\t\tArrayDeque<Token> tokens = new ArrayDeque<>();\n\n\t\tfor (int i = 0; i < expression.length(); i++) {\n\t\t\tchar operator = expression.charAt(i);\n\n\t\t\tToken token = null;\n\n\t\t\tswitch (operator) {\n\t\t\t\tcase '+':\n\t\t\t\t\tif(!tokens.isEmpty() && (tokens.peekLast().type == Token.NUMBER_TOKEN\n\t\t\t\t\t\t\t|| tokens.peekLast().type == Token.PARENTHESIS_CLOSE_TOKEN)) {\n\t\t\t\t\t\ttoken = new Token(Token.SUM_TOKEN);\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttoken = new Token(Token.UNARY_PLUS_TOKEN);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase '-':\n\t\t\t\t\tif(!tokens.isEmpty() && (tokens.peekLast().type == Token.NUMBER_TOKEN\n\t\t\t\t\t\t\t|| tokens.peekLast().type == Token.PARENTHESIS_CLOSE_TOKEN)) {\n\t\t\t\t\t\ttoken = new Token(Token.SUBTRACT_TOKEN);\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttoken = new Token(Token.UNARY_MINUS_TOKEN);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase '*':\n\t\t\t\t\tif(expression.length() > i+1 && expression.charAt(i+1) == '*') {// '**'\n\t\t\t\t\t\ti++;\n\t\t\t\t\t\ttoken = new Token(Token.EXP_TOKEN);\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttoken = new Token(Token.MULTIPLY_TOKEN);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase '×':\n\t\t\t\tcase 'x':\n\t\t\t\t\ttoken = new Token(Token.MULTIPLY_TOKEN);\n\t\t\t\t\tbreak;\n\t\t\t\tcase '/':\n\t\t\t\tcase '÷':\n\t\t\t\t\ttoken = new Token(Token.DIVIDE_TOKEN);\n\t\t\t\t\tbreak;\n\t\t\t\tcase '^':\n\t\t\t\t\ttoken = new Token(Token.EXP_TOKEN);\n\t\t\t\t\tbreak;\n\t\t\t\tcase '(':\n\t\t\t\t\ttoken = new Token(true);\n\t\t\t\t\tbreak;\n\t\t\t\tcase ')':\n\t\t\t\t\ttoken = new Token(false);\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\t//Numbers\n\t\t\t\t\tStringBuilder numberBuilder = new StringBuilder();\n\n\t\t\t\t\tif(checkOperatorIsPartOfNumber(operator)) {\n\t\t\t\t\t\tnumberBuilder.append(operator);\n\n\t\t\t\t\t\twhile (i+1 < expression.length()) {\n\t\t\t\t\t\t\toperator = expression.charAt(i+1);\n\t\t\t\t\t\t\tif(checkOperatorIsPartOfNumber(operator)) {\n\t\t\t\t\t\t\t\tnumberBuilder.append(operator);\n\t\t\t\t\t\t\t\ti++;\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif(numberBuilder.length() != 0) {\n\t\t\t\t\t\tDecimalFormat decimalFormat = (DecimalFormat) DecimalFormat.getInstance();\n\t\t\t\t\t\tdecimalFormat.setDecimalFormatSymbols(new DecimalFormatSymbols(Locale.US));\n\t\t\t\t\t\tdecimalFormat.setParseBigDecimal(true);\n\n\t\t\t\t\t\tBigDecimal number = null;\n\t\t\t\t\t\tString numberStr = numberBuilder.toString().replace(\",\",\".\");\n\t\t\t\t\t\tif (numberStr.matches(\"[^.]*\\\\.[^.]*\\\\..*\")) {\n\t\t\t\t\t\t\treturn Result.syntacticalError(); // multiple decimal points in one token\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tnumber = (BigDecimal) decimalFormat.parse(numberStr);\n\t\t\t\t\t\t} catch (ParseException e) {\n\t\t\t\t\t\t\treturn Result.syntacticalError();\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\ttoken = new Token(number);\n\t\t\t\t\t}\n\t\t\t}\n\n\t\t\tif(token == null) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\ttokens.addLast(token);\n\t\t}\n\n\t\treturn Result.result(tokens);\n\t}\n\n\tprivate static boolean checkOperatorIsPartOfNumber(char operator) {\n\t\treturn Character.isDigit(operator) || operator == '.' || operator == ',' || operator == 'E'\n\t\t\t\t|| operator == ' ' || operator == '\\'';\n\t}\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/customicon/ButtonHelper.java",
    "content": "package rocks.tbog.tblauncher.customicon;\n\nimport android.content.Context;\nimport android.view.View;\n\nimport androidx.annotation.NonNull;\n\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.ui.ListPopup;\n\npublic class ButtonHelper {\n\n    public static final String BTN_ID_PHONE = \"button://item_contact_action_phone\";\n    public static final String BTN_ID_MESSAGE = \"button://item_contact_action_message\";\n    public static final String BTN_ID_OPEN = \"button://item_contact_action_open\";\n    public static final String BTN_ID_LAUNCHER_PILL = \"button://launcher_pill\";\n    public static final String BTN_ID_LAUNCHER_WHITE = \"button://launcher_white\";\n\n    private ButtonHelper() {\n        // don't instantiate a namespace\n    }\n\n    public static boolean showButtonPopup(@NonNull View view, @NonNull ListPopup buttonMenu) {\n        final Context ctx = view.getContext();\n\n        // check if menu contains elements and if yes show it\n        if (!buttonMenu.getAdapter().isEmpty()) {\n            TBApplication.getApplication(ctx).registerPopup(buttonMenu);\n            buttonMenu.show(view);\n            return true;\n        }\n\n        return false;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/customicon/CustomShapePage.java",
    "content": "package rocks.tbog.tblauncher.customicon;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.graphics.Color;\nimport android.graphics.drawable.Animatable;\nimport android.graphics.drawable.ColorDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.text.Editable;\nimport android.text.TextWatcher;\nimport android.view.View;\nimport android.view.ViewTreeObserver;\nimport android.widget.GridView;\nimport android.widget.ImageView;\nimport android.widget.SeekBar;\nimport android.widget.TextView;\n\nimport androidx.annotation.IdRes;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.StringRes;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.appcompat.content.res.AppCompatResources;\nimport androidx.fragment.app.DialogFragment;\n\nimport com.github.dhaval2404.imagepicker.ImagePicker;\nimport com.github.dhaval2404.imagepicker.constant.ImageProvider;\n\nimport net.mm2d.color.chooser.ColorChooserDialog;\n\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.ListIterator;\nimport java.util.Objects;\n\nimport kotlin.Unit;\nimport rocks.tbog.tblauncher.CustomizeUI;\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.drawable.CodePointDrawable;\nimport rocks.tbog.tblauncher.drawable.DrawableUtils;\nimport rocks.tbog.tblauncher.drawable.FourCodePointDrawable;\nimport rocks.tbog.tblauncher.drawable.TextDrawable;\nimport rocks.tbog.tblauncher.drawable.TwoCodePointDrawable;\nimport rocks.tbog.tblauncher.handler.IconsHandler;\nimport rocks.tbog.tblauncher.utils.UIColors;\nimport rocks.tbog.tblauncher.utils.UISizes;\nimport rocks.tbog.tblauncher.utils.Utilities;\nimport rocks.tbog.tblauncher.utils.ViewHolderAdapter;\nimport rocks.tbog.tblauncher.utils.ViewHolderListAdapter;\n\nclass CustomShapePage extends PageAdapter.Page {\n    protected ShapedIconAdapter mShapesAdapter;\n    protected ShapedIconAdapter mShapedIconAdapter;\n    protected TextView mLettersView;\n    protected int mShape;\n    protected float mScale = 1.f;\n    protected int mBackground;\n    private int mLetters;\n\n    CustomShapePage(CharSequence name, View view) {\n        super(name, view);\n        final Context ctx = view.getContext();\n        mShape = TBApplication.iconsHandler(ctx).getSystemIconPack().getAdaptiveShape();\n        mLetters = UIColors.getContactActionColor(ctx);\n        mBackground = UIColors.getIconBackground(ctx);\n        if (mShape == DrawableUtils.SHAPE_NONE)\n            mShape = DrawableUtils.SHAPE_SQUARE;\n    }\n\n    @Override\n    void setupView(@NonNull DialogFragment dialogFragment, @Nullable OnItemClickListener iconClickListener, @Nullable OnItemClickListener iconLongClickListener) {\n        Context context = dialogFragment.requireContext();\n        mLettersView = pageView.findViewById(R.id.letters);\n        mLettersView.addTextChangedListener(new TextWatcher() {\n            @Override\n            public void beforeTextChanged(CharSequence s, int start, int count, int after) {\n            }\n\n            @Override\n            public void onTextChanged(CharSequence s, int start, int before, int count) {\n                generateTextIcons(s);\n            }\n\n            @Override\n            public void afterTextChanged(Editable s) {\n            }\n        });\n\n        // shape list toggle\n        setupToggle(R.id.shapeGridToggle, R.id.shapeGrid);\n        // scale bar toggle\n        setupToggle(R.id.scaleBarToggle, R.id.scaleBar);\n        // letters toggle\n        setupToggle(R.id.lettersToggle, R.id.lettersGroup);\n\n        addShapesList();\n        addIconsList(iconClickListener);\n        addScaleBar();\n\n        // add icon picker\n        {\n            PickedIconInfo iconInfo = new PickedIconInfo(AppCompatResources.getDrawable(context, R.drawable.ic_browse_add_icon), R.string.browse_add_icon);\n            mShapedIconAdapter.addItem(iconInfo);\n        }\n\n        final float colorPreviewRadius = dialogFragment.getResources().getDimension(R.dimen.color_preview_radius);\n        final int colorPreviewBorder = UISizes.dp2px(context, 1);\n        final int colorPreviewSize = dialogFragment.getResources().getDimensionPixelSize(R.dimen.color_preview_size);\n\n        addBackgroundColorChooser(colorPreviewRadius, colorPreviewBorder, colorPreviewSize);\n        addLetterColorChooser(colorPreviewRadius, colorPreviewBorder, colorPreviewSize);\n\n        generateShapes(context);\n    }\n\n    private void addShapesList() {\n        GridView shapeGridView = pageView.findViewById(R.id.shapeGrid);\n        mShapesAdapter = new ShapedIconAdapter();\n        shapeGridView.setAdapter(mShapesAdapter);\n        shapeGridView.setOnItemClickListener((parent, view, position, id) -> {\n            Activity activity = Utilities.getActivity(view);\n            if (activity == null)\n                return;\n\n            Object objItem = parent.getAdapter().getItem(position);\n            if (!(objItem instanceof NamedIconInfo) || ((NamedIconInfo) objItem).getPreview() == null)\n                return;\n            CharSequence name = ((NamedIconInfo) objItem).name;\n            for (int shape : DrawableUtils.SHAPE_LIST) {\n                if (name.equals(DrawableUtils.shapeName(activity, shape))) {\n                    mShape = shape;\n                    break;\n                }\n            }\n            reshapeIcons(activity);\n        });\n        CustomizeUI.setResultListPref(shapeGridView);\n    }\n\n    private void addIconsList(@Nullable OnItemClickListener iconClickListener) {\n        GridView gridView = pageView.findViewById(R.id.iconGrid);\n        mShapedIconAdapter = new ShapedIconAdapter();\n\n        gridView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {\n            @Override\n            public boolean onPreDraw() {\n                gridView.getViewTreeObserver().removeOnPreDrawListener(this);\n                gridView.setAdapter(mShapedIconAdapter);\n                return false;\n            }\n        });\n        //gridView.setAdapter(mShapedIconAdapter);\n\n        if (iconClickListener != null)\n            gridView.setOnItemClickListener((parent, view, position, id) -> {\n                Object item = parent.getAdapter().getItem(position);\n                if (item instanceof ShapedIconInfo && ((ShapedIconInfo) item).getPreview() != null)\n                    iconClickListener.onItemClick(parent.getAdapter(), view, position);\n            });\n        CustomizeUI.setResultListPref(gridView);\n    }\n\n    private void addScaleBar() {\n        SeekBar seekBar = pageView.findViewById(R.id.scaleBar);\n        seekBar.setMax(200);\n        seekBar.setProgress((int) (100.f * mScale));\n        final Runnable updateIcons = () -> {\n            mScale = 0.01f * seekBar.getProgress();\n            reshapeIcons(seekBar.getContext());\n        };\n        seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {\n            @Override\n            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {\n                seekBar.removeCallbacks(updateIcons);\n                seekBar.post(updateIcons);\n            }\n\n            @Override\n            public void onStartTrackingTouch(SeekBar seekBar) {\n\n            }\n\n            @Override\n            public void onStopTrackingTouch(SeekBar seekBar) {\n                seekBar.removeCallbacks(updateIcons);\n                seekBar.post(updateIcons);\n            }\n        });\n    }\n\n    private void addBackgroundColorChooser(float colorPreviewRadius, int colorPreviewBorder, int colorPreviewSize) {\n        TextView colorView = pageView.findViewById(R.id.backgroundColor);\n        {\n            Drawable drawable = UIColors.getPreviewDrawable(mBackground, colorPreviewBorder, colorPreviewRadius);\n            drawable.setBounds(0, 0, colorPreviewSize, colorPreviewSize);\n            colorView.setCompoundDrawables(null, null, drawable, null);\n        }\n        colorView.setOnClickListener(v -> {\n            Context ctx = v.getContext();\n            launchCustomColorDialog(ctx, mBackground, color -> {\n                mBackground = color;\n                Activity activity = Utilities.getActivity(v);\n                if (activity == null)\n                    return;\n                Drawable drawable = UIColors.getPreviewDrawable(mBackground, colorPreviewBorder, colorPreviewRadius);\n                drawable.setBounds(0, 0, colorPreviewSize, colorPreviewSize);\n                colorView.setCompoundDrawables(null, null, drawable, null);\n                generateShapes(activity);\n                reshapeIcons(activity);\n            });\n        });\n    }\n\n    private void addLetterColorChooser(float colorPreviewRadius, int colorPreviewBorder, int colorPreviewSize) {\n        TextView colorView = pageView.findViewById(R.id.lettersColor);\n        {\n            Drawable drawable = UIColors.getPreviewDrawable(mLetters, colorPreviewBorder, colorPreviewRadius);\n            drawable.setBounds(0, 0, colorPreviewSize, colorPreviewSize);\n            colorView.setCompoundDrawables(null, null, drawable, null);\n        }\n        colorView.setOnClickListener(v -> {\n            Context ctx = v.getContext();\n            launchCustomColorDialog(ctx, mLetters, color -> {\n                mLetters = color;\n                Activity activity = Utilities.getActivity(v);\n                if (activity == null)\n                    return;\n                Drawable drawable = UIColors.getPreviewDrawable(mLetters, colorPreviewBorder, colorPreviewRadius);\n                drawable.setBounds(0, 0, colorPreviewSize, colorPreviewSize);\n                colorView.setCompoundDrawables(null, null, drawable, null);\n                generateTextIcons(mLettersView.getText());\n            });\n        });\n    }\n\n    @Override\n    public void addPickedIcon(@NonNull Drawable pickedImage, String filename) {\n        mShapedIconAdapter.addItem(new PickedIconInfo(pickedImage, filename));\n    }\n\n    private void setupToggle(@IdRes int toggleTextView, @IdRes int viewToToggle) {\n        TextView textView = pageView.findViewById(toggleTextView);\n        textView.setOnClickListener(v -> {\n            View view = pageView.findViewById(viewToToggle);\n            if (\"hide\".equals(v.getTag())) {\n                view.setVisibility(View.GONE);\n                ((TextView) v).setCompoundDrawablesWithIntrinsicBounds(0, 0, android.R.drawable.arrow_down_float, 0);\n                v.setTag(\"show\");\n            } else {\n                view.setVisibility(View.VISIBLE);\n                view.requestFocus();\n                ((TextView) v).setCompoundDrawablesWithIntrinsicBounds(0, 0, android.R.drawable.arrow_up_float, 0);\n                v.setTag(\"hide\");\n            }\n        });\n        if (textView.getTag() == null) {\n            textView.setTag(\"hide\");\n            textView.performClick();\n        }\n    }\n\n    public void addIcon(@NonNull String name, @NonNull Drawable drawable) {\n        Context context = pageView.getContext();\n        Drawable shapedDrawable = DrawableUtils.applyIconMaskShape(context, drawable, mShape, mScale, mBackground);\n        NamedIconInfo iconInfo = new NamedIconInfo(name, shapedDrawable, drawable);\n        mShapedIconAdapter.addItem(iconInfo);\n    }\n\n    private void addTextIcon(CharSequence name, @NonNull TextDrawable icon) {\n        final Context ctx = pageView.getContext();\n        final ShapedIconAdapter adapter = mShapedIconAdapter;\n\n        icon.setTextColor(mLetters);\n        Drawable shapedIcon = DrawableUtils.applyIconMaskShape(ctx, icon, mShape, mScale, mBackground);\n        adapter.addItem(new LetterIconInfo(name, shapedIcon, icon));\n    }\n\n    private void generateTextIcons(@Nullable CharSequence text) {\n        final ShapedIconAdapter adapter = mShapedIconAdapter;\n\n        // remove all TextDrawable icons\n        for (Iterator<ShapedIconInfo> iterator = adapter.getList().iterator(); iterator.hasNext(); ) {\n            ShapedIconInfo info = iterator.next();\n            if (info instanceof LetterIconInfo)\n                iterator.remove();\n        }\n        adapter.notifyDataSetChanged();\n\n        final StringBuilder name = new StringBuilder();\n        final int length = Utilities.codePointsLength(text);\n        int pos = 0;\n        if (length >= 1) {\n            name.appendCodePoint(Character.codePointAt(text, pos));\n            TextDrawable icon = new CodePointDrawable(text);\n            addTextIcon(name.toString(), icon);\n        }\n        // two characters\n        if (length >= 2) {\n            pos = Utilities.getNextCodePointIndex(text, pos);\n            name.appendCodePoint(Character.codePointAt(text, pos));\n            TextDrawable icon = TwoCodePointDrawable.fromText(text, false);\n            addTextIcon(name.toString(), icon);\n        }\n        if (length >= 2) {\n            TextDrawable icon = TwoCodePointDrawable.fromText(text, true);\n            addTextIcon(name.toString(), icon);\n        }\n        // three characters\n        if (length >= 3) {\n            pos = Utilities.getNextCodePointIndex(text, pos);\n            name.appendCodePoint(Character.codePointAt(text, pos));\n            TextDrawable icon = FourCodePointDrawable.fromText(text, true);\n            addTextIcon(name.toString(), icon);\n        }\n        // four characters\n        if (length >= 4) {\n            pos = Utilities.getNextCodePointIndex(text, pos);\n            name.appendCodePoint(Character.codePointAt(text, pos));\n        }\n        if (length >= 3) {\n            TextDrawable icon = FourCodePointDrawable.fromText(text, false);\n            addTextIcon(name.toString(), icon);\n        }\n    }\n\n    private void generateShapes(Context context) {\n        final ShapedIconAdapter adapter = mShapesAdapter;\n        adapter.getList().clear();\n        adapter.notifyDataSetChanged();\n        Drawable drawable = new ColorDrawable(mBackground);\n        for (int shape : DrawableUtils.SHAPE_LIST) {\n            String name = DrawableUtils.shapeName(context, shape);\n            Drawable shapedDrawable;\n            if (shape == DrawableUtils.SHAPE_NONE) {\n                shapedDrawable = new ColorDrawable(Color.TRANSPARENT);\n            } else {\n                shapedDrawable = DrawableUtils.applyIconMaskShape(context, drawable, shape);\n            }\n            NamedIconInfo iconInfo = new NamedIconInfo(name, shapedDrawable, null);\n            adapter.addItem(iconInfo);\n        }\n    }\n\n    private void reshapeIcons(Context context) {\n        //generateTextIcons(null);\n        for (ListIterator<ShapedIconInfo> iterator = mShapedIconAdapter.getList().listIterator(); iterator.hasNext(); ) {\n            ShapedIconInfo iconInfo = iterator.next();\n            if (iconInfo.textId == R.string.icon_pack_loading)\n                continue;\n            if (iconInfo.textId == R.string.default_icon)\n                continue;\n            ShapedIconInfo newInfo = iconInfo.reshape(context, mShape, mScale, mBackground);\n            iterator.set(newInfo);\n        }\n        mShapedIconAdapter.notifyDataSetChanged();\n        //generateTextIcons(mLettersView.getText());\n    }\n\n    interface OnColorChanged {\n        void onColorChanged(int color);\n    }\n\n    private static void launchCustomColorDialog(@Nullable Context context, int selectedColor, @NonNull OnColorChanged listener) {\n        Activity activity = Utilities.getActivity(context);\n        if (!(activity instanceof AppCompatActivity))\n            return;\n\n        ColorChooserDialog.INSTANCE.registerListener((AppCompatActivity) activity, \"request color\", color -> {\n            listener.onColorChanged(color);\n            return Unit.INSTANCE;\n        }, null);\n        ColorChooserDialog.INSTANCE.show((AppCompatActivity) activity, \"request color\", selectedColor, true, ColorChooserDialog.TAB_PALETTE);\n\n//        Context themeWrapper = UITheme.getDialogThemedContext(context);\n//        DialogView dialogView = new DialogView(themeWrapper);\n//\n//        dialogView.init(selectedColor, (AppCompatActivity) activity);\n//        dialogView.setWithAlpha(true);\n//\n//        DialogInterface.OnClickListener buttonListener = (dialog, which) -> {\n//            if (which == DialogInterface.BUTTON_POSITIVE) {\n//                listener.onColorChanged(dialogView.getColor());\n//            }\n//            dialog.dismiss();\n//        };\n//\n//        final AlertDialog.Builder builder = new AlertDialog.Builder(themeWrapper)\n//                .setPositiveButton(android.R.string.ok, buttonListener)\n//                .setNegativeButton(android.R.string.cancel, buttonListener);\n//        builder.setView(dialogView);\n//        DialogHelper.setButtonBarBackground(builder.show());\n    }\n\n    static class LetterIconInfo extends NamedIconInfo {\n\n        LetterIconInfo(CharSequence name, Drawable icon, Drawable text) {\n            super(name, icon, text);\n        }\n\n        @Override\n        protected ShapedIconInfo reshape(Context context, int shape, float scale, int background) {\n            Drawable drawable = DrawableUtils.applyIconMaskShape(context, originalDrawable, shape, scale, background);\n            return new LetterIconInfo(name, drawable, originalDrawable);\n        }\n    }\n\n    static class DefaultIconInfo extends ShapedIconInfo {\n\n        DefaultIconInfo(IconsHandler.IconInfo icon) {\n            super(icon.getDrawable(), icon.getDrawable());\n        }\n\n        @Override\n        public Drawable getIcon() {\n            return null;\n        }\n    }\n\n    static class NamedIconInfo extends ShapedIconInfo {\n        final CharSequence name;\n\n        NamedIconInfo(CharSequence name, Drawable icon, Drawable origin) {\n            super(icon, origin);\n            this.name = name;\n        }\n\n        @Override\n        protected ShapedIconInfo reshape(Context context, int shape, float scale, int background) {\n            Drawable drawable = DrawableUtils.applyIconMaskShape(context, originalDrawable, shape, scale, background);\n            return new NamedIconInfo(name, drawable, originalDrawable);\n        }\n\n        @Nullable\n        @Override\n        CharSequence getText() {\n            return name;\n        }\n    }\n\n    public static class ShapedIconInfo {\n        protected final Drawable originalDrawable;\n        protected final Drawable iconDrawable;\n        @StringRes\n        protected int textId;\n\n        public ShapedIconInfo(Drawable icon, Drawable origin) {\n            iconDrawable = icon;\n            originalDrawable = origin;\n        }\n\n        protected ShapedIconInfo reshape(Context context, int shape, float scale, int background) {\n            Drawable drawable = DrawableUtils.applyIconMaskShape(context, originalDrawable, shape, scale, background);\n            ShapedIconInfo shapedIconInfo = new ShapedIconInfo(drawable, originalDrawable);\n            shapedIconInfo.textId = textId;\n            return shapedIconInfo;\n        }\n\n        public Drawable getIcon() {\n            return iconDrawable;\n        }\n\n        public Drawable getPreview() {\n            return iconDrawable;\n        }\n\n        @Nullable\n        CharSequence getText() {\n            return null;\n        }\n\n        @Override\n        public boolean equals(Object o) {\n            if (this == o)\n                return true;\n            if (o == null || getClass() != o.getClass())\n                return false;\n            ShapedIconInfo that = (ShapedIconInfo) o;\n            return Objects.equals(iconDrawable, that.iconDrawable) &&\n                   Objects.equals(textId, that.textId);\n        }\n\n        @Override\n        public int hashCode() {\n            return Objects.hash(iconDrawable, textId);\n        }\n    }\n\n    public static class ShapedIconVH extends ViewHolderAdapter.ViewHolder<ShapedIconInfo> {\n        View root;\n        ImageView icon;\n        TextView text1;\n\n        public ShapedIconVH(View view) {\n            super(view);\n            root = view;\n            icon = view.findViewById(android.R.id.icon);\n            text1 = view.findViewById(android.R.id.text1);\n        }\n\n        @Override\n        protected void setContent(ShapedIconInfo content, int position, @NonNull ViewHolderAdapter<ShapedIconInfo, ? extends ViewHolderAdapter.ViewHolder<ShapedIconInfo>> adapter) {\n            // set icon\n            Drawable preview = content.getPreview();\n            icon.setImageDrawable(preview);\n            icon.setVisibility(preview == null ? View.GONE : View.VISIBLE);\n            if (preview instanceof Animatable)\n                ((Animatable) preview).start();\n\n            //set text\n            CharSequence text = content.getText();\n            if (text != null)\n                text1.setText(text);\n            else if (content.textId != 0)\n                text1.setText(content.textId);\n            else\n                text1.setText(\"null\");\n        }\n    }\n\n    static class ShapedIconAdapter extends ViewHolderListAdapter<ShapedIconInfo, ShapedIconVH> {\n        protected ShapedIconAdapter() {\n            super(ShapedIconVH.class, R.layout.item_grid, new ArrayList<>());\n        }\n\n        List<ShapedIconInfo> getList() {\n            return mList;\n        }\n\n        void removeItem(ShapedIconInfo item) {\n            mList.remove(item);\n            notifyDataSetChanged();\n        }\n    }\n\n    public static class PickedIconInfo extends ShapedIconInfo {\n        @Nullable\n        String text = null;\n\n        public PickedIconInfo(Drawable icon, @StringRes int textId) {\n            super(icon, icon);\n            this.textId = textId;\n        }\n\n        public PickedIconInfo(Drawable icon, @Nullable String text) {\n            super(icon, icon);\n            this.text = text;\n        }\n\n        public PickedIconInfo(Drawable shaped, Drawable original, @Nullable String text) {\n            super(shaped, original);\n            this.text = text;\n        }\n\n        @Nullable\n        CharSequence getText() {\n            return text;\n        }\n\n        @Override\n        protected ShapedIconInfo reshape(Context context, int shape, float scale, int background) {\n            if (textId != 0)\n                return this;\n            Drawable drawable = DrawableUtils.applyIconMaskShape(context, originalDrawable, shape, scale, background);\n            return new PickedIconInfo(drawable, originalDrawable, text);\n        }\n\n        public boolean launchPicker(@NonNull IconSelectDialog iconSelectDialog, @NonNull View v) {\n            if (textId == 0)\n                return false;\n            Context ctx = v.getContext();\n            int size = UISizes.dp2px(ctx, R.dimen.icon_size) * 2;\n            ImagePicker\n                .with(iconSelectDialog)\n                .cropSquare()\n                .provider(ImageProvider.GALLERY)\n                .maxResultSize(size, size)\n                .saveDir(new File(ctx.getCacheDir(), \"ImagePicker\"))\n                .createIntent(intent -> {\n                    iconSelectDialog.imagePickerResult.launch(intent);\n                    return Unit.INSTANCE;\n                });\n            return true;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/customicon/DefaultButtonPage.java",
    "content": "package rocks.tbog.tblauncher.customicon;\n\nimport android.content.Context;\nimport android.graphics.drawable.Drawable;\nimport android.view.View;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.StringRes;\nimport androidx.core.content.ContextCompat;\nimport androidx.fragment.app.DialogFragment;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.drawable.DrawableUtils;\nimport rocks.tbog.tblauncher.handler.IconsHandler;\n\npublic class DefaultButtonPage extends CustomShapePage {\n    final private int mDefaultIcon;\n    @StringRes\n    final private int mDefaultName;\n    final private String mEntryName;\n\n    DefaultButtonPage(CharSequence name, View view, String entryName, int defaultIcon, @StringRes int defaultName) {\n        super(name, view);\n        mEntryName = entryName;\n        mDefaultIcon = defaultIcon;\n        mDefaultName = defaultName;\n        mScale = DrawableUtils.getScaleToFit(mShape);\n    }\n\n    @Override\n    void setupView(@NonNull DialogFragment dialogFragment, @Nullable OnItemClickListener iconClickListener, @Nullable OnItemClickListener iconLongClickListener) {\n        Context context = dialogFragment.requireContext();\n        super.setupView(dialogFragment, iconClickListener, iconLongClickListener);\n\n        final Drawable originalDrawable;\n        // default icon\n        {\n            originalDrawable = ContextCompat.getDrawable(context, mDefaultIcon);\n            IconsHandler.IconInfo iconHandlerIconInfo = new IconsHandler.IconInfo().setNonAdaptiveIcon(originalDrawable);\n            ShapedIconInfo iconInfo = new DefaultIconInfo(dialogFragment.getString(mDefaultName), iconHandlerIconInfo);\n            mShapedIconAdapter.addItem(iconInfo);\n        }\n\n        // customizable default icon\n        {\n            Drawable shapedDrawable = DrawableUtils.applyIconMaskShape(context, originalDrawable, mShape, mScale, mBackground);\n            ShapedIconInfo iconInfo = new NamedIconInfo(mEntryName, shapedDrawable, originalDrawable);\n            mShapedIconAdapter.addItem(iconInfo);\n        }\n\n        // this will call generateTextIcons\n        mLettersView.setText(mEntryName);\n    }\n\n    static class DefaultIconInfo extends CustomShapePage.DefaultIconInfo {\n        final String name;\n\n        DefaultIconInfo(@NonNull String name, IconsHandler.IconInfo icon) {\n            super(icon);\n            this.name = name;\n            textId = R.string.default_icon;\n        }\n\n        @Nullable\n        @Override\n        CharSequence getText() {\n            return name;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/customicon/IconAdapter.java",
    "content": "package rocks.tbog.tblauncher.customicon;\n\nimport androidx.annotation.NonNull;\n\nimport java.util.List;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.utils.ViewHolderListAdapter;\n\nclass IconAdapter extends ViewHolderListAdapter<IconData, IconViewHolder> {\n\n    IconAdapter(@NonNull List<IconData> objects) {\n        super(IconViewHolder.class, R.layout.custom_icon_item, objects);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/customicon/IconData.java",
    "content": "package rocks.tbog.tblauncher.customicon;\n\nimport android.graphics.drawable.Drawable;\n\nimport rocks.tbog.tblauncher.icons.DrawableInfo;\nimport rocks.tbog.tblauncher.icons.IconPackXML;\n\nclass IconData {\n    final DrawableInfo drawableInfo;\n    final IconPackXML iconPack;\n\n    IconData(IconPackXML iconPack, DrawableInfo drawableInfo) {\n        this.iconPack = iconPack;\n        this.drawableInfo = drawableInfo;\n    }\n\n    Drawable getIcon() {\n        return iconPack.getDrawable(drawableInfo);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/customicon/IconPackPage.java",
    "content": "package rocks.tbog.tblauncher.customicon;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.pm.PackageManager;\nimport android.graphics.drawable.Drawable;\nimport android.text.Editable;\nimport android.text.TextWatcher;\nimport android.util.Log;\nimport android.view.View;\nimport android.widget.BaseAdapter;\nimport android.widget.GridView;\nimport android.widget.ProgressBar;\nimport android.widget.TextView;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.collection.ArraySet;\nimport androidx.fragment.app.DialogFragment;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\n\nimport rocks.tbog.tblauncher.CustomizeUI;\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.icons.DrawableInfo;\nimport rocks.tbog.tblauncher.icons.IconPackXML;\nimport rocks.tbog.tblauncher.normalizer.StringNormalizer;\nimport rocks.tbog.tblauncher.utils.FuzzyScore;\nimport rocks.tbog.tblauncher.utils.UISizes;\nimport rocks.tbog.tblauncher.utils.Utilities;\n\nclass IconPackPage extends PageAdapter.Page {\n    private static final String TAG = IconPackPage.class.getSimpleName();\n    final ArrayList<IconData> iconDataList = new ArrayList<>();\n    final String packageName;\n    private ProgressBar mIconLoadingBar;\n    private GridView mGridView;\n    private TextView mSearch;\n    private IconPackXML mIconPack = null;\n    private final ArraySet<String> mInvalidDrawables = new ArraySet<>(0);\n\n    IconPackPage(CharSequence name, String packPackageName, View view) {\n        super(name, view);\n        packageName = packPackageName;\n    }\n\n    @Override\n    void setupView(@NonNull DialogFragment dialogFragment, @Nullable OnItemClickListener iconClickListener, @Nullable OnItemClickListener iconLongClickListener) {\n        Context context = dialogFragment.requireContext();\n        mIconLoadingBar = pageView.findViewById(R.id.iconLoadingBar);\n\n        Drawable packIcon = null;\n        // set page title\n        TextView textView = pageView.findViewById(android.R.id.text1);\n        textView.setText(dialogFragment.getResources().getString(R.string.icon_pack_content_list, packageName));\n        try {\n            packIcon = context.getPackageManager().getApplicationIcon(packageName);\n        } catch (PackageManager.NameNotFoundException ignored) {\n        }\n        if (packIcon != null) {\n            int size = UISizes.getResultIconSize(context);\n            packIcon.setBounds(0, 0, size, size);\n            textView.setCompoundDrawables(packIcon, null, null, null);\n        }\n\n        // set page icon grid\n        mGridView = pageView.findViewById(R.id.iconGrid);\n        IconAdapter iconAdapter = new IconAdapter(iconDataList);\n        mGridView.setAdapter(iconAdapter);\n        if (iconClickListener != null) {\n            mGridView.setOnItemClickListener((parent, view, position, id) -> iconClickListener.onItemClick(parent.getAdapter(), view, position));\n        }\n        if (iconLongClickListener != null) {\n            mGridView.setOnItemLongClickListener((parent, view, position, id) -> {\n                iconLongClickListener.onItemClick(parent.getAdapter(), view, position);\n                return true;\n            });\n        }\n        CustomizeUI.setResultListPref(mGridView);\n\n        // set page search bar\n        mSearch = pageView.findViewById(R.id.search);\n        mSearch.addTextChangedListener(new TextWatcher() {\n            public void afterTextChanged(Editable s) {\n                // Auto left-trim text.\n                if (s.length() > 0 && s.charAt(0) == ' ')\n                    s.delete(0, 1);\n            }\n\n            public void beforeTextChanged(CharSequence s, int start, int count, int after) {\n            }\n\n            public void onTextChanged(CharSequence s, int start, int before, int count) {\n                mSearch.post(() -> refreshList());\n            }\n        });\n        mSearch.requestFocus();\n\n\n        // show it's loading while we parse the icon pack XML\n        mIconLoadingBar.setVisibility(View.VISIBLE);\n        mGridView.setVisibility(View.GONE);\n    }\n\n    @Override\n    void loadData() {\n        super.loadData();\n\n        final ArraySet<String> invalidDrawables = new ArraySet<>(0);\n        // load the new pack\n        final IconPackXML pack = TBApplication.iconPackCache(pageView.getContext()).getIconPack(packageName);\n        Utilities.runAsync((t) -> {\n            Activity activity = Utilities.getActivity(pageView);\n            if (activity != null) {\n                pack.loadDrawables(activity.getPackageManager());\n                Collection<DrawableInfo> drawables = pack.getDrawableList();\n                for (DrawableInfo info : drawables) {\n                    if (info.getDrawableResId(pack) == 0) {\n                        invalidDrawables.add(info.getDrawableName());\n                    }\n                }\n            }\n        }, (t) -> {\n            Activity activity = Utilities.getActivity(pageView);\n            if (activity != null) {\n                mIconPack = pack;\n                mInvalidDrawables.clear();\n                mInvalidDrawables.addAll(invalidDrawables);\n                int invalidDrawablesSize = mInvalidDrawables.size();\n                if (invalidDrawablesSize > 0) {\n                    Log.w(TAG, \"icon pack `\" + mIconPack.getPackPackageName() + \"` has \" + invalidDrawablesSize + \" drawable(s) without resource id\");\n                }\n                refreshList();\n            } else\n                mIconPack = null;\n        });\n    }\n\n    private void refreshList() {\n        iconDataList.clear();\n        if (mIconPack != null) {\n            Collection<DrawableInfo> drawables = mIconPack.getDrawableList();\n            StringNormalizer.Result normalized = StringNormalizer.normalizeWithResult(mSearch.getText(), true);\n            FuzzyScore fuzzyScore = new FuzzyScore(normalized.codePoints);\n            for (DrawableInfo info : drawables) {\n                if (mInvalidDrawables.contains(info.getDrawableName()))\n                    continue;\n                if (fuzzyScore.match(info.getDrawableName()).match)\n                    iconDataList.add(new IconData(mIconPack, info));\n            }\n        }\n        mIconLoadingBar.setVisibility(View.GONE);\n        boolean showGridAndSearch = !iconDataList.isEmpty() || (mSearch.length() > 0);\n        mSearch.setVisibility(showGridAndSearch ? View.VISIBLE : View.GONE);\n        mGridView.setVisibility(showGridAndSearch ? View.VISIBLE : View.GONE);\n        ((BaseAdapter) mGridView.getAdapter()).notifyDataSetChanged();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/customicon/IconSelectDialog.java",
    "content": "package rocks.tbog.tblauncher.customicon;\n\nimport android.app.Activity;\nimport android.app.Dialog;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.database.Cursor;\nimport android.graphics.drawable.Drawable;\nimport android.net.Uri;\nimport android.os.Bundle;\nimport android.provider.OpenableColumns;\nimport android.util.Pair;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.TextView;\nimport android.widget.Toast;\n\nimport androidx.activity.result.ActivityResultLauncher;\nimport androidx.activity.result.contract.ActivityResultContracts;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.StringRes;\nimport androidx.core.content.res.ResourcesCompat;\nimport androidx.viewpager.widget.ViewPager;\n\nimport com.github.dhaval2404.imagepicker.ImagePicker;\n\nimport java.io.InputStream;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\n\nimport rocks.tbog.tblauncher.CustomizeUI;\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.db.DBHelper;\nimport rocks.tbog.tblauncher.db.ShortcutRecord;\nimport rocks.tbog.tblauncher.entry.EntryItem;\nimport rocks.tbog.tblauncher.entry.SearchEntry;\nimport rocks.tbog.tblauncher.entry.ShortcutEntry;\nimport rocks.tbog.tblauncher.entry.StaticEntry;\nimport rocks.tbog.tblauncher.handler.IconsHandler;\nimport rocks.tbog.tblauncher.icons.IconPack;\nimport rocks.tbog.tblauncher.result.ResultViewHelper;\nimport rocks.tbog.tblauncher.ui.DialogFragment;\nimport rocks.tbog.tblauncher.ui.DialogWrapper;\nimport rocks.tbog.tblauncher.ui.LinearAdapter;\nimport rocks.tbog.tblauncher.ui.ListPopup;\nimport rocks.tbog.tblauncher.utils.UISizes;\nimport rocks.tbog.tblauncher.utils.UserHandleCompat;\nimport rocks.tbog.tblauncher.utils.Utilities;\n\npublic class IconSelectDialog extends DialogFragment<Drawable> {\n    private Drawable mSelectedDrawable = null;\n    private ViewPager mViewPager;\n    private CustomShapePage mCustomShapePage = null;\n    private TextView mPreviewLabel;\n\n    ActivityResultLauncher<Intent> imagePickerResult;\n\n    @Override\n    public void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        imagePickerResult =\n            registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {\n                Context context = IconSelectDialog.this.requireContext();\n                int resultCode = result.getResultCode();\n                Intent data = result.getData();\n                Uri imageUri = data != null ? data.getData() : null;\n                if (resultCode == Activity.RESULT_OK && imageUri != null) {\n                    Drawable imageDrawable;\n                    try (InputStream is = context.getContentResolver().openInputStream(imageUri)) {\n                        imageDrawable = Drawable.createFromStream(is, imageUri.toString());\n                    } catch (Throwable ignore) {\n                        imageDrawable = null;\n                    }\n\n                    String filename = getFileName(context, imageUri);\n\n                    if (imageDrawable != null)\n                        IconSelectDialog.this.addPickedIcon(imageDrawable, filename);\n                } else if (resultCode == ImagePicker.RESULT_ERROR) {\n                    Toast.makeText(context, ImagePicker.getError(data), Toast.LENGTH_SHORT).show();\n                }\n            });\n\n    }\n\n    public static String getFileName(@NonNull Context context, @NonNull Uri uri) {\n        String result = null;\n        if (uri.getScheme().equals(\"content\")) {\n            try (Cursor cursor = context.getContentResolver().query(uri, null, null, null, null)) {\n                if (cursor != null && cursor.moveToFirst()) {\n                    int columnIdx = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);\n                    if (columnIdx != -1)\n                        result = cursor.getString(columnIdx);\n                }\n            }\n        }\n        if (result == null) {\n            result = uri.getPath();\n            int cut = result.lastIndexOf('/');\n            if (cut != -1) {\n                result = result.substring(cut + 1);\n            }\n        }\n        return result;\n    }\n\n    private void addPickedIcon(@NonNull Drawable pickedImage, String filename) {\n        if (!(mViewPager.getAdapter() instanceof PageAdapter))\n            return;\n        PageAdapter pageAdapter = (PageAdapter) mViewPager.getAdapter();\n        for (PageAdapter.Page page : pageAdapter.getPageIterable()) {\n            page.addPickedIcon(pickedImage, filename);\n        }\n    }\n\n    @Override\n    protected int layoutRes() {\n        return R.layout.dialog_icon_select;\n    }\n\n    @Nullable\n    @Override\n    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {\n        // make sure we use the dialog context\n        inflater = inflater.cloneInContext(requireDialog().getContext());\n        View view = super.onCreateView(inflater, container, savedInstanceState);\n        if (view == null)\n            return null;\n\n        mPreviewLabel = view.findViewById(R.id.previewLabel);\n        mViewPager = view.findViewById(R.id.viewPager);\n\n        CustomizeUI.setResultListPref(mPreviewLabel);\n        ResultViewHelper.applyResultItemShadow(mPreviewLabel);\n\n        PageAdapter pageAdapter = new PageAdapter();\n        mViewPager.setAdapter(pageAdapter);\n\n        // add system icons\n        addSystemIcons(inflater);\n\n        // add icon packs\n        ArrayList<Pair<String, String>> iconPacks = addIconPacks(inflater);\n\n        pageAdapter.notifyDataSetChanged();\n\n        pageAdapter.setupPageView(this);\n\n        if (mCustomShapePage instanceof SystemPage) {\n            ((SystemPage) mCustomShapePage).loadIconPackIcons(iconPacks);\n        }\n\n        return view;\n    }\n\n    /**\n     * Add ViewPager pages for every icon pack\n     *\n     * @param inflater used for inflating the page view\n     * @return a list of pairs with the icon pack package name and icon pack name\n     */\n    @NonNull\n    private ArrayList<Pair<String, String>> addIconPacks(LayoutInflater inflater) {\n        Context context = inflater.getContext();\n        IconsHandler iconsHandler = TBApplication.iconsHandler(context);\n        Map<String, String> iconPackNames = iconsHandler.getIconPackNames();\n        ArrayList<Pair<String, String>> iconPacks = new ArrayList<>(iconPackNames.size());\n        for (Map.Entry<String, String> packInfo : iconPackNames.entrySet())\n            iconPacks.add(new Pair<>(packInfo.getKey(), packInfo.getValue()));\n        IconPack<?> iconPack = iconsHandler.getCustomIconPack();\n        String selectedPackPackageName = iconPack != null ? iconPack.getPackPackageName() : \"\";\n        Collections.sort(iconPacks, (o1, o2) -> {\n            if (selectedPackPackageName.equals(o1.first))\n                return -1;\n            if (selectedPackPackageName.equals(o2.first))\n                return 1;\n            return o1.second.compareTo(o2.second);\n        });\n        for (Pair<String, String> packInfo : iconPacks) {\n            String packPackageName = packInfo.first;\n            String packName = packInfo.second;\n            if (selectedPackPackageName.equals(packPackageName))\n                packName = context.getString(R.string.selected_pack, packName);\n\n            // add page to ViewPager\n            addIconPackPage(inflater, mViewPager, packName, packPackageName);\n        }\n        return iconPacks;\n    }\n\n    /**\n     * Add ViewPager page for system icons\n     *\n     * @param inflater used for inflating the page view\n     */\n    private void addSystemIcons(LayoutInflater inflater) {\n        Context context = inflater.getContext();\n        Bundle args = getArguments() != null ? getArguments() : new Bundle();\n        if (args.containsKey(\"componentName\")) {\n            String name = args.getString(\"componentName\", \"\");\n            String entryName = args.getString(\"entryName\", \"\");\n            String pageName = context.getString(R.string.tab_app_icons, entryName);\n\n            ComponentName cn = UserHandleCompat.unflattenComponentName(name);\n            UserHandleCompat userHandle = UserHandleCompat.fromComponentName(context, name);\n\n            mCustomShapePage = addSystemPage(inflater, cn, userHandle, pageName);\n        } else if (args.containsKey(\"entryId\")) {\n            String entryId = args.getString(\"entryId\", \"\");\n            EntryItem entryItem = TBApplication.dataHandler(context).getPojo(entryId);\n            if (!(entryItem instanceof StaticEntry)) {\n                dismiss();\n                Toast.makeText(Utilities.getActivity(context), context.getString(R.string.entry_not_found, entryId), Toast.LENGTH_LONG).show();\n            } else {\n                StaticEntry staticEntry = (StaticEntry) entryItem;\n                String pageName = context.getString(R.string.tab_static_icons);\n                mCustomShapePage = addStaticEntryPage(inflater, staticEntry, pageName);\n            }\n        } else if (args.containsKey(\"shortcutId\")) {\n            String packageName = args.getString(\"packageName\", \"\");\n            String shortcutData = args.getString(\"shortcutData\", \"\");\n            ShortcutRecord shortcutRecord = null;\n            List<ShortcutRecord> shortcutRecordList = DBHelper.getShortcutsNoIcons(context, packageName);\n            for (ShortcutRecord rec : shortcutRecordList)\n                if (shortcutData.equals(rec.infoData)) {\n                    shortcutRecord = rec;\n                    break;\n                }\n            if (shortcutRecord == null) {\n                dismiss();\n                String shortcutId = args.getString(\"shortcutId\", \"\");\n                Toast.makeText(Utilities.getActivity(context), context.getString(R.string.entry_not_found, shortcutId), Toast.LENGTH_LONG).show();\n            } else {\n                mCustomShapePage = addShortcutPage(inflater, shortcutRecord, shortcutRecord.displayName);\n            }\n        } else if (args.containsKey(\"searchEntryId\")) {\n            String entryName = args.getString(\"searchName\", \"\");\n            String pageName = context.getString(R.string.tab_search_icon);\n            mCustomShapePage = addSearchEntryPage(inflater, entryName, pageName);\n        } else if (args.containsKey(\"buttonId\")) {\n            int defaultIcon = args.getInt(\"defaultIcon\");\n            String pageName = context.getString(R.string.tab_button_icon);\n            mCustomShapePage = addButtonPage(inflater, defaultIcon, pageName);\n        }\n    }\n\n\n    @Override\n    public void onStart() {\n        super.onStart();\n        Dialog dialog = getDialog();\n        if (dialog instanceof DialogWrapper) {\n            ((DialogWrapper) dialog).setOnWindowFocusChanged((dlg, hasFocus) -> {\n                if (hasFocus) {\n                    dlg.setOnWindowFocusChanged(null);\n\n                    //hack: fix the height of the dialog so it doesn't flicker\n                    setFixedHeight(getView());\n                }\n            });\n        }\n    }\n\n    private void setFixedHeight(View view) {\n        ViewGroup.LayoutParams params = view.getLayoutParams();\n        params.height = view.getMeasuredHeight();\n        view.setLayoutParams(params);\n    }\n\n    public void setSelectedDrawable(Drawable selected, Drawable preview) {\n        Context context = mViewPager.getContext();\n        mSelectedDrawable = selected;\n        @StringRes\n        int label = mSelectedDrawable == null ? R.string.default_icon_preview_label : R.string.custom_icon_preview_label;\n        mPreviewLabel.setText(label);\n        int size = UISizes.getResultIconSize(context);\n        Drawable icon = preview.getConstantState().newDrawable(context.getResources());\n        icon.setBounds(0, 0, size, size);\n        mPreviewLabel.setCompoundDrawables(null, null, icon, null);\n    }\n\n    private void addIconPackPage(@NonNull LayoutInflater inflater, ViewGroup container, String packName, String packPackageName) {\n        View view = inflater.inflate(R.layout.dialog_icon_select_page, container, false);\n        IconPackPage page = new IconPackPage(packName, packPackageName, view);\n        PageAdapter adapter = (PageAdapter) mViewPager.getAdapter();\n        if (adapter != null)\n            adapter.addPage(page);\n    }\n\n    private CustomShapePage addCustomShapePage(CustomShapePage page) {\n        PageAdapter adapter = (PageAdapter) mViewPager.getAdapter();\n        if (adapter != null)\n            adapter.addPage(page);\n        return page;\n    }\n\n    private CustomShapePage addSystemPage(LayoutInflater inflater, ComponentName cn, UserHandleCompat userHandle, String pageName) {\n        View view = inflater.inflate(R.layout.dialog_custom_shape_icon_select_page, mViewPager, false);\n        CustomShapePage page = new SystemPage(pageName, view, cn, userHandle);\n        return addCustomShapePage(page);\n    }\n\n    private CustomShapePage addStaticEntryPage(LayoutInflater inflater, StaticEntry staticEntry, String pageName) {\n        View view = inflater.inflate(R.layout.dialog_custom_shape_icon_select_page, mViewPager, false);\n        CustomShapePage page = new StaticEntryPage(pageName, view, staticEntry);\n        return addCustomShapePage(page);\n    }\n\n    private CustomShapePage addSearchEntryPage(LayoutInflater inflater, String entryName, String pageName) {\n        View view = inflater.inflate(R.layout.dialog_custom_shape_icon_select_page, mViewPager, false);\n        CustomShapePage page = new DefaultButtonPage(pageName, view, entryName, R.drawable.ic_search, R.string.default_static_icon);\n        return addCustomShapePage(page);\n    }\n\n    private CustomShapePage addShortcutPage(LayoutInflater inflater, ShortcutRecord shortcutRecord, String pageName) {\n        View view = inflater.inflate(R.layout.dialog_custom_shape_icon_select_page, mViewPager, false);\n        CustomShapePage page = new ShortcutPage(pageName, view, shortcutRecord);\n        return addCustomShapePage(page);\n    }\n\n    private CustomShapePage addButtonPage(LayoutInflater inflater, int defaultIcon, String pageName) {\n        View view = inflater.inflate(R.layout.dialog_custom_shape_icon_select_page, mViewPager, false);\n        CustomShapePage page = new DefaultButtonPage(pageName, view, \"\", defaultIcon, R.string.default_icon);\n        return addCustomShapePage(page);\n    }\n\n    public ListPopup getIconPackMenu(IconData iconData) {\n        final Context ctx = requireContext();\n        LinearAdapter adapter = new LinearAdapter();\n\n        adapter.add(new LinearAdapter.ItemTitle(iconData.drawableInfo.getDrawableName()));\n        adapter.add(new LinearAdapter.Item(ctx, R.string.choose_icon_menu_add));\n        adapter.add(new LinearAdapter.Item(ctx, R.string.choose_icon_menu_add2));\n\n        return ListPopup.create(ctx, adapter)\n            .setModal(true)\n            .setOnItemClickListener((a, v, pos) -> {\n                LinearAdapter.MenuItem item = ((LinearAdapter) a).getItem(pos);\n                @StringRes int stringId = 0;\n                if (item instanceof LinearAdapter.Item) {\n                    stringId = ((LinearAdapter.Item) a.getItem(pos)).stringId;\n                }\n                if (stringId == R.string.choose_icon_menu_add2) {\n                    if (mCustomShapePage != null)\n                        mCustomShapePage.addIcon(iconData.drawableInfo.getDrawableName(), iconData.getIcon());\n                    // set the first page as current\n                    mViewPager.setCurrentItem(0);\n                } else if (stringId == R.string.choose_icon_menu_add) {\n                    if (mCustomShapePage != null)\n                        mCustomShapePage.addIcon(iconData.drawableInfo.getDrawableName(), iconData.getIcon());\n                }\n            });\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n\n        Bundle args = getArguments() != null ? getArguments() : new Bundle();\n        if (args.containsKey(\"componentName\"))\n            customIconApp(args);\n        else if (args.containsKey(\"entryId\"))\n            customIconStaticEntry(args);\n        else if (args.containsKey(\"shortcutId\"))\n            customIconShortcut(args);\n        else if (args.containsKey(\"searchEntryId\"))\n            customIconSearchEntry(args);\n        else if (args.containsKey(\"buttonId\"))\n            customIconButton(args);\n        else {\n            dismiss();\n            Context ctx = requireContext();\n            Toast.makeText(Utilities.getActivity(ctx), ctx.getString(R.string.entry_not_found, \"\"), Toast.LENGTH_LONG).show();\n            return;\n        }\n\n        // OK button\n        {\n            View button = view.findViewById(android.R.id.button1);\n            button.setOnClickListener(v -> {\n                onConfirm(mSelectedDrawable);\n                dismiss();\n            });\n        }\n\n        // CANCEL button\n        {\n            View button = view.findViewById(android.R.id.button2);\n            button.setOnClickListener(v -> dismiss());\n        }\n    }\n\n    private void customIconApp(Bundle args) {\n        Context context = requireContext();\n\n        String name = args.getString(\"componentName\", \"\");\n        long customIcon = args.getLong(\"customIcon\", 0);\n        if (name.isEmpty()) {\n            dismiss();\n            String entryName = args.getString(\"entryName\", \"\");\n            Toast.makeText(Utilities.getActivity(context), context.getString(R.string.entry_not_found, entryName), Toast.LENGTH_LONG).show();\n            return;\n        }\n\n        IconsHandler iconsHandler = TBApplication.getApplication(context).iconsHandler();\n        ComponentName cn = UserHandleCompat.unflattenComponentName(name);\n        UserHandleCompat userHandle = UserHandleCompat.fromComponentName(context, name);\n\n        // Preview\n        initPreviewIcon(mPreviewLabel, ctx -> {\n            Drawable drawable = customIcon != 0 ? iconsHandler.getCustomIcon(name) : null;\n            if (drawable == null)\n                drawable = iconsHandler.getDrawableIconForPackage(cn, userHandle);\n            return drawable;\n        });\n    }\n\n    private static void initPreviewIcon(TextView preview, Utilities.GetDrawable asyncGet) {\n        Utilities.setViewAsync(preview, asyncGet, (view, drawable) -> {\n            Context ctx = view.getContext();\n            int size = UISizes.getResultIconSize(ctx);\n            Drawable icon = drawable.mutate();\n            icon.setBounds(0, 0, size, size);\n            ((TextView) view).setCompoundDrawables(null, null, icon, null);\n            int radius = UISizes.getResultListRadius(ctx);\n            int paddingTop = view.getPaddingTop();\n            int paddingBottom = view.getPaddingBottom();\n            view.setPadding(radius / 2, paddingTop, radius / 2, paddingBottom);\n        });\n    }\n\n    private void customIconStaticEntry(Bundle args) {\n        Context context = requireContext();\n\n        String entryId = args.getString(\"entryId\", \"\");\n        EntryItem entryItem = TBApplication.dataHandler(context).getPojo(entryId);\n        if (!(entryItem instanceof StaticEntry)) {\n            dismiss();\n            Toast.makeText(Utilities.getActivity(context), context.getString(R.string.entry_not_found, entryId), Toast.LENGTH_LONG).show();\n            return;\n        }\n        StaticEntry staticEntry = (StaticEntry) entryItem;\n\n        // Preview\n        initPreviewIcon(mPreviewLabel, staticEntry::getIconDrawable);\n    }\n\n    private void customIconSearchEntry(Bundle args) {\n        Context context = requireContext();\n\n        String entryId = args.getString(\"searchEntryId\", \"\");\n        EntryItem entryItem = TBApplication.dataHandler(context).getPojo(entryId);\n        if (!(entryItem instanceof SearchEntry)) {\n            dismiss();\n            Toast.makeText(Utilities.getActivity(context), context.getString(R.string.entry_not_found, entryId), Toast.LENGTH_LONG).show();\n            return;\n        }\n        SearchEntry searchEntry = (SearchEntry) entryItem;\n\n        // Preview\n        initPreviewIcon(mPreviewLabel, searchEntry::getIconDrawable);\n    }\n\n    private void customIconShortcut(Bundle args) {\n        Context context = requireContext();\n\n        String shortcutId = args.getString(\"shortcutId\", \"\");\n\n        EntryItem entryItem = TBApplication.dataHandler(context).getPojo(shortcutId);\n        if (!(entryItem instanceof ShortcutEntry)) {\n            dismiss();\n            Toast.makeText(Utilities.getActivity(context), context.getString(R.string.entry_not_found, shortcutId), Toast.LENGTH_LONG).show();\n            return;\n        }\n        ShortcutEntry shortcutEntry = (ShortcutEntry) entryItem;\n\n        // Preview\n        initPreviewIcon(mPreviewLabel, shortcutEntry::getIcon);\n    }\n\n    private void customIconButton(Bundle args) {\n        final int defaultIcon = args.getInt(\"defaultIcon\", 0);\n        final String buttonId = args.getString(\"buttonId\", null);\n        initPreviewIcon(mPreviewLabel, ctx -> {\n            if (buttonId != null) {\n                Drawable buttonIcon = TBApplication.iconsHandler(ctx).getButtonIcon(buttonId);\n                if (buttonIcon != null)\n                    return buttonIcon;\n            }\n            return ResourcesCompat.getDrawable(getResources(), defaultIcon, null);\n        });\n    }\n\n    @Override\n    public void onActivityCreated(@Nullable Bundle savedInstanceState) {\n        super.onActivityCreated(savedInstanceState);\n\n        PageAdapter adapter = (PageAdapter) mViewPager.getAdapter();\n        if (adapter != null) {\n            int selectedPage = mViewPager.getCurrentItem();\n            // allow the adapter to load as needed\n            mViewPager.addOnPageChangeListener(adapter);\n            // make sure we load the selected page\n            adapter.onPageSelected(selectedPage);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/customicon/IconViewHolder.java",
    "content": "package rocks.tbog.tblauncher.customicon;\n\nimport android.graphics.drawable.Drawable;\nimport android.util.Log;\nimport android.view.View;\nimport android.widget.ImageView;\n\nimport androidx.annotation.NonNull;\n\nimport java.lang.ref.WeakReference;\n\nimport rocks.tbog.tblauncher.WorkAsync.AsyncTask;\nimport rocks.tbog.tblauncher.WorkAsync.TaskRunner;\nimport rocks.tbog.tblauncher.result.ResultViewHelper;\nimport rocks.tbog.tblauncher.utils.ViewHolderAdapter;\n\npublic class IconViewHolder extends ViewHolderAdapter.ViewHolder<IconData> {\n    private final ImageView icon;\n    private AsyncLoad loader = null;\n\n    public IconViewHolder(View view) {\n        super(view);\n        icon = view.findViewById(android.R.id.icon);\n    }\n\n    @Override\n    protected void setContent(IconData content, int position, @NonNull ViewHolderAdapter<IconData, ? extends ViewHolderAdapter.ViewHolder<IconData>> adapter) {\n        if (loader != null)\n            loader.cancel(false);\n        loader = new AsyncLoad(this);\n        loader.execute(content);\n    }\n\n    static class AsyncLoad extends AsyncTask<IconData, Drawable> {\n        private static final String TAG = AsyncLoad.class.getSimpleName();\n        private final WeakReference<IconViewHolder> holder;\n\n        protected AsyncLoad(IconViewHolder holder) {\n            super();\n            this.holder = new WeakReference<>(holder);\n        }\n\n        @Override\n        protected void onPreExecute() {\n            IconViewHolder h = holder.get();\n            if (h == null || h.loader != this)\n                return;\n            h.icon.setImageDrawable(null);\n        }\n\n        @Override\n        protected Drawable doInBackground(IconData iconData) {\n            Drawable drawable = iconData.getIcon();\n            if (drawable == null)\n                Log.w(TAG, \"drawable `\" + iconData.drawableInfo.getDrawableName() + \"` from icon pack `\" + iconData.iconPack.getPackPackageName() + \"` doesn't load\");\n            return drawable;\n        }\n\n        @Override\n        protected void onPostExecute(Drawable drawable) {\n            if (drawable == null)\n                return;\n            IconViewHolder h = holder.get();\n            if (h == null || h.loader != this)\n                return;\n            h.loader = null;\n            h.icon.setImageDrawable(drawable);\n            h.icon.setScaleX(0f);\n            h.icon.setScaleY(0f);\n            h.icon.setRotation((drawable.hashCode() & 1) == 1 ? 180f : -180f);\n            h.icon.animate().scaleX(1f).scaleY(1f).rotation(0f).start();\n        }\n\n        public void execute(IconData content) {\n            TaskRunner.executeOnExecutor(ResultViewHelper.EXECUTOR_LOAD_ICON, this, content);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/customicon/PageAdapter.java",
    "content": "package rocks.tbog.tblauncher.customicon;\n\nimport android.graphics.drawable.Drawable;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.Adapter;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.fragment.app.DialogFragment;\nimport androidx.viewpager.widget.ViewPager;\n\nimport java.util.ArrayList;\n\nclass PageAdapter extends androidx.viewpager.widget.PagerAdapter implements ViewPager.OnPageChangeListener {\n\n    private final ArrayList<Page> pageList = new ArrayList<>(0);\n    private int mScrollState = ViewPager.SCROLL_STATE_IDLE;\n\n    void addPage(Page page) {\n        pageList.add(page);\n    }\n\n    @NonNull\n    Iterable<Page> getPageIterable() {\n        return pageList;\n    }\n\n    public void setupPageView(@NonNull IconSelectDialog iconSelectDialog) {\n        // touch listener\n        Page.OnItemClickListener iconClickListener = (adapter, v, position) -> {\n            if (adapter instanceof IconAdapter) {\n                IconData item = ((IconAdapter) adapter).getItem(position);\n                Drawable icon = item.getIcon();\n                iconSelectDialog.setSelectedDrawable(icon, icon);\n            } else if (adapter instanceof CustomShapePage.ShapedIconAdapter) {\n                CustomShapePage.ShapedIconInfo item = ((CustomShapePage.ShapedIconAdapter) adapter).getItem(position);\n                if (item instanceof SystemPage.PickedIconInfo) {\n                    if (((SystemPage.PickedIconInfo) item).launchPicker(iconSelectDialog, v))\n                        return;\n                }\n                iconSelectDialog.setSelectedDrawable(item.getIcon(), item.getPreview());\n            }\n        };\n        // long touch listener\n        Page.OnItemClickListener iconLongClickListener = (adapter, v, position) -> {\n            if (adapter instanceof IconAdapter) {\n                IconData item = ((IconAdapter) adapter).getItem(position);\n                iconSelectDialog.getIconPackMenu(item).show(v);\n            }\n        };\n        // setup pages\n        for (Page page : getPageIterable())\n            page.setupView(iconSelectDialog, iconClickListener, iconLongClickListener);\n    }\n\n    @Override\n    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {\n        //Log.d(\"ISDialog\", String.format(\"onPageScrolled %d %.2f\", position, positionOffset));\n        if (mScrollState != ViewPager.SCROLL_STATE_SETTLING) {\n            Page pageLeft = pageList.get(position);\n            if (!pageLeft.bDataLoaded)\n                pageLeft.loadData();\n            if ((position + 1) < pageList.size()) {\n                Page pageRight = pageList.get(position + 1);\n                if (!pageRight.bDataLoaded)\n                    pageRight.loadData();\n            }\n        }\n    }\n\n    @Override\n    public void onPageSelected(int position) {\n        //Log.d(\"ISDialog\", String.format(\"onPageSelected %d\", position));\n        Page page = pageList.get(position);\n        if (!page.bDataLoaded)\n            page.loadData();\n    }\n\n    @Override\n    public void onPageScrollStateChanged(int state) {\n        //Log.d(\"ISDialog\", String.format(\"onPageScrollStateChanged %d\", state));\n        mScrollState = state;\n    }\n\n    static abstract class Page {\n        final CharSequence pageName;\n        final View pageView;\n        boolean bDataLoaded = false;\n\n        public interface OnItemClickListener {\n            void onItemClick(Adapter adapter, View view, int position);\n        }\n\n        Page(CharSequence name, View view) {\n            pageName = name;\n            pageView = view;\n        }\n\n        abstract void setupView(@NonNull DialogFragment dialogFragment, @Nullable OnItemClickListener iconClickListener, @Nullable OnItemClickListener iconLongClickListener);\n\n        public void addPickedIcon(@NonNull Drawable pickedImage, String filename) {\n            // do nothing in the base class, override to handle image picked from gallery\n        }\n\n        void loadData() {\n            bDataLoaded = true;\n        }\n    }\n\n    @Override\n    public int getCount() {\n        return pageList.size();\n    }\n\n    @Override\n    public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {\n        if (!(object instanceof Page))\n            throw new IllegalStateException(\"WTF?\");\n        return ((Page) object).pageView == view;\n    }\n\n    @Nullable\n    @Override\n    public CharSequence getPageTitle(int position) {\n        return pageList.get(position).pageName;\n    }\n\n    @NonNull\n    @Override\n    public Object instantiateItem(@NonNull ViewGroup container, int position) {\n        Page page = pageList.get(position);\n        container.addView(page.pageView);\n        return page;\n    }\n\n    @Override\n    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {\n        if (!(object instanceof Page))\n            throw new IllegalStateException(\"WTF?\");\n        Page page = (Page) object;\n        container.removeView(page.pageView);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/customicon/ShortcutPage.java",
    "content": "package rocks.tbog.tblauncher.customicon;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.BitmapDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.view.View;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.fragment.app.DialogFragment;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.db.ShortcutRecord;\nimport rocks.tbog.tblauncher.handler.IconsHandler;\nimport rocks.tbog.tblauncher.shortcut.ShortcutUtil;\nimport rocks.tbog.tblauncher.drawable.DrawableUtils;\n\npublic class ShortcutPage extends CustomShapePage {\n    private final ShortcutRecord mShortcutRecord;\n\n    ShortcutPage(CharSequence name, View view, ShortcutRecord shortcutRecord) {\n        super(name, view);\n        mShortcutRecord = shortcutRecord;\n    }\n\n    @Override\n    void setupView(@NonNull DialogFragment dialogFragment, @Nullable OnItemClickListener iconClickListener, @Nullable OnItemClickListener iconLongClickListener) {\n        Context context = dialogFragment.requireContext();\n        super.setupView(dialogFragment, iconClickListener, iconLongClickListener);\n\n        final Drawable defaultIcon;\n        // default icon\n        {\n            Bitmap bitmap = ShortcutUtil.getInitialIcon(context, mShortcutRecord.dbId);\n            defaultIcon = new BitmapDrawable(dialogFragment.getResources(), bitmap);\n            Drawable drawable = TBApplication.iconsHandler(context).applyShortcutMask(context, bitmap);\n            IconsHandler.IconInfo iconHandlerIconInfo = new IconsHandler.IconInfo().setNonAdaptiveIcon(drawable);\n            ShapedIconInfo iconInfo = new StaticEntryPage.DefaultIconInfo(dialogFragment.getString(R.string.default_static_icon, mShortcutRecord.displayName), iconHandlerIconInfo);\n            iconInfo.textId = R.string.default_icon;\n            mShapedIconAdapter.addItem(iconInfo);\n        }\n\n        // add background\n        {\n            Drawable shapedDrawable = DrawableUtils.applyIconMaskShape(context, defaultIcon, mShape, mScale, mBackground);\n            ShapedIconInfo iconInfo = new NamedIconInfo(\"\", shapedDrawable, defaultIcon);\n            mShapedIconAdapter.addItem(iconInfo);\n        }\n\n        // this will call generateTextIcons\n        mLettersView.setText(mShortcutRecord.displayName);\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/customicon/StaticEntryPage.java",
    "content": "package rocks.tbog.tblauncher.customicon;\n\nimport android.content.Context;\nimport android.graphics.drawable.Drawable;\nimport android.view.View;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.fragment.app.DialogFragment;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.entry.StaticEntry;\nimport rocks.tbog.tblauncher.drawable.DrawableUtils;\nimport rocks.tbog.tblauncher.handler.IconsHandler;\n\npublic class StaticEntryPage extends CustomShapePage {\n    private final StaticEntry mStaticEntry;\n\n    StaticEntryPage(CharSequence name, View view, StaticEntry staticEntry) {\n        super(name, view);\n        mStaticEntry = staticEntry;\n        mScale = DrawableUtils.getScaleToFit(mShape);\n    }\n\n    @Override\n    void setupView(@NonNull DialogFragment dialogFragment, @Nullable OnItemClickListener iconClickListener, @Nullable OnItemClickListener iconLongClickListener) {\n        Context context = dialogFragment.requireContext();\n        super.setupView(dialogFragment, iconClickListener, iconLongClickListener);\n\n        final Drawable originalDrawable;\n        // default icon\n        {\n            originalDrawable = mStaticEntry.getDefaultDrawable(context);\n            IconsHandler.IconInfo iconHandlerIconInfo = new IconsHandler.IconInfo().setNonAdaptiveIcon(originalDrawable);\n            ShapedIconInfo iconInfo = new DefaultIconInfo(dialogFragment.getString(R.string.default_static_icon, mStaticEntry.getName()), iconHandlerIconInfo);\n            mShapedIconAdapter.addItem(iconInfo);\n        }\n\n        // customizable default icon\n        {\n            Drawable shapedDrawable = DrawableUtils.applyIconMaskShape(context, originalDrawable, mShape, mScale, mBackground);\n            ShapedIconInfo iconInfo = new NamedIconInfo(mStaticEntry.getName(), shapedDrawable, originalDrawable);\n            mShapedIconAdapter.addItem(iconInfo);\n        }\n\n        // this will call generateTextIcons\n        mLettersView.setText(mStaticEntry.getName());\n    }\n\n    static class DefaultIconInfo extends CustomShapePage.DefaultIconInfo {\n        final String name;\n\n        DefaultIconInfo(@NonNull String name, IconsHandler.IconInfo icon) {\n            super(icon);\n            this.name = name;\n            textId = R.string.default_icon;\n        }\n\n        @Nullable\n        @Override\n        CharSequence getText() {\n            return name;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/customicon/SystemPage.java",
    "content": "package rocks.tbog.tblauncher.customicon;\n\nimport android.app.Activity;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.pm.LauncherActivityInfo;\nimport android.content.pm.LauncherApps;\nimport android.content.pm.PackageManager;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.BitmapDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.os.Build;\nimport android.util.Pair;\nimport android.view.View;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.StringRes;\nimport androidx.collection.ArraySet;\nimport androidx.fragment.app.DialogFragment;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.drawable.DrawableUtils;\nimport rocks.tbog.tblauncher.handler.IconsHandler;\nimport rocks.tbog.tblauncher.icons.DrawableInfo;\nimport rocks.tbog.tblauncher.icons.IconPackXML;\nimport rocks.tbog.tblauncher.utils.UserHandleCompat;\nimport rocks.tbog.tblauncher.utils.Utilities;\n\npublic class SystemPage extends CustomShapePage {\n    private final ComponentName componentName;\n    private final UserHandleCompat userHandle;\n\n    SystemPage(CharSequence name, View view, ComponentName cn, UserHandleCompat uh) {\n        super(name, view);\n        componentName = cn;\n        userHandle = uh;\n    }\n\n    @Override\n    void setupView(@NonNull DialogFragment dialogFragment, @Nullable OnItemClickListener iconClickListener, @Nullable OnItemClickListener iconLongClickListener) {\n        super.setupView(dialogFragment, iconClickListener, iconLongClickListener);\n\n        addSystemIcons(dialogFragment.getContext(), mShapedIconAdapter);\n\n        // this will call generateTextIcons\n        //mLettersView.setText(pageName);\n    }\n\n    private void addSystemIcons(Context context, ShapedIconAdapter adapter) {\n        ArraySet<Bitmap> dSet = new ArraySet<>(3);\n\n        // add default icon\n        {\n            IconsHandler iconsHandler = TBApplication.getApplication(context).iconsHandler();\n            IconsHandler.IconInfo icon = iconsHandler.getIconForPackage(componentName, userHandle);\n\n            //checkDuplicateDrawable(dSet, drawable);\n\n            ShapedIconInfo iconInfo = new DefaultIconInfo(icon);\n            iconInfo.textId = R.string.default_icon;\n            adapter.addItem(iconInfo);\n        }\n\n        // add getActivityIcon(componentName)\n        {\n            Drawable drawable = null;\n            try {\n                drawable = context.getPackageManager().getActivityIcon(componentName);\n            } catch (PackageManager.NameNotFoundException ignored) {\n            }\n            if (drawable != null) {\n                if (checkDuplicateDrawable(dSet, drawable)) {\n                    {\n                        Drawable shapedDrawable = DrawableUtils.applyIconMaskShape(context, drawable, mShape, mScale, mBackground);\n                        addQuickOption(R.string.custom_icon_activity, shapedDrawable, drawable, adapter);\n                    }\n                    if (DrawableUtils.isAdaptiveIconDrawable(drawable)) {\n                        Drawable noBackground = DrawableUtils.applyAdaptiveIconBackgroundShape(context, drawable, DrawableUtils.SHAPE_SQUARE, true);\n                        Drawable shapedDrawable = DrawableUtils.applyIconMaskShape(context, noBackground, mShape, mScale, mBackground);\n                        addQuickOption(R.string.custom_icon_activity_adaptive_no_background, shapedDrawable, noBackground, adapter);\n                    }\n                }\n            }\n        }\n\n        // add getApplicationIcon(packageName)\n        {\n            Drawable drawable = null;\n            try {\n                drawable = context.getPackageManager().getApplicationIcon(componentName.getPackageName());\n            } catch (PackageManager.NameNotFoundException ignored) {\n            }\n            if (drawable != null) {\n                if (checkDuplicateDrawable(dSet, drawable)) {\n                    Drawable shapedDrawable = DrawableUtils.applyIconMaskShape(context, drawable, mShape, mScale, mBackground);\n                    addQuickOption(R.string.custom_icon_application, shapedDrawable, drawable, adapter);\n                }\n            }\n        }\n\n        // add Activity BadgedIcon\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            LauncherApps launcher = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);\n            assert launcher != null;\n            List<LauncherActivityInfo> icons = launcher.getActivityList(componentName.getPackageName(), userHandle.getRealHandle());\n            for (LauncherActivityInfo info : icons) {\n                Drawable drawable = info.getBadgedIcon(0);\n\n                if (drawable != null) {\n                    if (checkDuplicateDrawable(dSet, drawable)) {\n                        Drawable shapedDrawable = DrawableUtils.applyIconMaskShape(context, drawable, mShape, mScale, mBackground);\n                        addQuickOption(R.string.custom_icon_badged, shapedDrawable, drawable, adapter);\n                    }\n                }\n            }\n        }\n    }\n\n    private boolean checkDuplicateDrawable(ArraySet<Bitmap> set, Drawable drawable) {\n        Bitmap b = null;\n        if (drawable instanceof BitmapDrawable)\n            b = ((BitmapDrawable) drawable).getBitmap();\n\n        if (set.contains(b))\n            return false;\n\n        set.add(b);\n        return true;\n    }\n\n    private static void addQuickOption(@StringRes int textId, Drawable shapedDrawable, Drawable drawable, ShapedIconAdapter adapter) {\n        if (!(shapedDrawable instanceof BitmapDrawable))\n            return;\n\n        ShapedIconInfo iconInfo = new ShapedIconInfo(shapedDrawable, drawable);\n        iconInfo.textId = textId;\n        adapter.addItem(iconInfo);\n    }\n\n    public void loadIconPackIcons(List<Pair<String, String>> iconPacks) {\n        if (iconPacks.isEmpty())\n            return;\n        final Context ctx = pageView.getContext();\n        final ShapedIconInfo placeholderItem = new ShapedIconInfo(DrawableUtils.getProgressBarIndeterminate(ctx), null);\n        placeholderItem.textId = R.string.icon_pack_loading;\n        {\n            mShapedIconAdapter.addItem(placeholderItem);\n        }\n        final ArrayList<NamedIconInfo> options = new ArrayList<>();\n        Utilities.runAsync((t) -> {\n            for (Pair<String, String> packInfo : iconPacks) {\n                String packPackageName = packInfo.first;\n                String packName = packInfo.second;\n                Activity activity = Utilities.getActivity(pageView);\n                if (activity != null) {\n                    IconPackXML pack = TBApplication.iconPackCache(activity).getIconPack(packPackageName);\n                    pack.load(activity.getPackageManager());\n                    DrawableInfo info = pack.getComponentDrawable(activity, componentName, userHandle);\n                    Drawable drawable = pack.getDrawable(info);\n                    if (drawable != null) {\n                        Drawable shapedDrawable = DrawableUtils.applyIconMaskShape(activity, drawable, mShape, mScale, mBackground);\n                        NamedIconInfo iconInfo = new NamedIconInfo(packName, shapedDrawable, drawable);\n                        options.add(iconInfo);\n                    }\n                } else {\n                    break;\n                }\n            }\n        }, (t) -> {\n            Activity activity = Utilities.getActivity(pageView);\n            if (activity != null) {\n                final ShapedIconAdapter adapter = mShapedIconAdapter;\n                adapter.removeItem(placeholderItem);\n                adapter.addItems(options);\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/dataprovider/ActionProvider.java",
    "content": "package rocks.tbog.tblauncher.dataprovider;\n\nimport android.content.Context;\nimport android.view.View;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.StringRes;\n\nimport java.util.Collections;\nimport java.util.Iterator;\nimport java.util.List;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.TBLauncherActivity;\nimport rocks.tbog.tblauncher.entry.ActionEntry;\nimport rocks.tbog.tblauncher.entry.EntryItem;\nimport rocks.tbog.tblauncher.handler.DataHandler;\nimport rocks.tbog.tblauncher.searcher.HistorySearcher;\nimport rocks.tbog.tblauncher.searcher.Searcher;\nimport rocks.tbog.tblauncher.searcher.TagList;\nimport rocks.tbog.tblauncher.ui.ListPopup;\nimport rocks.tbog.tblauncher.utils.DebugInfo;\n\npublic class ActionProvider extends DBProvider<ActionEntry> {\n\n    private static final ActionEntry[] s_entries = new ActionEntry[19];\n    @StringRes\n    private static final int[] s_names = new int[19];\n\n    static {\n        int cnt = 0;\n        {\n            String id = ActionEntry.SCHEME + \"toggle/grid\";\n            ActionEntry actionEntry = new ActionEntry(id, R.drawable.ic_grid);\n            actionEntry.setAction((v, flags) -> {\n                TBLauncherActivity act = TBApplication.launcherActivity(v.getContext());\n                if (act == null)\n                    return;\n                // toggle grid/list layout\n                if (act.behaviour.isGridLayout())\n                    act.behaviour.setListLayout();\n                else\n                    act.behaviour.setGridLayout();\n            });\n            s_names[cnt] = R.string.action_toggle_grid;\n            s_entries[cnt++] = actionEntry;\n        }\n        // show apps sorted by name\n        {\n            String id = ActionEntry.SCHEME + \"show/apps/byName\";\n            ActionEntry actionEntry = new ActionEntry(id, R.drawable.ic_apps_list_az);\n            actionEntry.setAction((v, flags) -> {\n                TBApplication app = TBApplication.getApplication(v.getContext());\n                TBLauncherActivity act = app.launcherActivity();\n                if (act == null)\n                    return;\n                Provider<? extends EntryItem> provider = app.getDataHandler().getAppProvider();\n                act.quickList.toggleProvider(v, provider, EntryItem.NAME_COMPARATOR);\n                act.behaviour.setListLayout();\n            });\n            s_names[cnt] = R.string.action_show_apps;\n            s_entries[cnt++] = actionEntry;\n        }\n        // show apps sorted by name in reverse order\n        {\n            String id = ActionEntry.SCHEME + \"show/apps/byNameReversed\";\n            ActionEntry actionEntry = new ActionEntry(id, R.drawable.ic_apps_list_za);\n            actionEntry.setAction((v, flags) -> {\n                TBApplication app = TBApplication.getApplication(v.getContext());\n                TBLauncherActivity act = app.launcherActivity();\n                if (act == null)\n                    return;\n                Provider<? extends EntryItem> provider = app.getDataHandler().getAppProvider();\n                act.quickList.toggleProvider(v, provider, Collections.reverseOrder(EntryItem.NAME_COMPARATOR));\n                act.behaviour.setListLayout();\n            });\n            s_names[cnt] = R.string.action_show_apps_reversed;\n            s_entries[cnt++] = actionEntry;\n        }\n        // show apps in a 4 column grid sorted by name\n        {\n            String id = ActionEntry.SCHEME + \"show/grid4c/apps/byName\";\n            ActionEntry actionEntry = new ActionEntry(id, R.drawable.ic_apps_grid_az);\n            actionEntry.setAction((v, flags) -> {\n                TBApplication app = TBApplication.getApplication(v.getContext());\n                TBLauncherActivity act = app.launcherActivity();\n                if (act == null)\n                    return;\n                Provider<? extends EntryItem> provider = app.getDataHandler().getAppProvider();\n                act.quickList.toggleProvider(v, provider, EntryItem.NAME_COMPARATOR);\n                act.behaviour.setGridLayout(4);\n            });\n            s_names[cnt] = R.string.action_show_apps_grid4;\n            s_entries[cnt++] = actionEntry;\n        }\n        // show apps in a 4 column grid sorted by name in reverse order\n        {\n            String id = ActionEntry.SCHEME + \"show/grid4c/apps/byNameReversed\";\n            ActionEntry actionEntry = new ActionEntry(id, R.drawable.ic_apps_grid_za);\n            actionEntry.setAction((v, flags) -> {\n                TBApplication app = TBApplication.getApplication(v.getContext());\n                TBLauncherActivity act = app.launcherActivity();\n                if (act == null)\n                    return;\n                Provider<? extends EntryItem> provider = app.getDataHandler().getAppProvider();\n                act.quickList.toggleProvider(v, provider, Collections.reverseOrder(EntryItem.NAME_COMPARATOR));\n                act.behaviour.setGridLayout(4);\n            });\n            s_names[cnt] = R.string.action_show_apps_grid4_reversed;\n            s_entries[cnt++] = actionEntry;\n        }\n\n        // show contacts sorted by name\n        {\n            String id = ActionEntry.SCHEME + \"show/contacts/byName\";\n            ActionEntry actionEntry = new ActionEntry(id, R.drawable.ic_contacts_az);\n            actionEntry.setAction((v, flags) -> {\n                TBApplication app = TBApplication.getApplication(v.getContext());\n                TBLauncherActivity act = app.launcherActivity();\n                if (act == null)\n                    return;\n                Provider<? extends EntryItem> provider = app.getDataHandler().getContactsProvider();\n                act.quickList.toggleProvider(v, provider, EntryItem.NAME_COMPARATOR);\n            });\n            s_names[cnt] = R.string.action_show_contacts;\n            s_entries[cnt++] = actionEntry;\n        }\n        // show contacts sorted by name in reverse order\n        {\n            String id = ActionEntry.SCHEME + \"show/contacts/byNameReversed\";\n            ActionEntry actionEntry = new ActionEntry(id, R.drawable.ic_contacts_za);\n            actionEntry.setAction((v, flags) -> {\n                TBApplication app = TBApplication.getApplication(v.getContext());\n                TBLauncherActivity act = app.launcherActivity();\n                if (act == null)\n                    return;\n                Provider<? extends EntryItem> provider = app.getDataHandler().getContactsProvider();\n                act.quickList.toggleProvider(v, provider, Collections.reverseOrder(EntryItem.NAME_COMPARATOR));\n            });\n            s_names[cnt] = R.string.action_show_contacts_reversed;\n            s_entries[cnt++] = actionEntry;\n        }\n        // show shortcuts sorted by name\n        {\n            String id = ActionEntry.SCHEME + \"show/shortcuts/byName\";\n            ActionEntry actionEntry = new ActionEntry(id, R.drawable.ic_shortcuts_az);\n            actionEntry.setAction((v, flags) -> {\n                TBApplication app = TBApplication.getApplication(v.getContext());\n                TBLauncherActivity act = app.launcherActivity();\n                if (act == null)\n                    return;\n                Provider<? extends EntryItem> provider = app.getDataHandler().getShortcutsProvider();\n                act.quickList.toggleProvider(v, provider, EntryItem.NAME_COMPARATOR);\n            });\n            s_names[cnt] = R.string.action_show_shortcuts;\n            s_entries[cnt++] = actionEntry;\n        }\n        // show shortcuts sorted by name in reverse order\n        {\n            String id = ActionEntry.SCHEME + \"show/shortcuts/byNameReversed\";\n            ActionEntry actionEntry = new ActionEntry(id, R.drawable.ic_shortcuts_za);\n            actionEntry.setAction((v, flags) -> {\n                TBApplication app = TBApplication.getApplication(v.getContext());\n                TBLauncherActivity act = app.launcherActivity();\n                if (act == null)\n                    return;\n                Provider<? extends EntryItem> provider = app.getDataHandler().getShortcutsProvider();\n                act.quickList.toggleProvider(v, provider, Collections.reverseOrder(EntryItem.NAME_COMPARATOR));\n            });\n            s_names[cnt] = R.string.action_show_shortcuts_reversed;\n            s_entries[cnt++] = actionEntry;\n        }\n        // show favorites sorted by name (removed by load task if not enabled)\n        {\n            String id = ActionEntry.SCHEME + \"show/favorites/byName\";\n            ActionEntry actionEntry = new ActionEntry(id, R.drawable.ic_favorites);\n            actionEntry.setAction((v, flags) -> {\n                TBApplication app = TBApplication.getApplication(v.getContext());\n                TBLauncherActivity act = app.launcherActivity();\n                if (act == null)\n                    return;\n                ModProvider provider = app.getDataHandler().getModProvider();\n                act.quickList.toggleProvider(v, provider, EntryItem.NAME_COMPARATOR);\n            });\n            s_names[cnt] = R.string.action_show_favorites;\n            s_entries[cnt++] = actionEntry;\n        }\n        // show history sorted by how recent it was accessed\n        {\n            String id = ActionEntry.SCHEME + \"show/history/recency\";\n            ActionEntry actionEntry = new ActionEntry(id, R.drawable.ic_history);\n            actionEntry.setAction((v, f) -> toggleSearch(v, \"recency\", HistorySearcher.class));\n            s_names[cnt] = R.string.action_show_history_recency;\n            s_entries[cnt++] = actionEntry;\n        }\n        // show history sorted by how frequent it was accessed\n        {\n            String id = ActionEntry.SCHEME + \"show/history/frequency\";\n            ActionEntry actionEntry = new ActionEntry(id, R.drawable.ic_history);\n            actionEntry.setAction((v, f) -> toggleSearch(v, \"frequency\", HistorySearcher.class));\n            s_names[cnt] = R.string.action_show_history_frequency;\n            s_entries[cnt++] = actionEntry;\n        }\n        // show history sorted based on frequency * recency\n        // frequency = #launches_for_app / #all_launches\n        // recency = 1 / position_of_app_in_normal_history\n        {\n            String id = ActionEntry.SCHEME + \"show/history/frecency\";\n            ActionEntry actionEntry = new ActionEntry(id, R.drawable.ic_history);\n            actionEntry.setAction((v, f) -> toggleSearch(v, \"frecency\", HistorySearcher.class));\n            s_names[cnt] = R.string.action_show_history_frecency;\n            s_entries[cnt++] = actionEntry;\n        }\n        // show history sorted by how frequent it was accessed in the last 36 hours\n        {\n            String id = ActionEntry.SCHEME + \"show/history/adaptive\";\n            ActionEntry actionEntry = new ActionEntry(id, R.drawable.ic_history);\n            actionEntry.setAction((v, f) -> toggleSearch(v, \"adaptive\", HistorySearcher.class));\n            s_names[cnt] = R.string.action_show_history_adaptive;\n            s_entries[cnt++] = actionEntry;\n        }\n        {\n            String id = ActionEntry.SCHEME + \"show/untagged\";\n            ActionEntry actionEntry = new ActionEntry(id, R.drawable.ic_untagged);\n            actionEntry.setAction((v, f) -> toggleSearch(v, \"untagged\", TagList.class));\n            s_names[cnt] = R.string.action_show_untagged;\n            s_entries[cnt++] = actionEntry;\n        }\n        {\n            String id = ActionEntry.SCHEME + \"show/tags/menu\";\n            ActionEntry actionEntry = new ActionEntry(id, R.drawable.ic_tags);\n            actionEntry.setAction((v, flags) -> {\n                Context ctx = v.getContext();\n                TBApplication app = TBApplication.getApplication(ctx);\n                ListPopup menu = app.tagsHandler().getTagsMenu(ctx);\n                app.registerPopup(menu);\n                menu.show(v);\n            });\n            s_names[cnt] = R.string.show_tags_menu;\n            s_entries[cnt++] = actionEntry;\n        }\n        {\n            String id = ActionEntry.SCHEME + \"show/tags/list\";\n            ActionEntry actionEntry = new ActionEntry(id, R.drawable.ic_tags);\n            actionEntry.setAction((v, f) -> toggleSearch(v, \"list\", TagList.class));\n            s_names[cnt] = R.string.show_tags_list;\n            s_entries[cnt++] = actionEntry;\n        }\n        {\n            String id = ActionEntry.SCHEME + \"show/tags/listReversed\";\n            ActionEntry actionEntry = new ActionEntry(id, R.drawable.ic_tags);\n            actionEntry.setAction((v, f) -> toggleSearch(v, \"listReversed\", TagList.class));\n            s_names[cnt] = R.string.show_tags_list_reversed;\n            s_entries[cnt++] = actionEntry;\n        }\n        {\n            String id = ActionEntry.SCHEME + \"reload/providers\";\n            ActionEntry actionEntry = new ActionEntry(id, R.drawable.ic_refresh);\n            actionEntry.setAction((v, flags) -> {\n                Context ctx = v.getContext();\n                TBApplication.dataHandler(ctx).reloadProviders();\n            });\n            s_names[cnt] = R.string.action_reload;\n            s_entries[cnt++] = actionEntry;\n        }\n\n        //noinspection ConstantConditions\n        if (cnt != s_entries.length || cnt != s_names.length)\n            throw new IllegalStateException(\"ActionEntry static list size\");\n    }\n\n    private static void toggleSearch(@NonNull View v, @NonNull String query, @NonNull Class<? extends Searcher> searcherClass) {\n        TBLauncherActivity act = TBApplication.launcherActivity(v.getContext());\n        if (act != null)\n            act.quickList.toggleSearch(v, query, searcherClass);\n    }\n\n    public ActionProvider(@NonNull Context context) {\n        super(context);\n    }\n\n    @Override\n    protected DBLoader<ActionEntry> newLoadTask() {\n        return new UpdateFromModsLoader<ActionEntry>(this, s_entries, s_names) {\n            @Override\n            public List<ActionEntry> getEntryItems(DataHandler dataHandler) {\n                List<ActionEntry> entries = super.getEntryItems(dataHandler);\n                Context context = dataHandler.getContext();\n                if (context == null || !DebugInfo.enableFavorites(context)) {\n                    // remove debug entry\n                    for (Iterator<ActionEntry> iterator = entries.iterator(); iterator.hasNext(); ) {\n                        ActionEntry entry = iterator.next();\n                        if (entry.id.endsWith(\"show/favorites/byName\"))\n                            iterator.remove();\n                    }\n                }\n                return entries;\n            }\n        };\n    }\n\n    @Override\n    public boolean mayFindById(@NonNull String id) {\n        return id.startsWith(ActionEntry.SCHEME);\n    }\n\n    @NonNull\n    public String getDefaultName(@NonNull String id) {\n        for (int idx = 0; idx < s_entries.length; idx += 1) {\n            if (id.equals(s_entries[idx].id))\n                return context.getString(s_names[idx]);\n        }\n        return \"null\";\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/dataprovider/AppCacheProvider.java",
    "content": "package rocks.tbog.tblauncher.dataprovider;\n\nimport android.util.Log;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.WorkerThread;\n\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.concurrent.CountDownLatch;\n\nimport rocks.tbog.tblauncher.entry.AppEntry;\nimport rocks.tbog.tblauncher.handler.AppsHandler;\nimport rocks.tbog.tblauncher.normalizer.StringNormalizer;\nimport rocks.tbog.tblauncher.searcher.ISearcher;\nimport rocks.tbog.tblauncher.utils.FuzzyScore;\nimport rocks.tbog.tblauncher.utils.Timer;\n\npublic class AppCacheProvider implements IProvider<AppEntry> {\n    final static String TAG = \"AppCP\";\n    final private AppsHandler appsHandler;\n\n    public AppCacheProvider(@NonNull AppsHandler handler) {\n        appsHandler = handler;\n    }\n\n    @WorkerThread\n    @Override\n    public void requestResults(String query, ISearcher searcher) {\n        StringNormalizer.Result queryNormalized = StringNormalizer.normalizeWithResult(query, false);\n\n        if (queryNormalized.codePoints.length == 0) {\n            return;\n        }\n\n        final CountDownLatch latch = new CountDownLatch(1);\n        // notify that the tags are loaded\n        appsHandler.runWhenLoaded(latch::countDown);\n        // wait for the tags to load\n        try {\n            latch.await();\n        } catch (InterruptedException e) {\n            Log.e(TAG, \"waiting for TagsHandler\", e);\n        }\n\n        final Collection<AppEntry> entries = appsHandler.getAllApps();\n\n        FuzzyScore fuzzyScore = new FuzzyScore(queryNormalized.codePoints);\n\n        EntryToResultUtils.tagsCheckResults(entries, fuzzyScore, searcher);\n    }\n\n    public void reload(boolean cancelCurrentLoadTask) {\n    }\n\n    @Override\n    public boolean isLoaded() {\n        return true;\n    }\n\n    @Override\n    public Timer getLoadDuration() {\n        return null;\n    }\n\n    @Override\n    public void setDirty() {\n        // do nothing, we already have the full list of items\n    }\n\n    @Override\n    public int getLoadStep() {\n        return LOAD_STEP_1;\n    }\n\n    @Override\n    public boolean mayFindById(@NonNull String id) {\n        return false;\n    }\n\n    @Override\n    public AppEntry findById(@NonNull String id) {\n        return null;\n    }\n\n    @Nullable\n    @Override\n    public List<AppEntry> getPojos() {\n        return null;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/dataprovider/AppProvider.java",
    "content": "package rocks.tbog.tblauncher.dataprovider;\n\nimport android.content.BroadcastReceiver;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.IntentFilter;\nimport android.content.pm.LauncherApps;\nimport android.os.Build;\nimport android.os.Process;\nimport android.os.UserManager;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.RequiresApi;\nimport androidx.annotation.WorkerThread;\nimport androidx.core.app.ActivityCompat;\nimport androidx.core.content.ContextCompat;\n\nimport java.util.ArrayList;\nimport java.util.Objects;\n\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.broadcast.PackageAddedRemovedHandler;\nimport rocks.tbog.tblauncher.entry.AppEntry;\nimport rocks.tbog.tblauncher.loader.LoadAppEntry;\nimport rocks.tbog.tblauncher.loader.LoadCacheApps;\nimport rocks.tbog.tblauncher.searcher.ISearcher;\nimport rocks.tbog.tblauncher.utils.UserHandleCompat;\n\npublic class AppProvider extends Provider<AppEntry> {\n\n    boolean mInitialLoad = true;\n    AppsCallback mAppsCallback = null;\n    final BroadcastReceiver mProfileReceiver = new BroadcastReceiver() {\n        @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)\n        @Override\n        public void onReceive(Context context, Intent intent) {\n            if (Objects.equals(intent.getAction(), Intent.ACTION_MANAGED_PROFILE_ADDED)) {\n                AppProvider.this.reload(true);\n            } else if (Objects.equals(intent.getAction(), Intent.ACTION_MANAGED_PROFILE_REMOVED)) {\n//                android.os.UserHandle profile = intent.getParcelableExtra(Intent.EXTRA_USER);\n\n//                final UserManager manager = (UserManager) AppProvider.this.getSystemService(Context.USER_SERVICE);\n//                assert manager != null;\n//                UserHandleCompat user = new UserHandleCompat(manager.getSerialNumberForUser(profile), profile);\n\n//                DataHandler dataHandler = TBApplication.getApplication(context).getDataHandler();\n//                dataHandler.removeFromExcluded(user);\n//                dataHandler.removeFromMods(user);\n                AppProvider.this.reload(true);\n            }\n        }\n    };\n    final PackageAddedRemovedHandler mPackageAddedRemovedHandler = new PackageAddedRemovedHandler();\n\n    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)\n    static class AppsCallback extends LauncherApps.Callback {\n        private final Context context;\n\n        AppsCallback(Context context) {\n            this.context = context;\n\n        }\n\n        @Override\n        public void onPackageAdded(String packageName, android.os.UserHandle user) {\n            handleEvent(Intent.ACTION_PACKAGE_ADDED, packageName, user, false);\n        }\n\n        @Override\n        public void onPackageChanged(String packageName, android.os.UserHandle user) {\n            handleEvent(Intent.ACTION_PACKAGE_CHANGED, packageName, user, true);\n        }\n\n        @Override\n        public void onPackageRemoved(String packageName, android.os.UserHandle user) {\n            handleEvent(Intent.ACTION_PACKAGE_REMOVED, packageName, user, false);\n        }\n\n        @Override\n        public void onPackagesAvailable(String[] packageNames, android.os.UserHandle user, boolean replacing) {\n            handleEvent(Intent.ACTION_MEDIA_MOUNTED, null, user, replacing);\n        }\n\n\n        @Override\n        public void onPackagesUnavailable(String[] packageNames, android.os.UserHandle user, boolean replacing) {\n            handleEvent(Intent.ACTION_MEDIA_UNMOUNTED, null, user, replacing);\n        }\n\n        @Override\n        public void onPackagesSuspended(String[] packageNames, android.os.UserHandle user) {\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n                handleEvent(Intent.ACTION_PACKAGES_SUSPENDED, null, user, false);\n            }\n        }\n\n        @Override\n        public void onPackagesUnsuspended(String[] packageNames, android.os.UserHandle user) {\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n                handleEvent(Intent.ACTION_PACKAGES_UNSUSPENDED, null, user, false);\n            }\n        }\n\n        private void handleEvent(String action, String packageName, android.os.UserHandle user, boolean replacing) {\n            if (!Process.myUserHandle().equals(user)) {\n                final UserManager manager = (UserManager) context.getSystemService(Context.USER_SERVICE);\n                PackageAddedRemovedHandler.handleEvent(context,\n                    action,\n                    packageName,\n                    new UserHandleCompat(manager.getSerialNumberForUser(user), user),\n                    replacing\n                );\n            }\n        }\n    }\n\n    @Override\n    public void onCreate() {\n        if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            // Package install/uninstall events for the main\n            // profile are still handled using PackageAddedRemovedHandler itself\n\n            final LauncherApps launcher = (LauncherApps) this.getSystemService(Context.LAUNCHER_APPS_SERVICE);\n            assert launcher != null;\n\n            mAppsCallback = new AppsCallback(this);\n            launcher.registerCallback(mAppsCallback);\n\n            // Try to clean up app-related data when profile is removed\n            IntentFilter filter = new IntentFilter();\n            filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);\n            filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);\n            ActivityCompat.registerReceiver(this, mProfileReceiver, filter, ContextCompat.RECEIVER_EXPORTED);\n        }\n\n        // Get notified when app changes on standard user profile\n        IntentFilter appChangedFilter = new IntentFilter();\n        appChangedFilter.addAction(Intent.ACTION_PACKAGE_ADDED);\n        appChangedFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);\n        appChangedFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);\n        appChangedFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);\n        appChangedFilter.addAction(Intent.ACTION_MEDIA_REMOVED);\n        appChangedFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);\n        appChangedFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n            appChangedFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED);\n            appChangedFilter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED);\n        }\n        appChangedFilter.addDataScheme(\"package\");\n        appChangedFilter.addDataScheme(\"file\");\n        ActivityCompat.registerReceiver(this, mPackageAddedRemovedHandler, appChangedFilter, ContextCompat.RECEIVER_EXPORTED);\n\n        super.onCreate();\n    }\n\n    @Override\n    public void onDestroy() {\n        unregisterReceiver(mProfileReceiver);\n        unregisterReceiver(mPackageAddedRemovedHandler);\n        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {\n            LauncherApps launcher = (LauncherApps) this.getSystemService(Context.LAUNCHER_APPS_SERVICE);\n            assert launcher != null;\n            launcher.unregisterCallback(mAppsCallback);\n        }\n        super.onDestroy();\n    }\n\n\n    public void reload(boolean cancelCurrentLoadTask) {\n        super.reload(cancelCurrentLoadTask);\n        if (!isLoaded() && !isLoading()) {\n            if (mInitialLoad) {\n                // Use DB cache to speed things up. We'll reload after.\n                this.initialize(new LoadCacheApps(this));\n            } else {\n                this.initialize(new LoadAppEntry(this));\n            }\n        }\n    }\n\n    @Override\n    public void loadOver(ArrayList<AppEntry> results) {\n        super.loadOver(results);\n        if (mInitialLoad) {\n            mInitialLoad = false;\n            // Got DB cache. Do a reload later.\n            TBApplication.dataHandler(this).runAfterLoadOver(() -> {\n                this.reload(false);\n            });\n        } else {\n            TBApplication.appsHandler(this).setAppCache(results);\n        }\n    }\n\n    /**\n     * @param query    The string to search for\n     * @param searcher The receiver of results\n     */\n\n    @WorkerThread\n    @Override\n    public void requestResults(String query, ISearcher searcher) {\n        for (AppEntry pojo : pojos)\n            pojo.resetResultInfo();\n\n        EntryToResultUtils.recursiveWordCheck(pojos, query, searcher, EntryToResultUtils::tagsCheckResults, AppEntry.class);\n    }\n\n    /**\n     * Return a Pojo\n     *\n     * @param id we're looking for\n     * @return an AppEntry, or null\n     */\n    @Override\n    public AppEntry findById(@NonNull String id) {\n        for (AppEntry pojo : pojos) {\n            if (pojo.id.equals(id)) {\n                return pojo;\n            }\n        }\n\n        return null;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/dataprovider/CalculatorProvider.java",
    "content": "package rocks.tbog.tblauncher.dataprovider;\n\nimport java.math.BigDecimal;\nimport java.text.NumberFormat;\nimport java.util.ArrayDeque;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\nimport rocks.tbog.tblauncher.calculator.Calculator;\nimport rocks.tbog.tblauncher.calculator.Result;\nimport rocks.tbog.tblauncher.calculator.ShuntingYard;\nimport rocks.tbog.tblauncher.calculator.Tokenizer;\nimport rocks.tbog.tblauncher.entry.CalculatorEntry;\nimport rocks.tbog.tblauncher.searcher.ISearcher;\n\n\npublic class CalculatorProvider extends SimpleProvider<CalculatorEntry> {\n    private final Pattern computableRegexp;\n    // A regexp to detect plain numbers (including phone numbers)\n    private final Pattern numberOnlyRegexp;\n    private final NumberFormat LOCALIZED_NUMBER_FORMATTER = NumberFormat.getInstance();\n\n    public CalculatorProvider() {\n        //This should try to match as much as possible without going out of the expression,\n        //even if the expression is not actually a computable operation.\n        computableRegexp = Pattern.compile(\"^[\\\\-.,\\\\d+*×x/÷^'()]+$\");\n        numberOnlyRegexp = Pattern.compile(\"^\\\\+?[.,()\\\\d]+$\");\n    }\n\n    @Override\n    public void requestResults(String query, ISearcher searcher) {\n        String spacelessQuery = query.replaceAll(\"\\\\s+\", \"\");\n        // Now create matcher object.\n        Matcher m = computableRegexp.matcher(spacelessQuery);\n        if (m.find()) {\n            if (numberOnlyRegexp.matcher(spacelessQuery).find()) {\n                return;\n            }\n\n            String operation = m.group();\n\n            Result<ArrayDeque<Tokenizer.Token>> tokenized = Tokenizer.tokenize(operation);\n            String readableResult;\n\n            if (tokenized.syntacticalError) {\n                return;\n            } else if (tokenized.arithmeticalError) {\n                return;\n            } else {\n                Result<ArrayDeque<Tokenizer.Token>> posfixed = ShuntingYard.infixToPostfix(tokenized.result);\n\n                if (posfixed.syntacticalError) {\n                    return;\n                } else if (posfixed.arithmeticalError) {\n                    return;\n                } else {\n                    Result<BigDecimal> result = Calculator.calculateExpression(posfixed.result);\n\n                    if (result.syntacticalError) {\n                        return;\n                    } else if (result.arithmeticalError) {\n                        return;\n                    } else {\n                        String localizedNumber = LOCALIZED_NUMBER_FORMATTER.format(result.result);\n                        readableResult = \" = \" + localizedNumber;\n                    }\n                }\n            }\n\n            String queryProcessed = operation + readableResult;\n            CalculatorEntry pojo = new CalculatorEntry(queryProcessed);\n            pojo.setRelevance(pojo.normalizedName, null);\n\n            pojo.boostRelevance(19);\n            searcher.addResult(pojo);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/dataprovider/ContactsProvider.java",
    "content": "package rocks.tbog.tblauncher.dataprovider;\n\nimport android.content.SharedPreferences;\nimport android.database.ContentObserver;\nimport android.provider.ContactsContract;\nimport android.util.Log;\n\nimport androidx.annotation.WorkerThread;\nimport androidx.preference.PreferenceManager;\n\nimport java.util.Collection;\n\nimport rocks.tbog.tblauncher.Permission;\nimport rocks.tbog.tblauncher.entry.ContactEntry;\nimport rocks.tbog.tblauncher.loader.LoadContactsEntry;\nimport rocks.tbog.tblauncher.normalizer.PhoneNormalizer;\nimport rocks.tbog.tblauncher.normalizer.StringNormalizer;\nimport rocks.tbog.tblauncher.searcher.ISearcher;\nimport rocks.tbog.tblauncher.utils.FuzzyScore;\n\npublic class ContactsProvider extends Provider<ContactEntry> {\n    private final static String TAG = \"ContactsProvider\";\n    private final ContentObserver cObserver = new ContentObserver(null) {\n\n        @Override\n        public void onChange(boolean selfChange) {\n            //reload contacts\n            Log.i(TAG, \"Contacts changed, reloading provider.\");\n            reload(true);\n        }\n    };\n\n    public void reload(boolean cancelCurrentLoadTask) {\n        super.reload(cancelCurrentLoadTask);\n        if (!isLoaded() && !isLoading())\n            this.initialize(new LoadContactsEntry(this));\n    }\n\n    @Override\n    public void onCreate() {\n        super.onCreate();\n        // register content observer if we have permission\n        if (Permission.checkPermission(this, Permission.PERMISSION_READ_CONTACTS)) {\n            getContentResolver().registerContentObserver(ContactsContract.Contacts.CONTENT_URI, false, cObserver);\n        } else {\n            Permission.askPermission(Permission.PERMISSION_READ_CONTACTS, new Permission.PermissionResultListener() {\n                @Override\n                public void onGranted() {\n                    // Great! Reload the contact provider. We're done :)\n                    reload(true);\n                }\n\n                @Override\n                public void onDenied() {\n                    SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(ContactsProvider.this);\n                    pref.edit().putBoolean(\"enable-contacts\", false).apply();\n                }\n            });\n        }\n    }\n\n    @Override\n    public void onDestroy() {\n        super.onDestroy();\n        //deregister content observer\n        getContentResolver().unregisterContentObserver(cObserver);\n    }\n\n    @Override\n    public void requestResults(String query, ISearcher searcher) {\n        for (ContactEntry pojo : pojos)\n            pojo.resetResultInfo();\n\n        EntryToResultUtils.recursiveWordCheck(pojos, query, searcher, ContactsProvider::checkResults, ContactEntry.class);\n    }\n\n    @WorkerThread\n    public static void checkResults(Collection<ContactEntry> entries, FuzzyScore fuzzyScore, ISearcher searcher) {\n        Log.d(TAG, \"checkResults count=\" + entries.size() + \" \" + fuzzyScore);\n\n        for (ContactEntry entry : entries) {\n            FuzzyScore.MatchInfo scoreInfo = fuzzyScore.match(entry.normalizedName.codePoints);\n\n            StringNormalizer.Result matchedText = entry.normalizedName;\n            FuzzyScore.MatchInfo matchedInfo = FuzzyScore.MatchInfo.copyOrNewInstance(scoreInfo, null);\n\n            if (entry.normalizedNickname != null) {\n                scoreInfo = fuzzyScore.match(entry.normalizedNickname.codePoints);\n                if (scoreInfo.match && (!matchedInfo.match || scoreInfo.score > matchedInfo.score)) {\n                    matchedText = entry.normalizedNickname;\n                    matchedInfo = FuzzyScore.MatchInfo.copyOrNewInstance(scoreInfo, matchedInfo);\n                }\n            }\n            if (!matchedInfo.match && entry.normalizedPhone != null && fuzzyScore.getPatternLength() > 2) {\n                // search for the phone number\n                scoreInfo = fuzzyScore.match(entry.normalizedPhone.codePoints);\n                if (scoreInfo.match && scoreInfo.score > matchedInfo.score) {\n                    matchedText = entry.normalizedPhone;\n                    matchedInfo = FuzzyScore.MatchInfo.copyOrNewInstance(scoreInfo, matchedInfo);\n                }\n            }\n\n            entry.addResultMatch(matchedText, matchedInfo);\n\n            if (matchedInfo.match) {\n                int boost = Math.min(30, entry.getTimesContacted());\n                if (entry.isStarred()) {\n                    boost += 40;\n                }\n                entry.boostRelevance(boost);\n                if (!searcher.addResult(entry))\n                    return;\n            }\n        }\n    }\n\n    /**\n     * Find a ContactsPojo from a phoneNumber\n     * If many contacts match, the one most often contacted will be returned\n     *\n     * @param phoneNumber phone number to find (will be normalized)\n     * @return a contactpojo, or null.\n     */\n    public ContactEntry findByPhone(String phoneNumber) {\n        StringNormalizer.Result simplifiedPhoneNumber = PhoneNormalizer.simplifyPhoneNumber(phoneNumber);\n\n        for (ContactEntry pojo : pojos) {\n            if (pojo.normalizedPhone.equals(simplifiedPhoneNumber)) {\n                return pojo;\n            }\n        }\n\n        return null;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/dataprovider/DBProvider.java",
    "content": "package rocks.tbog.tblauncher.dataprovider;\n\nimport android.content.Context;\nimport android.util.Log;\n\nimport androidx.annotation.MainThread;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.WorkerThread;\n\nimport java.lang.ref.WeakReference;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\nimport rocks.tbog.tblauncher.BuildConfig;\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.TBLauncherActivity;\nimport rocks.tbog.tblauncher.WorkAsync.AsyncTask;\nimport rocks.tbog.tblauncher.WorkAsync.TaskRunner;\nimport rocks.tbog.tblauncher.entry.EntryItem;\nimport rocks.tbog.tblauncher.handler.DataHandler;\nimport rocks.tbog.tblauncher.searcher.ISearcher;\nimport rocks.tbog.tblauncher.utils.Timer;\n\npublic abstract class DBProvider<T extends EntryItem> implements IProvider<T> {\n    final Context context;\n    protected List<T> entryList = new ArrayList<>();\n\n    private boolean mIsLoaded = false;\n    private DBLoader<T> mLoadTask = null;\n    protected final Timer mTimer = new Timer();\n\n    public DBProvider(Context context) {\n        this.context = context;\n    }\n\n    @Override\n    public void requestResults(String query, ISearcher searcher) {\n    }\n\n    @Override\n    public void reload(boolean cancelCurrentLoadTask) {\n        if (!cancelCurrentLoadTask && mLoadTask != null)\n            return;\n        setDirty();\n        Log.i(Provider.TAG, \"Starting provider: \" + this.getClass().getSimpleName());\n        mTimer.start();\n        mLoadTask = newLoadTask();\n        mLoadTask.execute();\n    }\n\n    protected abstract DBLoader<T> newLoadTask();\n\n    @Override\n    public boolean isLoaded() {\n        return mIsLoaded;\n    }\n\n    @Override\n    public Timer getLoadDuration() {\n        return mTimer;\n    }\n\n    protected void setLoaded() {\n        mIsLoaded = true;\n    }\n\n    @Override\n    public void setDirty() {\n        // mark this as not loaded and wait for DataHandler to call reload\n        mIsLoaded = false;\n        if (mLoadTask != null)\n            mLoadTask.cancel(true);\n        mLoadTask = null;\n    }\n\n    @Override\n    public int getLoadStep() {\n        return LOAD_STEP_2;\n    }\n\n    /**\n     * Whether or not this provider may be able to find a pojo with the specified id\n     *\n     * @param id id we're looking for\n     * @return true if the provider can handle the query; does not guarantee it will!\n     */\n    @Override\n    public boolean mayFindById(@NonNull String id) {\n        return false;\n    }\n\n    /**\n     * Try to find a record by its id\n     *\n     * @param id id we're looking for\n     * @return null if not found\n     */\n    @Override\n    public T findById(@NonNull String id) {\n        for (T entryItem : entryList) {\n            if (entryItem.id.equals(id)) {\n                return entryItem;\n            }\n        }\n        return null;\n    }\n\n    @Nullable\n    @Override\n    public List<T> getPojos() {\n        if (BuildConfig.DEBUG)\n            return Collections.unmodifiableList(entryList);\n        return entryList;\n    }\n\n    protected abstract static class DBLoader<T extends EntryItem> extends AsyncTask<Void, List<T>> {\n        protected final WeakReference<DBProvider<T>> weakProvider;\n\n        public DBLoader(DBProvider<T> provider) {\n            super();\n            weakProvider = new WeakReference<>(provider);\n        }\n\n        @Nullable\n        protected Context getContext() {\n            DBProvider<T> provider = weakProvider.get();\n            return provider != null ? provider.context : null;\n        }\n\n        @WorkerThread\n        @Override\n        protected List<T> doInBackground(Void param) {\n            Context ctx = getContext();\n            if (ctx == null)\n                return null;\n\n            DataHandler dataHandler = TBApplication.getApplication(ctx).getDataHandler();\n\n            return getEntryItems(dataHandler);\n        }\n\n        @WorkerThread\n        abstract List<T> getEntryItems(DataHandler dataHandler);\n\n        @MainThread\n        @Override\n        protected void onPostExecute(List<T> entryItems) {\n            DBProvider<T> provider = weakProvider.get();\n            if (entryItems == null || provider == null || provider.mLoadTask != this)\n                return;\n\n            // get the result\n            provider.entryList = entryItems;\n\n            // mark the provider as loaded\n            provider.setLoaded();\n            provider.mLoadTask = null;\n\n            provider.mTimer.stop();\n            Log.i(\"time\", \"Time to load \" + provider.getClass().getSimpleName() + \": \" + provider.mTimer);\n\n            DataHandler.sendBroadcast(provider.context, TBLauncherActivity.LOAD_OVER, provider.getClass().getSimpleName());\n        }\n\n        public void execute() {\n            TaskRunner.executeOnExecutor(DataHandler.EXECUTOR_PROVIDERS, this);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/dataprovider/DialProvider.java",
    "content": "package rocks.tbog.tblauncher.dataprovider;\n\nimport androidx.annotation.NonNull;\n\nimport java.util.regex.Pattern;\n\nimport rocks.tbog.tblauncher.entry.ContactEntry;\nimport rocks.tbog.tblauncher.entry.DialContactEntry;\nimport rocks.tbog.tblauncher.searcher.ISearcher;\n\npublic class DialProvider extends SimpleProvider<ContactEntry> {\n\n    // See https://github.com/Neamar/KISS/issues/1137\n    private final Pattern phonePattern;\n\n    private final DialContactEntry resultEntry;\n\n    public DialProvider() {\n        phonePattern = Pattern.compile(\"^[*+0-9# ]{3,}$\");\n        resultEntry = new DialContactEntry();\n    }\n\n    @Override\n    public boolean mayFindById(@NonNull String id) {\n        return id.startsWith(DialContactEntry.SCHEME);\n    }\n\n    @Override\n    public DialContactEntry findById(@NonNull String id) {\n        if (resultEntry.id.equals(id))\n            return resultEntry;\n        return null;\n    }\n\n    @Override\n    public void requestResults(String query, ISearcher searcher) {\n        // Append an item only if query looks like a phone number and device has phone capabilities\n        if (phonePattern.matcher(query).find()) {\n            searcher.addResult(getResult(query));\n        }\n    }\n\n    /**\n     * @param phoneNumber phone number to use in the result\n     * @return a result that may have a fake id.\n     */\n    private ContactEntry getResult(String phoneNumber) {\n        DialContactEntry pojo = resultEntry;\n        pojo.setPhone(phoneNumber);\n        pojo.setName(phoneNumber, false);\n        pojo.setRelevance(pojo.normalizedName, null);\n        String phoneNumberAfterFirstCharacter = phoneNumber.substring(1);\n        if (!phoneNumberAfterFirstCharacter.contains(\"*\") && !phoneNumberAfterFirstCharacter.contains(\"+\")) {\n            // No * and no + (except maybe as a first character), likely to be a phone number and not a Calculator expression\n            pojo.boostRelevance(20);\n        } else {\n            // Query may be a phone number or a calculator expression, more likely to be an expression\n            // Calculator expressions have a relevance of 19, so use something lower\n            pojo.boostRelevance(15);\n        }\n        return pojo;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/dataprovider/EntryToResultUtils.java",
    "content": "package rocks.tbog.tblauncher.dataprovider;\n\nimport android.util.Log;\n\nimport androidx.annotation.WorkerThread;\n\nimport java.util.Collection;\n\nimport rocks.tbog.tblauncher.entry.EntryItem;\nimport rocks.tbog.tblauncher.entry.EntryWithTags;\nimport rocks.tbog.tblauncher.normalizer.StringNormalizer;\nimport rocks.tbog.tblauncher.searcher.ISearcher;\nimport rocks.tbog.tblauncher.searcher.ResultBuffer;\nimport rocks.tbog.tblauncher.utils.FuzzyScore;\n\npublic class EntryToResultUtils {\n    final static String TAG = \"E2R\";\n\n    interface CheckResults<T> {\n        void checkResults(Collection<T> entries, FuzzyScore fuzzyScore, ISearcher searcher);\n    }\n\n    @WorkerThread\n    public static <T extends EntryItem> void recursiveWordCheck(Collection<T> entries, String query, ISearcher searcher, CheckResults<T> action, Class<T> typeClass) {\n        int pos = query.lastIndexOf(' ');\n        if (pos > 0) {\n            String queryLeft = query.substring(0, pos).trim();\n            String queryRight = query.substring(pos + 1).trim();\n\n            StringNormalizer.Result queryNormalizedRight = StringNormalizer.normalizeWithResult(queryRight, false);\n            if (queryNormalizedRight.codePoints.length > 0) {\n                ResultBuffer<T> buffer = new ResultBuffer<>(searcher.tagsEnabled(), typeClass);\n                recursiveWordCheck(entries, queryLeft, buffer, action, typeClass);\n\n                FuzzyScore fuzzyScoreRight = new FuzzyScore(queryNormalizedRight.codePoints);\n                action.checkResults(buffer.getEntryItems(), fuzzyScoreRight, searcher);\n                return;\n            }\n        }\n\n        StringNormalizer.Result queryNormalized = StringNormalizer.normalizeWithResult(query, false);\n        if (queryNormalized.codePoints.length == 0)\n            return;\n\n        FuzzyScore fuzzyScore = new FuzzyScore(queryNormalized.codePoints);\n        action.checkResults(entries, fuzzyScore, searcher);\n    }\n\n    @WorkerThread\n    public static void tagsCheckResults(Collection<? extends EntryWithTags> entries, FuzzyScore fuzzyScore, ISearcher searcher) {\n        Log.d(TAG, \"tagsCheckResults count=\" + entries.size() + \" \" + fuzzyScore);\n\n        for (EntryWithTags entry : entries) {\n            if (entry.isHiddenByUser()) {\n                continue;\n            }\n\n            FuzzyScore.MatchInfo scoreInfo = fuzzyScore.match(entry.normalizedName.codePoints);\n\n            StringNormalizer.Result matchedText = entry.normalizedName;\n            FuzzyScore.MatchInfo matchedInfo = FuzzyScore.MatchInfo.copyOrNewInstance(scoreInfo, null);\n\n            if (searcher.tagsEnabled()) {\n                // check relevance for tags\n                for (EntryWithTags.TagDetails tag : entry.getTags()) {\n                    // fuzzyScore.match will return the same object\n                    scoreInfo = fuzzyScore.match(tag.normalized.codePoints);\n                    if (scoreInfo.match && (!matchedInfo.match || scoreInfo.score > matchedInfo.score)) {\n                        matchedText = tag.normalized;\n                        matchedInfo = FuzzyScore.MatchInfo.copyOrNewInstance(scoreInfo, matchedInfo);\n                    }\n                }\n            }\n\n            entry.addResultMatch(matchedText, matchedInfo);\n\n            if (matchedInfo.match && !searcher.addResult(entry)) {\n                return;\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/dataprovider/FilterProvider.java",
    "content": "package rocks.tbog.tblauncher.dataprovider;\n\nimport android.content.Context;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.StringRes;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.entry.AppEntry;\nimport rocks.tbog.tblauncher.entry.ContactEntry;\nimport rocks.tbog.tblauncher.entry.FilterEntry;\nimport rocks.tbog.tblauncher.entry.ShortcutEntry;\n\npublic class FilterProvider extends DBProvider<FilterEntry> {\n\n    private static final FilterEntry[] s_entries = new FilterEntry[3];\n    @StringRes\n    private static final int[] s_names = new int[3];\n\n    static {\n        int cnt = 0;\n        // apps filter\n        {\n            String id = FilterEntry.SCHEME + \"applications\";\n            FilterEntry filter = new FilterEntry(id, R.drawable.ic_apps, AppEntry.SCHEME);\n            filter.setOnClickListener(v -> {\n                Context ctx = v.getContext();\n                AppProvider provider = TBApplication.getApplication(ctx).getDataHandler().getAppProvider();\n                TBApplication.quickList(ctx).toggleFilter(v, provider);\n            });\n            s_names[cnt] = R.string.filter_apps;\n            s_entries[cnt++] = filter;\n        }\n        // contacts filter\n        {\n            String id = FilterEntry.SCHEME + \"contacts\";\n            FilterEntry filter = new FilterEntry(id, R.drawable.ic_contacts, ContactEntry.SCHEME);\n            filter.setOnClickListener(v -> {\n                Context ctx = v.getContext();\n                ContactsProvider provider = TBApplication.dataHandler(ctx).getContactsProvider();\n                TBApplication.quickList(ctx).toggleFilter(v, provider);\n            });\n            s_names[cnt] = R.string.filter_contacts;\n            s_entries[cnt++] = filter;\n        }\n        // pinned shortcuts filter\n        {\n            String id = FilterEntry.SCHEME + \"shortcuts\";\n            FilterEntry filter = new FilterEntry(id, R.drawable.ic_shortcuts, ShortcutEntry.SCHEME);\n            filter.setOnClickListener(v -> {\n                Context ctx = v.getContext();\n                ShortcutsProvider provider = TBApplication.dataHandler(ctx).getShortcutsProvider();\n                TBApplication.quickList(ctx).toggleFilter(v, provider);\n            });\n            s_names[cnt] = R.string.filter_shortcuts;\n            s_entries[cnt++] = filter;\n        }\n\n        //noinspection ConstantConditions\n        if (cnt != s_entries.length || cnt != s_names.length)\n            throw new IllegalStateException(\"FilterEntry static list size\");\n    }\n\n    public FilterProvider(Context context) {\n        super(context);\n    }\n\n    @Override\n    protected DBLoader<FilterEntry> newLoadTask() {\n        return new UpdateFromModsLoader<>(this, s_entries, s_names);\n    }\n\n    @Override\n    public boolean mayFindById(@NonNull String id) {\n        return id.startsWith(FilterEntry.SCHEME);\n    }\n\n    @NonNull\n    public String getDefaultName(@NonNull String id) {\n        for (int idx = 0; idx < s_entries.length; idx += 1) {\n            if (id.equals(s_entries[idx].id))\n                return context.getString(s_names[idx]);\n        }\n        return \"null\";\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/dataprovider/IProvider.java",
    "content": "package rocks.tbog.tblauncher.dataprovider;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.WorkerThread;\n\nimport java.util.List;\n\nimport rocks.tbog.tblauncher.entry.EntryItem;\nimport rocks.tbog.tblauncher.searcher.ISearcher;\nimport rocks.tbog.tblauncher.utils.Timer;\n\n/**\n * Public interface exposed by every KISS data provider\n */\npublic interface IProvider<T extends EntryItem> {\n    int LOAD_STEP_1 = 0;\n    int LOAD_STEP_2 = 1;\n    int LOAD_STEP_3 = 2;\n    int[] LOAD_STEPS = new int[] {LOAD_STEP_1, LOAD_STEP_2, LOAD_STEP_3};\n\n    /**\n     * Post search results for the given query string to the searcher\n     *  @param query        Some string query (usually provided by the user)\n     * @param searcher The receiver of results\n     */\n    @WorkerThread\n    void requestResults(String query, ISearcher searcher);\n\n    /**\n     * Reload the data stored in this provider\n     * <p>\n     * `LOAD_OVER` will be emitted once the reload is complete. The data provider\n     * will stay usable (using it's old data) during the reload.\n     * @param cancelCurrentLoadTask pass true to stop current loading task and start another;\n     *                              pass false to do nothing if already loading\n     */\n    void reload(boolean cancelCurrentLoadTask);\n\n    /**\n     * Indicate whether this provider has already loaded it's data\n     * <p>\n     * If this method returns `false` then the client may listen for the\n     * `LOAD_OVER` intent for notification of when the provider is ready.\n     *\n     * @return Is the provider ready to process search results?\n     */\n    boolean isLoaded();\n\n    /**\n     * User for debug, this is the last load duration\n     *\n     * @return amount of time it took for this provider to load. null if\n     */\n    @Nullable\n    Timer getLoadDuration();\n\n    /**\n     * Indicate that some providers have reloaded and this one may need to also reload\n     */\n    void setDirty();\n\n    /**\n     * Return the loading step for this provider\n     * @return one of the LOAD_STEPS\n     */\n    int getLoadStep();\n\n    /**\n     * Tells whether or not this provider may be able to find the pojo with\n     * specified id\n     *\n     * @param id id we're looking for\n     * @return true if the provider can handle the query ; does not guarantee it\n     * will!\n     */\n    boolean mayFindById(@NonNull String id);\n\n    /**\n     * Try to find a record by its id\n     *\n     * @param id id we're looking for\n     * @return null if not found\n     */\n    T findById(@NonNull String id);\n\n    /**\n     * Get a list of all pojos, do not modify this list!\n     *\n     * @return list of all entries\n     */\n    @Nullable\n    List<T> getPojos();\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/dataprovider/ModProvider.java",
    "content": "package rocks.tbog.tblauncher.dataprovider;\n\nimport android.content.Context;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport rocks.tbog.tblauncher.db.ModRecord;\nimport rocks.tbog.tblauncher.entry.EntryItem;\nimport rocks.tbog.tblauncher.entry.ICustomIconEntry;\nimport rocks.tbog.tblauncher.handler.DataHandler;\n\n/**\n * This provider is loaded last and is responsible for setting custom names and icons\n */\npublic class ModProvider extends DBProvider<EntryItem> {\n\n    public ModProvider(Context context) {\n        super(context);\n    }\n\n    @Override\n    public int getLoadStep() {\n        return LOAD_STEP_3;\n    }\n\n    @Override\n    protected DBLoader<EntryItem> newLoadTask() {\n        return new FavLoader(this);\n    }\n\n    private static class FavLoader extends DBProvider.DBLoader<EntryItem> {\n\n        public FavLoader(DBProvider<EntryItem> provider) {\n            super(provider);\n        }\n\n        @Override\n        List<EntryItem> getEntryItems(DataHandler dataHandler) {\n            List<ModRecord> list = dataHandler.getMods();\n            ArrayList<EntryItem> favList = new ArrayList<>(list.size());\n            // get EntryItem from ModRecord\n            for (ModRecord fav : list) {\n                EntryItem entry = dataHandler.getPojo(fav.record);\n                if (entry == null)\n                    continue;\n                else if (entry instanceof ICustomIconEntry) {\n                    if (fav.hasCustomIcon() && !((ICustomIconEntry) entry).hasCustomIcon())\n                        ((ICustomIconEntry) entry).setCustomIcon();\n                }\n                if (fav.hasCustomName())\n                    entry.setName(fav.displayName);\n                favList.add(entry);\n            }\n\n            return favList;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/dataprovider/Provider.java",
    "content": "package rocks.tbog.tblauncher.dataprovider;\n\nimport android.app.Service;\nimport android.content.Intent;\nimport android.os.Binder;\nimport android.os.IBinder;\nimport android.util.Log;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\nimport rocks.tbog.tblauncher.BuildConfig;\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.TBLauncherActivity;\nimport rocks.tbog.tblauncher.entry.EntryItem;\nimport rocks.tbog.tblauncher.handler.DataHandler;\nimport rocks.tbog.tblauncher.loader.LoadEntryItem;\nimport rocks.tbog.tblauncher.utils.Timer;\n\npublic abstract class Provider<T extends EntryItem> extends Service implements IProvider<T> {\n    final static String TAG = \"Provider\";\n\n    /**\n     * Binder given to clients\n     */\n    private final IBinder binder = new LocalBinder();\n    /**\n     * Storage for search items used by this provider\n     */\n    protected List<T> pojos = Collections.emptyList();\n    private boolean loaded = false;\n    private LoadEntryItem<T> loader = null;\n    /**\n     * Scheme used to build ids for the pojos created by this provider\n     */\n    @NonNull\n    private String pojoScheme = \"(none)://\";\n\n    private final Timer mTimer = new Timer();\n\n    /**\n     * (Re-)load the providers resources when the provider has been completely initialized\n     * by the Android system\n     */\n    @Override\n    public void onCreate() {\n        super.onCreate();\n\n        TBApplication.dataHandler(this).onProviderRecreated(this);\n        this.reload(true);\n    }\n\n    protected boolean isLoading() {\n        return loader != null;\n    }\n\n    protected void initialize(@NonNull LoadEntryItem<T> loader) {\n        mTimer.start();\n\n        if (this.loader != null)\n            this.loader.cancel(false);\n\n        Log.i(TAG, \"Starting provider: \" + this.getClass().getSimpleName());\n\n        loader.setProvider(this);\n        this.loader = loader;\n        this.pojoScheme = loader.getScheme();\n        this.loader.execute();\n    }\n\n    public void reload(boolean cancelCurrentLoadTask) {\n        if (!cancelCurrentLoadTask && loader != null)\n            return;\n        loaded = false;\n        // Handled at subclass level\n        if (pojos.size() > 0) {\n            Log.v(TAG, \"Reloading provider: \" + this.getClass().getSimpleName());\n        }\n    }\n\n    @Override\n    public void setDirty() {\n        // do nothing, we don't depend on any other provider\n    }\n\n    @Override\n    public boolean isLoaded() {\n        return this.loaded;\n    }\n\n    @Nullable\n    @Override\n    public Timer getLoadDuration() {\n        return mTimer;\n    }\n\n    @Override\n    public int getLoadStep() {\n        return LOAD_STEP_1;\n    }\n\n    public void loadOver(ArrayList<T> results) {\n        mTimer.stop();\n\n        Log.i(TAG, \"Time to load \" + this.getClass().getSimpleName() + \": \" + mTimer);\n\n        // Store results\n        this.pojos = results;\n        this.loaded = true;\n        this.loader = null;\n\n        // Broadcast this event\n        DataHandler.sendBroadcast(this, TBLauncherActivity.LOAD_OVER, getClass().getSimpleName());\n    }\n\n    @NonNull\n    public String getScheme() {\n        return pojoScheme;\n    }\n\n    /**\n     * Tells whether or not this provider may be able to find the pojo with\n     * specified id\n     *\n     * @param id id we're looking for\n     * @return true if the provider can handle the query ; does not guarantee it\n     * will!\n     */\n    public boolean mayFindById(@NonNull String id) {\n        return id.startsWith(pojoScheme);\n    }\n\n    /**\n     * Try to find a record by its id\n     *\n     * @param id id we're looking for\n     * @return null if not found\n     */\n    public T findById(@NonNull String id) {\n        for (T pojo : pojos) {\n            if (pojo.id.equals(id)) {\n                return pojo;\n            }\n        }\n        return null;\n    }\n\n    @Nullable\n    @Override\n    public List<T> getPojos() {\n        if (BuildConfig.DEBUG)\n            return Collections.unmodifiableList(pojos);\n        return pojos;\n    }\n\n    @Override\n    public int onStartCommand(Intent intent, int flags, int startId) {\n        // We want this service to continue running until it is explicitly\n        // stopped, so return sticky.\n        return START_STICKY;\n    }\n\n    @Override\n    public IBinder onBind(Intent intent) {\n        return this.binder;\n    }\n\n    /**\n     * Class used for the client Binder.  Because we know this service always\n     * runs in the same process as its clients, we don't need to deal with IPC.\n     */\n    public class LocalBinder extends Binder {\n        public IProvider<T> getService() {\n            // Return this instance of the provider so that clients can call public methods\n            return Provider.this;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/dataprovider/QuickListProvider.java",
    "content": "package rocks.tbog.tblauncher.dataprovider;\n\nimport android.content.Context;\nimport android.util.Log;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.TBLauncherActivity;\nimport rocks.tbog.tblauncher.db.DBHelper;\nimport rocks.tbog.tblauncher.db.ModRecord;\nimport rocks.tbog.tblauncher.entry.EntryItem;\nimport rocks.tbog.tblauncher.entry.PlaceholderEntry;\nimport rocks.tbog.tblauncher.handler.DataHandler;\n\npublic class QuickListProvider extends DBProvider<EntryItem> {\n    private final static String TAG = QuickListProvider.class.getSimpleName();\n\n    public QuickListProvider(Context context) {\n        super(context);\n    }\n\n    @Override\n    public int getLoadStep() {\n        return LOAD_STEP_1;\n    }\n\n//    @Override\n//    public List<EntryItem> getPojos() {\n//        boolean needsSorting = false;\n//\n//        Collection<ModRecord> recordIds = mQuickListFavRecords.values();\n//        boolean remakeEntries = false;\n//        if (entryList.size() == recordIds.size()) {\n//            for (EntryItem entryItem : entryList) {\n//                if (!mQuickListFavRecords.containsKey(entryItem.id)) {\n//                    Log.d(TAG, \"remake: not found \" + entryItem.id);\n//                    remakeEntries = true;\n//                    break;\n//                }\n//            }\n//        } else {\n//            Log.d(TAG, \"remake: \" + entryList.size() + \" \\u2260 \" + recordIds.size());\n//            remakeEntries = true;\n//        }\n//        if (remakeEntries) {\n//            needsSorting = true;\n//            entryList.clear();\n//            // make them all placeholders, we'll replace later\n//            for (ModRecord fav : recordIds) {\n//                PlaceholderEntry entry = new PlaceholderEntry(fav.record, fav.position);\n//                entry.setName(fav.displayName);\n//                if (fav.hasCustomIcon())\n//                    entry.setCustomIcon();\n//                entryList.add(entry);\n//            }\n//        }\n//\n//        ArrayList<EntryItem> toAdd = new ArrayList<>();\n//        DataHandler dataHandler = TBApplication.dataHandler(context);\n//\n//        // replace placeholders with the correct entry\n//        for (Iterator<EntryItem> iterator = entryList.iterator(); iterator.hasNext(); ) {\n//            EntryItem entryItem = iterator.next();\n//            if (entryItem instanceof PlaceholderEntry) {\n//                needsSorting = true;\n//                entryItem = dataHandler.getPojo(entryItem.id);\n//                if (entryItem != null) {\n//                    toAdd.add(entryItem);\n//                    iterator.remove();\n//                }\n//            }\n//        }\n//        entryList.addAll(toAdd);\n//        // if we have replaced some PlaceholderEntry then we need to sort again\n//        if (needsSorting) {\n//            // sort entryList\n//            Collections.sort(entryList, (o1, o2) -> {\n//                ModRecord p1 = mQuickListFavRecords.get(o1.id);\n//                ModRecord p2 = mQuickListFavRecords.get(o2.id);\n//                if (p1 == null || p1.position == null || p2 == null || p2.position == null)\n//                    return 0;\n//                return p1.position.compareTo(p2.position);\n//            });\n//        }\n//        return super.getPojos();\n//    }\n\n    private void fixPlaceholders() {\n        DataHandler dataHandler = TBApplication.dataHandler(context);\n        int replaceCount = 0;\n        for (int idx = 0; idx < entryList.size(); idx += 1) {\n            EntryItem entryItem = entryList.get(idx);\n            if (entryItem instanceof PlaceholderEntry) {\n                entryItem = dataHandler.getPojo(entryItem.id);\n                if (entryItem != null) {\n                    entryList.set(idx, entryItem);\n                    replaceCount += 1;\n                }\n            }\n        }\n        Log.i(TAG, \"replaced \" + replaceCount + \"/\" + entryList.size() + \" placeholder(s)\");\n    }\n\n    @Override\n    protected DBLoader<EntryItem> newLoadTask() {\n        return new QuickListLoader(this);\n    }\n\n    private static class QuickListLoader extends DBProvider.DBLoader<EntryItem> {\n        public QuickListLoader(DBProvider<EntryItem> provider) {\n            super(provider);\n        }\n\n        @Override\n        List<EntryItem> getEntryItems(DataHandler dataHandler) {\n            Context context = getContext();\n            if (context == null)\n                return null;\n            ArrayList<ModRecord> records = DBHelper.getMods(context);\n            int quickListSize = 0;\n            // count only items in the QuickList\n            for (ModRecord rec : records) {\n                if (rec.isInQuickList())\n                    quickListSize += 1;\n            }\n\n            ArrayList<EntryItem> quickList = new ArrayList<>(quickListSize);\n            // get EntryItem from ModRecord\n            for (ModRecord fav : records) {\n                if (!fav.isInQuickList())\n                    continue;\n                PlaceholderEntry entry = new PlaceholderEntry(fav.record, fav.position);\n                entry.setName(fav.displayName);\n                if (fav.hasCustomIcon())\n                    entry.setCustomIcon();\n                quickList.add(entry);\n            }\n\n            Collections.sort(quickList, (o1, o2) -> {\n                String p1 = ((PlaceholderEntry) o1).position;\n                String p2 = ((PlaceholderEntry) o2).position;\n                if (p1 == null || p2 == null)\n                    return 0;\n                return p1.compareTo(p2);\n            });\n\n            return quickList;\n        }\n\n        @Override\n        protected void onPostExecute(List<EntryItem> entryItems) {\n            super.onPostExecute(entryItems);\n            Context context = getContext();\n            if (context == null)\n                return;\n            TBApplication.dataHandler(context).runAfterLoadOver(() -> {\n                DBProvider<EntryItem> provider = weakProvider.get();\n                if (provider instanceof QuickListProvider) {\n                    ((QuickListProvider) provider).fixPlaceholders();\n                    TBLauncherActivity launcherActivity = TBApplication.launcherActivity(provider.context);\n                    if (launcherActivity != null)\n                        launcherActivity.queueDockReload();\n                }\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/dataprovider/SearchProvider.java",
    "content": "package rocks.tbog.tblauncher.dataprovider;\n\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.webkit.URLUtil;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.collection.ArraySet;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.Set;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\nimport rocks.tbog.tblauncher.BuildConfig;\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.entry.OpenUrlEntry;\nimport rocks.tbog.tblauncher.entry.SearchEngineEntry;\nimport rocks.tbog.tblauncher.entry.SearchEntry;\nimport rocks.tbog.tblauncher.normalizer.StringNormalizer;\nimport rocks.tbog.tblauncher.searcher.ISearcher;\nimport rocks.tbog.tblauncher.utils.FuzzyScore;\n\npublic class SearchProvider extends SimpleProvider<SearchEntry> {\n    private static final String URL_REGEX = \"^(?:[a-z]+://)?(?:[a-z0-9-]|[^\\\\x00-\\\\x7F])+(?:[.](?:[a-z0-9-]|[^\\\\x00-\\\\x7F])+)+.*$\";\n    public static final Pattern urlPattern = Pattern.compile(URL_REGEX);\n    private final SharedPreferences prefs;\n    private final ArrayList<SearchEngineEntry> searchEngines = new ArrayList<>();\n    private final Context context;\n\n    @NonNull\n    public static Set<String> getDefaultSearchProviders(Context context) {\n        String[] defaultSearchProviders = context.getResources().getStringArray(R.array.defaultSearchProviders);\n        return new ArraySet<>(Arrays.asList(defaultSearchProviders));\n    }\n\n    @NonNull\n    public static Set<String> getAvailableSearchProviders(Context context, SharedPreferences prefs) {\n        Set<String> availableProviders = prefs.getStringSet(\"available-search-providers\", null);\n        if (availableProviders == null)\n            availableProviders = SearchProvider.getDefaultSearchProviders(context);\n        if (BuildConfig.DEBUG)\n            return Collections.unmodifiableSet(availableProviders);\n        return availableProviders;\n    }\n\n    @NonNull\n    public static Set<String> getSelectedProviderNames(Context context, SharedPreferences prefs) {\n        Set<String> selectedProviders = prefs.getStringSet(\"selected-search-provider-names\", null);\n        if (selectedProviders == null) {\n            Set<String> availableProviders = getAvailableSearchProviders(context, prefs);\n            selectedProviders = new ArraySet<>(availableProviders.size());\n            for (String availableProvider : availableProviders)\n                selectedProviders.add(getProviderName(availableProvider));\n        }\n        return selectedProviders;\n    }\n\n    @NonNull\n    public static String sanitizeProviderName(@Nullable String name) {\n        if (name == null)\n            return \"[name]\";\n        while (name.contains(\"|\"))\n            name = name.replace('|', ' ');\n        return name;\n    }\n\n    @NonNull\n    public static String sanitizeProviderUrl(@Nullable String url) {\n        if (url == null)\n            return \"%s\";\n        if (!url.contains(\"%s\"))\n            return url + \"%s\";\n        return url;\n    }\n\n    public SearchProvider(Context context, SharedPreferences sharedPreferences) {\n        super();\n        this.context = context.getApplicationContext();\n        this.prefs = sharedPreferences;\n        reload(false);\n    }\n\n    @Override\n    public void reload(boolean cancelCurrentLoadTask) {\n        searchEngines.clear();\n\n        Set<String> availableSearchProviders = SearchProvider.getAvailableSearchProviders(context, prefs);\n        Set<String> selectedProviderNames = SearchProvider.getSelectedProviderNames(context, prefs);\n\n        for (String searchProvider : availableSearchProviders) {\n            String name = getProviderName(searchProvider);\n            if (selectedProviderNames.contains(name)) {\n                String url = getProviderUrl(searchProvider);\n                SearchEngineEntry entry = new SearchEngineEntry(name, url);\n                if (url != null)\n                    searchEngines.add(entry);\n            }\n        }\n    }\n\n    @Override\n    public boolean mayFindById(@NonNull String id) {\n        return id.startsWith(SearchEngineEntry.SCHEME);\n    }\n\n    @Override\n    public SearchEntry findById(@NonNull String id) {\n        for (SearchEngineEntry entry : searchEngines)\n            if (entry.id.equals(id))\n                return entry;\n        return null;\n    }\n\n    @Override\n    public void requestResults(String query, ISearcher searcher) {\n        searcher.addResult(getResults(query).toArray(new SearchEntry[0]));\n    }\n\n    @NonNull\n    private ArrayList<SearchEntry> getResults(String query) {\n        ArrayList<SearchEntry> records = new ArrayList<>();\n        StringNormalizer.Result queryNormalized = StringNormalizer.normalizeWithResult(query, false);\n\n        if (queryNormalized.codePoints.length == 0) {\n            return records;\n        }\n\n        if (prefs.getBoolean(\"enable-search\", true)) {\n            // Get default search engine\n            String defaultSearchEngine = prefs.getString(\"default-search-provider\", \"Google\");\n            for (SearchEngineEntry entry : searchEngines) {\n                entry.setQuery(query);\n                entry.setRelevance(entry.normalizedName, null);\n                // Super low relevance, should never be displayed before anything\n                entry.boostRelevance(-500);\n                if (entry.getName().equals(defaultSearchEngine))\n                    // Display default search engine slightly higher\n                    entry.boostRelevance(100);\n\n                records.add(entry);\n            }\n        }\n\n        if (prefs.getBoolean(\"enable-url\", true)) {\n            FuzzyScore fuzzyScore = new FuzzyScore(queryNormalized.codePoints);\n\n            // Open URLs directly (if I type http://something.com for instance)\n            Matcher m = urlPattern.matcher(query);\n            if (m.find()) {\n                String guessedUrl = URLUtil.guessUrl(query);\n                if (URLUtil.isHttpUrl(guessedUrl))\n                    guessedUrl = \"https://\" + guessedUrl.substring(7);\n                if (URLUtil.isValidUrl(guessedUrl)) {\n                    SearchEntry pojo = new OpenUrlEntry(query, guessedUrl);\n                    pojo.setName(guessedUrl);\n                    FuzzyScore.MatchInfo matchInfo = fuzzyScore.match(pojo.normalizedName.codePoints);\n                    pojo.setRelevance(pojo.normalizedName, matchInfo);\n                    records.add(pojo);\n                }\n            }\n        }\n        return records;\n    }\n\n    @Nullable\n    public static String getProviderUrl(@NonNull String searchProvider) {\n        int pos = searchProvider.indexOf(\"|\");\n        if (pos >= 0)\n            return searchProvider.substring(pos + 1);\n        if (URLUtil.isValidUrl(searchProvider))\n            return searchProvider;\n        return null;\n    }\n\n    @NonNull\n    public static String getProviderName(@NonNull String searchProvider) {\n        int pos = searchProvider.indexOf(\"|\");\n        if (pos >= 0)\n            return searchProvider.substring(0, pos);\n        return \"null\";\n    }\n\n    @NonNull\n    public static String makeProvider(@NonNull String name, @NonNull String url) {\n        return sanitizeProviderName(name) + \"|\" + sanitizeProviderUrl(url);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/dataprovider/ShortcutsProvider.java",
    "content": "package rocks.tbog.tblauncher.dataprovider;\n\nimport android.content.BroadcastReceiver;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.IntentFilter;\nimport android.content.pm.LauncherApps;\nimport android.content.pm.ShortcutInfo;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.Drawable;\nimport android.os.Build;\nimport android.os.Parcelable;\nimport android.os.UserHandle;\nimport android.util.Log;\nimport android.widget.Toast;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.RequiresApi;\nimport androidx.annotation.WorkerThread;\nimport androidx.core.app.ActivityCompat;\nimport androidx.core.content.ContextCompat;\n\nimport java.util.List;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.db.ShortcutRecord;\nimport rocks.tbog.tblauncher.entry.ShortcutEntry;\nimport rocks.tbog.tblauncher.loader.LoadShortcutsEntryItem;\nimport rocks.tbog.tblauncher.searcher.ISearcher;\nimport rocks.tbog.tblauncher.shortcut.ShortcutUtil;\nimport rocks.tbog.tblauncher.utils.Utilities;\n\npublic class ShortcutsProvider extends Provider<ShortcutEntry> {\n    private static boolean notifiedKissNotDefaultLauncher = false;\n\n    private static final String ACTION_INSTALL_SHORTCUT = \"com.android.launcher.action.INSTALL_SHORTCUT\";\n\n    AppsCallback appsCallback = null;\n    final BroadcastReceiver mProfileReceiver = new BroadcastReceiver() {\n        @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)\n        @Override\n        public void onReceive(Context context, Intent intent) {\n            if (Intent.ACTION_MANAGED_PROFILE_ADDED.equals(intent.getAction())) {\n//                ShortcutsProvider.this.reload();\n            } else if (Intent.ACTION_MANAGED_PROFILE_REMOVED.equals(intent.getAction())) {\n//                android.os.UserHandle profile = intent.getParcelableExtra(Intent.EXTRA_USER);\n//\n//                final UserManager manager = (UserManager) ShortcutsProvider.this.getSystemService(Context.USER_SERVICE);\n//                assert manager != null;\n//                UserHandleCompat user = new UserHandleCompat(manager.getSerialNumberForUser(profile), profile);\n//\n//                DataHandler dataHandler = TBApplication.getApplication(context).getDataHandler();\n//                dataHandler.removeFromExcluded(user);\n//                dataHandler.removeFromFavorites(user);\n//                ShortcutsProvider.this.reload();\n            } else if (ACTION_INSTALL_SHORTCUT.equals(intent.getAction())) {\n                Intent i = intent.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);\n                String name = intent.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);\n                Parcelable bitmap = intent.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);\n\n                if (i == null) {\n                    Log.w(\"SHC\", \"Shortcut intent is null \" + name);\n                    return;\n                }\n\n                Drawable icon = null;\n                if (bitmap instanceof Bitmap) {\n                    icon = Utilities.createIconDrawable((Bitmap) bitmap, context);\n                } else {\n                    Parcelable extra = intent.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);\n                    if (extra instanceof Intent.ShortcutIconResource) {\n                        icon = Utilities.createIconDrawable((Intent.ShortcutIconResource) extra, context);\n                    }\n                }\n                if (icon == null) {\n                    icon = TBApplication.getApplication(context).iconsHandler().getDefaultActivityIcon(context);\n                }\n\n                ShortcutRecord record = new ShortcutRecord();\n                record.displayName = name;\n                record.infoData = i.toUri(Intent.URI_INTENT_SCHEME);\n                record.iconPng = ShortcutUtil.getIconBlob(icon);\n                record.packageName = i.getPackage();\n                if (record.packageName == null)\n                    record.packageName = i.getComponent() != null ? i.getComponent().getPackageName() : \"\";\n                if (!TBApplication.getApplication(context).getDataHandler().addShortcut(record))\n                    Log.w(\"SHC\", \"Failed to add shortcut \" + name);\n            }\n        }\n    };\n\n    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)\n    static class AppsCallback extends LauncherApps.Callback {\n        private final Context context;\n\n        AppsCallback(Context context) {\n            this.context = context;\n\n        }\n\n        @Override\n        public void onPackageRemoved(String packageName, UserHandle user) {\n\n        }\n\n        @Override\n        public void onPackageAdded(String packageName, UserHandle user) {\n\n        }\n\n        @Override\n        public void onPackageChanged(String packageName, UserHandle user) {\n\n        }\n\n        @Override\n        public void onPackagesAvailable(String[] packageNames, UserHandle user, boolean replacing) {\n\n        }\n\n        @Override\n        public void onPackagesUnavailable(String[] packageNames, UserHandle user, boolean replacing) {\n\n        }\n\n        @RequiresApi(api = Build.VERSION_CODES.O)\n        @Override\n        public void onShortcutsChanged(@NonNull String packageName, @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {\n            super.onShortcutsChanged(packageName, shortcuts, user);\n            for (ShortcutInfo info : shortcuts) {\n                String action = null;\n                ComponentName component = null;\n                String intentPackage = null;\n                Intent i = info.getIntent();\n                if (i != null) {\n                    action = i.getAction();\n                    component = i.getComponent();\n                    intentPackage = i.getPackage();\n                }\n                Log.i(\"SHC\", \"Shortcut changed for `\" + packageName + \"`\" +\n                    \"\\naction      \" + action +\n                    \"\\ncomponent   \" + component +\n                    \"\\nintentPack  \" + intentPackage +\n                    \"\\nshortLabel  \" + info.getShortLabel() +\n                    \"\\nlongLabel   \" + info.getLongLabel() +\n                    \"\\nisImmutable \" + info.isImmutable() +\n                    \"\\nisEnabled   \" + info.isEnabled() +\n                    \"\\nisPinned    \" + info.isPinned() +\n                    \"\\ninManifest  \" + info.isDeclaredInManifest() +\n                    \"\\nisDynamic   \" + info.isDynamic());\n            }\n        }\n    }\n\n    @Override\n    public void onCreate() {\n        if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            // Package install/uninstall events for the main\n            // profile are still handled using PackageAddedRemovedHandler itself\n\n//            final LauncherApps launcher = (LauncherApps) this.getSystemService(Context.LAUNCHER_APPS_SERVICE);\n//            assert launcher != null;\n//\n//            appsCallback = new AppsCallback(this);\n//            launcher.registerCallback(appsCallback);\n\n            // Try to clean up app-related data when profile is removed\n            IntentFilter filter = new IntentFilter();\n            filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);\n            filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);\n            filter.addAction(ACTION_INSTALL_SHORTCUT);\n            ActivityCompat.registerReceiver(this, mProfileReceiver, filter, ContextCompat.RECEIVER_EXPORTED);\n        }\n\n        super.onCreate();\n    }\n\n    @Override\n    public void onDestroy() {\n        unregisterReceiver(mProfileReceiver);\n        if (appsCallback != null) {\n            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {\n                LauncherApps launcher = (LauncherApps) this.getSystemService(Context.LAUNCHER_APPS_SERVICE);\n                assert launcher != null;\n                launcher.unregisterCallback(appsCallback);\n            }\n        }\n        super.onDestroy();\n    }\n\n    @Override\n    public void reload(boolean cancelCurrentLoadTask) {\n        super.reload(cancelCurrentLoadTask);\n\n        if (!isLoaded() && !isLoading()) {\n            try {\n                // If the user tries to add a new shortcut, but KISS isn't the default launcher\n                // AND the services are not running (low memory), then we won't be able to\n                // spawn a new service on Android 8.1+.\n                this.initialize(new LoadShortcutsEntryItem(this));\n            } catch (IllegalStateException e) {\n                if (!notifiedKissNotDefaultLauncher) {\n                    // Only display this message once per process\n                    Toast.makeText(this, R.string.unable_to_initialize_shortcuts, Toast.LENGTH_LONG).show();\n                }\n                notifiedKissNotDefaultLauncher = true;\n                e.printStackTrace();\n            }\n        }\n    }\n\n    @WorkerThread\n    @Override\n    public void requestResults(String query, ISearcher searcher) {\n        for (ShortcutEntry pojo : pojos)\n            pojo.resetResultInfo();\n\n        EntryToResultUtils.recursiveWordCheck(pojos, query, searcher, EntryToResultUtils::tagsCheckResults, ShortcutEntry.class);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/dataprovider/SimpleProvider.java",
    "content": "package rocks.tbog.tblauncher.dataprovider;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport java.util.List;\n\nimport rocks.tbog.tblauncher.entry.EntryItem;\nimport rocks.tbog.tblauncher.searcher.ISearcher;\nimport rocks.tbog.tblauncher.utils.Timer;\n\n/**\n * Unlike normal providers, simple providers are not Android Services but classic Android class\n * Android Services are expensive to create, and use a lot of memory,\n * so whenever we can, we avoid using them.\n */\npublic abstract class SimpleProvider<T extends EntryItem> implements IProvider<T> {\n\n    @Override\n    public void requestResults(String query, ISearcher searcher) {\n    }\n\n    @Override\n    public void reload(boolean cancelCurrentLoadTask) {\n    }\n\n    @Override\n    public final boolean isLoaded() {\n        return true;\n    }\n\n    @Nullable\n    @Override\n    public Timer getLoadDuration() {\n        return null;\n    }\n\n    @Override\n    public void setDirty() {\n    }\n\n    @Override\n    public int getLoadStep() {\n        return LOAD_STEP_1;\n    }\n\n    @Override\n    public boolean mayFindById(@NonNull String id) {\n        return false;\n    }\n\n    @Override\n    public T findById(@NonNull String id) {\n        return null;\n    }\n\n    @Nullable\n    @Override\n    public List<T> getPojos() {\n        return null;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/dataprovider/TagsProvider.java",
    "content": "package rocks.tbog.tblauncher.dataprovider;\n\nimport android.content.Context;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport rocks.tbog.tblauncher.db.ModRecord;\nimport rocks.tbog.tblauncher.entry.TagEntry;\nimport rocks.tbog.tblauncher.handler.DataHandler;\n\npublic class TagsProvider extends DBProvider<TagEntry> {\n\n    public TagsProvider(Context context) {\n        super(context);\n    }\n\n    @Override\n    protected DBLoader<TagEntry> newLoadTask() {\n        return new FavLoader(this);\n    }\n\n    @Nullable\n    public static TagEntry newTagEntryCheckId(String id) {\n        if (id.startsWith(TagEntry.SCHEME)) {\n            TagEntry tagEntry = new TagEntry(id);\n            String tagName = id.substring(TagEntry.SCHEME.length());\n            tagEntry.setName(tagName);\n            return tagEntry;\n        }\n        return null;\n    }\n\n    @NonNull\n    public static String getTagId(@NonNull String tagName) {\n        return TagEntry.SCHEME + tagName;\n    }\n\n    @NonNull\n    private static TagEntry newTagEntry(@NonNull String id, @NonNull String tagName) {\n        TagEntry tagEntry = new TagEntry(id);\n        tagEntry.setName(tagName);\n        return tagEntry;\n    }\n\n    @Override\n    public boolean mayFindById(@NonNull String id) {\n        return id.startsWith(TagEntry.SCHEME);\n    }\n\n    @NonNull\n    public TagEntry getTagEntry(String tagName) {\n        String id = getTagId(tagName);\n        TagEntry entryItem = findById(id);\n        if (entryItem == null)\n            return newTagEntry(id, tagName);\n        return entryItem;\n    }\n\n    public void addTagEntry(TagEntry tagEntry) {\n        if (null == findById(tagEntry.id))\n            entryList.add(tagEntry);\n    }\n\n    private static class FavLoader extends DBProvider.DBLoader<TagEntry> {\n\n        public FavLoader(DBProvider<TagEntry> provider) {\n            super(provider);\n        }\n\n        @Override\n        List<TagEntry> getEntryItems(DataHandler dataHandler) {\n            ArrayList<TagEntry> tagList = new ArrayList<>();\n            List<ModRecord> mods = dataHandler.getMods();\n            // get TagEntry from ModRecord\n            for (ModRecord mod : mods) {\n                TagEntry entry = newTagEntryCheckId(mod.record);\n                if (entry == null)\n                    continue;\n                if (mod.hasCustomIcon())\n                    entry.setCustomIcon();\n                tagList.add(entry);\n            }\n\n            return tagList;\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/dataprovider/UpdateFromModsLoader.java",
    "content": "package rocks.tbog.tblauncher.dataprovider;\n\nimport android.content.Context;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport rocks.tbog.tblauncher.handler.DataHandler;\nimport rocks.tbog.tblauncher.db.ModRecord;\nimport rocks.tbog.tblauncher.entry.StaticEntry;\n\npublic class UpdateFromModsLoader<T extends StaticEntry> extends DBProvider.DBLoader<T> {\n    private final T[] mEntries;\n    private final int[] mNames;\n\n    public UpdateFromModsLoader(DBProvider<T> provider, T[] entries, int[] names) {\n        super(provider);\n        this.mEntries = entries;\n        this.mNames = names;\n    }\n\n    @Override\n    public List<T> getEntryItems(DataHandler dataHandler) {\n        DBProvider<T> provider = weakProvider.get();\n        if (provider == null)\n            return null;\n        Context context = provider.context;\n\n        ArrayList<T> output = new ArrayList<>(mEntries.length);\n        // copy static entries to returned list, also update the names\n        for (int idx = 0; idx < mEntries.length; idx++) {\n            T entry = mEntries[idx];\n            entry.setName(context.getString(mNames[idx]));\n            output.add(entry);\n        }\n\n        List<ModRecord> mods = dataHandler.getMods();\n        // update custom settings from favorites\n        for (ModRecord mod : mods) {\n            T entry = null;\n            if (provider.mayFindById(mod.record)) {\n                for (T e : output) {\n                    if (e.id.equals(mod.record)) {\n                        entry = e;\n                        break;\n                    }\n                }\n            }\n            if (entry != null) {\n                if (mod.hasCustomName())\n                    entry.setName(mod.displayName);\n                if (mod.hasCustomIcon())\n                    entry.setCustomIcon();\n            }\n        }\n\n        return output;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/db/AppRecord.java",
    "content": "package rocks.tbog.tblauncher.db;\n\npublic final class AppRecord extends FlagsRecord {\n    public static final int FLAG_DEFAULT_NAME = 0x000001;\n    public static final int FLAG_CUSTOM_NAME = 0x000002;\n    public static final int FLAG_CUSTOM_ICON = 0x000004;\n    public static final int FLAG_APP_HIDDEN = 0x000008;\n    private static final int MASK_SAVE_DB_FLAGS = FLAG_DEFAULT_NAME | FLAG_CUSTOM_NAME | FLAG_CUSTOM_ICON | FLAG_APP_HIDDEN;\n    public static final int FLAG_VALIDATED = 0x080000;\n\n    public long dbId = -1;\n\n    public String displayName;\n\n    public String componentName;\n\n    public AppRecord() {\n        flags = FLAG_DEFAULT_NAME;\n    }\n\n    @Override\n    public int getFlagsDB() {\n        return flags & MASK_SAVE_DB_FLAGS;\n    }\n\n    public boolean hasCustomName() {\n        return (flags & FLAG_CUSTOM_NAME) == FLAG_CUSTOM_NAME;\n    }\n\n    public boolean hasCustomIcon() {\n        return (flags & FLAG_CUSTOM_ICON) == FLAG_CUSTOM_ICON;\n    }\n\n    public boolean isHidden() {\n        return (flags & FLAG_APP_HIDDEN) == FLAG_APP_HIDDEN;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/db/DB.java",
    "content": "package rocks.tbog.tblauncher.db;\n\nimport android.content.ContentValues;\nimport android.content.Context;\nimport android.database.sqlite.SQLiteDatabase;\nimport android.database.sqlite.SQLiteOpenHelper;\nimport android.util.Log;\n\nimport rocks.tbog.tblauncher.entry.FilterEntry;\n\nclass DB extends SQLiteOpenHelper {\n\n    private final static String DB_NAME = \"kiss.s3db\";\n    private final static int DB_VERSION = 13;\n\n    DB(Context context) {\n        super(context, DB_NAME, null, DB_VERSION);\n    }\n\n    @Override\n    public void onCreate(SQLiteDatabase database) {\n        createHistory(database);\n        createTags(database);\n        addAppsTable(database);\n        createShortcutsTable(database);\n        createFavoritesTable(database, true);\n        createWidgetsTable(database);\n    }\n\n    void createHistory(SQLiteDatabase database) {\n        database.execSQL(\"CREATE TABLE history ( _id INTEGER PRIMARY KEY AUTOINCREMENT, \\\"query\\\" TEXT, \\\"record\\\" TEXT NOT NULL, \\\"timeStamp\\\" INTEGER DEFAULT 0 NOT NULL)\");\n    }\n\n    void createTags(SQLiteDatabase database) {\n        database.execSQL(\"CREATE TABLE \\\"tags\\\" (\\\"tag\\\" TEXT NOT NULL, \\\"record\\\" TEXT NOT NULL)\");\n        database.execSQL(\"CREATE INDEX idx_tags_record ON \\\"tags\\\"(\\\"record\\\");\");\n    }\n\n    private void addTimeStamps(SQLiteDatabase database) {\n        database.execSQL(\"ALTER TABLE \\\"history\\\" ADD COLUMN \\\"timeStamp\\\" INTEGER DEFAULT 0 NOT NULL\");\n    }\n\n    private void addAppsTable(SQLiteDatabase db) {\n        db.execSQL(\"CREATE TABLE \\\"apps\\\" ( _id INTEGER PRIMARY KEY AUTOINCREMENT, display_name TEXT NOT NULL DEFAULT '', component_name TEXT NOT NULL UNIQUE, custom_flags INTEGER DEFAULT 0, custom_icon BLOB DEFAULT NULL, cached_icon BLOB DEFAULT NULL)\");\n        db.execSQL(\"CREATE INDEX \\\"index_component\\\" ON \\\"apps\\\"(component_name);\");\n    }\n\n    private void createShortcutsTable(SQLiteDatabase db) {\n        db.execSQL(\"CREATE TABLE \\\"shortcuts\\\" ( _id INTEGER PRIMARY KEY AUTOINCREMENT, \\\"name\\\" TEXT NOT NULL, \\\"package\\\" TEXT, \\\"info_data\\\" TEXT, \\\"icon_png\\\" BLOB, \\\"custom_flags\\\" INTEGER DEFAULT 0)\");\n    }\n\n    void createFavoritesTable(SQLiteDatabase db, boolean generateDefaults) {\n        db.execSQL(\"CREATE TABLE \\\"favorites\\\" ( \\\"record\\\" TEXT NOT NULL UNIQUE, \\\"position\\\" TEXT NOT NULL, \\\"custom_flags\\\" INTEGER DEFAULT 0, \\\"name\\\" TEXT DEFAULT NULL, \\\"custom_icon\\\" BLOB DEFAULT NULL )\");\n\n        if (!generateDefaults)\n            return;\n\n        // generate default values\n        ContentValues values = new ContentValues();\n        {\n            values.put(\"record\", FilterEntry.SCHEME + \"applications\");\n            values.put(\"position\", \"0\");\n            values.put(\"custom_flags\", ModRecord.FLAG_SHOW_IN_QUICK_LIST);\n            db.insertWithOnConflict(\"favorites\", null, values, SQLiteDatabase.CONFLICT_IGNORE);\n        }\n        {\n            values.put(\"record\", FilterEntry.SCHEME + \"contacts\");\n            values.put(\"position\", \"1\");\n            values.put(\"custom_flags\", ModRecord.FLAG_SHOW_IN_QUICK_LIST);\n            db.insertWithOnConflict(\"favorites\", null, values, SQLiteDatabase.CONFLICT_IGNORE);\n        }\n        {\n            values.put(\"record\", FilterEntry.SCHEME + \"shortcuts\");\n            values.put(\"position\", \"2\");\n            values.put(\"custom_flags\", ModRecord.FLAG_SHOW_IN_QUICK_LIST);\n            db.insertWithOnConflict(\"favorites\", null, values, SQLiteDatabase.CONFLICT_IGNORE);\n        }\n    }\n\n    private void createWidgetsTable(SQLiteDatabase db) {\n        db.execSQL(\"CREATE TABLE \\\"widgets\\\" (_id INTEGER PRIMARY KEY AUTOINCREMENT, \\\"appWidgetId\\\" INTEGER NOT NULL, \\\"properties\\\" TEXT)\");\n    }\n\n    @Override\n    public void onUpgrade(SQLiteDatabase database, int oldVersion, int newVersion) {\n        Log.d(\"onUpgrade\", \"Updating database from version \" + oldVersion + \" to version \" + newVersion);\n        // See\n        // http://www.drdobbs.com/database/using-sqlite-on-android/232900584\n        if (oldVersion < newVersion) {\n            switch (oldVersion) {\n                case 1:\n                case 2:\n                case 3:\n                    database.execSQL(\"CREATE TABLE shortcuts ( _id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, package TEXT,\"\n                            + \"icon TEXT, intent_uri TEXT NOT NULL, icon_blob BLOB)\");\n                    // fall through\n                case 4:\n                    createTags(database);\n                    // fall through\n                case 5:\n                    addTimeStamps(database);\n                    addAppsTable(database);\n                    // fall through\n                case 6:\n                    database.execSQL(\"DROP TABLE \\\"shortcuts\\\"\");\n                    createShortcutsTable(database);\n                    database.execSQL(\"DROP TABLE \\\"tags\\\"\");\n                    createTags(database);\n                    // fall through\n                case 7:\n                    createFavoritesTable(database, true);\n                    // fall through\n                case 8:\n                    database.execSQL(\"ALTER TABLE \\\"apps\\\" ADD COLUMN \\\"custom_icon\\\" BLOB DEFAULT NULL\");\n                    // fall through\n                case 9:\n                    createWidgetsTable(database);\n                    // fall through\n                case 10:\n                    database.execSQL(\"ALTER TABLE \\\"favorites\\\" ADD COLUMN \\\"name\\\" TEXT DEFAULT NULL\");\n                    database.execSQL(\"ALTER TABLE \\\"favorites\\\" ADD COLUMN \\\"custom_icon\\\" BLOB DEFAULT NULL\");\n                    // fall through\n                case 11:\n                    database.execSQL(\"PRAGMA foreign_keys=off\");\n                    database.beginTransaction();\n                    try {\n                        database.execSQL(\"DROP TABLE IF EXISTS \\\"widgets_old\\\"\");\n                        database.execSQL(\"ALTER TABLE \\\"widgets\\\" RENAME TO \\\"widgets_old\\\"\");\n                        createWidgetsTable(database);\n                        database.execSQL(\"INSERT INTO \\\"widgets\\\" SELECT * FROM \\\"widgets_old\\\"\");\n                        database.execSQL(\"DROP TABLE \\\"widgets_old\\\"\");\n\n                        database.setTransactionSuccessful();\n                    } finally {\n                        database.endTransaction();\n                        database.execSQL(\"PRAGMA foreign_keys=on\");\n                    }\n                    // fall through\n                case 12:\n                    database.execSQL(\"ALTER TABLE \\\"apps\\\" ADD COLUMN \\\"cached_icon\\\" BLOB DEFAULT NULL\");\n                    // fall through\n                default:\n                    break;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/db/DBHelper.java",
    "content": "package rocks.tbog.tblauncher.db;\n\nimport android.content.ContentValues;\nimport android.content.Context;\nimport android.database.Cursor;\nimport android.database.sqlite.SQLiteDatabase;\nimport android.database.sqlite.SQLiteStatement;\nimport android.text.TextUtils;\nimport android.util.Log;\nimport android.util.Pair;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport rocks.tbog.tblauncher.entry.AppEntry;\nimport rocks.tbog.tblauncher.entry.EntryItem;\nimport rocks.tbog.tblauncher.entry.ShortcutEntry;\nimport rocks.tbog.tblauncher.entry.TagEntry;\nimport rocks.tbog.tblauncher.utils.PrefCache;\n\npublic class DBHelper {\n    private static final String TAG = DBHelper.class.getSimpleName();\n    private static DB database = null;\n    private static final String[] TABLE_COLUMNS_APPS = new String[]{\"_id\", \"display_name\", \"component_name\", \"custom_flags\"};//, \"custom_icon\", \"cached_icon\"};\n    private static final String[] TABLE_APPS_CUSTOM_ICON = new String[]{\"custom_icon\"};\n    private static final String[] TABLE_APPS_CACHED_ICON = new String[]{\"cached_icon\"};\n    private static final String[] TABLE_MODS_CUSTOM_ICON = new String[]{\"custom_icon\"};\n    private static final String[] TABLE_COLUMNS_MODS = new String[]{\"record\", \"position\", \"custom_flags\", \"name\"};//, \"custom_icon\"};\n    private static final String[] TABLE_COLUMNS_SHORTCUTS = new String[]{\"_id\", \"name\", \"package\", \"info_data\", \"icon_png\", \"custom_flags\"};\n    private static final String[] TABLE_COLUMNS_SHORTCUTS_NO_ICON = new String[]{\"_id\", \"name\", \"package\", \"info_data\", \"custom_flags\"};\n\n    private DBHelper() {\n    }\n\n    private static SQLiteDatabase getDatabase(Context context) {\n        synchronized (DBHelper.class) {\n            if (database == null) {\n                database = new DB(context);\n            }\n            return database.getReadableDatabase();\n        }\n    }\n\n    private static ArrayList<ValuedHistoryRecord> readCursor(Cursor cursor) {\n        cursor.moveToFirst();\n\n        ArrayList<ValuedHistoryRecord> records = new ArrayList<>(cursor.getCount());\n        while (!cursor.isAfterLast()) {\n            ValuedHistoryRecord entry = new ValuedHistoryRecord();\n\n            entry.record = cursor.getString(0);\n            entry.value = cursor.getLong(1);\n\n            records.add(entry);\n            cursor.moveToNext();\n        }\n        cursor.close();\n\n        return records;\n    }\n\n    /**\n     * Insert new item into history\n     *\n     * @param context android context\n     * @param query   query to insert\n     * @param record  record to insert\n     */\n    public static void insertHistory(Context context, String query, String record) {\n        SQLiteDatabase db = getDatabase(context);\n        ContentValues values = new ContentValues();\n        values.put(\"query\", query);\n        values.put(\"record\", record);\n        values.put(\"timeStamp\", System.currentTimeMillis());\n        long rowId = db.insert(\"history\", null, values);\n        Log.d(TAG, \"insertHistory rowId \" + rowId);\n\n        if (Math.random() <= 0.005) {\n            // Roughly every 200 inserts, clean up the history of items older than 3 months\n            long monthsAgo = 7776000000L; // 1000 * 60 * 60 * 24 * 30 * 3;\n            db.delete(\"history\", \"timeStamp < ?\", new String[]{Long.toString(System.currentTimeMillis() - monthsAgo)});\n            // And vacuum the DB for speed\n            db.execSQL(\"VACUUM\");\n        }\n    }\n\n    public static void removeFromHistory(Context context, String record) {\n        SQLiteDatabase db = getDatabase(context);\n        db.delete(\"history\", \"record = ?\", new String[]{record});\n    }\n\n    public static void clearHistory(Context context) {\n        SQLiteDatabase db = getDatabase(context);\n        db.delete(\"history\", \"\", null);\n    }\n\n    public static void setHistory(Context context, Collection<ValuedHistoryRecord> history) {\n        SQLiteDatabase db = getDatabase(context);\n        db.beginTransaction();\n        try {\n            db.execSQL(\"DROP TABLE IF EXISTS \\\"history\\\"\");\n            database.createHistory(db);\n            ContentValues values = new ContentValues(3);\n            for (ValuedHistoryRecord rec : history) {\n                values.put(\"record\", rec.record);\n                values.put(\"query\", rec.name);\n                values.put(\"timeStamp\", rec.value);\n                db.insert(\"history\", null, values);\n            }\n\n            db.setTransactionSuccessful();\n        } finally {\n            db.endTransaction();\n        }\n    }\n\n    private static Cursor getHistoryByFrecency(SQLiteDatabase db, int limit) {\n        // Since smart history sql uses a group by we don't use the whole history but a limit of recent apps\n        int historyWindowSize = limit * 30;\n\n        // order history based on frequency * recency\n        // frequency = #launches_for_app / #all_launches\n        // recency = 1 / position_of_app_in_normal_history\n        String sql = \"SELECT record, count(*) FROM \" +\n            \" (\" +\n            \"   SELECT * FROM history ORDER BY _id DESC \" +\n            \"   LIMIT \" + historyWindowSize + \"\" +\n            \" ) small_history \" +\n            \" GROUP BY record \" +\n            \" ORDER BY \" +\n            \"   count(*) * 1.0 / (select count(*) from history LIMIT \" + historyWindowSize + \") / ((SELECT _id FROM history ORDER BY _id DESC LIMIT 1) - max(_id) + 0.001) \" +\n            \" DESC \" +\n            \" LIMIT \" + limit;\n        return db.rawQuery(sql, null);\n    }\n\n    private static Cursor getHistoryByFrequency(SQLiteDatabase db, int limit) {\n        // order history based on frequency\n        String sql = \"SELECT record, count(*) FROM history\" +\n            \" GROUP BY record \" +\n            \" ORDER BY count(*) DESC \" +\n            \" LIMIT \" + limit;\n        return db.rawQuery(sql, null);\n    }\n\n    private static Cursor getHistoryByRecency(SQLiteDatabase db, int limit) {\n        return db.query(true, \"history\", new String[]{\"record\", \"1\"}, null, null,\n            null, null, \"_id DESC\", Integer.toString(limit));\n    }\n\n    /**\n     * Get the most used history items adaptively based on a set period of time\n     *\n     * @param db    The SQL db\n     * @param hours How many hours back we want to test frequency against\n     * @param limit Maximum result size\n     * @return Cursor\n     */\n    private static Cursor getHistoryByAdaptive(SQLiteDatabase db, int hours, int limit) {\n        // order history based on frequency\n        String sql = \"SELECT record, count(*) FROM history \" +\n            \"WHERE timeStamp >= 0 \" +\n            \"AND timeStamp >\" + (System.currentTimeMillis() - (hours * 3600000)) +\n            \" GROUP BY record \" +\n            \" ORDER BY count(*) DESC \" +\n            \" LIMIT \" + limit;\n        return db.rawQuery(sql, null);\n    }\n\n    @NonNull\n    static ArrayList<ValuedHistoryRecord> getHistoryRaw(@NonNull Context context) {\n        SQLiteDatabase db = getDatabase(context);\n\n        ArrayList<ValuedHistoryRecord> records;\n        try (Cursor cursor = db.query(\"history\", new String[]{\"record\", \"query\", \"timeStamp\"}, null, null, null, null, \"\\\"_id\\\" ASC\")) {\n\n            records = new ArrayList<>(cursor.getCount());\n            while (cursor.moveToNext()) {\n                ValuedHistoryRecord entry = new ValuedHistoryRecord();\n\n                entry.record = cursor.getString(0);\n                entry.name = cursor.getString(1);\n                entry.value = cursor.getLong(2);\n\n                records.add(entry);\n            }\n        }\n        return records;\n    }\n\n    public enum HistoryMode {\n        RECENCY,\n        FRECENCY,\n        FREQUENCY,\n        ADAPTIVE,\n    }\n\n    /**\n     * Retrieve previous query history\n     *\n     * @param context android context\n     * @param limit   max number of items to retrieve\n     * @return records with number of use\n     */\n    @NonNull\n    public static List<ValuedHistoryRecord> getHistory(Context context, int limit, HistoryMode historyMode) {\n        List<ValuedHistoryRecord> records = null;\n\n        SQLiteDatabase db = getDatabase(context);\n\n        Cursor cursor = null;\n        switch (historyMode) {\n            case FRECENCY:\n                cursor = getHistoryByFrecency(db, limit);\n                break;\n            case FREQUENCY:\n                cursor = getHistoryByFrequency(db, limit);\n                break;\n            case ADAPTIVE:\n                cursor = getHistoryByAdaptive(db, PrefCache.getHistoryAdaptive(context), limit);\n                break;\n            case RECENCY:\n                cursor = getHistoryByRecency(db, limit);\n                break;\n        }\n        if (cursor != null) {\n            records = readCursor(cursor);\n            cursor.close();\n        }\n        if (records == null)\n            records = Collections.emptyList();\n\n        return records;\n    }\n\n\n    /**\n     * Retrieve history size\n     *\n     * @param context android context\n     * @return total number of use for the application\n     */\n    public static int getHistoryLength(Context context) {\n        SQLiteDatabase db = getDatabase(context);\n\n        int historyLength = 0;\n        // Cursor query (boolean distinct, String table, String[] columns,\n        // String selection, String[] selectionArgs, String groupBy, String\n        // having, String orderBy, String limit)\n        try (Cursor cursor = db.query(false, \"history\", new String[]{\"COUNT(*)\"},\n            null, null, null, null, null, null)) {\n\n            cursor.moveToFirst();\n            historyLength = cursor.getInt(0);\n        }\n        return historyLength;\n    }\n\n    /**\n     * Retrieve previously selected items for the query\n     *\n     * @param context android context\n     * @param query   query to run\n     * @return records with number of use\n     */\n    public static ArrayList<ValuedHistoryRecord> getPreviousResultsForQuery(Context context,\n                                                                            String query) {\n        ArrayList<ValuedHistoryRecord> records;\n        SQLiteDatabase db = getDatabase(context);\n\n        // Cursor query (String table, String[] columns, String selection,\n        // String[] selectionArgs, String groupBy, String having, String\n        // orderBy)\n        try (Cursor cursor = db.query(\"history\", new String[]{\"record\", \"COUNT(*) AS count\"},\n            \"query LIKE ?\", new String[]{query + \"%\"}, \"record\", null, \"COUNT(*) DESC\", \"10\")) {\n            records = readCursor(cursor);\n        }\n        return records;\n    }\n\n    public static boolean insertApp(Context context, AppEntry entry) {\n        SQLiteDatabase db = getDatabase(context);\n\n        ContentValues values = new ContentValues();\n        values.put(\"name\", entry.getName());\n        values.put(\"component_name\", entry.getUserComponentName());\n\n        return -1 != db.insert(\"apps\", null, values);\n    }\n\n    public static boolean insertShortcut(@NonNull Context context, @NonNull ShortcutRecord shortcut) {\n        SQLiteDatabase db = getDatabase(context);\n        // Do not add duplicate shortcuts\n        try (Cursor cursor = db.query(\"shortcuts\", new String[]{\"package\", \"info_data\"},\n            \"package = ? AND info_data = ?\", new String[]{shortcut.packageName, shortcut.infoData}, null, null, null, null)) {\n            // cursor contains duplicates\n            if (cursor.getCount() > 0) {\n                return false;\n            }\n        }\n\n        ContentValues values = new ContentValues();\n        values.put(\"name\", shortcut.displayName);\n        values.put(\"package\", shortcut.packageName);\n        values.put(\"info_data\", shortcut.infoData);\n        values.put(\"icon_png\", shortcut.iconPng);\n        values.put(\"custom_flags\", shortcut.getFlagsDB());\n\n        return -1 != db.insert(\"shortcuts\", null, values);\n    }\n\n    public static void removeShortcut(@NonNull Context context, @NonNull ShortcutEntry shortcut) {\n        SQLiteDatabase db = getDatabase(context);\n        db.delete(\"shortcuts\", \"package = ? AND info_data = ?\", new String[]{shortcut.packageName, shortcut.shortcutData});\n    }\n\n    public static void removeShortcut(@NonNull Context context, long dbId) {\n        SQLiteDatabase db = getDatabase(context);\n        db.delete(\"shortcuts\", \"_id=?\", new String[]{String.valueOf(dbId)});\n    }\n\n    public static void renameShortcut(@NonNull Context context, @NonNull ShortcutEntry shortcut, String newName) {\n        SQLiteDatabase db = getDatabase(context);\n        String sql = \"UPDATE \\\"shortcuts\\\" SET \\\"name\\\"=? WHERE \\\"package\\\"=? AND \\\"info_data\\\"=?\";\n        try {\n            SQLiteStatement statement = db.compileStatement(sql);\n            statement.bindString(1, newName);\n            statement.bindString(2, shortcut.packageName);\n            statement.bindString(3, shortcut.shortcutData);\n            int count = statement.executeUpdateDelete();\n            if (count != 1) {\n                Log.e(TAG, \"Update name count = \" + count);\n            }\n            statement.close();\n        } catch (Exception e) {\n            Log.e(TAG, \"rename shortcut\", e);\n        }\n    }\n\n    /**\n     * Retrieve a list of all shortcuts for current package name, without icons.\n     * Useful when we remove an app and need to also remove the shortcuts for it.\n     */\n    @NonNull\n    public static List<ShortcutRecord> getShortcutsNoIcons(@NonNull Context context, @NonNull String packageName) {\n        SQLiteDatabase db = getDatabase(context);\n\n        ArrayList<ShortcutRecord> records;\n        try (Cursor cursor = db.query(\"shortcuts\",\n            TABLE_COLUMNS_SHORTCUTS_NO_ICON,\n            \"package = ?\", new String[]{packageName},\n            null, null, null)) {\n\n            records = new ArrayList<>(cursor.getCount());\n            while (cursor.moveToNext()) {\n                ShortcutRecord entry = new ShortcutRecord();\n\n                entry.dbId = cursor.getLong(0);\n                entry.displayName = cursor.getString(1);\n                entry.packageName = cursor.getString(2);\n                entry.infoData = cursor.getString(3);\n                entry.flags = cursor.getInt(4);\n\n                records.add(entry);\n            }\n        }\n\n        return records;\n    }\n\n    /**\n     * Retrieve a list of all shortcuts, without icons.\n     */\n    @NonNull\n    public static ArrayList<ShortcutRecord> getShortcutsNoIcons(@NonNull Context context) {\n        SQLiteDatabase db = getDatabase(context);\n\n        ArrayList<ShortcutRecord> records;\n        try (Cursor cursor = db.query(\"shortcuts\",\n            TABLE_COLUMNS_SHORTCUTS_NO_ICON,\n            null, null, null, null, null)) {\n\n            records = new ArrayList<>(cursor.getCount());\n            while (cursor.moveToNext()) {\n                ShortcutRecord entry = new ShortcutRecord();\n\n                entry.dbId = cursor.getLong(0);\n                entry.displayName = cursor.getString(1);\n                entry.packageName = cursor.getString(2);\n                entry.infoData = cursor.getString(3);\n                entry.flags = cursor.getInt(4);\n\n                records.add(entry);\n            }\n        }\n\n        return records;\n    }\n\n    @Nullable\n    public static byte[] getShortcutIcon(@NonNull Context context, long dbId) {\n        SQLiteDatabase db = getDatabase(context);\n\n        byte[] iconBlob = null;\n        try (Cursor cursor = db.query(\"shortcuts\", new String[]{\"icon_png\"},\n            \"_id = ?\", new String[]{Long.toString(dbId)},\n            null, null, null)) {\n\n            if (cursor.moveToNext()) {\n                iconBlob = cursor.getBlob(0);\n            }\n        }\n        return iconBlob;\n    }\n\n    /**\n     * Remove shortcuts for a given package name\n     */\n    public static void removeShortcuts(Context context, String packageName) {\n        SQLiteDatabase db = getDatabase(context);\n\n        // remove shortcuts\n        db.delete(\"shortcuts\", \"package = ?\", new String[]{packageName});\n    }\n\n    public static void removeAllShortcuts(Context context) {\n        SQLiteDatabase db = getDatabase(context);\n        // delete whole table\n        db.delete(\"shortcuts\", null, null);\n        //db.execSQL(\"vacuum\"); //https://www.sqlitetutorial.net/sqlite-vacuum/\n    }\n\n    /**\n     * Insert new tag for given {@link rocks.tbog.tblauncher.entry.EntryItem}\n     *\n     * @param context android context\n     * @param tag     tag name to insert\n     * @param entry   EntryItem\n     */\n    public static void addTag(Context context, String tag, EntryItem entry) {\n        SQLiteDatabase db = getDatabase(context);\n        ContentValues values = new ContentValues();\n        values.put(\"tag\", tag);\n        values.put(\"record\", entry.id);\n        db.insert(\"tags\", null, values);\n    }\n\n\n    /**\n     * Delete a tag from a given {@link rocks.tbog.tblauncher.entry.EntryItem}\n     *\n     * @param context android context\n     * @param tag     tag name to remove\n     * @param entryId EntryItem.id\n     * @return number of records affected\n     */\n    public static int removeTag(Context context, String tag, String entryId) {\n        SQLiteDatabase db = getDatabase(context);\n\n        return db.delete(\"tags\", \"tag = ? AND record = ?\", new String[]{tag, entryId});\n    }\n\n    /**\n     * @param context android context\n     * @param tagName what tag to rename\n     * @param newName the new name of the tag\n     * @return number of records affected\n     */\n    public static int renameTag(Context context, String tagName, String newName, @Nullable TagEntry tagEntry, @Nullable TagEntry newEntry) {\n        SQLiteDatabase db = getDatabase(context);\n\n        ContentValues values = new ContentValues();\n\n        if (tagEntry != null && newEntry != null) {\n            values.put(\"record\", newEntry.id);\n            int count = db.updateWithOnConflict(\"favorites\", values, \"record = ?\", new String[]{tagEntry.id}, SQLiteDatabase.CONFLICT_REPLACE);\n            if (count != 1) {\n                Log.e(TAG, \"Update favorites in rename tag; count = \" + count);\n            }\n        }\n\n        values.clear();\n        values.put(\"tag\", newName);\n\n        return db.update(\"tags\", values, \"tag = ?\", new String[]{tagName});\n    }\n\n    /**\n     * @param context android context\n     * @return HashMap with EntryItem id as key and an ArrayList of tags for each\n     */\n    @NonNull\n    public static Map<String, List<String>> loadTags(Context context) {\n        Map<String, List<String>> records;\n        SQLiteDatabase db = getDatabase(context);\n        try (Cursor cursor = db.query(\"tags\", new String[]{\"record\", \"tag\"},\n            null, null, null, null, null)) {\n            records = new HashMap<>(cursor.getCount());\n            while (cursor.moveToNext()) {\n                String id = cursor.getString(0);\n                String tag = cursor.getString(1);\n                List<String> tagList = records.get(id);\n                if (tagList == null)\n                    records.put(id, tagList = new ArrayList<>());\n                tagList.add(tag);\n            }\n        }\n        return records;\n    }\n\n    /**\n     * Drop all previous tags and set the ones provided in the map\n     *\n     * @param context android context\n     * @param tagsMap map with tag names as key and a list of ids as value\n     */\n    public static void setTagsMap(Context context, Map<String, ? extends Collection<String>> tagsMap) {\n        SQLiteDatabase db = getDatabase(context);\n        db.beginTransaction();\n        try {\n            //db.delete(\"tags\", null, null);\n            db.execSQL(\"DROP TABLE IF EXISTS \\\"tags\\\"\");\n            database.createTags(db);\n            ContentValues values = new ContentValues(2);\n            for (Map.Entry<String, ? extends Collection<String>> entry : tagsMap.entrySet()) {\n                values.put(\"tag\", entry.getKey());\n                for (String record : entry.getValue()) {\n                    values.put(\"record\", record);\n                    db.insert(\"tags\", null, values);\n                }\n            }\n\n            db.setTransactionSuccessful();\n        } finally {\n            db.endTransaction();\n        }\n    }\n\n    /**\n     * Add all tags from the provided map values\n     *\n     * @param context android context\n     * @param tags    map with record names as key and a list of tags as value\n     */\n    public static void addTags(Context context, Map<String, ? extends Collection<String>> tags) {\n        SQLiteDatabase db = getDatabase(context);\n        db.beginTransaction();\n        try {\n            ContentValues values = new ContentValues(2);\n            for (Map.Entry<String, ? extends Collection<String>> entry : tags.entrySet()) {\n                values.put(\"record\", entry.getKey());\n                for (String tag : entry.getValue()) {\n                    values.put(\"tag\", tag);\n                    db.insert(\"tags\", null, values);\n                }\n            }\n\n            db.setTransactionSuccessful();\n        } finally {\n            db.endTransaction();\n        }\n    }\n\n    /**\n     * @param context android context\n     * @return List of all tags\n     */\n    @NonNull\n    public static List<String> loadTagList(Context context) {\n        List<String> tags;\n        SQLiteDatabase db = getDatabase(context);\n        try (Cursor cursor = db.query(\"tags\", new String[]{\"tag\"},\n            null, null, \"tag\", null, null)) {\n            tags = new ArrayList<>(cursor.getCount());\n            while (cursor.moveToNext()) {\n                String tag = cursor.getString(0);\n                tags.add(tag);\n            }\n        }\n        return tags;\n    }\n\n    /**\n     * @param context android context\n     * @param record  the id of the app\n     * @return HashMap with EntryItem id as key and an ArrayList of tags for each\n     */\n    @NonNull\n    public static List<String> loadTags(Context context, String record) {\n        List<String> tagList;\n        SQLiteDatabase db = getDatabase(context);\n        try (Cursor cursor = db.query(\"tags\", new String[]{\"tag\"},\n            \"record=?\", new String[]{record}, null, null, null)) {\n            tagList = new ArrayList<>(cursor.getCount());\n            while (cursor.moveToNext()) {\n                String tag = cursor.getString(0);\n                tagList.add(tag);\n            }\n        }\n        return tagList;\n    }\n\n    @NonNull\n    public static HashMap<String, AppRecord> getAppsData(Context context) {\n        HashMap<String, AppRecord> records;\n        SQLiteDatabase db = getDatabase(context);\n        try (Cursor cursor = db.query(\"apps\", TABLE_COLUMNS_APPS,\n            null, null, null, null, null)) {\n            records = new HashMap<>(cursor.getCount());\n            while (cursor.moveToNext()) {\n                AppRecord entry = new AppRecord();\n\n                entry.dbId = cursor.getLong(0);\n                entry.displayName = cursor.getString(1);\n                entry.componentName = cursor.getString(2);\n                entry.flags = cursor.getInt(3);\n\n                records.put(entry.componentName, entry);\n            }\n        }\n\n        return records;\n    }\n\n    public static void insertOrUpdateApps(Context context, ArrayList<AppRecord> appRecords) {\n        SQLiteDatabase db = getDatabase(context);\n        db.beginTransaction();\n        ContentValues values = new ContentValues();\n        try {\n            for (AppRecord app : appRecords) {\n                values.put(\"display_name\", app.displayName);\n                values.put(\"component_name\", app.componentName);\n                values.put(\"custom_flags\", app.getFlagsDB());\n                if (app.dbId == -1) {\n                    // insert\n                    db.insertWithOnConflict(\"apps\", null, values, SQLiteDatabase.CONFLICT_IGNORE);\n                } else {\n                    // update\n                    db.updateWithOnConflict(\"apps\", values, \"_id=\" + app.dbId, null, SQLiteDatabase.CONFLICT_IGNORE);\n                }\n            }\n\n            db.setTransactionSuccessful();\n        } finally {\n            db.endTransaction();\n        }\n    }\n\n    public static void deleteApps(Context context, ArrayList<AppRecord> appRecords) {\n        SQLiteDatabase db = getDatabase(context);\n        String[] list = new String[appRecords.size()];\n        for (int i = 0; i < appRecords.size(); i++) {\n            AppRecord rec = appRecords.get(i);\n            list[i] = String.valueOf(rec.dbId);\n        }\n        String whereClause = String.format(\"_id IN (%s)\", TextUtils.join(\",\", Collections.nCopies(list.length, \"?\")));\n        db.delete(\"apps\", whereClause, list);\n    }\n\n    public static void setCustomAppName(Context context, String componentName, String newName) {\n        SQLiteDatabase db = getDatabase(context);\n        String sql = \"UPDATE apps SET display_name=?,custom_flags=custom_flags|? WHERE component_name=?\";\n        try {\n            SQLiteStatement statement = db.compileStatement(sql);\n            statement.bindString(1, newName);\n            statement.bindLong(2, AppRecord.FLAG_CUSTOM_NAME);\n            statement.bindString(3, componentName);\n            int count = statement.executeUpdateDelete();\n            if (count != 1) {\n                Log.e(TAG, \"Update name; count = \" + count);\n            }\n            statement.close();\n        } catch (Exception e) {\n            Log.e(TAG, \"Insert or Update custom app name\", e);\n        }\n    }\n\n    public static boolean setAppHidden(Context context, String componentName) {\n        boolean ret = false;\n        SQLiteDatabase db = getDatabase(context);\n        String sql = \"UPDATE \\\"apps\\\" SET \\\"custom_flags\\\"=\\\"custom_flags\\\"|? WHERE \\\"component_name\\\"=?\";\n        try {\n            SQLiteStatement statement = db.compileStatement(sql);\n            statement.bindLong(1, AppRecord.FLAG_APP_HIDDEN);\n            statement.bindString(2, componentName);\n            int count = statement.executeUpdateDelete();\n            if (count == 1) {\n                ret = true;\n            } else {\n                Log.e(TAG, \"Update name; count = \" + count);\n            }\n            statement.close();\n        } catch (Exception e) {\n            Log.e(TAG, \"Insert or Update custom app name\", e);\n        }\n        return ret;\n    }\n\n    public static boolean removeAppHidden(Context context, String componentName) {\n        boolean ret = false;\n        SQLiteDatabase db = getDatabase(context);\n        String sql = \"UPDATE \\\"apps\\\" SET \\\"custom_flags\\\"=\\\"custom_flags\\\"&~? WHERE \\\"component_name\\\"=?\";\n        try {\n            SQLiteStatement statement = db.compileStatement(sql);\n            statement.bindLong(1, AppRecord.FLAG_APP_HIDDEN);\n            statement.bindString(2, componentName);\n            int count = statement.executeUpdateDelete();\n            if (count == 1) {\n                ret = true;\n            } else {\n                Log.e(TAG, \"Update name; count = \" + count);\n            }\n            statement.close();\n        } catch (Exception e) {\n            Log.e(TAG, \"Insert or Update custom app name\", e);\n        }\n        return ret;\n    }\n\n    public static void setCustomStaticEntryName(Context context, String entryId, String newName) {\n        SQLiteDatabase db = getDatabase(context);\n        int count;\n        String sql = \"UPDATE favorites SET name=?,custom_flags=custom_flags|? WHERE record=?\";\n        try {\n            SQLiteStatement statement = db.compileStatement(sql);\n            statement.bindString(1, newName);\n            statement.bindLong(2, ModRecord.FLAG_CUSTOM_NAME);\n            statement.bindString(3, entryId);\n            count = statement.executeUpdateDelete();\n            if (count != 1) {\n                Log.e(TAG, \"Update name for `\" + entryId + \"`; count = \" + count);\n            }\n            statement.close();\n        } catch (Exception e) {\n            Log.e(TAG, \"Update custom static entry name for `\" + entryId + \"`\", e);\n            count = -1;\n        }\n        if (count < 1) {\n            ContentValues values = new ContentValues();\n            values.put(\"record\", entryId);\n            values.put(\"position\", \"\");\n            values.put(\"name\", newName);\n            values.put(\"custom_flags\", ModRecord.FLAG_CUSTOM_NAME);\n            db.insertWithOnConflict(\"favorites\", null, values, SQLiteDatabase.CONFLICT_REPLACE);\n        }\n    }\n\n    public static void removeCustomAppName(Context context, String componentName, String defaultName) {\n        SQLiteDatabase db = getDatabase(context);\n        String sql = \"UPDATE apps SET display_name=?,custom_flags=custom_flags&~? WHERE component_name=?\";\n        try {\n            SQLiteStatement statement = db.compileStatement(sql);\n            statement.bindString(1, defaultName);\n            statement.bindLong(2, AppRecord.FLAG_CUSTOM_NAME);\n            statement.bindString(3, componentName);\n            int count = statement.executeUpdateDelete();\n            if (count != 1) {\n                Log.e(TAG, \"Reset name; count = \" + count);\n            }\n            statement.close();\n        } catch (Exception e) {\n            Log.e(TAG, \"Insert or Update custom app name\", e);\n        }\n    }\n\n    @Nullable\n    private static AppRecord getAppRecord(SQLiteDatabase db, String componentName) {\n        String[] selArgs = new String[]{componentName};\n        try (Cursor cursor = db.query(\"apps\", TABLE_COLUMNS_APPS,\n            \"component_name=?\", selArgs, null, null, null)) {\n            if (cursor.moveToNext()) {\n                AppRecord entry = new AppRecord();\n\n                entry.dbId = cursor.getLong(0);\n                entry.displayName = cursor.getString(1);\n                entry.componentName = cursor.getString(2);\n                entry.flags = cursor.getInt(3);\n\n                return entry;\n            }\n        }\n        return null;\n    }\n\n    public static boolean setCachedAppIcon(Context context, String componentName, byte[] icon) {\n        SQLiteDatabase db = getDatabase(context);\n        String sql = \"UPDATE apps SET cached_icon=? WHERE component_name=?\";\n        try {\n            SQLiteStatement statement = db.compileStatement(sql);\n            statement.bindBlob(1, icon);\n            statement.bindString(2, componentName);\n            int count = statement.executeUpdateDelete();\n            if (count != 1) {\n                Log.e(TAG, \"setCachedAppIcon; count = \" + count);\n            }\n            statement.close();\n        } catch (Exception e) {\n            Log.e(TAG, \"Insert or Update cached app icon `\" + componentName + \"`\", e);\n            return false;\n        }\n        return true;\n    }\n\n    public static AppRecord setCustomAppIcon(Context context, String componentName, byte[] icon) {\n        SQLiteDatabase db = getDatabase(context);\n        String sql = \"UPDATE apps SET custom_flags=custom_flags|?, custom_icon=? WHERE component_name=?\";\n        try {\n            SQLiteStatement statement = db.compileStatement(sql);\n            statement.bindLong(1, AppRecord.FLAG_CUSTOM_ICON);\n            statement.bindBlob(2, icon);\n            statement.bindString(3, componentName);\n            int count = statement.executeUpdateDelete();\n            if (count != 1) {\n                Log.e(TAG, \"Update icon; count = \" + count);\n            }\n            statement.close();\n        } catch (Exception e) {\n            Log.e(TAG, \"Insert or Update custom app icon `\" + componentName + \"`\", e);\n        }\n\n        return getAppRecord(db, componentName);\n    }\n\n    public static void setCustomStaticEntryIcon(Context context, String entryId, byte[] icon) {\n        SQLiteDatabase db = getDatabase(context);\n        int count;\n        String sql = \"UPDATE favorites SET custom_flags=custom_flags|?, custom_icon=? WHERE record=?\";\n        try {\n            SQLiteStatement statement = db.compileStatement(sql);\n            statement.bindLong(1, ModRecord.FLAG_CUSTOM_ICON);\n            statement.bindBlob(2, icon);\n            statement.bindString(3, entryId);\n            count = statement.executeUpdateDelete();\n            if (count != 1) {\n                Log.w(TAG, \"Update icon for `\" + entryId + \"`; count = \" + count);\n            }\n            statement.close();\n        } catch (Exception e) {\n            Log.e(TAG, \"Update custom fav icon `\" + entryId + \"`\", e);\n            count = -1;\n        }\n\n        if (count < 1) {\n            ContentValues values = new ContentValues();\n            values.put(\"record\", entryId);\n            values.put(\"position\", \"\");\n            values.put(\"custom_icon\", icon);\n            values.put(\"custom_flags\", ModRecord.FLAG_CUSTOM_ICON);\n            db.insertWithOnConflict(\"favorites\", null, values, SQLiteDatabase.CONFLICT_REPLACE);\n        }\n    }\n\n    public static AppRecord removeCustomAppIcon(Context context, String componentName) {\n        SQLiteDatabase db = getDatabase(context);\n        String sql = \"UPDATE apps SET custom_flags=custom_flags&~?, custom_icon=NULL WHERE component_name=?\";\n        try {\n            SQLiteStatement statement = db.compileStatement(sql);\n            statement.bindLong(1, AppRecord.FLAG_CUSTOM_ICON);\n            statement.bindString(2, componentName);\n            int count = statement.executeUpdateDelete();\n            if (count != 1) {\n                Log.e(TAG, \"Reset icon; count = \" + count);\n            }\n            statement.close();\n        } catch (Exception e) {\n            Log.e(TAG, \"Insert or Update custom app name\", e);\n        }\n\n        return getAppRecord(db, componentName);\n    }\n\n    public static void removeCustomStaticEntryIcon(Context context, String entryId) {\n        SQLiteDatabase db = getDatabase(context);\n        String sql = \"UPDATE favorites SET custom_flags=custom_flags&~?, custom_icon=NULL WHERE record=?\";\n        try {\n            SQLiteStatement statement = db.compileStatement(sql);\n            statement.bindLong(1, ModRecord.FLAG_CUSTOM_ICON);\n            statement.bindString(2, entryId);\n            int count = statement.executeUpdateDelete();\n            if (count != 1) {\n                Log.e(TAG, \"Reset `\" + entryId + \"` icon; count = \" + count);\n            }\n            statement.close();\n        } catch (Exception e) {\n            Log.e(TAG, \"Reset custom entry `\" + entryId + \"` icon\", e);\n        }\n    }\n\n    public static void removeCustomStaticEntryName(Context context, String entryId) {\n        SQLiteDatabase db = getDatabase(context);\n        String sql = \"UPDATE favorites SET custom_flags=custom_flags&~?, name=NULL WHERE record=?\";\n        try {\n            SQLiteStatement statement = db.compileStatement(sql);\n            statement.bindLong(1, ModRecord.FLAG_CUSTOM_NAME);\n            statement.bindString(2, entryId);\n            int count = statement.executeUpdateDelete();\n            if (count != 1) {\n                Log.e(TAG, \"Reset `\" + entryId + \"` name; count = \" + count);\n            }\n            statement.close();\n        } catch (Exception e) {\n            Log.e(TAG, \"Reset custom entry `\" + entryId + \"` name\", e);\n        }\n    }\n\n    @Nullable\n    private static byte[] getAppIcon(Context context, String componentName, String[] dbColumn) {\n        SQLiteDatabase db = getDatabase(context);\n        String[] selArgs = new String[]{componentName};\n        try (Cursor cursor = db.query(\"apps\", dbColumn,\n            \"component_name=?\", selArgs, null, null, null)) {\n            if (cursor.moveToNext()) {\n                return cursor.getBlob(0);\n            }\n        }\n        return null;\n    }\n\n    @Nullable\n    public static byte[] getCachedAppIcon(Context context, String componentName) {\n        return getAppIcon(context, componentName, TABLE_APPS_CACHED_ICON);\n    }\n\n    @Nullable\n    public static byte[] getCustomAppIcon(Context context, String componentName) {\n        return getAppIcon(context, componentName, TABLE_APPS_CUSTOM_ICON);\n    }\n\n    @Nullable\n    public static byte[] getCustomFavIcon(Context context, String record) {\n        SQLiteDatabase db = getDatabase(context);\n        String[] selArgs = new String[]{record};\n        try (Cursor cursor = db.query(\"favorites\", TABLE_MODS_CUSTOM_ICON,\n            \"record=?\", selArgs, null, null, null)) {\n            if (cursor.moveToNext()) {\n                return cursor.getBlob(0);\n            }\n        }\n        return null;\n    }\n\n    public static void setMod(Context context, ModRecord fav) {\n        SQLiteDatabase db = getDatabase(context);\n\n        ContentValues values = new ContentValues();\n        values.put(\"record\", fav.record);\n        values.put(\"position\", fav.position);\n        values.put(\"custom_flags\", fav.getFlagsDB());\n\n        int rows = db.update(\"favorites\", values, \"record=?\", new String[]{fav.record});\n        if (rows == 0)\n            db.insertWithOnConflict(\"favorites\", null, values, SQLiteDatabase.CONFLICT_REPLACE);\n//        try {\n//            db.replaceOrThrow(\"favorites\", null, values);\n//        } catch (SQLException e) {\n//            Log.e(TAG, \"setFavorite \" + fav.record);\n//        }\n    }\n\n    public static void setMods(Context context, Collection<Pair<ModRecord, byte[]>> favRecords) {\n        SQLiteDatabase db = getDatabase(context);\n        db.beginTransaction();\n        try {\n            db.execSQL(\"DROP TABLE IF EXISTS \\\"favorites\\\"\");\n            database.createFavoritesTable(db, false);\n\n            ContentValues values = new ContentValues();\n            for (Pair<ModRecord, byte[]> pair : favRecords) {\n                ModRecord fav = pair.first;\n                byte[] icon = pair.second;\n                values.put(\"record\", fav.record);\n                values.put(\"position\", fav.position == null ? \"\" : fav.position);\n                values.put(\"custom_flags\", fav.getFlagsDB());\n                values.put(TABLE_MODS_CUSTOM_ICON[0], icon);\n                db.insertWithOnConflict(\"favorites\", null, values, SQLiteDatabase.CONFLICT_REPLACE);\n            }\n\n            db.setTransactionSuccessful();\n        } finally {\n            db.endTransaction();\n        }\n    }\n\n    public static boolean removeMod(Context context, String record) {\n        SQLiteDatabase db = getDatabase(context);\n\n        if (0 == db.delete(\"favorites\", \"record=?\", new String[]{record})) {\n            Log.e(TAG, \"removeFavorite \" + record);\n            return false;\n        }\n        return true;\n    }\n\n    @NonNull\n    public static ArrayList<ModRecord> getMods(@NonNull Context context) {\n        ArrayList<ModRecord> list;\n        SQLiteDatabase db = getDatabase(context);\n        try (Cursor c = db.query(\"favorites\", TABLE_COLUMNS_MODS, null, null, null, null, \"position\")) {\n            list = new ArrayList<>(c.getCount());\n            while (c.moveToNext()) {\n                ModRecord fav = new ModRecord();\n                fav.record = c.getString(0);\n                fav.position = c.getString(1);\n                fav.setFlags(c.getInt(2));\n                fav.displayName = c.getString(3);\n                list.add(fav);\n            }\n        }\n        return list;\n    }\n\n    public static boolean updateQuickListPosition(@NonNull Context context, String record, String position) {\n        SQLiteDatabase db = getDatabase(context);\n        String sql = \"UPDATE \\\"favorites\\\" SET \\\"custom_flags\\\"=(\\\"custom_flags\\\"|?), \\\"position\\\"=? WHERE \\\"record\\\"=?\";\n        try {\n            SQLiteStatement statement = db.compileStatement(sql);\n            statement.bindLong(1, ModRecord.FLAG_SHOW_IN_QUICK_LIST);\n            statement.bindString(2, position);\n            statement.bindString(3, record);\n            int count = statement.executeUpdateDelete();\n            if (count != 1) {\n                Log.e(TAG, \"Update position; count = \" + count);\n                return false;\n            }\n            statement.close();\n        } catch (Exception e) {\n            Log.e(TAG, \"set flag and position\", e);\n            return false;\n        }\n        return true;\n    }\n\n    public static ArrayList<WidgetRecord> getWidgets(@NonNull Context context) {\n        SQLiteDatabase db = getDatabase(context);\n        ArrayList<WidgetRecord> list;\n        try (Cursor c = db.query(\"widgets\", new String[]{\"appWidgetId\", \"properties\"}, null, null, null, null, null)) {\n            list = new ArrayList<>(c.getCount());\n            while (c.moveToNext()) {\n                int appWidgetId = c.getInt(0);\n                String properties = c.getString(1);\n                WidgetRecord rec = WidgetRecord.loadFromDB(appWidgetId, properties);\n                list.add(rec);\n            }\n        }\n        return list;\n    }\n\n    public static void addWidget(@NonNull Context context, WidgetRecord rec) {\n        SQLiteDatabase db = getDatabase(context);\n        ContentValues values = new ContentValues();\n        values.put(\"appWidgetId\", rec.appWidgetId);\n        values.put(\"properties\", rec.packedProperties());\n        db.insert(\"widgets\", null, values);\n    }\n\n    public static void removeWidget(@NonNull Context context, int appWidgetId) {\n        SQLiteDatabase db = getDatabase(context);\n        db.delete(\"widgets\", \"appWidgetId=?\", new String[]{String.valueOf(appWidgetId)});\n    }\n\n    public static void removeWidgetPlaceholder(@NonNull Context context, int appWidgetId, String provider) {\n        SQLiteDatabase db = getDatabase(context);\n        //TODO: escape the provider\n        String[] whereArgs = new String[]{String.valueOf(appWidgetId), \"<provider>\" + provider + \"</provider>\"};\n        db.delete(\"widgets\", \"appWidgetId=? AND properties LIKE '%' || ? || '%'\", whereArgs);\n    }\n\n    public static void setWidgetProperties(@NonNull Context context, WidgetRecord rec) {\n        SQLiteDatabase db = getDatabase(context);\n\n        ContentValues values = new ContentValues();\n        values.put(\"properties\", rec.packedProperties());\n\n        int affectedRows = db.update(\"widgets\", values, \"appWidgetId=?\", new String[]{String.valueOf(rec.appWidgetId)});\n        if (affectedRows == 0)\n            addWidget(context, rec);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/db/ExportedData.java",
    "content": "package rocks.tbog.tblauncher.db;\n\nimport android.annotation.SuppressLint;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.util.Log;\nimport android.util.Pair;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.collection.ArraySet;\nimport androidx.preference.PreferenceManager;\n\nimport org.xmlpull.v1.XmlPullParser;\nimport org.xmlpull.v1.XmlPullParserException;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.SettingsActivity;\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.TBLauncherActivity;\nimport rocks.tbog.tblauncher.widgets.WidgetManager;\nimport rocks.tbog.tblauncher.handler.TagsHandler;\nimport rocks.tbog.tblauncher.utils.PrefCache;\nimport rocks.tbog.tblauncher.utils.Utilities;\n\npublic class ExportedData {\n    private static final String TAG = \"XParse\";\n\n    // XTN = xml tag name\n    public static final String XTN_TAG_LIST = \"taglist\";\n    public static final String XTN_TAG_LIST_ITEM = \"tag\";\n    public static final String XTN_TAG_LIST_ITEM_ID = \"item\";\n    public static final String XTN_MOD_LIST = \"favlist\";        // modification list\n    public static final String XTN_MOD_LIST_ITEM = \"favorite\";  // modification entry\n    public static final String XTN_MOD_LIST_ITEM_ID = \"id\";\n    public static final String XTN_APP_LIST = \"applist\";\n    public static final String XTN_APP_LIST_ITEM = \"app\";\n    public static final String XTN_APP_LIST_ITEM_ID = \"component\";\n    public static final String XTN_UI_LIST = \"interface\";\n    public static final String XTN_PREF_LIST = \"preferences\";\n    public static final String XTN_PREF_LIST_ITEM = \"preference\";\n    public static final String XTN_WIDGET_LIST = \"widgets\";\n    public static final String XTN_WIDGET_LIST_ITEM = \"widget\";\n    public static final String XTN_HISTORY_LIST = \"history\";\n    public static final String XTN_HISTORY_LIST_ITEM = \"item\";\n\n    // HashMap with tag name as key and an ArrayList of records for each\n    private final HashMap<String, List<String>> mTags = new HashMap<>();\n    private final HashMap<String, Object> mPreferences = new HashMap<>();\n    private final ArrayList<ModRecord> mMods = new ArrayList<>();\n    private final ArrayList<AppRecord> mApplications = new ArrayList<>();\n    private final ArrayList<PlaceholderWidgetRecord> mWidgets = new ArrayList<>();\n    private final HashMap<FlagsRecord, byte[]> mIcons = new HashMap<>();\n    private final ArrayList<ValuedHistoryRecord> mHistory = new ArrayList<>();\n    private boolean bTagListLoaded = false;\n    private boolean bModListLoaded = false;\n    private boolean bAppListLoaded = false;\n    private boolean bPrefListLoaded = false;\n    private boolean bWidgetListLoaded = false;\n    private boolean bHistoryListLoaded = false;\n\n    void parseTagList(@NonNull XmlPullParser xpp, int eventType) throws IOException, XmlPullParserException {\n        String currentTag = null;\n        boolean bTagItem = false;\n        boolean bTagListFinished = false;\n        while (eventType != XmlPullParser.END_DOCUMENT) {\n            switch (eventType) {\n                case XmlPullParser.START_TAG:\n                    int attrCount = xpp.getAttributeCount();\n                    switch (xpp.getName()) {\n                        case XTN_TAG_LIST_ITEM:\n                            for (int attrIdx = 0; attrIdx < attrCount; attrIdx += 1) {\n                                String attrName = xpp.getAttributeName(attrIdx);\n                                if (\"name\".equals(attrName)) {\n                                    currentTag = xpp.getAttributeValue(attrIdx);\n                                }\n                            }\n                            break;\n                        case XTN_TAG_LIST_ITEM_ID:\n                            bTagItem = currentTag != null;\n                            break;\n                        case XTN_TAG_LIST:\n                            for (int attrIdx = 0; attrIdx < attrCount; attrIdx += 1) {\n                                String attrName = xpp.getAttributeName(attrIdx);\n                                if (\"version\".equals(attrName)) {\n                                    String tagListVersion = xpp.getAttributeValue(attrIdx);\n                                    Log.d(TAG, \"tagList version \" + tagListVersion);\n                                }\n                            }\n                            break;\n                        default:\n                            Log.d(TAG, \"ignored \" + xpp.getName());\n                    }\n                    break;\n                case XmlPullParser.END_TAG:\n                    switch (xpp.getName()) {\n                        case XTN_TAG_LIST_ITEM:\n                            currentTag = null;\n                            // fall-through\n                        case XTN_TAG_LIST_ITEM_ID:\n                            bTagItem = false;\n                            break;\n                        case XTN_TAG_LIST:\n                            bTagListFinished = true;\n                            break;\n                    }\n                    break;\n                case XmlPullParser.TEXT:\n                    if (bTagItem && currentTag != null) {\n                        addRecordTag(xpp.getText(), currentTag);\n                    }\n                    break;\n            }\n            if (bTagListFinished)\n                break;\n            eventType = xpp.next();\n        }\n        bTagListLoaded = true;\n    }\n\n    public void parseFavorites(XmlPullParser xpp, int eventType) throws IOException, XmlPullParserException {\n        ModRecord currentFav = null;\n        boolean bFavListFinished = false;\n        String lastTag = null;\n        String iconEncoding = null;\n        while (eventType != XmlPullParser.END_DOCUMENT) {\n            switch (eventType) {\n                case XmlPullParser.START_TAG:\n                    int attrCount = xpp.getAttributeCount();\n                    switch (xpp.getName()) {\n                        case XTN_MOD_LIST_ITEM:\n                            currentFav = new ModRecord();\n                            lastTag = null;\n                            break;\n                        case XTN_MOD_LIST_ITEM_ID:\n                        case \"flags\":\n                        case \"name\":\n                        case \"quicklist\":\n                            if (currentFav != null)\n                                lastTag = xpp.getName();\n                            break;\n                        case \"icon\":\n                            if (currentFav != null)\n                                lastTag = xpp.getName();\n                            iconEncoding = null;\n                            for (int attrIdx = 0; attrIdx < attrCount; attrIdx += 1) {\n                                String attrName = xpp.getAttributeName(attrIdx);\n                                if (\"encoding\".equals(attrName)) {\n                                    iconEncoding = xpp.getAttributeValue(attrIdx);\n                                }\n                            }\n                            break;\n                        case XTN_MOD_LIST:\n                            for (int attrIdx = 0; attrIdx < attrCount; attrIdx += 1) {\n                                String attrName = xpp.getAttributeName(attrIdx);\n                                if (\"version\".equals(attrName)) {\n                                    String tagListVersion = xpp.getAttributeValue(attrIdx);\n                                    Log.d(TAG, \"favList version \" + tagListVersion);\n                                }\n                            }\n                            break;\n                        default:\n                            Log.d(TAG, \"ignored \" + xpp.getName());\n                    }\n                    break;\n                case XmlPullParser.END_TAG:\n                    switch (xpp.getName()) {\n                        case XTN_MOD_LIST_ITEM:\n                            if (currentFav != null && currentFav.record != null)\n                                mMods.add(currentFav);\n                            currentFav = null;\n                            // fall-through\n                        case XTN_MOD_LIST_ITEM_ID:\n                        case \"flags\":\n                        case \"name\":\n                        case \"icon\":\n                        case \"quicklist\":\n                            lastTag = null;\n                            break;\n                        case XTN_MOD_LIST:\n                            bFavListFinished = true;\n                            break;\n                    }\n                    break;\n                case XmlPullParser.TEXT:\n                    if (lastTag != null && currentFav != null) {\n                        switch (lastTag) {\n                            case XTN_MOD_LIST_ITEM_ID:\n                                currentFav.record = xpp.getText();\n                                if (currentFav.record.isEmpty())\n                                    currentFav.record = null;\n                                break;\n                            case \"flags\":\n                                try {\n                                    currentFav.setFlags(Integer.parseInt(xpp.getText()));\n                                } catch (NumberFormatException ignored) {\n                                    currentFav.setFlags(0);\n                                }\n                                break;\n                            case \"name\":\n                                currentFav.displayName = xpp.getText();\n                                break;\n                            case \"icon\":\n                                addIcon(currentFav, xpp.getText(), iconEncoding);\n                                break;\n                            case \"quicklist\":\n                                currentFav.position = xpp.getText();\n                                break;\n                        }\n                    }\n                    break;\n            }\n            if (bFavListFinished)\n                break;\n            eventType = xpp.next();\n        }\n        bModListLoaded = true;\n    }\n\n    public void parseApplications(XmlPullParser xpp, int eventType) throws IOException, XmlPullParserException {\n        AppRecord currentApp = null;\n        boolean bAppListFinished = false;\n        String lastTag = null;\n        String iconEncoding = null;\n        while (eventType != XmlPullParser.END_DOCUMENT) {\n            switch (eventType) {\n                case XmlPullParser.START_TAG:\n                    int attrCount = xpp.getAttributeCount();\n                    switch (xpp.getName()) {\n                        case XTN_APP_LIST_ITEM:\n                            currentApp = new AppRecord();\n                            lastTag = null;\n                            break;\n                        case XTN_APP_LIST_ITEM_ID:\n                        case \"flags\":\n                        case \"name\":\n                            if (currentApp != null)\n                                lastTag = xpp.getName();\n                            break;\n                        case \"icon\":\n                            if (currentApp != null)\n                                lastTag = xpp.getName();\n                            iconEncoding = null;\n                            for (int attrIdx = 0; attrIdx < attrCount; attrIdx += 1) {\n                                String attrName = xpp.getAttributeName(attrIdx);\n                                if (\"encoding\".equals(attrName)) {\n                                    iconEncoding = xpp.getAttributeValue(attrIdx);\n                                }\n                            }\n                            break;\n                        case XTN_APP_LIST:\n                            for (int attrIdx = 0; attrIdx < attrCount; attrIdx += 1) {\n                                String attrName = xpp.getAttributeName(attrIdx);\n                                if (\"version\".equals(attrName)) {\n                                    String tagListVersion = xpp.getAttributeValue(attrIdx);\n                                    Log.d(TAG, \"appList version \" + tagListVersion);\n                                }\n                            }\n                            break;\n                        default:\n                            Log.d(TAG, \"ignored \" + xpp.getName());\n                    }\n                    break;\n                case XmlPullParser.END_TAG:\n                    switch (xpp.getName()) {\n                        case XTN_APP_LIST_ITEM:\n                            if (currentApp != null && currentApp.componentName != null)\n                                mApplications.add(currentApp);\n                            currentApp = null;\n                            // fall-through\n                        case XTN_APP_LIST_ITEM_ID:\n                        case \"flags\":\n                        case \"name\":\n                        case \"icon\":\n                            lastTag = null;\n                            break;\n                        case XTN_APP_LIST:\n                            bAppListFinished = true;\n                            break;\n                    }\n                    break;\n                case XmlPullParser.TEXT:\n                    if (lastTag != null && currentApp != null) {\n                        switch (lastTag) {\n                            case XTN_APP_LIST_ITEM_ID:\n                                currentApp.componentName = xpp.getText();\n                                if (currentApp.componentName.isEmpty())\n                                    currentApp.componentName = null;\n                                break;\n                            case \"flags\":\n                                try {\n                                    currentApp.setFlags(Integer.parseInt(xpp.getText()));\n                                } catch (NumberFormatException ignored) {\n                                    currentApp.setFlags(0);\n                                }\n                                break;\n                            case \"name\":\n                                currentApp.displayName = xpp.getText();\n                                break;\n                            case \"icon\":\n                                addIcon(currentApp, xpp.getText(), iconEncoding);\n                                break;\n                        }\n                    }\n                    break;\n            }\n            if (bAppListFinished)\n                break;\n            try {\n                eventType = xpp.next();\n            } catch (IOException e) {\n                if (currentApp != null)\n                    Log.e(TAG, \"currentApp \" + currentApp.componentName + \" \" + currentApp.displayName);\n                throw new IOException(\"app xpp.next\", e);\n            }\n        }\n        bAppListLoaded = true;\n    }\n\n    void parsePreferences(@NonNull XmlPullParser xpp, int eventType) throws IOException, XmlPullParserException {\n        String prefName = null;\n        Object prefValue = null;\n        boolean addTextToValueSet = false;\n        boolean bPrefListFinished = false;\n        while (eventType != XmlPullParser.END_DOCUMENT) {\n            switch (eventType) {\n                case XmlPullParser.START_TAG:\n                    int attrCount = xpp.getAttributeCount();\n                    switch (xpp.getName()) {\n                        case XTN_PREF_LIST_ITEM:\n                            for (int attrIdx = 0; attrIdx < attrCount; attrIdx += 1) {\n                                String attrName = xpp.getAttributeName(attrIdx);\n                                switch (attrName) {\n                                    case \"key\":\n                                        prefName = xpp.getAttributeValue(attrIdx);\n                                        break;\n                                    case \"value\":\n                                        prefValue = xpp.getAttributeValue(attrIdx);\n                                        break;\n                                    case \"bool\":\n                                        prefValue = Boolean.parseBoolean(xpp.getAttributeValue(attrIdx));\n                                        break;\n                                    case \"int\":\n                                        try {\n                                            prefValue = Integer.parseInt(xpp.getAttributeValue(attrIdx));\n                                        } catch (NumberFormatException ignored) {\n                                            prefValue = 0;\n                                        }\n                                        break;\n                                    case \"color\":\n                                        try {\n                                            String str = xpp.getAttributeValue(attrIdx).substring(1);\n                                            int length = str.length();\n                                            if (length > 6)\n                                                str = str.substring(length - 6);\n                                            prefValue = Integer.parseInt(str, 16);\n                                        } catch (NumberFormatException ignored) {\n                                            prefValue = 0;\n                                        }\n                                        break;\n                                    case \"argb\":\n                                        try {\n                                            String str = xpp.getAttributeValue(attrIdx).substring(1);\n                                            long parsed = Long.parseLong(str, 16);\n                                            prefValue = (int) parsed;\n                                        } catch (NumberFormatException ignored) {\n                                            prefValue = 0;\n                                        }\n                                        break;\n                                    case \"set\":\n                                        // we get Strings from the XML parser, no need to keep Objects\n                                        prefValue = new ArraySet<String>();\n                                        addTextToValueSet = false;\n                                        break;\n                                    default:\n                                        Log.d(TAG, \"ignored attribute \" + xpp.getAttributeValue(attrIdx) + \" from tag \" + xpp.getName());\n                                }\n                            }\n                            break;\n                        case XTN_UI_LIST:\n                        case XTN_PREF_LIST:\n                            for (int attrIdx = 0; attrIdx < attrCount; attrIdx += 1) {\n                                String attrName = xpp.getAttributeName(attrIdx);\n                                if (\"version\".equals(attrName)) {\n                                    String prefListVersion = xpp.getAttributeValue(attrIdx);\n                                    Log.d(TAG, \"prefList version \" + prefListVersion);\n                                }\n                            }\n                            break;\n                        case \"item\":\n                            if (prefValue instanceof ArraySet)\n                                addTextToValueSet = true;\n                            else\n                                Log.d(TAG, \"expected Set, found \" + prefValue);\n                            break;\n                        default:\n                            Log.d(TAG, \"ignored \" + xpp.getName());\n                    }\n                    break;\n                case XmlPullParser.END_TAG:\n                    switch (xpp.getName()) {\n                        case XTN_PREF_LIST_ITEM:\n                            if (prefName != null && prefValue != null)\n                                mPreferences.put(prefName, prefValue);\n                            prefName = null;\n                            prefValue = null;\n                            break;\n                        case XTN_UI_LIST:\n                        case XTN_PREF_LIST:\n                            bPrefListFinished = true;\n                            break;\n                        case \"item\":\n                            addTextToValueSet = false;\n                            break;\n                    }\n                    break;\n                case XmlPullParser.TEXT:\n                    if (addTextToValueSet) {\n                        @SuppressWarnings(\"unchecked\")\n                        ArraySet<String> set = (ArraySet<String>) prefValue;\n                        set.add(xpp.getText());\n                    } else if (prefName != null)\n                        Log.d(TAG, \"preference `\" + prefName + \"` has text `\" + xpp.getText() + \"`\");\n                    break;\n            }\n            if (bPrefListFinished)\n                break;\n            eventType = xpp.next();\n        }\n        bPrefListLoaded = true;\n    }\n\n    public void parseWidgets_v1(XmlPullParser xpp, int eventType) throws IOException, XmlPullParserException {\n        PlaceholderWidgetRecord currentWidget = null;\n        boolean bWidgetListFinished = false;\n        String lastTag = null;\n        String iconEncoding = null;\n        while (eventType != XmlPullParser.END_DOCUMENT) {\n            switch (eventType) {\n                case XmlPullParser.START_TAG:\n                    int attrCount = xpp.getAttributeCount();\n                    switch (xpp.getName()) {\n                        case XTN_WIDGET_LIST_ITEM:\n                            currentWidget = new PlaceholderWidgetRecord();\n                            lastTag = null;\n                            for (int attrIdx = 0; attrIdx < attrCount; attrIdx += 1) {\n                                String attrName = xpp.getAttributeName(attrIdx);\n                                if (\"id\".equals(attrName)) {\n                                    try {\n                                        currentWidget.appWidgetId = Integer.parseInt(xpp.getAttributeValue(attrIdx));\n                                    } catch (NumberFormatException ignored) {\n                                        currentWidget.appWidgetId = WidgetManager.INVALID_WIDGET_ID;\n                                    }\n                                }\n                            }\n                            break;\n                        case \"name\":\n                        case \"provider\":\n                            if (currentWidget != null)\n                                lastTag = xpp.getName();\n                            break;\n                        case \"preview\":\n                            if (currentWidget != null)\n                                lastTag = xpp.getName();\n                            iconEncoding = null;\n                            for (int attrIdx = 0; attrIdx < attrCount; attrIdx += 1) {\n                                String attrName = xpp.getAttributeName(attrIdx);\n                                if (\"encoding\".equals(attrName)) {\n                                    iconEncoding = xpp.getAttributeValue(attrIdx);\n                                }\n                            }\n                            break;\n                        case \"properties\":\n                            if (currentWidget != null) {\n                                WidgetRecord widgetRecord = new WidgetRecord();\n                                widgetRecord.appWidgetId = currentWidget.appWidgetId;\n                                widgetRecord.parseProperties(xpp, eventType);\n                                currentWidget.copyFrom(widgetRecord);\n                            }\n                            break;\n                        case XTN_WIDGET_LIST:\n                            for (int attrIdx = 0; attrIdx < attrCount; attrIdx += 1) {\n                                String attrName = xpp.getAttributeName(attrIdx);\n                                if (\"version\".equals(attrName)) {\n                                    String listVersion = xpp.getAttributeValue(attrIdx);\n                                    Log.d(TAG, \"widgetList version \" + listVersion);\n                                }\n                            }\n                            break;\n                        default:\n                            Log.d(TAG, \"ignored \" + xpp.getName());\n                    }\n                    break;\n                case XmlPullParser.END_TAG:\n                    switch (xpp.getName()) {\n                        case XTN_WIDGET_LIST_ITEM:\n                            if (currentWidget != null)\n                                mWidgets.add(currentWidget);\n                            currentWidget = null;\n                            // fall-through\n                        case \"name\":\n                        case \"provider\":\n                        case \"preview\":\n                            lastTag = null;\n                            break;\n                        case XTN_WIDGET_LIST:\n                            bWidgetListFinished = true;\n                            break;\n                    }\n                    break;\n                case XmlPullParser.TEXT:\n                    if (lastTag != null && currentWidget != null)\n                        switch (lastTag) {\n                            case \"name\":\n                                currentWidget.name = xpp.getText();\n                                break;\n                            case \"provider\":\n                                currentWidget.provider = ComponentName.unflattenFromString(xpp.getText());\n                                break;\n                            case \"preview\":\n                                currentWidget.preview = Utilities.decodeIcon(xpp.getText(), iconEncoding);\n                                break;\n                        }\n                    break;\n            }\n            if (bWidgetListFinished)\n                break;\n            eventType = xpp.next();\n        }\n        bWidgetListLoaded = true;\n    }\n\n    public void parseWidgets_v2(XmlPullParser xpp, int eventType) throws IOException, XmlPullParserException {\n        PlaceholderWidgetRecord currentWidget = null;\n        boolean bWidgetListFinished = false;\n        while (eventType != XmlPullParser.END_DOCUMENT) {\n            switch (eventType) {\n                case XmlPullParser.START_TAG:\n                    int attrCount = xpp.getAttributeCount();\n                    switch (xpp.getName()) {\n                        case XTN_WIDGET_LIST_ITEM:\n                            currentWidget = new PlaceholderWidgetRecord();\n                            for (int attrIdx = 0; attrIdx < attrCount; attrIdx += 1) {\n                                String attrName = xpp.getAttributeName(attrIdx);\n                                if (\"id\".equals(attrName)) {\n                                    try {\n                                        currentWidget.appWidgetId = Integer.parseInt(xpp.getAttributeValue(attrIdx));\n                                    } catch (NumberFormatException ignored) {\n                                        currentWidget.appWidgetId = WidgetManager.INVALID_WIDGET_ID;\n                                    }\n                                }\n                            }\n                            break;\n                        case \"properties\":\n                            if (currentWidget != null)\n                                currentWidget.parseProperties(xpp, eventType);\n                            break;\n                        case XTN_WIDGET_LIST:\n                            for (int attrIdx = 0; attrIdx < attrCount; attrIdx += 1) {\n                                String attrName = xpp.getAttributeName(attrIdx);\n                                if (\"version\".equals(attrName)) {\n                                    String prefListVersion = xpp.getAttributeValue(attrIdx);\n                                    Log.d(TAG, \"widgetList version \" + prefListVersion);\n                                }\n                            }\n                            break;\n                        default:\n                            Log.d(TAG, \"ignored \" + xpp.getName());\n                    }\n                    break;\n                case XmlPullParser.END_TAG:\n                    switch (xpp.getName()) {\n                        case XTN_WIDGET_LIST_ITEM:\n                            if (currentWidget != null)\n                                mWidgets.add(currentWidget);\n                            currentWidget = null;\n                            break;\n                        case XTN_WIDGET_LIST:\n                            bWidgetListFinished = true;\n                            break;\n                    }\n                    break;\n            }\n            if (bWidgetListFinished)\n                break;\n            eventType = xpp.next();\n        }\n        bWidgetListLoaded = true;\n    }\n\n    public void parseHistory(XmlPullParser xpp, int eventType) throws IOException, XmlPullParserException {\n        ValuedHistoryRecord currentRecord = null;\n        boolean bHistoryListFinished = false;\n        String lastTag = null;\n        while (eventType != XmlPullParser.END_DOCUMENT) {\n            switch (eventType) {\n                case XmlPullParser.START_TAG:\n                    int attrCount = xpp.getAttributeCount();\n                    switch (xpp.getName()) {\n                        case XTN_HISTORY_LIST_ITEM:\n                            lastTag = null;\n                            currentRecord = new ValuedHistoryRecord();\n                            for (int attrIdx = 0; attrIdx < attrCount; attrIdx += 1) {\n                                String attrName = xpp.getAttributeName(attrIdx);\n                                if (\"time\".equals(attrName)) {\n                                    try {\n                                        currentRecord.value = Long.parseLong(xpp.getAttributeValue(attrIdx));\n                                    } catch (NumberFormatException ignored) {\n                                        currentRecord.value = 0;\n                                    }\n                                }\n                            }\n                            break;\n                        case \"id\":\n                        case \"query\":\n                            if (currentRecord != null)\n                                lastTag = xpp.getName();\n                            break;\n                        case XTN_HISTORY_LIST:\n                            for (int attrIdx = 0; attrIdx < attrCount; attrIdx += 1) {\n                                String attrName = xpp.getAttributeName(attrIdx);\n                                if (\"version\".equals(attrName)) {\n                                    String listVersion = xpp.getAttributeValue(attrIdx);\n                                    Log.d(TAG, \"historyList version \" + listVersion);\n                                }\n                            }\n                            break;\n                        default:\n                            Log.d(TAG, \"ignored \" + xpp.getName());\n                    }\n                    break;\n                case XmlPullParser.TEXT:\n                    if (lastTag != null && currentRecord != null)\n                        switch (lastTag) {\n                            case \"id\":\n                                currentRecord.record = xpp.getText();\n                                break;\n                            case \"query\":\n                                currentRecord.name = xpp.getText();\n                                break;\n                        }\n                    break;\n                case XmlPullParser.END_TAG:\n                    switch (xpp.getName()) {\n                        case XTN_HISTORY_LIST_ITEM:\n                            if (currentRecord != null)\n                                mHistory.add(currentRecord);\n                            currentRecord = null;\n                            // fall-through\n                        case \"id\":\n                        case \"query\":\n                            lastTag = null;\n                            break;\n                        case XTN_HISTORY_LIST:\n                            bHistoryListFinished = true;\n                            break;\n                    }\n                    break;\n            }\n            if (bHistoryListFinished)\n                break;\n            eventType = xpp.next();\n        }\n        bHistoryListLoaded = true;\n    }\n\n    private void addIcon(@NonNull FlagsRecord rec, String text, @Nullable String encoding) {\n        if (text == null) {\n            mIcons.remove(rec);\n            return;\n        }\n        byte[] icon = Utilities.decodeIcon(text, encoding);\n        if (icon != null)\n            mIcons.put(rec, icon);\n    }\n\n    private void addRecordTag(@Nullable String record, @NonNull String tagName) {\n        if (record == null)\n            return;\n        record = record.trim();\n        if (record.isEmpty())\n            return;\n        List<String> records = mTags.get(tagName);\n        if (records == null)\n            mTags.put(tagName, records = new ArrayList<>());\n        records.add(record);\n    }\n\n\n    public void saveToDB(@NonNull Context context, @NonNull Method method) {\n        saveTags(context, method);\n        saveMods(context, method);\n        saveApplications(context, method);\n        savePreferences(context, method);\n        restoreWidgets(context, method);\n        saveHistory(context, method);\n        TBApplication.dataHandler(context).reloadProviders();\n    }\n\n    private void saveTags(@NonNull Context context, Method method) {\n        if (!bTagListLoaded)\n            return;\n        HashMap<String, Set<String>> tags = new HashMap<>();\n        if (method == Method.OVERWRITE || method == Method.APPEND) {\n            // load from DB first\n            Map<String, List<String>> tagsDB = DBHelper.loadTags(context);\n            for (Map.Entry<String, List<String>> entry : tagsDB.entrySet()) {\n                Set<String> entryIdSet = tags.get(entry.getKey());\n                if (entryIdSet == null)\n                    tags.put(entry.getKey(), entryIdSet = new ArraySet<>(0));\n                entryIdSet.addAll(entry.getValue());\n            }\n        }\n        for (Map.Entry<String, List<String>> entry : mTags.entrySet()) {\n            Set<String> entryIdSet = null;\n            if (method == Method.APPEND) {\n                entryIdSet = tags.get(entry.getKey());\n            }\n            if (entryIdSet == null)\n                tags.put(entry.getKey(), entryIdSet = new ArraySet<>(0));\n\n            entryIdSet.addAll(entry.getValue());\n        }\n\n        // filter out tags for apps we don't have installed\n        TagsHandler.validateTags(context, tags);\n        // store the tags in the DB\n        DBHelper.setTagsMap(context, tags);\n        // reload from DB to emulate a fresh start\n        TBApplication.tagsHandler(context).loadFromDB(true);\n    }\n\n    private void saveMods(Context context, Method method) {\n        if (!bModListLoaded)\n            return;\n        HashMap<String, Pair<ModRecord, byte[]>> mods = new HashMap<>();\n        if (method == Method.OVERWRITE || method == Method.APPEND) {\n            List<ModRecord> modDB = DBHelper.getMods(context);\n            for (ModRecord rec : modDB)\n                mods.put(rec.record, new Pair<>(rec, mIcons.get(rec)));\n        }\n        for (ModRecord modRecord : mMods) {\n            if (method == Method.APPEND && mods.containsKey(modRecord.record))\n                continue;\n            mods.put(modRecord.record, new Pair<>(modRecord, mIcons.get(modRecord)));\n        }\n        DBHelper.setMods(context, mods.values());\n    }\n\n    private void saveApplications(Context context, Method method) {\n        if (!bAppListLoaded)\n            return;\n\n        Map<String, AppRecord> cachedApps = TBApplication.appsHandler(context).getAppRecords(context);\n        if (method == Method.OVERWRITE || method == Method.SET) {\n            // make sure the validate flag is off\n            for (AppRecord rec : cachedApps.values())\n                rec.clearFlags(AppRecord.FLAG_VALIDATED);\n\n            for (AppRecord importedRec : mApplications) {\n                AppRecord rec = cachedApps.get(importedRec.componentName);\n                if (rec == null)\n                    continue;\n                // validate apps that are found in the imported list\n                rec.setFlags(AppRecord.FLAG_VALIDATED);\n\n                // overwrite\n                if (rec.isHidden() && !importedRec.isHidden())\n                    DBHelper.removeAppHidden(context, rec.componentName);\n                if (rec.hasCustomName() && !importedRec.hasCustomName()) {\n                    String name = importedRec.displayName != null ? importedRec.displayName : \"\";\n                    DBHelper.removeCustomAppName(context, rec.componentName, name);\n                }\n                if (rec.hasCustomIcon() && importedRec.hasCustomIcon())\n                    DBHelper.removeCustomAppIcon(context, rec.componentName);\n            }\n            if (method == Method.SET) {\n                // clean apps that don't appear in the import\n                for (AppRecord rec : cachedApps.values()) {\n                    if (rec.isFlagSet(AppRecord.FLAG_VALIDATED))\n                        continue;\n                    if (rec.isHidden())\n                        DBHelper.removeAppHidden(context, rec.componentName);\n                    if (rec.hasCustomName()) {\n                        String name = rec.displayName != null ? rec.displayName : \"\";\n                        DBHelper.removeCustomAppName(context, rec.componentName, name);\n                    }\n                    if (rec.hasCustomIcon())\n                        DBHelper.removeCustomAppIcon(context, rec.componentName);\n                }\n            }\n        }\n\n        for (AppRecord importedRec : mApplications) {\n            AppRecord rec = cachedApps.get(importedRec.componentName);\n            // if app not found (on device) there no need to customize it\n            if (rec == null)\n                continue;\n            if (importedRec.isHidden())\n                DBHelper.setAppHidden(context, importedRec.componentName);\n            if (importedRec.hasCustomName())\n                DBHelper.setCustomAppName(context, importedRec.componentName, importedRec.displayName);\n            if (importedRec.hasCustomIcon())\n                DBHelper.setCustomAppIcon(context, importedRec.componentName, mIcons.get(importedRec));\n        }\n    }\n\n    @SuppressLint(\"ApplySharedPref\")\n    private void savePreferences(Context context, Method method) {\n        if (!bPrefListLoaded || method == Method.APPEND)\n            return;\n\n        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);\n        SharedPreferences.Editor editor = preferences.edit();\n\n        if (method == Method.SET) {\n            editor.clear().commit();\n\n            PreferenceManager.setDefaultValues(context, R.xml.preferences, true);\n            PreferenceManager.setDefaultValues(context, R.xml.preference_features, true);\n\n            editor = preferences.edit();\n        }\n\n        for (Map.Entry<String, Object> entry : mPreferences.entrySet()) {\n            Object value = entry.getValue();\n            if (value instanceof String)\n                editor.putString(entry.getKey(), (String) value);\n            else if (value instanceof Integer)\n                editor.putInt(entry.getKey(), (Integer) value);\n            else if (value instanceof Boolean)\n                editor.putBoolean(entry.getKey(), (Boolean) value);\n            else if (value instanceof ArraySet) {\n                @SuppressWarnings(\"unchecked\")\n                ArraySet<String> set = (ArraySet<String>) value;\n                editor.putStringSet(entry.getKey(), set);\n            }\n        }\n\n        if (PrefCache.migratePreferences(context, mPreferences, editor))\n            Log.i(TAG, \"Preferences migration done.\");\n\n        editor.commit();\n\n        for (String key : mPreferences.keySet())\n            SettingsActivity.onSharedPreferenceChanged(context, preferences, key);\n    }\n\n    private void restoreWidgets(Context context, Method method) {\n        if (!bWidgetListLoaded)\n            return;\n\n        TBLauncherActivity launcherActivity = TBApplication.launcherActivity(context);\n        if (launcherActivity == null)\n            return;\n\n        WidgetManager wm = launcherActivity.widgetManager;\n        wm.onBeforeRestoreFromBackup(method != Method.APPEND);\n\n        final boolean append = method == Method.APPEND;\n        for (PlaceholderWidgetRecord widget : mWidgets) {\n            wm.restoreFromBackup(append, widget);\n        }\n\n        wm.onAfterRestoreFromBackup(method == Method.SET);\n    }\n\n    private void saveHistory(Context context, Method method) {\n        if (!bHistoryListLoaded)\n            return;\n\n        List<ValuedHistoryRecord> history;\n        if (method == Method.SET) {\n            history = mHistory;\n        } else {\n            // load from DB first\n            history = DBHelper.getHistoryRaw(context);\n            long time = history.isEmpty() ? 0 : history.get(history.size() - 1).value;\n            for (ValuedHistoryRecord rec : mHistory) {\n                if (rec.value > time)\n                    history.add(rec);\n            }\n        }\n\n        DBHelper.setHistory(context, history);\n    }\n\n    public enum Method {OVERWRITE, APPEND, SET}\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/db/FlagsRecord.java",
    "content": "package rocks.tbog.tblauncher.db;\n\npublic abstract class FlagsRecord {\n    protected int flags = 0;\n\n    public abstract int getFlagsDB();\n\n    public void setFlags(int flags) {\n        this.flags = flags;\n    }\n\n    public void addFlags(int flags) {\n        this.flags |= flags;\n    }\n\n    public void clearFlags(int flags) {\n        this.flags &= ~flags;\n    }\n\n    public boolean isFlagSet(int flag) {\n        return (flags & flag) == flag;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/db/ModRecord.java",
    "content": "package rocks.tbog.tblauncher.db;\n\npublic class ModRecord extends FlagsRecord {\n    public static final int FLAG_SHOW_IN_QUICK_LIST = 1;\n    public static final int FLAG_CUSTOM_NAME = 1 << 1;\n    public static final int FLAG_CUSTOM_ICON = 1 << 2;\n    private static final int MASK_SAVE_DB_FLAGS = FLAG_SHOW_IN_QUICK_LIST | FLAG_CUSTOM_NAME | FLAG_CUSTOM_ICON;\n    public static final int FLAG_VALIDATED = 1 << 3;\n\n    public String record;\n    public String position;\n    public String displayName;\n\n    @Override\n    public int getFlagsDB() {\n        return flags & MASK_SAVE_DB_FLAGS;\n    }\n\n    public boolean isInQuickList() {\n        return (flags & FLAG_SHOW_IN_QUICK_LIST) == FLAG_SHOW_IN_QUICK_LIST;\n    }\n\n    public boolean hasCustomName() {\n        return (flags & FLAG_CUSTOM_NAME) == FLAG_CUSTOM_NAME;\n    }\n\n    public boolean hasCustomIcon() {\n        return (flags & FLAG_CUSTOM_ICON) == FLAG_CUSTOM_ICON;\n    }\n\n    public boolean canBeCulled() {\n        return getFlagsDB() == 0;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/db/PlaceholderWidgetRecord.java",
    "content": "package rocks.tbog.tblauncher.db;\n\nimport android.content.ComponentName;\nimport android.util.Base64;\nimport android.util.Log;\n\nimport androidx.annotation.NonNull;\n\nimport org.xmlpull.v1.XmlPullParser;\nimport org.xmlpull.v1.XmlPullParserException;\n\nimport java.io.IOException;\n\nimport rocks.tbog.tblauncher.utils.SimpleXmlWriter;\nimport rocks.tbog.tblauncher.utils.Utilities;\n\npublic class PlaceholderWidgetRecord extends WidgetRecord {\n\n    private static final String TAG = \"PWRec\";\n    public String name;\n    public ComponentName provider;\n    public byte[] preview;\n\n    @Override\n    protected <T extends WidgetRecord> void copyFrom(@NonNull T o) {\n        super.copyFrom(o);\n        if (o instanceof PlaceholderWidgetRecord) {\n            PlaceholderWidgetRecord p = (PlaceholderWidgetRecord) o;\n            name = p.name;\n            provider = p.provider;\n            preview = p.preview;\n        }\n    }\n\n    @Override\n    public void parseProperties(@NonNull XmlPullParser xpp, int eventType) throws IOException, XmlPullParserException {\n        boolean bPlaceholderFinished = false;\n        String lastTag = null;\n        String iconEncoding = null;\n        while (eventType != XmlPullParser.END_DOCUMENT) {\n            switch (eventType) {\n                case XmlPullParser.START_TAG:\n                    int attrCount = xpp.getAttributeCount();\n                    switch (xpp.getName()) {\n                        case \"widget\":\n                            super.parseProperties(xpp, eventType);\n                            break;\n                        case \"preview\":\n                            iconEncoding = null;\n                            for (int attrIdx = 0; attrIdx < attrCount; attrIdx += 1) {\n                                String attrName = xpp.getAttributeName(attrIdx);\n                                if (\"encoding\".equals(attrName)) {\n                                    iconEncoding = xpp.getAttributeValue(attrIdx);\n                                }\n                            }\n                            //fall-through\n                        case \"name\":\n                        case \"provider\":\n                            lastTag = xpp.getName();\n                            break;\n                        default:\n                            Log.d(TAG, \"ignored \" + xpp.getName());\n                    }\n                    break;\n                case XmlPullParser.END_TAG:\n                    switch (xpp.getName()) {\n                        case \"name\":\n                        case \"provider\":\n                        case \"preview\":\n                            lastTag = null;\n                            break;\n                        case \"placeholder\": // importing from DB\n                        case \"properties\": // importing from backup\n                            bPlaceholderFinished = true;\n                            break;\n                    }\n                case XmlPullParser.TEXT:\n                    if (lastTag != null)\n                        switch (lastTag) {\n                            case \"name\":\n                                name = xpp.getText();\n                                break;\n                            case \"provider\":\n                                provider = ComponentName.unflattenFromString(xpp.getText());\n                                break;\n                            case \"preview\":\n                                preview = Utilities.decodeIcon(xpp.getText(), iconEncoding);\n                                break;\n                        }\n                    break;\n            }\n            if (bPlaceholderFinished)\n                break;\n            eventType = xpp.next();\n        }\n    }\n\n    @Override\n    public void writeProperties(@NonNull SimpleXmlWriter simpleXmlWriter, boolean addRoot) throws IOException {\n        if (addRoot)\n            simpleXmlWriter.startTag(\"placeholder\");\n\n        super.writeProperties(simpleXmlWriter, true);\n        simpleXmlWriter\n                .startTag(\"name\")\n                .content(name)\n                .endTag(\"name\")\n                .startTag(\"provider\")\n                .content(provider != null ? provider.flattenToString() : \"\")\n                .endTag(\"provider\");\n        if (preview != null) {\n            byte[] base64enc = Base64.encode(preview, Base64.NO_WRAP);\n            simpleXmlWriter.startTag(\"preview\")\n                    .attribute(\"encoding\", \"base64\")\n                    .content(base64enc)\n                    .endTag(\"preview\");\n        }\n\n        if (addRoot)\n            simpleXmlWriter.endTag(\"placeholder\");\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/db/ShortcutRecord.java",
    "content": "package rocks.tbog.tblauncher.db;\n\npublic class ShortcutRecord extends FlagsRecord {\n    public static final int FLAG_HIDE_BADGE = 1;\n    public static final int FLAG_OREO = 1 << 1;\n    private static final int MASK_SAVE_DB_FLAGS = FLAG_HIDE_BADGE | FLAG_OREO;\n    public static final int FLAG_VALIDATED = 1 << 3;\n\n    public long dbId = -1;\n\n    public String displayName;\n\n    public String packageName;\n\n    public String infoData;\n\n    public byte[] iconPng;\n\n    @Override\n    public int getFlagsDB() {\n        return flags & MASK_SAVE_DB_FLAGS;\n    }\n\n    public boolean isOreo() {\n        return (flags & FLAG_OREO) == FLAG_OREO;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/db/ValuedHistoryRecord.java",
    "content": "package rocks.tbog.tblauncher.db;\n\npublic class ValuedHistoryRecord {\n    /**\n     * ID for the record\n     */\n    public String record;\n\n    /**\n     * Name for the record\n     */\n    public String name;\n\n    /**\n     * Context dependant value, e.g. number of access\n     */\n    public long value;\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/db/WidgetRecord.java",
    "content": "package rocks.tbog.tblauncher.db;\n\nimport android.appwidget.AppWidgetHostView;\nimport android.util.Log;\nimport android.util.Xml;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport org.xmlpull.v1.XmlPullParser;\nimport org.xmlpull.v1.XmlPullParserException;\n\nimport java.io.IOException;\nimport java.io.StringReader;\nimport java.io.StringWriter;\n\nimport rocks.tbog.tblauncher.widgets.WidgetManager;\nimport rocks.tbog.tblauncher.widgets.WidgetLayout;\nimport rocks.tbog.tblauncher.utils.SimpleXmlWriter;\n\npublic class WidgetRecord {\n\n    private static final String TAG = \"WRec\";\n    public int appWidgetId;\n    public int width;\n    public int height;\n    public int left;\n    public int top;\n    public int screen;\n    protected String packedProperties = null;\n\n    public WidgetRecord() {\n    }\n\n    public WidgetRecord(@Nullable WidgetRecord rec) {\n        this();\n        if (rec != null)\n            copyFrom(rec);\n    }\n\n    protected <T extends WidgetRecord> void copyFrom(@NonNull T o) {\n        appWidgetId = o.appWidgetId;\n        width = o.width;\n        height = o.height;\n        left = o.left;\n        top = o.top;\n        screen = o.screen;\n        packedProperties = o.packedProperties;\n    }\n\n    @NonNull\n    public static WidgetRecord loadFromDB(int widgetId, String properties) {\n        WidgetRecord rec;\n        if (widgetId == WidgetManager.INVALID_WIDGET_ID)\n            rec = new PlaceholderWidgetRecord();\n        else\n            rec = new WidgetRecord();\n\n        XmlPullParser xpp = Xml.newPullParser();\n        try {\n            xpp.setInput(new StringReader(properties));\n            int eventType = xpp.getEventType();\n            rec.parseProperties(xpp, eventType);\n        } catch (Exception e) {\n            Log.e(TAG, \"parse XML properties\", e);\n        }\n\n        rec.appWidgetId = widgetId;\n        return rec;\n    }\n\n    public void parseProperties(@NonNull XmlPullParser xpp, int eventType) throws IOException, XmlPullParserException {\n        boolean bWidgetFinished = false;\n        while (eventType != XmlPullParser.END_DOCUMENT) {\n            switch (eventType) {\n                case XmlPullParser.START_TAG:\n                    int attrCount = xpp.getAttributeCount();\n                    switch (xpp.getName()) {\n                        case \"size\":\n                            for (int attrIdx = 0; attrIdx < attrCount; attrIdx += 1) {\n                                String attrName = xpp.getAttributeName(attrIdx);\n                                switch (attrName) {\n                                    case \"width\":\n                                        width = parseInt(xpp.getAttributeValue(attrIdx));\n                                        break;\n                                    case \"height\":\n                                        height = parseInt(xpp.getAttributeValue(attrIdx));\n                                        break;\n                                }\n                            }\n                            break;\n                        case \"position\":\n                            for (int attrIdx = 0; attrIdx < attrCount; attrIdx += 1) {\n                                String attrName = xpp.getAttributeName(attrIdx);\n                                switch (attrName) {\n                                    case \"left\":\n                                        left = parseInt(xpp.getAttributeValue(attrIdx));\n                                        break;\n                                    case \"top\":\n                                        top = parseInt(xpp.getAttributeValue(attrIdx));\n                                        break;\n                                    case \"screen\":\n                                        screen = parseInt(xpp.getAttributeValue(attrIdx));\n                                        break;\n                                }\n                            }\n                            break;\n                        case \"widget\":  // reading from DB\n                        case \"properties\": // importing from backup\n                            break;\n                        default:\n                            Log.d(TAG, \"ignored \" + xpp.getName());\n                    }\n                    break;\n                case XmlPullParser.END_TAG:\n                    switch (xpp.getName()) {\n                        case \"widget\":  // reading from DB\n                        case \"properties\": // importing from backup\n                            bWidgetFinished = true;\n                    }\n            }\n            if (bWidgetFinished)\n                break;\n            eventType = xpp.next();\n        }\n    }\n\n    public void writeProperties(@NonNull SimpleXmlWriter simpleXmlWriter, boolean addRoot) throws IOException {\n        if (addRoot)\n            simpleXmlWriter.startTag(\"widget\");\n\n        simpleXmlWriter\n                .startTag(\"size\")\n                .attribute(\"width\", width)\n                .attribute(\"height\", height)\n                .endTag(\"size\")\n                .startTag(\"position\")\n                .attribute(\"left\", left)\n                .attribute(\"top\", top)\n                .attribute(\"screen\", screen)\n                .endTag(\"position\");\n\n        if (addRoot)\n            simpleXmlWriter.endTag(\"widget\");\n    }\n\n    static int parseInt(String value) {\n        try {\n            return Integer.parseInt(value);\n        } catch (NumberFormatException ignored) {\n            return 0;\n        }\n    }\n\n    public String packedProperties() {\n        if (packedProperties == null) {\n            //TODO: use a writer that extends BufferedWriter because that's what KXmlSerializer expects\n            StringWriter writer = new StringWriter();\n            SimpleXmlWriter sx = SimpleXmlWriter.getNewInstance();\n            try {\n                sx.setOutput(writer);\n                writeProperties(sx, true);\n                sx.endDocument();\n            } catch (Exception e) {\n                Log.e(TAG, \"pack properties\", e);\n            }\n            packedProperties = writer.toString();\n        }\n        return packedProperties;\n    }\n\n    public void saveProperties(AppWidgetHostView view) {\n        packedProperties = null;\n        final WidgetLayout.PageLayoutParams lp = (WidgetLayout.PageLayoutParams) view.getLayoutParams();\n        width = lp.width;\n        height = lp.height;\n        left = lp.leftMargin;\n        top = lp.topMargin;\n        screen = lp.screenPage;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/db/XmlExport.java",
    "content": "package rocks.tbog.tblauncher.db;\n\nimport android.appwidget.AppWidgetProviderInfo;\nimport android.content.Context;\nimport android.graphics.drawable.Drawable;\nimport android.util.Base64;\nimport android.util.Log;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.preference.Preference;\nimport androidx.preference.PreferenceGroup;\n\nimport java.io.IOException;\nimport java.io.Writer;\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.handler.TagsHandler;\nimport rocks.tbog.tblauncher.widgets.WidgetManager;\nimport rocks.tbog.tblauncher.shortcut.ShortcutUtil;\nimport rocks.tbog.tblauncher.utils.SimpleXmlWriter;\n\npublic class XmlExport {\n\n    private static final String TAG = \"XExport\";\n\n    public static void tagsXml(@NonNull Context context, @NonNull Writer writer) throws IOException {\n        SimpleXmlWriter sx = SimpleXmlWriter.getNewInstance();\n        sx.setOutput(writer);\n\n        sx.setIndentation(true);\n        sx.startDocument();\n\n        tagsXml(context, sx);\n\n        sx.endDocument();\n    }\n\n    public static void tagsXml(@NonNull Context context, @NonNull SimpleXmlWriter sx) throws IOException {\n        sx.startTag(ExportedData.XTN_TAG_LIST).attribute(\"version\", \"1\");\n\n        TagsHandler tagsHandler = TBApplication.tagsHandler(context);\n        Set<String> tags = tagsHandler.getAllTags();\n        for (String tagName : tags) {\n            sx.startTag(ExportedData.XTN_TAG_LIST_ITEM).attribute(\"name\", tagName);\n            for (String idName : tagsHandler.getAllEntryIds(tagName)) {\n                sx.startTag(ExportedData.XTN_TAG_LIST_ITEM_ID).content(idName).endTag(ExportedData.XTN_TAG_LIST_ITEM_ID);\n            }\n            sx.endTag(ExportedData.XTN_TAG_LIST_ITEM);\n        }\n\n        sx.endTag(ExportedData.XTN_TAG_LIST);\n    }\n\n    public static void modificationsXml(@NonNull Context context, @NonNull Writer writer) throws IOException {\n        SimpleXmlWriter sx = SimpleXmlWriter.getNewInstance();\n        sx.setOutput(writer);\n\n        sx.setIndentation(true);\n        sx.startDocument();\n\n        modificationsXml(context, sx);\n\n        sx.endDocument();\n    }\n\n    public static void modificationsXml(@NonNull Context context, @NonNull SimpleXmlWriter sx) throws IOException {\n        sx.startTag(ExportedData.XTN_MOD_LIST).attribute(\"version\", \"1\");\n\n        List<ModRecord> modRecords = TBApplication.dataHandler(context).getMods();\n        for (ModRecord fav : modRecords) {\n            sx.startTag(ExportedData.XTN_MOD_LIST_ITEM)\n                    .startTag(ExportedData.XTN_MOD_LIST_ITEM_ID).content(fav.record).endTag(ExportedData.XTN_MOD_LIST_ITEM_ID)\n                    .startTag(\"flags\").content(fav.getFlagsDB()).endTag(\"flags\");\n\n            if (fav.hasCustomName() && fav.displayName != null) {\n                sx.startTag(\"name\")\n                        .content(fav.displayName)\n                        .endTag(\"name\");\n            }\n\n            if (fav.hasCustomIcon()) {\n                byte[] favIcon = DBHelper.getCustomFavIcon(context, fav.record);\n                if (favIcon != null) {\n                    byte[] base64enc = Base64.encode(favIcon, Base64.NO_WRAP);\n                    sx.startTag(\"icon\")\n                            .attribute(\"encoding\", \"base64\")\n                            .content(base64enc)\n                            .endTag(\"icon\");\n                }\n            }\n\n            if (fav.isInQuickList()) {\n                sx.startTag(\"quicklist\")\n                        .content(fav.position)\n                        .endTag(\"quicklist\");\n            }\n\n            sx.endTag(ExportedData.XTN_MOD_LIST_ITEM);\n        }\n\n        sx.endTag(ExportedData.XTN_MOD_LIST);\n    }\n\n    public static void applicationsXml(@NonNull Context context, @NonNull Writer writer) throws IOException {\n        SimpleXmlWriter sx = SimpleXmlWriter.getNewInstance();\n        sx.setOutput(writer);\n\n        sx.setIndentation(true);\n        sx.startDocument();\n\n        applicationsXml(context, sx);\n\n        sx.endDocument();\n    }\n\n    public static void applicationsXml(@NonNull Context context, @NonNull SimpleXmlWriter sx) throws IOException {\n        sx.startTag(ExportedData.XTN_APP_LIST).attribute(\"version\", \"1\");\n\n        Map<String, AppRecord> cachedApps = TBApplication.appsHandler(context).getAppRecords(context);\n        for (AppRecord app : cachedApps.values()) {\n            // if there is no custom settings, skip this app\n            if (app.getFlagsDB() == AppRecord.FLAG_DEFAULT_NAME)\n                continue;\n\n            sx.startTag(ExportedData.XTN_APP_LIST_ITEM)\n                    .startTag(ExportedData.XTN_APP_LIST_ITEM_ID).content(app.componentName).endTag(ExportedData.XTN_APP_LIST_ITEM_ID)\n                    .startTag(\"flags\").content(app.getFlagsDB()).endTag(\"flags\");\n\n            if (app.displayName != null && !app.displayName.isEmpty()) {\n                sx.startTag(\"name\")\n                        .content(app.displayName)\n                        .endTag(\"name\");\n            }\n\n            if (app.hasCustomIcon()) {\n                byte[] appIcon = DBHelper.getCustomAppIcon(context, app.componentName);\n                if (appIcon != null) {\n                    byte[] base64enc = Base64.encode(appIcon, Base64.NO_WRAP);\n                    sx.startTag(\"icon\")\n                            .attribute(\"encoding\", \"base64\")\n                            .content(base64enc)\n                            .endTag(\"icon\");\n                }\n            }\n\n            sx.endTag(ExportedData.XTN_APP_LIST_ITEM);\n        }\n\n        sx.endTag(ExportedData.XTN_APP_LIST);\n    }\n\n    public static void interfaceXml(@NonNull PreferenceGroup rootPref, @NonNull Writer writer) throws IOException {\n        SimpleXmlWriter sx = SimpleXmlWriter.getNewInstance();\n        sx.setOutput(writer);\n\n        sx.setIndentation(true);\n        sx.startDocument();\n\n        interfaceXml(rootPref, sx);\n\n        sx.endDocument();\n    }\n\n    public static void interfaceXml(@NonNull PreferenceGroup rootPref, @NonNull SimpleXmlWriter sx) throws IOException {\n        sx.startTag(ExportedData.XTN_UI_LIST).attribute(\"version\", \"1\");\n\n        // we remove the key from the map after it's exported to avoid duplicates\n        Map<String, ?> prefMap = new HashMap<>(rootPref.getSharedPreferences().getAll());\n        Preference pref;\n\n        // do not export the following\n        prefMap.remove(\"pin-auto-confirm\");\n\n        pref = rootPref.findPreference(\"ui-holder\");\n        if ((pref instanceof PreferenceGroup))\n            recursiveWritePreferences(sx, (PreferenceGroup) pref, prefMap);\n\n        pref = rootPref.findPreference(\"quick-list-section\");\n        if ((pref instanceof PreferenceGroup))\n            recursiveWritePreferences(sx, (PreferenceGroup) pref, prefMap);\n\n        pref = rootPref.findPreference(\"shortcut-section\");\n        if ((pref instanceof PreferenceGroup))\n            recursiveWritePreferences(sx, (PreferenceGroup) pref, prefMap);\n\n        pref = rootPref.findPreference(\"tags-section\");\n        if ((pref instanceof PreferenceGroup))\n            recursiveWritePreferences(sx, (PreferenceGroup) pref, prefMap);\n\n        sx.endTag(ExportedData.XTN_UI_LIST);\n    }\n\n    public static void preferencesXml(@NonNull PreferenceGroup rootPref, @NonNull Writer writer) throws IOException {\n        SimpleXmlWriter sx = SimpleXmlWriter.getNewInstance();\n        sx.setOutput(writer);\n\n        sx.setIndentation(true);\n        sx.startDocument();\n\n        preferencesXml(rootPref, sx);\n\n        sx.endDocument();\n    }\n\n    public static void preferencesXml(@NonNull PreferenceGroup rootPref, @NonNull SimpleXmlWriter sx) throws IOException {\n        sx.startTag(ExportedData.XTN_PREF_LIST).attribute(\"version\", \"1\");\n\n        // we remove the key from the map after it's exported to avoid duplicates\n        Map<String, ?> prefMap = new HashMap<>(rootPref.getSharedPreferences().getAll());\n\n        recursiveWritePreferences(sx, rootPref, prefMap);\n\n        sx.endTag(ExportedData.XTN_PREF_LIST);\n\n        for (Map.Entry<String, ?> entry : prefMap.entrySet()) {\n            Log.w(TAG, \"not saved pref `\" + entry.getKey() + \"` with value \" + entry.getValue());\n        }\n    }\n\n    public static void widgetsXml(@NonNull Context context, @NonNull Writer writer) throws IOException {\n        SimpleXmlWriter sx = SimpleXmlWriter.getNewInstance();\n        sx.setOutput(writer);\n\n        sx.setIndentation(true);\n        sx.startDocument();\n\n        widgetsXml(context, sx);\n\n        sx.endDocument();\n    }\n\n    public static void widgetsXml(@NonNull Context context, @NonNull SimpleXmlWriter sx) throws IOException {\n        sx.startTag(ExportedData.XTN_WIDGET_LIST).attribute(\"version\", \"2\");\n\n        //TBApplication.widgetManager(context).\n\n        List<WidgetRecord> widgets = DBHelper.getWidgets(context);\n        for (WidgetRecord widget : widgets) {\n            AppWidgetProviderInfo appWidgetProviderInfo = WidgetManager.getWidgetProviderInfo(context, widget.appWidgetId);\n            sx.startTag(ExportedData.XTN_WIDGET_LIST_ITEM).attribute(\"id\", widget.appWidgetId);\n            // we use PlaceholderWidgetRecord because it has the info we need to restore\n            PlaceholderWidgetRecord widgetRecord = new PlaceholderWidgetRecord();\n            widgetRecord.copyFrom(widget);\n            if (appWidgetProviderInfo != null) {\n                widgetRecord.name = WidgetManager.getWidgetName(context, appWidgetProviderInfo);\n                widgetRecord.provider = appWidgetProviderInfo.provider;\n                Drawable preview = WidgetManager.getWidgetPreview(context, appWidgetProviderInfo);\n                widgetRecord.preview = ShortcutUtil.getIconBlob(preview);\n            }\n            {\n                sx.startTag(\"properties\");\n                widgetRecord.writeProperties(sx, false);\n                sx.endTag(\"properties\");\n            }\n            sx.endTag(ExportedData.XTN_WIDGET_LIST_ITEM);\n        }\n\n        sx.endTag(ExportedData.XTN_WIDGET_LIST);\n    }\n\n    public static void historyXml(@NonNull Context context, @NonNull Writer writer) throws IOException {\n        SimpleXmlWriter sx = SimpleXmlWriter.getNewInstance();\n        sx.setOutput(writer);\n\n        sx.setIndentation(true);\n        sx.startDocument();\n\n        historyXml(context, sx);\n\n        sx.endDocument();\n    }\n\n    public static void historyXml(@NonNull Context context, @NonNull SimpleXmlWriter sx) throws IOException {\n        sx.startTag(ExportedData.XTN_HISTORY_LIST).attribute(\"version\", \"1\");\n\n        List<ValuedHistoryRecord> history = DBHelper.getHistoryRaw(context);\n        for (ValuedHistoryRecord historyRecord : history) {\n            String query = historyRecord.name;\n            sx.startTag(ExportedData.XTN_HISTORY_LIST_ITEM).attribute(\"time\", historyRecord.value);\n\n            sx.startTag(\"id\").content(historyRecord.record).endTag(\"id\");\n\n            if (query != null)\n                sx.startTag(\"query\").content(historyRecord.name).endTag(\"query\");\n\n            sx.endTag(ExportedData.XTN_HISTORY_LIST_ITEM);\n        }\n\n        sx.endTag(ExportedData.XTN_HISTORY_LIST);\n    }\n\n    public static void backupXml(@NonNull PreferenceGroup rootPref, @NonNull Writer writer) throws IOException {\n        Context context = rootPref.getContext().getApplicationContext();\n        SimpleXmlWriter sx = SimpleXmlWriter.getNewInstance();\n        sx.setOutput(writer);\n\n        sx.setIndentation(true);\n        sx.startDocument();\n\n        sx.startTag(\"backup\");\n\n        tagsXml(context, sx);\n        modificationsXml(context, sx);\n        applicationsXml(context, sx);\n        preferencesXml(rootPref, sx);\n        widgetsXml(context, sx);\n        historyXml(context, sx);\n\n        sx.endTag(\"backup\");\n\n        sx.endDocument();\n    }\n\n    private static void recursiveWritePreferences(@NonNull SimpleXmlWriter sx, @NonNull PreferenceGroup prefGroup, @NonNull Map<String, ?> prefMap) throws IOException {\n        int prefCount = prefGroup.getPreferenceCount();\n        for (int prefIdx = 0; prefIdx < prefCount; prefIdx += 1) {\n            Preference pref = prefGroup.getPreference(prefIdx);\n            if (pref instanceof PreferenceGroup) {\n                //Log.d(TAG, \"recursiveWritePreferences \" + pref.getKey());\n                recursiveWritePreferences(sx, (PreferenceGroup) pref, prefMap);\n                continue;\n            }\n            final String key = pref.getKey();\n            // write preference and remove the key to prevent duplicates\n            writePreference(sx, key, prefMap.remove(key));\n        }\n    }\n\n    private static void writePreference(@NonNull SimpleXmlWriter sx, @NonNull String key, @Nullable Object value) throws IOException {\n        if (value == null) {\n            // skip this as we don't have a value\n        } else if (value instanceof String)\n            sx.startTag(ExportedData.XTN_PREF_LIST_ITEM)\n                    .attribute(\"key\", key)\n                    .attribute(\"value\", (String) value)\n                    .endTag(ExportedData.XTN_PREF_LIST_ITEM);\n        else if (value instanceof Integer) {\n            sx.startTag(ExportedData.XTN_PREF_LIST_ITEM)\n                    .attribute(\"key\", key);\n            if (key.contains(\"-color\"))\n                sx.attribute(\"color\", String.format(\"#%06x\", ((Integer) value) & 0xffffff));\n            else if (key.contains(\"-argb\"))\n                sx.attribute(\"argb\", String.format(\"#%08x\", (Integer) value));\n            else\n                sx.attribute(\"int\", ((Integer) value).toString());\n            sx.endTag(ExportedData.XTN_PREF_LIST_ITEM);\n        } else if (value instanceof Boolean)\n            sx.startTag(ExportedData.XTN_PREF_LIST_ITEM)\n                    .attribute(\"key\", key)\n                    .attribute(\"bool\", ((Boolean) value).toString())\n                    .endTag(ExportedData.XTN_PREF_LIST_ITEM);\n        else if (value instanceof Set) {\n            Set<?> set = (Set<?>) value;\n            // find contained object type\n            String type = \"object\";\n            {\n                Iterator<?> iterator = set.iterator();\n                if (iterator.hasNext()) {\n                    Object item = iterator.next();\n                    if (item instanceof String)\n                        type = \"string\";\n                }\n            }\n            sx.startTag(ExportedData.XTN_PREF_LIST_ITEM)\n                    .attribute(\"key\", key)\n                    .attribute(\"set\", type);\n            for (Object item : set) {\n                sx.startTag(\"item\")\n                        .content(item.toString())\n                        .endTag(\"item\");\n            }\n            sx.endTag(ExportedData.XTN_PREF_LIST_ITEM);\n        } else {\n            Log.d(TAG, \"skipped pref `\" + key + \"` with value \" + value);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/db/XmlImport.java",
    "content": "package rocks.tbog.tblauncher.db;\n\nimport android.content.Context;\nimport android.util.Log;\nimport android.widget.Toast;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport org.xmlpull.v1.XmlPullParser;\nimport org.xmlpull.v1.XmlPullParserException;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.utils.FileUtils;\n\npublic class XmlImport {\n    private static final String TAG = \"XImport\";\n\n    public static boolean settingsXml(@NonNull Context context, @NonNull File file, @NonNull ExportedData.Method method) {\n        boolean ok = false;\n        try (InputStream inputStream = new FileInputStream(file)) {\n            ok = settingsXml(context, FileUtils.getXmlParser(context, inputStream), method);\n        } catch (Exception e) {\n            Log.e(TAG, \"new FileInputStream \" + file.toString(), e);\n        }\n        return ok;\n    }\n\n    public static boolean settingsXml(@NonNull Context context, @Nullable XmlPullParser xpp, @NonNull ExportedData.Method method) {\n        if (xpp == null)\n            return false;\n        ExportedData settings = new ExportedData();\n        try {\n            int eventType = xpp.getEventType();\n            while (eventType != XmlPullParser.END_DOCUMENT) {\n                int attrCount = xpp.getAttributeCount();\n                if (eventType == XmlPullParser.START_TAG) {\n                    switch (xpp.getName()) {\n                        case ExportedData.XTN_TAG_LIST:\n                            settings.parseTagList(xpp, eventType);\n                            break;\n                        case ExportedData.XTN_MOD_LIST:\n                            settings.parseFavorites(xpp, eventType);\n                            break;\n                        case ExportedData.XTN_APP_LIST:\n                            settings.parseApplications(xpp, eventType);\n                            break;\n                        case ExportedData.XTN_UI_LIST:\n                        case ExportedData.XTN_PREF_LIST:\n                            settings.parsePreferences(xpp, eventType);\n                            break;\n                        case ExportedData.XTN_WIDGET_LIST:\n                            String widgetListVersion = null;\n                            for (int attrIdx = 0; attrIdx < attrCount; attrIdx += 1) {\n                                String attrName = xpp.getAttributeName(attrIdx);\n                                if (\"version\".equals(attrName)) {\n                                    widgetListVersion = xpp.getAttributeValue(attrIdx);\n                                }\n                            }\n                            if (\"1\".equals(widgetListVersion)) {\n                                settings.parseWidgets_v1(xpp, eventType);\n                            } else {\n                                settings.parseWidgets_v2(xpp, eventType);\n                            }\n                            break;\n                        case ExportedData.XTN_HISTORY_LIST:\n                            settings.parseHistory(xpp, eventType);\n                            break;\n                        default:\n                            Log.d(TAG, \"ignored \" + xpp.getName());\n                    }\n                }\n                eventType = xpp.next();\n            }\n        } catch (XmlPullParserException | IOException e) {\n            Log.e(TAG, \"parsing settingsXml\", e);\n            Toast.makeText(context, R.string.error_fail_import, Toast.LENGTH_LONG).show();\n            return false;\n        }\n        settings.saveToDB(context, method);\n        return true;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/drawable/CodePointDrawable.java",
    "content": "package rocks.tbog.tblauncher.drawable;\n\nimport android.graphics.drawable.Drawable;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\npublic class CodePointDrawable extends TextDrawable {\n    private final State mState;\n\n    public CodePointDrawable(int codePoint) {\n        this(new State(codePoint));\n    }\n\n    public CodePointDrawable(CharSequence text) {\n        this(Character.codePointAt(text, 0));\n    }\n\n    public CodePointDrawable(State state) {\n        super();\n        mState = state;\n    }\n\n    @Nullable\n    @Override\n    public ConstantState getConstantState() {\n        return mState;\n    }\n\n    @Override\n    protected char[] getText(int line) {\n        return Character.toChars(mState.mCodePoint);\n    }\n\n    protected static class State extends ConstantState {\n        final int mCodePoint;\n\n        protected State(int cp) {\n            mCodePoint = cp;\n        }\n\n        @NonNull\n        @Override\n        public Drawable newDrawable() {\n            return new CodePointDrawable(this);\n        }\n\n        @Override\n        public int getChangingConfigurations() {\n            return 0;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/drawable/DrawableUtils.java",
    "content": "package rocks.tbog.tblauncher.drawable;\n\nimport android.annotation.SuppressLint;\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.content.res.TypedArray;\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.graphics.BitmapShader;\nimport android.graphics.Canvas;\nimport android.graphics.Color;\nimport android.graphics.Paint;\nimport android.graphics.Path;\nimport android.graphics.PointF;\nimport android.graphics.RectF;\nimport android.graphics.Shader.TileMode;\nimport android.graphics.drawable.AdaptiveIconDrawable;\nimport android.graphics.drawable.Animatable;\nimport android.graphics.drawable.BitmapDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.os.Build;\nimport android.util.TypedValue;\nimport android.widget.ImageView;\n\nimport androidx.annotation.ColorInt;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.StyleableRes;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.utils.UIColors;\n\npublic class DrawableUtils {\n\n    public static final int SHAPE_NONE = 0;\n    public static final int SHAPE_CIRCLE = 1;\n    public static final int SHAPE_SQUARE = 2;\n    public static final int SHAPE_SQUIRCLE = 3;\n    public static final int SHAPE_ROUND_RECT = 4;\n    private static final int SHAPE_TEARDROP_BR = 5;\n    private static final int SHAPE_TEARDROP_BL = 6;\n    private static final int SHAPE_TEARDROP_TL = 7;\n    private static final int SHAPE_TEARDROP_TR = 8;\n    private static final int SHAPE_TEARDROP_RND = 9;\n    public static final int SHAPE_HEXAGON = 10;\n    public static final int SHAPE_OCTAGON = 11;\n    public static final int SHAPE_ROUND_HEXAGON = 12;\n    public static final int SHAPE_ROUND_OCTAGON = 13;\n\n    public static final int[] SHAPE_LIST = {\n            SHAPE_NONE,\n            SHAPE_CIRCLE,\n            SHAPE_SQUARE,\n            SHAPE_SQUIRCLE,\n            SHAPE_ROUND_RECT,\n            SHAPE_TEARDROP_BR,\n            SHAPE_TEARDROP_BL,\n            SHAPE_TEARDROP_TL,\n            SHAPE_TEARDROP_TR,\n            SHAPE_HEXAGON,\n            SHAPE_OCTAGON,\n            SHAPE_ROUND_HEXAGON,\n            SHAPE_ROUND_OCTAGON,\n    };\n\n    private static final Paint PAINT = new Paint();\n    private static final Path SHAPE_PATH = new Path();\n    private static final RectF RECT_F = new RectF();\n\n    @NonNull\n    public static String shapeName(Context context, int shape) {\n        Resources res = context.getResources();\n        String[] values = res.getStringArray(R.array.adaptiveValues);\n        String strShape = String.valueOf(shape);\n        for (int i = 0; i < values.length; i += 1) {\n            if (strShape.equals(values[i])) {\n                String[] names = res.getStringArray(R.array.adaptiveEntries);\n                return names[i];\n            }\n        }\n        return \"\";\n    }\n\n    // https://stackoverflow.com/questions/3035692/how-to-convert-a-drawable-to-a-bitmap\n    @NonNull\n    public static Bitmap drawableToBitmap(@NonNull Drawable drawable, int width, int height) {\n        if (drawable instanceof BitmapDrawable) {\n            BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;\n            if (bitmapDrawable.getBitmap() != null) {\n                return bitmapDrawable.getBitmap();\n            }\n        }\n\n        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); // Single color bitmap will be created of 1x1 pixel\n\n        Canvas canvas = new Canvas(bitmap);\n        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());\n        drawable.draw(canvas);\n        return bitmap;\n    }\n\n    public static float getScaleToFit(int shape) {\n        return 1.f / (1.f + 2.f * getMarginToFit(shape));\n    }\n\n    public static Drawable applyIconMaskShape(Context ctx, Drawable icon, int shape) {\n        return applyIconMaskShape(ctx, icon, shape, 1.f, Color.TRANSPARENT);\n    }\n\n    public static Drawable applyIconMaskShape(Context ctx, Drawable icon, int shape, boolean fitInside) {\n        if (!fitInside || isAdaptiveIconDrawable(icon))\n            return applyIconMaskShape(ctx, icon, shape);\n\n        final int color = UIColors.getIconBackground(ctx);\n        final float scale = getScaleToFit(shape);\n        return applyIconMaskShape(ctx, icon, shape, scale, color);\n    }\n\n//    public static Drawable applyIconMaskShape(Context ctx, Drawable icon, int shape, @ColorInt int backgroundColor) {\n//        return applyIconMaskShape(ctx, icon, shape, 1.f, backgroundColor);\n//    }\n\n    /**\n     * Get percent of icon to use as margin. We use this to avoid clipping the image.\n     *\n     * @param shape from SHAPE_*\n     * @return margin size\n     */\n    private static float getMarginToFit(int shape) {\n        switch (shape) {\n            case SHAPE_CIRCLE:\n            case SHAPE_TEARDROP_BR:\n            case SHAPE_TEARDROP_BL:\n            case SHAPE_TEARDROP_TL:\n            case SHAPE_TEARDROP_TR:\n                return 0.2071f;  // (sqrt(2)-1)/2 to make a square fit in a circle\n            case SHAPE_SQUIRCLE:\n                return 0.1f;\n            case SHAPE_ROUND_RECT:\n                return 0.05f;\n            case SHAPE_ROUND_HEXAGON:\n            case SHAPE_HEXAGON:\n                return 0.26f;\n            case SHAPE_ROUND_OCTAGON:\n            case SHAPE_OCTAGON:\n                return 0.25f;\n        }\n        return 0.f;\n    }\n\n    /**\n     * Get size of bitmap used when applying a shape on an icon\n     *\n     * @param ctx   android context to get resources\n     * @param shape from SHAPE_*\n     * @return icon size\n     */\n    private static int getIconSize(@NonNull Context ctx, int shape) {\n        int iconSize = ctx.getResources().getDimensionPixelSize(R.dimen.icon_size);\n        return (shape == SHAPE_NONE || shape == SHAPE_SQUARE) ? iconSize : (2 * iconSize);\n    }\n\n    @SuppressLint(\"NewApi\")\n    public static Drawable applyAdaptiveIconBackgroundShape(Context ctx, Drawable icon, int shape, boolean onlyForeground) {\n        if (!isAdaptiveIconDrawable(icon))\n            return null;\n        AdaptiveIconDrawable adaptiveIcon = (AdaptiveIconDrawable) icon;\n\n        int layerSize = Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 108f, ctx.getResources().getDisplayMetrics()));\n        int iconSize = Math.round(layerSize / (1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction()));\n        int layerOffset = (layerSize - iconSize) / 2;\n\n        // Create a bitmap of the icon to use it as the shader of the outputBitmap\n        Bitmap iconBitmap = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888);\n        Canvas iconCanvas = new Canvas(iconBitmap);\n\n        if (!onlyForeground) {\n            Drawable bgDrawable = adaptiveIcon.getBackground();\n            if (bgDrawable != null) {\n                // Stretch adaptive layers because they are 108dp and the icon size is 48dp\n                bgDrawable.setBounds(-layerOffset, -layerOffset, iconSize + layerOffset, iconSize + layerOffset);\n                bgDrawable.draw(iconCanvas);\n            }\n        }\n\n        Drawable fgDrawable = adaptiveIcon.getForeground();\n        if (fgDrawable != null) {\n            fgDrawable.setBounds(-layerOffset, -layerOffset, iconSize + layerOffset, iconSize + layerOffset);\n            fgDrawable.draw(iconCanvas);\n        }\n\n        Bitmap outputBitmap;\n        Canvas outputCanvas;\n        final Paint outputPaint = PAINT;\n        outputPaint.reset();\n        outputPaint.setFlags(Paint.ANTI_ALIAS_FLAG);\n\n        outputBitmap = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888);\n        outputCanvas = new Canvas(outputBitmap);\n\n        outputPaint.setShader(new BitmapShader(iconBitmap, TileMode.CLAMP, TileMode.CLAMP));\n        cropIconShape(outputCanvas, outputPaint, shape);\n        outputPaint.setShader(null);\n\n        return new BitmapDrawable(ctx.getResources(), outputBitmap);\n    }\n\n    /**\n     * Handle adaptive icons for compatible devices\n     */\n    @SuppressLint(\"NewApi\")\n    public static Drawable applyIconMaskShape(Context ctx, Drawable icon, int shape, float scale, @ColorInt int backgroundColor) {\n        if (shape == SHAPE_NONE)\n            return icon;\n        if (shape == SHAPE_TEARDROP_RND)\n            shape = SHAPE_TEARDROP_BR + (icon.hashCode() % 4);\n\n        Bitmap outputBitmap;\n        Canvas outputCanvas;\n        final Paint outputPaint = PAINT;\n        outputPaint.reset();\n        outputPaint.setFlags(Paint.ANTI_ALIAS_FLAG);\n        if (isAdaptiveIconDrawable(icon)) {\n            AdaptiveIconDrawable adaptiveIcon = (AdaptiveIconDrawable) icon;\n\n            int layerSize = Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 108f, ctx.getResources().getDisplayMetrics()));\n            int iconSize = Math.round(layerSize / (1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction()));\n            int layerOffset = (layerSize - iconSize) / 2;\n\n            // Create a bitmap of the icon to use it as the shader of the outputBitmap\n            Bitmap iconBitmap = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888);\n            Canvas iconCanvas = new Canvas(iconBitmap);\n\n            {\n                Drawable bgDrawable = adaptiveIcon.getBackground();\n                if (bgDrawable != null) {\n                    // Stretch adaptive layers because they are 108dp and the icon size is 48dp\n                    bgDrawable.setBounds(-layerOffset, -layerOffset, iconSize + layerOffset, iconSize + layerOffset);\n                    bgDrawable.draw(iconCanvas);\n                }\n\n                Drawable fgDrawable = adaptiveIcon.getForeground();\n                if (fgDrawable != null) {\n                    int iconOffset = (int) (layerSize * (scale - 1.f) * .5f + .5f);\n                    layerOffset += iconOffset;\n                    fgDrawable.setBounds(-layerOffset, -layerOffset, iconSize + layerOffset, iconSize + layerOffset);\n                    fgDrawable.draw(iconCanvas);\n                }\n            }\n\n            outputBitmap = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888);\n            outputCanvas = new Canvas(outputBitmap);\n\n            outputPaint.setShader(new BitmapShader(iconBitmap, TileMode.CLAMP, TileMode.CLAMP));\n            cropIconShape(outputCanvas, outputPaint, shape);\n            outputPaint.setShader(null);\n        }\n        // If icon is not adaptive, put it in a white canvas to make it have a unified shape\n        else if (icon != null) {\n            // make icon bigger than required when we crop (cropping makes jagged edges)\n            int iconSize = getIconSize(ctx, shape);\n            // compute shrink factor and icon position to fit inside the shape\n            int iconOffset = (int) (iconSize * (1.f - scale) * .5f + .5f);\n            if (iconOffset >= iconSize / 2)\n                iconOffset = iconSize / 2 - 1;\n\n            outputBitmap = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888);\n            outputCanvas = new Canvas(outputBitmap);\n            outputPaint.setColor(backgroundColor);\n\n            // Shrink icon so that it fits the shape\n            int bottomRightCorner = iconSize - iconOffset;\n            icon.setBounds(iconOffset, iconOffset, bottomRightCorner, bottomRightCorner);\n\n            cropIconShape(outputCanvas, outputPaint, shape);\n            icon.draw(outputCanvas);\n        } else {\n            int iconSize = getIconSize(ctx, shape);\n\n            outputBitmap = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888);\n            outputCanvas = new Canvas(outputBitmap);\n            outputPaint.setColor(0xFF000000);\n\n            cropIconShape(outputCanvas, outputPaint, shape);\n        }\n        return new BitmapDrawable(ctx.getResources(), outputBitmap);\n    }\n\n    /**\n     * Set the shape of adaptive icons\n     *\n     * @param shape type of shape: DrawableUtils.SHAPE_*\n     */\n    private static void cropIconShape(Canvas canvas, Paint paint, int shape) {\n        final float iconSize = canvas.getHeight();\n        final Path path = SHAPE_PATH;\n        path.rewind();\n\n        switch (shape) {\n            case SHAPE_CIRCLE: {\n                int radius = (int) iconSize / 2;\n                canvas.drawCircle(radius, radius, radius, paint);\n\n                path.addCircle(radius, radius, radius, Path.Direction.CCW);\n                break;\n            }\n            case SHAPE_SQUIRCLE: {\n                int h = (int) iconSize / 2;\n                float c = iconSize / 2.333f;\n                path.moveTo(h, 0f);\n                path.cubicTo(h + c, 0, iconSize, h - c, iconSize, h);\n                path.cubicTo(iconSize, h + c, h + c, iconSize, h, iconSize);\n                path.cubicTo(h - c, iconSize, 0, h + c, 0, h);\n                path.cubicTo(0, h - c, h - c, 0, h, 0);\n                path.close();\n\n                canvas.drawPath(path, paint);\n                break;\n            }\n            case SHAPE_SQUARE:\n                canvas.drawRect(0f, 0f, iconSize, iconSize, paint);\n\n                path.addRect(0f, 0f, iconSize, iconSize, Path.Direction.CCW);\n                break;\n            case SHAPE_ROUND_RECT:\n                RECT_F.set(0f, 0f, iconSize, iconSize);\n                canvas.drawRoundRect(RECT_F, iconSize / 8f, iconSize / 12f, paint);\n\n                path.addRoundRect(RECT_F, iconSize / 8f, iconSize / 12f, Path.Direction.CCW);\n                break;\n            case SHAPE_TEARDROP_RND: // this is handled before we get here\n            case SHAPE_TEARDROP_BR:\n                RECT_F.set(0f, 0f, iconSize, iconSize);\n                path.addArc(RECT_F, 90, 270);\n                path.lineTo(iconSize, iconSize * 0.70f);\n                RECT_F.set(iconSize * 0.70f, iconSize * 0.70f, iconSize, iconSize);\n                path.arcTo(RECT_F, 0, 90, false);\n                path.close();\n\n                canvas.drawPath(path, paint);\n                break;\n            case SHAPE_TEARDROP_BL:\n                RECT_F.set(0f, 0f, iconSize, iconSize);\n                path.addArc(RECT_F, 180, 270);\n                path.lineTo(iconSize * .3f, iconSize);\n                RECT_F.set(0f, iconSize * .7f, iconSize * .3f, iconSize);\n                path.arcTo(RECT_F, 90, 90, false);\n                path.close();\n\n                canvas.drawPath(path, paint);\n                break;\n            case SHAPE_TEARDROP_TL:\n                RECT_F.set(0f, 0f, iconSize, iconSize);\n                path.addArc(RECT_F, 270, 270);\n                path.lineTo(0, iconSize * .3f);\n                RECT_F.set(0f, 0f, iconSize * .3f, iconSize * .3f);\n                path.arcTo(RECT_F, 180, 90, false);\n                path.close();\n\n                canvas.drawPath(path, paint);\n                break;\n            case SHAPE_TEARDROP_TR:\n                RECT_F.set(0f, 0f, iconSize, iconSize);\n                path.addArc(RECT_F, 0, 270);\n                path.lineTo(iconSize * .7f, 0f);\n                RECT_F.set(iconSize * .7f, 0f, iconSize, iconSize * .3f);\n                path.arcTo(RECT_F, 270, 90, false);\n                path.close();\n\n                canvas.drawPath(path, paint);\n                break;\n            case SHAPE_HEXAGON:\n                for (int deg = 0; deg < 360; deg += 60) {\n                    double rad = Math.toRadians(deg);\n                    float x = ((float) Math.cos(rad) * .5f + .5f) * iconSize;\n                    float y = ((float) Math.sin(rad) * .5f + .5f) * iconSize;\n                    if (deg == 0)\n                        path.moveTo(x, y);\n                    else\n                        path.lineTo(x, y);\n                }\n                path.close();\n\n                canvas.drawPath(path, paint);\n                break;\n            case SHAPE_OCTAGON:\n                for (int deg = 22; deg < 360; deg += 45) {\n                    double rad = Math.toRadians(deg + .5);\n                    float x = ((float) Math.cos(rad) * .5f + .5f) * iconSize;\n                    float y = ((float) Math.sin(rad) * .5f + .5f) * iconSize;\n\n                    // scale it up to fill the rectangle\n                    x = x * 1.0824f - x * 0.0824f;\n                    y = y * 1.0824f - y * 0.0824f;\n\n                    if (deg == 22)\n                        path.moveTo(x, y);\n                    else\n                        path.lineTo(x, y);\n                }\n                path.close();\n\n                canvas.drawPath(path, paint);\n                break;\n            case SHAPE_ROUND_HEXAGON: {\n                final PointProvider gen = (i, p) -> {\n                    int deg = i * 60;\n                    double rad = Math.toRadians(deg);\n                    p.x = ((float) Math.cos(rad) * .5f + .5f) * iconSize;\n                    p.y = ((float) Math.sin(rad) * .5f + .5f) * iconSize;\n                };\n                roundedPolyPath(path, gen, 6, iconSize * .16f);\n                path.close();\n\n                canvas.drawPath(path, paint);\n                break;\n            }\n            case SHAPE_ROUND_OCTAGON: {\n                final PointProvider gen = (i, p) -> {\n                    int deg = 22 + i * 45;\n                    double rad = Math.toRadians(deg + .5);\n                    p.x = ((float) Math.cos(rad) * .5f + .5f) * iconSize;\n                    p.y = ((float) Math.sin(rad) * .5f + .5f) * iconSize;\n                };\n                roundedPolyPath(path, gen, 8, iconSize * .2f);\n                path.close();\n\n                canvas.drawPath(path, paint);\n                break;\n            }\n        }\n        // make sure we don't draw outside the shape\n        canvas.clipPath(path);\n    }\n\n    /**\n     * polygon vertices provider\n     */\n    interface PointProvider {\n        /**\n         * Generate vertex position for index\n         *\n         * @param in_pointIdx input vertex index\n         * @param out_point   output vertex position\n         */\n        void get(int in_pointIdx, @NonNull PointF out_point);\n    }\n\n    /**\n     * Helper class to store vector information\n     */\n    static class Vector2D {\n        float x, y;     // position\n        double len;     // magnitude\n        double nx, ny;  // normalized\n        double ang;     // direction\n\n        /**\n         * Compute vector information from two points\n         *\n         * @param A vector from\n         * @param B vector to\n         */\n        void set(PointF A, PointF B) {\n            // x,y as vec\n            x = B.x - A.x;\n            y = B.y - A.y;\n            // length of vec\n            len = Math.sqrt(x * x + y * y);\n            // normalised\n            nx = x / len;\n            ny = y / len;\n            // direction of vec\n            ang = Math.atan2(ny, nx);\n        }\n    }\n\n    /**\n     * Source: https://riptutorial.com/html5-canvas/example/18766/render-a-rounded-polygon-\n     * Adds from the `point` provider to `path` rounded corners of radius. If the corner angle is too small to fit\n     * the radius or the distance between corners does not allow room the corners radius is reduced to a best fit.\n     *\n     * @param path       geometric contour to add the polygon to\n     * @param point      provider of polygon vertices positions\n     * @param pointCount vertices count\n     * @param radius     desired circle radius to use for rounding\n     */\n    private static void roundedPolyPath(@NonNull Path path, PointProvider point, int pointCount, float radius) {\n        final PointF p1 = new PointF();\n        final PointF p2 = new PointF();\n        final PointF p3 = new PointF();\n        final Vector2D v1 = new Vector2D();\n        final Vector2D v2 = new Vector2D();\n\n        point.get(pointCount - 1, p1); // start at end of path\n        path.moveTo(p1.x, p1.y);\n        for (int i = 0; i < pointCount; i += 1) {\n            point.get(i, p2); // the corner point that is being rounded\n            point.get((i + 1) % pointCount, p3);\n            // get the corner as vectors out away from corner\n            v1.set(p2, p1); // vec back from corner point\n            v2.set(p2, p3); // vec forward from corner point\n            // get corners cross product (arc sin of angle)\n            final double sinA = v1.nx * v2.ny - v1.ny * v2.nx;    // cross product\n            // get cross product of first line and perpendicular second line\n            final double sinA90 = v1.nx * v2.nx - v1.ny * -v2.ny; // cross product to normal of line 2\n            double angle = Math.asin(sinA);                 // get the angle\n            int radDirection = 1;                           // may need to reverse the radius\n            boolean anticlockwise = false;\n            // find the correct quadrant for circle center\n            if (sinA90 < 0.0) {\n                if (angle < 0.0) {\n                    angle = Math.PI + angle; // add 180 to move us to the 3 quadrant\n                } else {\n                    angle = Math.PI - angle; // move back into the 2nd quadrant\n                    radDirection = -1;\n                    anticlockwise = true;\n                }\n            } else {\n                if (angle > 0.0) {\n                    radDirection = -1;\n                    anticlockwise = true;\n                }\n            }\n            final double halfAngle = angle / 2.0;\n            // get distance from corner to point where round corner touches line\n            double lenOut = Math.abs(Math.cos(halfAngle) * radius / Math.sin(halfAngle));\n            final double cRadius;\n            final double minHalfLineLength = Math.min(v1.len * .5, v2.len * .5);\n            if (lenOut > minHalfLineLength) { // fix if longer than half line length\n                lenOut = minHalfLineLength;\n                // adjust the radius of corner rounding to fit\n                cRadius = Math.abs(lenOut * Math.sin(halfAngle) / Math.cos(halfAngle));\n            } else {\n                cRadius = radius;\n            }\n            // move out from corner along second line to point where rounded circle touches\n            double x = p2.x + v2.nx * lenOut;\n            double y = p2.y + v2.ny * lenOut;\n            // move away from line to circle center\n            x += -v2.ny * cRadius * radDirection;\n            y += v2.nx * cRadius * radDirection;\n            // x,y is the rounded corner circle center\n            RECT_F.set((float) (x - cRadius), (float) (y - cRadius), (float) (x + cRadius), (float) (y + cRadius));\n\n            double startAngle = v1.ang + Math.PI / 2.0 * radDirection;\n            double endAngle = v2.ang - Math.PI / 2.0 * radDirection;\n            if (!anticlockwise && startAngle > endAngle)\n                endAngle += Math.PI * 2.0;\n            else if (anticlockwise && startAngle < endAngle)\n                endAngle -= Math.PI * 2.0;\n            final float sweepAngle = (float) Math.toDegrees(endAngle - startAngle);\n\n            path.arcTo(RECT_F, (float) Math.toDegrees(startAngle), sweepAngle, false); // draw the arc clockwise\n\n            p1.set(p2);\n        }\n    }\n\n    public static boolean isAdaptiveIconDrawable(Drawable drawable) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            return drawable instanceof AdaptiveIconDrawable;\n        }\n        return false;\n    }\n\n    @Nullable\n    public static Drawable getProgressBarIndeterminate(Context context) {\n        @SuppressLint(\"ResourceType\") @StyleableRes\n        final int[] attrs = {android.R.attr.indeterminateDrawable};\n        final int attrs_indeterminateDrawable_index = 0;\n        TypedArray a = context.obtainStyledAttributes(android.R.style.Widget_ProgressBar, attrs);\n        try {\n            Drawable drawable = a.getDrawable(attrs_indeterminateDrawable_index);\n            if (drawable instanceof Animatable)\n                ((Animatable) drawable).start();\n            return drawable;\n        } catch (Exception ignored) {\n            return null;\n        } finally {\n            a.recycle();\n        }\n    }\n\n    public static boolean setImageDrawable(@Nullable ImageView icon, @Nullable byte[] bitmap) {\n        if (icon == null)\n            return false;\n        BitmapDrawable drawable = getBitmapDrawable(icon.getContext(), bitmap);\n        icon.setImageDrawable(drawable);\n        return drawable != null;\n    }\n\n    @Nullable\n    public static BitmapDrawable getBitmapDrawable(@NonNull Context context, @Nullable byte[] bitmap) {\n        if (bitmap != null) {\n            Bitmap decodedBitmap = BitmapFactory.decodeByteArray(bitmap, 0, bitmap.length);\n            if (decodedBitmap != null) {\n                return new BitmapDrawable(context.getResources(), decodedBitmap);\n            }\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/drawable/FourCodePointDrawable.java",
    "content": "package rocks.tbog.tblauncher.drawable;\n\nimport android.graphics.drawable.Drawable;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport rocks.tbog.tblauncher.utils.Utilities;\n\npublic class FourCodePointDrawable extends TextDrawable {\n    private final State mState;\n\n    public FourCodePointDrawable(int cp1, int cp2, int cp3, int cp4) {\n        this(new State(cp1, cp2, cp3, cp4));\n    }\n\n    public FourCodePointDrawable(State state) {\n        super();\n        mState = state;\n    }\n\n    @NonNull\n    public static FourCodePointDrawable fromText(CharSequence text, boolean soloFirstLine) {\n        int idx = 0;\n        int cp1 = Character.codePointAt(text, idx);\n        idx = Utilities.getNextCodePointIndex(text, idx);\n        int cp2 = 0;\n        if (!soloFirstLine) {\n            cp2 = Character.codePointAt(text, idx);\n            idx = Utilities.getNextCodePointIndex(text, idx);\n        }\n        int cp3 = Character.codePointAt(text, idx);\n        idx = Utilities.getNextCodePointIndex(text, idx);\n        int cp4 = idx < text.length() ? Character.codePointAt(text, idx) : 0;\n        return new FourCodePointDrawable(cp1, cp2, cp3, cp4);\n    }\n\n    @Nullable\n    @Override\n    public ConstantState getConstantState() {\n        return mState;\n    }\n\n    @Override\n    protected int getLineCount() {\n        return 2;\n    }\n\n    @Override\n    protected char[] getText(int line) {\n        int i = line * 2;\n        int cp1 = mState.mCodePoint[i];\n        int cp2 = mState.mCodePoint[i + 1];\n        if (cp2 == 0)\n            return Character.toChars(cp1);\n        int cc1 = Character.charCount(cp1);\n        int cc2 = Character.charCount(cp2);\n        char[] result = new char[cc1 + cc2];\n        System.arraycopy(Character.toChars(cp1), 0, result, 0, cc1);\n        System.arraycopy(Character.toChars(cp2), 0, result, cc1, cc2);\n        return result;\n    }\n\n    protected static class State extends ConstantState {\n        final int[] mCodePoint = new int[4];\n\n        protected State(int cp1, int cp2, int cp3, int cp4) {\n            mCodePoint[0] = cp1;\n            mCodePoint[1] = cp2;\n            mCodePoint[2] = cp3;\n            mCodePoint[3] = cp4;\n        }\n\n        @NonNull\n        @Override\n        public Drawable newDrawable() {\n            return new FourCodePointDrawable(this);\n        }\n\n        @Override\n        public int getChangingConfigurations() {\n            return 0;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/drawable/LoadingDrawable.java",
    "content": "package rocks.tbog.tblauncher.drawable;\n\nimport android.animation.ValueAnimator;\nimport android.graphics.Canvas;\nimport android.graphics.Matrix;\nimport android.graphics.Path;\nimport android.graphics.Rect;\nimport android.graphics.drawable.Animatable;\nimport android.view.animation.LinearInterpolator;\n\nimport androidx.annotation.NonNull;\n\nimport java.util.ArrayList;\n\n/**\n * This drawable is best used when the view is set to have\n * adjustViewBounds=\"true\"\n * scaleType=\"fitCenter\"\n * either width or height have a size or match_parent\n * Set Intrinsic size to 1x1 if you want to use scaleType=\"fitXY\" in the view\n */\n\npublic class LoadingDrawable extends SquareDrawable implements Animatable, ValueAnimator.AnimatorUpdateListener {\n    private final ArrayList<Shape> mShapeList = new ArrayList<>(0);\n    private final Path mShapePath;\n    private ValueAnimator mShapeListAnimator = null;\n\n    final static private float SHAPE_SIZE_PERCENT = 0.22f;\n    final static private float CORNER_SMOOTHING_PERCENT = 0.05f;\n\n    public LoadingDrawable() {\n        super();\n        mShapePath = new Path();\n    }\n\n    @Override\n    public void draw(@NonNull Canvas canvas) {\n//        mPaint.setColor(0x7F0000ff);\n//        canvas.drawRect(0, 0, (float)canvas.getWidth(), (float)canvas.getHeight(), mPaint);\n//        mPaint.setColor(0x7Fff0000);\n//        canvas.drawRect(mRect, mPaint);\n//        mPaint.setColor(0x7F00ff00);\n        //mPaint.setPathEffect(new CornerPathEffect(mRect.width() * CORNER_SMOOTHING_PERCENT));\n        canvas.drawPath(mShapePath, mPaint);\n    }\n\n    @Override\n    protected void onBoundsChange(Rect bounds) {\n        super.onBoundsChange(bounds);\n\n        Rect rect = getCenterRect(bounds);\n\n        // generate shapes\n        mShapeList.clear();\n        //if (mShapeList.isEmpty())\n        {\n            int size = rect.width();\n\n            int shapeSize = (int) (size * SHAPE_SIZE_PERCENT);\n            int padding = (size - 3 * shapeSize) / 6;\n\n            mShapeList.ensureCapacity(3 * 3);\n\n            int posY = rect.top + padding;\n            for (int x = 0; x < 3; x += 1) {\n                int posX = rect.left + padding;\n                for (int y = 0; y < 3; y += 1) {\n                    mShapeList.add(new Shape(shapeSize, shapeSize, posX, posY));\n                    posX += padding + padding + shapeSize;\n                }\n                posY += padding + padding + shapeSize;\n            }\n\n            updatePath(0f);\n        }\n    }\n\n    @Override\n    public void start() {\n        if (mShapeListAnimator == null) {\n            mShapeListAnimator = ValueAnimator.ofFloat(0, 360);\n            mShapeListAnimator.setDuration(3000);\n            mShapeListAnimator.addUpdateListener(this);\n            mShapeListAnimator.setRepeatCount(ValueAnimator.INFINITE);\n            mShapeListAnimator.setInterpolator(new LinearInterpolator());\n        }\n\n        if (mShapeListAnimator.isRunning())\n            return;\n\n//        if (mAnimator.isPaused())\n//            mAnimator.resume();\n//        else\n        mShapeListAnimator.start();\n        mPaint.setAntiAlias(true);\n    }\n\n    @Override\n    public void stop() {\n        if (mShapeListAnimator != null)\n            mShapeListAnimator.end();\n        mPaint.setAntiAlias(false);\n        invalidateSelf();\n    }\n\n    @Override\n    public boolean isRunning() {\n        if (mShapeListAnimator == null)\n            return false;\n        return mShapeListAnimator.isRunning();\n    }\n\n    @Override\n    public void onAnimationUpdate(ValueAnimator animation) {\n        float value = (Float) animation.getAnimatedValue();\n        updatePath(value);\n    }\n\n    private void updatePath(float value) {\n        mShapePath.reset();\n        for (Shape shape : mShapeList)\n            shape.addToPath(mShapePath, value);\n\n        invalidateSelf();\n    }\n\n    private static class Shape {\n        final Rect mRect;\n        final Matrix mat = new Matrix();\n        final float[] mPoints = new float[8];\n\n        Shape(int width, int height, int posX, int posY) {\n            mRect = new Rect(0, 0, width, height);\n            mRect.offset(posX, posY);\n        }\n\n        void addToPath(Path path, float angle) {\n            // top-left\n            mPoints[0] = mRect.left;\n            mPoints[1] = mRect.top;\n            // top-right\n            mPoints[2] = mRect.right;\n            mPoints[3] = mRect.top;\n            // bottom-right\n            mPoints[4] = mRect.right;\n            mPoints[5] = mRect.bottom;\n            // bottom-left\n            mPoints[6] = mRect.left;\n            mPoints[7] = mRect.bottom;\n\n            //mat.reset();\n            mat.setRotate(angle, mRect.centerX(), mRect.centerY());\n            mat.mapPoints(mPoints);\n\n            path.moveTo(mPoints[0], mPoints[1]);\n            path.lineTo(mPoints[2], mPoints[3]);\n            path.lineTo(mPoints[4], mPoints[5]);\n            path.lineTo(mPoints[6], mPoints[7]);\n            path.close();\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/drawable/SizeWrappedDrawable.java",
    "content": "package rocks.tbog.tblauncher.drawable;\n\nimport android.content.res.ColorStateList;\nimport android.graphics.BlendMode;\nimport android.graphics.Canvas;\nimport android.graphics.ColorFilter;\nimport android.graphics.Outline;\nimport android.graphics.PorterDuff;\nimport android.graphics.Rect;\nimport android.graphics.drawable.Drawable;\nimport android.os.Build;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.RequiresApi;\n\npublic class SizeWrappedDrawable extends Drawable {\n    @NonNull\n    private final Drawable mDrawable;\n    private final int mSize;\n\n    public SizeWrappedDrawable(@NonNull Drawable drawable, int size) {\n        mDrawable = drawable;\n        mSize = size;\n    }\n\n    @Override\n    public void draw(@NonNull Canvas canvas) {\n        mDrawable.draw(canvas);\n    }\n\n    @Override\n    public void setAlpha(int alpha) {\n        mDrawable.setAlpha(alpha);\n    }\n\n    @Override\n    public void setColorFilter(@Nullable ColorFilter colorFilter) {\n        mDrawable.setColorFilter(colorFilter);\n    }\n\n    @SuppressWarnings(\"deprecation\")\n    @Override\n    public int getOpacity() {\n        return mDrawable.getOpacity();\n    }\n\n    @Override\n    public int getIntrinsicWidth() {\n        return mSize;\n    }\n\n    @Override\n    public int getIntrinsicHeight() {\n        return mSize;\n    }\n\n    @Override\n    protected void onBoundsChange(Rect bounds) {\n        mDrawable.setBounds(bounds);\n    }\n\n    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)\n    @Override\n    public void setTint(int tintColor) {\n        mDrawable.setTint(tintColor);\n    }\n\n    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)\n    @Override\n    public void setTintList(@Nullable ColorStateList tint) {\n        mDrawable.setTintList(tint);\n    }\n\n    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)\n    @Override\n    public void setTintMode(@Nullable PorterDuff.Mode tintMode) {\n        mDrawable.setTintMode(tintMode);\n    }\n\n    @RequiresApi(api = Build.VERSION_CODES.Q)\n    @Override\n    public void setTintBlendMode(@Nullable BlendMode blendMode) {\n        mDrawable.setTintBlendMode(blendMode);\n    }\n\n    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)\n    @Override\n    public void setHotspot(float x, float y) {\n        mDrawable.setHotspot(x, y);\n    }\n\n    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)\n    @Override\n    public void setHotspotBounds(int left, int top, int right, int bottom) {\n        mDrawable.setHotspotBounds(left, top, right, bottom);\n    }\n\n    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)\n    @Override\n    public void getOutline(@NonNull Outline outline) {\n        mDrawable.getOutline(outline);\n    }\n\n    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)\n    @NonNull\n    @Override\n    public Rect getDirtyBounds() {\n        return mDrawable.getDirtyBounds();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/drawable/SquareDrawable.java",
    "content": "package rocks.tbog.tblauncher.drawable;\n\nimport android.graphics.ColorFilter;\nimport android.graphics.Paint;\nimport android.graphics.PixelFormat;\nimport android.graphics.Rect;\nimport android.graphics.drawable.Drawable;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\n/**\n * This drawable is best used when the view is set to have\n * adjustViewBounds=\"true\"\n * scaleType=\"fitCenter\"\n * either width or height have a size or match_parent\n * Set Intrinsic size to 1x1 if you want to use scaleType=\"fitXY\" in the view\n */\n\npublic abstract class SquareDrawable extends Drawable {\n    protected final Paint mPaint;\n\n//    @Override\n//    public int getIntrinsicWidth() {\n//        return 1;\n//    }\n//\n//    @Override\n//    public int getIntrinsicHeight() {\n//        return 1;\n//    }\n\n    public SquareDrawable() {\n        super();\n        mPaint = new Paint();\n        mPaint.setColor(0xffffffff);\n        mPaint.setStyle(Paint.Style.FILL);\n        mPaint.setAntiAlias(false);\n    }\n\n    @Override\n    public void setAlpha(int alpha) {\n        mPaint.setAlpha(alpha);\n    }\n\n    @Override\n    public void setColorFilter(@Nullable ColorFilter colorFilter) {\n        mPaint.setColorFilter(colorFilter);\n    }\n\n    @SuppressWarnings(\"deprecation\")\n    @Override\n    public int getOpacity() {\n        return PixelFormat.TRANSLUCENT;\n    }\n\n    protected Rect getCenterRect(@NonNull Rect bounds) {\n        Rect rect = new Rect();\n        rect.set(bounds);\n\n        // make it a square and center the content\n        if (rect.width() != rect.height()) {\n            int size = Math.min(rect.width(), rect.height());\n            int rad = size / 2;\n            // compute width\n            rect.left = rect.centerX() - rad;\n            rect.right = rect.left + size;\n            // compute height\n            rect.top = rect.centerY() - rad;\n            rect.bottom = rect.top + size;\n        }\n\n        return rect;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/drawable/TextDrawable.java",
    "content": "package rocks.tbog.tblauncher.drawable;\n\nimport android.graphics.Canvas;\nimport android.graphics.Color;\nimport android.graphics.Paint;\nimport android.graphics.PointF;\nimport android.graphics.Rect;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\npublic abstract class TextDrawable extends SquareDrawable {\n\n    protected PointF[] cachedLinePos = null;\n    protected float[] cachedLineSize = null;\n    protected char[][] cachedText = null;\n    protected int mTextColor = Color.WHITE;\n\n    public TextDrawable() {\n        mPaint.setTextAlign(Paint.Align.LEFT);\n        mPaint.setAntiAlias(true);\n    }\n\n    @Nullable\n    @Override\n    public abstract ConstantState getConstantState();\n\n    public void setTextColor(int color) {\n        mTextColor = color;\n    }\n\n    protected int getLineCount() {\n        return 1;\n    }\n\n    protected abstract char[] getText(int line);\n\n    @Override\n    protected void onBoundsChange(Rect bounds) {\n        super.onBoundsChange(bounds);\n        Rect rect = getCenterRect(bounds);\n        precacheTextPosAndSize(rect);\n    }\n\n    protected void precacheTextPosAndSize(Rect rect) {\n        final int lineCount = getLineCount();\n        final float cHeight = rect.height();\n        final float cWidth = rect.width();\n\n        // cache text\n        char[][] text = new char[lineCount][];\n        for (int line = 0; line < lineCount; line += 1)\n            text[line] = getText(line);\n\n        cachedText = text;\n        cachedLinePos = new PointF[lineCount];\n        cachedLineSize = new float[lineCount];\n\n        mPaint.setTextSize(cHeight);\n        Rect[] lineRect = new Rect[lineCount];\n        float heightSum = 0f;\n        for (int line = 0; line < lineCount; line += 1) {\n            lineRect[line] = new Rect();\n            mPaint.getTextBounds(text[line], 0, text[line].length, lineRect[line]);\n            heightSum += lineRect[line].height();\n        }\n\n        float[] expectedSize = new float[lineCount];\n\n        // find size for each line to fill the height\n        for (int line = 0; line < lineCount; line += 1) {\n            expectedSize[line] = lineRect[line].height() / heightSum * cHeight;\n            // use binary search to find a text size to fit the expectedSize\n            float minTextSize = 0.f;\n            float maxTextSize = expectedSize[line] * 2.f;\n            while (minTextSize < maxTextSize) {\n                mPaint.setTextSize((minTextSize + maxTextSize) * .5f);\n                mPaint.getTextBounds(text[line], 0, text[line].length, lineRect[line]);\n                if (lineRect[line].height() < expectedSize[line])\n                    minTextSize = (int) mPaint.getTextSize() + 1;\n                else\n                    maxTextSize = (int) (mPaint.getTextSize() - .01f);\n            }\n            cachedLineSize[line] = maxTextSize;\n        }\n\n        // find size for each line to fill the width\n        for (int line = 0; line < lineCount; line += 1) {\n            // use binary search to find a text size to fit the width\n            float minTextSize = 0.f;\n            float maxTextSize = cachedLineSize[line];\n            while (minTextSize < maxTextSize) {\n                mPaint.setTextSize((minTextSize + maxTextSize) * .5f);\n                mPaint.getTextBounds(text[line], 0, text[line].length, lineRect[line]);\n                if (lineRect[line].width() < cWidth)\n                    minTextSize = mPaint.getTextSize() + 1.f;\n                else\n                    maxTextSize = mPaint.getTextSize() - 1.f;\n            }\n            cachedLineSize[line] = maxTextSize;\n        }\n\n        // set line position\n        float lineOffset = 0f;\n        for (int line = 0; line < lineCount; line += 1) {\n            // center text inspired from https://stackoverflow.com/a/32081250\n            float x = cWidth * .5f - lineRect[line].width() * .5f - lineRect[line].left;\n            float y = expectedSize[line] * .5f + lineRect[line].height() * .5f - lineRect[line].bottom;\n\n            y += lineOffset;\n            cachedLinePos[line] = new PointF(rect.left + x, rect.top + y);\n\n            lineOffset += expectedSize[line];\n        }\n    }\n\n    @Override\n    public void draw(@NonNull Canvas canvas) {\n        // precacheTextPosAndSize may not be called before the first draw\n        if (cachedText == null)\n            return;\n\n        final int lineCount = getLineCount();\n        for (int line = 0; line < lineCount; line += 1) {\n            char[] text = cachedText[line];\n            float x = cachedLinePos[line].x;\n            float y = cachedLinePos[line].y;\n\n            mPaint.setTextSize(cachedLineSize[line]);\n\n            mPaint.setStyle(Paint.Style.FILL);\n            mPaint.setColor(mTextColor);\n            canvas.drawText(text, 0, text.length, x, y, mPaint);\n\n            mPaint.setStyle(Paint.Style.STROKE);\n            mPaint.setStrokeWidth(0.f); // 0 = hairline, always draws a single pixel independent of the canvas's matrix\n            mPaint.setColor(Color.BLACK);\n            canvas.drawText(text, 0, text.length, x, y, mPaint);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/drawable/TwoCodePointDrawable.java",
    "content": "package rocks.tbog.tblauncher.drawable;\n\nimport android.graphics.drawable.Drawable;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport rocks.tbog.tblauncher.utils.Utilities;\n\npublic class TwoCodePointDrawable extends TextDrawable {\n    private final State mState;\n    private boolean bVertical = false;\n\n    public TwoCodePointDrawable(int cp1, int cp2) {\n        this(new State(cp1, cp2));\n    }\n\n    public TwoCodePointDrawable(State state) {\n        super();\n        mState = state;\n    }\n\n    @NonNull\n    public static TwoCodePointDrawable fromText(CharSequence text, boolean vertical) {\n        int cp1 = Character.codePointAt(text, 0);\n        int cp2 = Character.codePointAt(text, Utilities.getNextCodePointIndex(text, 0));\n        TwoCodePointDrawable drawable = new TwoCodePointDrawable(cp1, cp2);\n        drawable.setVertical(vertical);\n        return drawable;\n    }\n\n    public void setVertical(boolean vertical) {\n        bVertical = vertical;\n    }\n\n    @Nullable\n    @Override\n    public ConstantState getConstantState() {\n        return mState;\n    }\n\n    @Override\n    protected int getLineCount() {\n        return bVertical ? 2 : 1;\n    }\n\n    @Override\n    protected char[] getText(int line) {\n        final int cp1 = mState.mCodePoint1;\n        final int cp2 = mState.mCodePoint2;\n        if (bVertical)\n            return line == 0 ? Character.toChars(cp1) : Character.toChars(cp2);\n        int c1 = Character.charCount(cp1);\n        int c2 = Character.charCount(cp2);\n        char[] result = new char[c1 + c2];\n        System.arraycopy(Character.toChars(cp1), 0, result, 0, c1);\n        System.arraycopy(Character.toChars(cp2), 0, result, c1, c2);\n        return result;\n    }\n\n    protected static class State extends ConstantState {\n        final int mCodePoint1;\n        final int mCodePoint2;\n\n        protected State(int cp1, int cp2) {\n            mCodePoint1 = cp1;\n            mCodePoint2 = cp2;\n        }\n\n        @NonNull\n        @Override\n        public Drawable newDrawable() {\n            return new TwoCodePointDrawable(this);\n        }\n\n        @Override\n        public int getChangingConfigurations() {\n            return 0;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/entry/ActionEntry.java",
    "content": "package rocks.tbog.tblauncher.entry;\n\nimport android.content.Context;\nimport android.graphics.drawable.Drawable;\nimport android.view.View;\nimport android.widget.Toast;\n\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport rocks.tbog.tblauncher.BuildConfig;\nimport rocks.tbog.tblauncher.R;\n\npublic class ActionEntry extends StaticEntry {\n    public static final String SCHEME = \"action://\";\n    private DoAction action = null;\n    private Drawable icon = null;\n\n    public interface DoAction {\n        void doAction(View view, int flags);\n    }\n\n    public ActionEntry(@NonNull String id, @NonNull Drawable icon) {\n        super(id, 0);\n        if (BuildConfig.DEBUG && !id.startsWith(SCHEME)) {\n            throw new IllegalStateException(\"Invalid \" + ActionEntry.class.getSimpleName() + \" id `\" + id + \"`\");\n        }\n        this.icon = icon;\n    }\n\n    public ActionEntry(@NonNull String id, @DrawableRes int icon) {\n        super(id, icon);\n        if (BuildConfig.DEBUG && !id.startsWith(SCHEME)) {\n            throw new IllegalStateException(\"Invalid \" + ActionEntry.class.getSimpleName() + \" id `\" + id + \"`\");\n        }\n    }\n\n    @Override\n    public void displayResult(@NonNull View view, int drawFlags) {\n        super.displayResult(view, drawFlags);\n        view.setTag(R.id.tag_actionId, id);\n    }\n\n    @Override\n    public void doLaunch(@NonNull View view, int flags) {\n        if (action == null) {\n            Toast.makeText(view.getContext(), \"`\" + id + \"` not implemented\", Toast.LENGTH_LONG).show();\n            return;\n        }\n        action.doAction(view, flags);\n    }\n\n    public void setAction(@Nullable DoAction action) {\n        this.action = action;\n    }\n\n    @Override\n    public Drawable getDefaultDrawable(Context context) {\n        if (icon != null)\n            return icon;\n        return super.getDefaultDrawable(context);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/entry/AppEntry.java",
    "content": "package rocks.tbog.tblauncher.entry;\n\nimport static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;\nimport static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_MANIFEST;\n\nimport android.annotation.TargetApi;\nimport android.app.Activity;\nimport android.content.ActivityNotFoundException;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.pm.ApplicationInfo;\nimport android.content.pm.LauncherActivityInfo;\nimport android.content.pm.LauncherApps;\nimport android.content.pm.PackageManager;\nimport android.content.pm.ShortcutInfo;\nimport android.graphics.ColorFilter;\nimport android.graphics.Rect;\nimport android.graphics.drawable.Drawable;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.provider.Settings;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\nimport android.widget.TextView;\nimport android.widget.Toast;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.RequiresApi;\nimport androidx.annotation.StringRes;\nimport androidx.annotation.WorkerThread;\n\nimport java.util.List;\n\nimport rocks.tbog.tblauncher.Behaviour;\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.handler.IconsHandler;\nimport rocks.tbog.tblauncher.preference.ContentLoadHelper;\nimport rocks.tbog.tblauncher.result.AsyncSetEntryDrawable;\nimport rocks.tbog.tblauncher.result.ResultViewHelper;\nimport rocks.tbog.tblauncher.shortcut.ShortcutUtil;\nimport rocks.tbog.tblauncher.ui.LinearAdapter;\nimport rocks.tbog.tblauncher.ui.ListPopup;\nimport rocks.tbog.tblauncher.utils.DebugInfo;\nimport rocks.tbog.tblauncher.utils.DialogHelper;\nimport rocks.tbog.tblauncher.utils.PrefCache;\nimport rocks.tbog.tblauncher.utils.RootHandler;\nimport rocks.tbog.tblauncher.utils.UserHandleCompat;\nimport rocks.tbog.tblauncher.utils.Utilities;\n\npublic final class AppEntry extends EntryWithTags {\n\n    public static final String SCHEME = \"app://\";\n    private static final int[] RESULT_LAYOUT = {R.layout.item_app, R.layout.item_grid, R.layout.item_dock};\n\n    @NonNull\n    public final ComponentName componentName;\n    @NonNull\n    private final UserHandleCompat userHandle;\n\n    private final IconInfo iconInfo = new IconInfo();\n    private boolean hiddenByUser = false;\n    private boolean excludedFromHistory = false;\n\n    private static class IconInfo {\n        public boolean isDynamic = false;\n        public Boolean fitInside = null;\n        public long customIcon = 0;\n        public int cacheIconId = 0;\n\n        public void setIconInfo(IconsHandler.IconInfo icon) {\n            isDynamic = icon.isDynamic();\n            fitInside = icon.getFitInside();\n        }\n\n        public void setCustomIcon(long dbId) {\n            customIcon = dbId;\n            cacheIconId += 1;\n            isDynamic = false;\n            fitInside = null;\n        }\n\n        public void clearCustomIcon() {\n            customIcon = 0;\n            cacheIconId = 0;\n        }\n    }\n\n    public AppEntry(@NonNull ComponentName component, @NonNull UserHandleCompat user) {\n        this(component.getPackageName(), component.getClassName(), user);\n    }\n\n    public AppEntry(@NonNull String packageName, @NonNull String activityName, @NonNull UserHandleCompat user) {\n        super(generateAppId(packageName, activityName, user));\n        componentName = new ComponentName(packageName, activityName);\n        userHandle = user;\n    }\n\n    /**\n     * Generate a unique {@link AppEntry} id from {@link ComponentName} and {@link UserHandleCompat}\n     *\n     * @param component component {@link ComponentName}\n     * @param user      user handle\n     * @return unique id with SCHEME prefix\n     */\n    @NonNull\n    public static String generateAppId(@NonNull ComponentName component, @NonNull UserHandleCompat user) {\n        return SCHEME + user.getUserComponentName(component);\n    }\n\n    @NonNull\n    public static String generateAppId(@NonNull String packageName, @NonNull String activityName, @NonNull UserHandleCompat user) {\n        return SCHEME + user.getUserComponentName(packageName, activityName);\n    }\n\n    @NonNull\n    @Override\n    public String getIconCacheId() {\n        return id + iconInfo.cacheIconId;\n    }\n\n    public String getUserComponentName() {\n        return userHandle.getUserComponentName(componentName);\n    }\n\n    protected String getPackageName() {\n        return componentName.getPackageName();\n    }\n\n    @Override\n    public boolean isHiddenByUser() {\n        return hiddenByUser;\n    }\n\n    public void setHiddenByUser(boolean hiddenByUser) {\n        this.hiddenByUser = hiddenByUser;\n    }\n\n    @Override\n    public boolean isExcludedFromHistory() {\n        return excludedFromHistory;\n    }\n\n    public void setExcludedFromHistory(boolean excludedFromHistory) {\n        this.excludedFromHistory = excludedFromHistory;\n    }\n\n    public boolean canUninstall() {\n        return userHandle.isCurrentUser();\n    }\n\n    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)\n    protected List<LauncherActivityInfo> getActivityList(LauncherApps launcher) {\n        return launcher.getActivityList(componentName.getPackageName(), userHandle.getRealHandle());\n    }\n\n    @WorkerThread\n    public Drawable getIconDrawable(Context context) {\n        IconsHandler iconsHandler = TBApplication.getApplication(context).iconsHandler();\n        if (iconInfo.customIcon != 0) {\n            Drawable drawable = iconsHandler.getCustomIcon(getUserComponentName());\n            if (drawable != null)\n                return drawable;\n            else\n                iconsHandler.restoreDefaultIcon(this);\n        }\n        IconsHandler.IconInfo icon = iconsHandler.getIconForPackage(componentName, userHandle);\n        iconInfo.setIconInfo(icon);\n        return icon.getDrawable();\n    }\n\n    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)\n    public android.os.UserHandle getRealHandle() {\n        return userHandle.getRealHandle();\n    }\n\n    public void setCustomIcon(long dbId) {\n        iconInfo.setCustomIcon(dbId);\n    }\n\n    public void clearCustomIcon() {\n        iconInfo.clearCustomIcon();\n    }\n\n    public long getCustomIcon() {\n        return iconInfo.customIcon;\n    }\n\n    ///////////////////////////////////////////////////////////////////////////////////////////////\n    // Result methods\n    ///////////////////////////////////////////////////////////////////////////////////////////////\n\n    public static int[] getResultLayout() {\n        return RESULT_LAYOUT;\n    }\n\n    @Override\n    public int getResultLayout(int drawFlags) {\n        return Utilities.checkFlag(drawFlags, FLAG_DRAW_LIST) ? RESULT_LAYOUT[0] :\n            (Utilities.checkFlag(drawFlags, FLAG_DRAW_GRID) ? RESULT_LAYOUT[1] :\n                RESULT_LAYOUT[2]);\n    }\n\n    @Override\n    public void displayResult(@NonNull View view, int drawFlags) {\n        if (Utilities.checkFlag(drawFlags, FLAG_DRAW_LIST)) {\n            displayListResult(view, drawFlags);\n            ResultViewHelper.applyListRowPreferences((ViewGroup) view);\n        } else {\n            displayGridResult(view, drawFlags);\n        }\n    }\n\n    private void displayGridResult(@NonNull View view, int drawFlags) {\n        TextView nameView = view.findViewById(android.R.id.text1);\n        if (Utilities.checkFlag(drawFlags, FLAG_DRAW_NAME)) {\n            ResultViewHelper.displayHighlighted(relevance, normalizedName, getName(), nameView);\n            nameView.setVisibility(View.VISIBLE);\n        } else {\n            nameView.setText(getName());\n            nameView.setVisibility(View.GONE);\n        }\n\n        ImageView appIcon = view.findViewById(android.R.id.icon);\n        ImageView bottomRightIcon = view.findViewById(android.R.id.icon2);\n        if (Utilities.checkFlag(drawFlags, FLAG_DRAW_ICON)) {\n            ColorFilter colorFilter = ResultViewHelper.setIconColorFilter(appIcon, drawFlags);\n            appIcon.setVisibility(View.VISIBLE);\n            ResultViewHelper.setIconAsync(drawFlags, this, appIcon, AsyncSetEntryIcon.class, AppEntry.class);\n\n            if (bottomRightIcon != null) {\n                if (isHiddenByUser()) {\n                    bottomRightIcon.setVisibility(View.VISIBLE);\n                    bottomRightIcon.setImageResource(R.drawable.ic_eye_crossed);\n                    bottomRightIcon.setColorFilter(colorFilter);\n                } else {\n                    bottomRightIcon.setVisibility(View.GONE);\n                }\n            }\n        } else {\n            appIcon.setImageDrawable(null);\n            appIcon.setVisibility(View.GONE);\n            if (bottomRightIcon != null)\n                bottomRightIcon.setVisibility(View.GONE);\n        }\n\n        ResultViewHelper.applyPreferences(drawFlags, nameView, appIcon);\n    }\n\n    private void displayListResult(@NonNull View view, int drawFlags) {\n        final Context context = view.getContext();\n\n        TextView nameView = view.findViewById(R.id.item_app_name);\n        ResultViewHelper.displayHighlighted(relevance, normalizedName, getName(), nameView);\n\n        TextView tagsView = view.findViewById(R.id.item_app_tag);\n        // Hide tags view if tags are empty\n        if (getTags().isEmpty()) {\n            tagsView.setVisibility(View.GONE);\n        } else if (ResultViewHelper.displayHighlighted(relevance, getTags(), tagsView, context)\n            || Utilities.checkFlag(drawFlags, FLAG_DRAW_TAGS)) {\n            tagsView.setVisibility(View.VISIBLE);\n            ResultViewHelper.applyResultItemShadow(tagsView);\n        } else {\n            tagsView.setVisibility(View.GONE);\n        }\n\n        ImageView appIcon = view.findViewById(android.R.id.icon);\n        ImageView bottomRightIcon = view.findViewById(android.R.id.icon2);\n        if (Utilities.checkFlag(drawFlags, FLAG_DRAW_ICON)) {\n            ColorFilter colorFilter = ResultViewHelper.setIconColorFilter(appIcon, drawFlags);\n            appIcon.setVisibility(View.VISIBLE);\n            ResultViewHelper.setIconAsync(drawFlags, this, appIcon, AsyncSetEntryIcon.class, AppEntry.class);\n\n            if (isHiddenByUser()) {\n                bottomRightIcon.setColorFilter(colorFilter);\n                bottomRightIcon.setVisibility(View.VISIBLE);\n                bottomRightIcon.setImageResource(R.drawable.ic_eye_crossed);\n            } else {\n                bottomRightIcon.setVisibility(View.GONE);\n            }\n        } else {\n            appIcon.setImageDrawable(null);\n            appIcon.setVisibility(View.GONE);\n            bottomRightIcon.setVisibility(View.GONE);\n        }\n\n        //TODO: enable notification badges\n//        if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {\n//            SharedPreferences notificationPrefs = context.getSharedPreferences(NotificationListener.NOTIFICATION_PREFERENCES_NAME, Context.MODE_PRIVATE);\n//            ImageView notificationView = view.findViewById(R.id.item_notification_dot);\n//            notificationView.setVisibility(notificationPrefs.contains(getPackageName()) ? View.VISIBLE : View.GONE);\n//            notificationView.setTag(getPackageName());\n//\n//            int primaryColor = UIColors.getPrimaryColor(context);\n//            notificationView.setColorFilter(primaryColor);\n//        }\n\n        ResultViewHelper.applyPreferences(drawFlags, nameView, tagsView, appIcon);\n    }\n\n    static class ShortcutItem extends LinearAdapter.ItemString {\n        @NonNull\n        ShortcutInfo shortcutInfo;\n\n        public ShortcutItem(@NonNull String string, @NonNull ShortcutInfo info) {\n            super(string);\n            shortcutInfo = info;\n        }\n    }\n\n    @Override\n    protected ListPopup buildPopupMenu(Context context, LinearAdapter adapter, View parentView, int flags) {\n        List<ContentLoadHelper.CategoryItem> categoryTitle = PrefCache.getResultPopupOrder(context);\n\n        for (ContentLoadHelper.CategoryItem categoryItem : categoryTitle) {\n            int titleStringId = categoryItem.textId;\n            if (titleStringId == R.string.popup_title_hist_fav) {\n                adapter.add(new LinearAdapter.ItemTitle(context, R.string.popup_title_hist_fav));\n                //adapter.add(new LinearAdapter.Item(context, R.string.menu_exclude));\n                adapter.add(new LinearAdapter.Item(context, R.string.menu_remove_history));\n                adapter.add(new LinearAdapter.Item(context, R.string.menu_quick_list_add));\n                adapter.add(new LinearAdapter.Item(context, R.string.menu_quick_list_remove));\n                if (isHiddenByUser())\n                    adapter.add(new LinearAdapter.Item(context, R.string.menu_show));\n                else\n                    adapter.add(new LinearAdapter.Item(context, R.string.menu_hide));\n            } else if (titleStringId == R.string.popup_title_customize) {\n                adapter.add(new LinearAdapter.ItemTitle(context, R.string.popup_title_customize));\n                if (getTags().isEmpty())\n                    adapter.add(new LinearAdapter.Item(context, R.string.menu_tags_add));\n                else\n                    adapter.add(new LinearAdapter.Item(context, R.string.menu_tags_edit));\n                adapter.add(new LinearAdapter.Item(context, R.string.menu_app_rename));\n                adapter.add(new LinearAdapter.Item(context, R.string.menu_custom_icon));\n            } else if (titleStringId == R.string.popup_title_link) {\n                adapter.add(new LinearAdapter.ItemTitle(context, R.string.popup_title_link));\n                adapter.add(new LinearAdapter.Item(context, R.string.menu_app_details));\n                adapter.add(new LinearAdapter.Item(context, R.string.menu_app_store));\n\n                try {\n                    // app installed under /system can't be uninstalled\n                    ApplicationInfo ai;\n                    if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n                        LauncherApps launcher = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);\n                        assert launcher != null;\n                        //LauncherActivityInfo info = launcher.getActivityList(this.appPojo().packageName, this.appPojo().userHandle.getRealHandle()).get(0);\n                        LauncherActivityInfo info = getActivityList(launcher).get(0);\n                        ai = info.getApplicationInfo();\n\n                    } else {\n                        ai = context.getPackageManager().getApplicationInfo(getPackageName(), 0);\n                    }\n\n                    if ((ai.flags & ApplicationInfo.FLAG_SYSTEM) == 0 && canUninstall()) {\n                        adapter.add(new LinearAdapter.Item(context, R.string.menu_app_uninstall));\n                    }\n                } catch (PackageManager.NameNotFoundException | IndexOutOfBoundsException e) {\n                    // should not happen\n                }\n\n                // append root menu if available\n                RootHandler rootHandler = TBApplication.rootHandler(context);\n                if (rootHandler.isRootActivated() && rootHandler.isRootAvailable()) {\n                    adapter.add(new LinearAdapter.Item(context, R.string.menu_app_hibernate));\n                }\n            } else if (titleStringId == R.string.popup_title_shortcut_dynamic) {\n                int shortcutCount = 0;\n                if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n                    List<ShortcutInfo> list = ShortcutUtil.getShortcut(context, getPackageName(), FLAG_MATCH_MANIFEST | FLAG_MATCH_DYNAMIC);\n                    for (ShortcutInfo info : list) {\n                        CharSequence label = info.getLongLabel();\n                        if (label == null)\n                            label = info.getShortLabel();\n                        if (label == null)\n                            continue;\n                        if (shortcutCount == 0)\n                            adapter.add(new LinearAdapter.ItemTitle(context, R.string.popup_title_shortcut_dynamic));\n                        adapter.add(new ShortcutItem(label.toString(), info));\n                        shortcutCount += 1;\n                    }\n                }\n            } else if (titleStringId == R.string.popup_title_debug) {\n                if (DebugInfo.itemIconInfo(context)) {\n                    adapter.add(new LinearAdapter.ItemTitle(context, R.string.popup_title_debug));\n                    adapter.add(new LinearAdapter.ItemString(\"icon custom: \" + getCustomIcon()));\n                    adapter.add(new LinearAdapter.ItemString(\"cacheIconId: \" + iconInfo.cacheIconId));\n                    adapter.add(new LinearAdapter.ItemString(\"icon dynamic: \" + iconInfo.isDynamic));\n                    adapter.add(new LinearAdapter.ItemString(\"icon fitInside: \" + iconInfo.fitInside));\n                }\n            }\n        }\n\n        if (Utilities.checkFlag(flags, LAUNCHED_FROM_QUICK_LIST)) {\n            adapter.add(new LinearAdapter.ItemTitle(context, R.string.menu_popup_title_settings));\n            adapter.add(new LinearAdapter.Item(context, R.string.menu_popup_quick_list_customize));\n        }\n\n        return inflatePopupMenu(context, adapter);\n    }\n\n    @Override\n    protected boolean popupMenuClickHandler(@NonNull final View view, @NonNull LinearAdapter.MenuItem item, int stringId, View parentView) {\n        Context ctx = view.getContext();\n        if (item instanceof ShortcutItem) {\n            TBApplication.behaviour(ctx).beforeLaunchOccurred();\n            final ShortcutInfo shortcutInfo = ((ShortcutItem) item).shortcutInfo;\n            parentView.postDelayed(() -> {\n                Activity activity = Utilities.getActivity(parentView);\n                if (activity == null)\n                    return;\n\n                ShortcutEntry.doOreoLaunch(activity, parentView, shortcutInfo);\n\n                TBApplication.behaviour(activity).afterLaunchOccurred();\n            }, Behaviour.LAUNCH_DELAY);\n\n            return true;\n        }\n        if (stringId == R.string.menu_app_details) {\n            launchAppDetails(ctx, parentView);\n            return true;\n        } else if (stringId == R.string.menu_app_store) {\n            launchAppStore(ctx, parentView);\n            return true;\n        } else if (stringId == R.string.menu_app_uninstall) {\n            launchUninstall(ctx);\n            return true;\n        } else if (stringId == R.string.menu_app_hibernate) {\n            hibernate(ctx);\n            return true;\n//            case R.string.menu_app_hibernate:\n//                hibernate(context, appPojo);\n//                return true;\n        } else if (stringId == R.string.menu_exclude) {\n            LinearAdapter adapter = new LinearAdapter();\n            ListPopup menu = ListPopup.create(ctx, adapter);\n\n            adapter.add(new LinearAdapter.Item(ctx, R.string.menu_exclude_history));\n            adapter.add(new LinearAdapter.Item(ctx, R.string.menu_exclude_kiss));\n\n            menu.setOnItemClickListener((a, v, pos) -> {\n                LinearAdapter.MenuItem menuItem = ((LinearAdapter) a).getItem(pos);\n                @StringRes int id = 0;\n                if (menuItem instanceof LinearAdapter.Item) {\n                    id = ((LinearAdapter.Item) a.getItem(pos)).stringId;\n                }\n                if (id == R.string.menu_exclude_history) {\n                    //excludeFromHistory(v.getContext(), appPojo());\n                    Toast.makeText(ctx, \"Not Implemented\", Toast.LENGTH_LONG).show();\n                } else if (id == R.string.menu_exclude_kiss) {\n                    //excludeFromKiss(v.getContext(), appPojo(), parent);\n                    Toast.makeText(ctx, \"Work in progress\", Toast.LENGTH_LONG).show();\n                }\n            });\n            menu.show(parentView);\n            TBApplication.getApplication(ctx).registerPopup(menu);\n            return true;\n        } else if (stringId == R.string.menu_hide) {\n            if (TBApplication.dataHandler(ctx).addToHidden(this)) {\n                setHiddenByUser(true);\n                TBApplication.behaviour(ctx).refreshSearchRecord(this);\n                //Toast.makeText(ctx, \"App \"+getName()+\" hidden from search\", Toast.LENGTH_LONG).show();\n            }\n        } else if (stringId == R.string.menu_show) {\n            if (TBApplication.dataHandler(ctx).removeFromHidden(this)) {\n                setHiddenByUser(false);\n                TBApplication.behaviour(ctx).refreshSearchRecord(this);\n                //Toast.makeText(ctx, \"App \"+getName()+\" shown in searches\", Toast.LENGTH_LONG).show();\n            }\n        } else if (stringId == R.string.menu_tags_add || stringId == R.string.menu_tags_edit) {\n            TBApplication.behaviour(ctx).launchEditTagsDialog(this);\n            return true;\n        } else if (stringId == R.string.menu_app_rename) {\n            launchRenameDialog(ctx);\n            return true;\n        } else if (stringId == R.string.menu_custom_icon) {\n            TBApplication.behaviour(ctx).launchCustomIconDialog(this);\n            return true;\n        }\n\n        return super.popupMenuClickHandler(view, item, stringId, parentView);\n    }\n\n    @Override\n    public void doLaunch(@NonNull View v, int flags) {\n        Context context = v.getContext();\n        // If AppResult, find the icon\n        View potentialIcon = v.findViewById(android.R.id.icon);\n        try {\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n                LauncherApps launcher = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);\n                assert launcher != null;\n\n                // We're on a modern Android and can display activity animations\n                Bundle startActivityOptions = Utilities.makeStartActivityOptions(potentialIcon);\n                Rect sourceBounds = Utilities.getOnScreenRect(potentialIcon);\n                launcher.startMainActivity(componentName, getRealHandle(), sourceBounds, startActivityOptions);\n            } else {\n                Intent intent = new Intent(Intent.ACTION_MAIN);\n                intent.addCategory(Intent.CATEGORY_LAUNCHER);\n                intent.setComponent(componentName);\n                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);\n                Utilities.setIntentSourceBounds(intent, v);\n                Bundle startActivityOptions = Utilities.makeStartActivityOptions(potentialIcon);\n                context.startActivity(intent, startActivityOptions);\n            }\n        } catch (ActivityNotFoundException | NullPointerException | SecurityException e) {\n            // Application was just removed?\n            // (null pointer exception can be thrown on Lollipop+ when app is missing)\n            Toast.makeText(context, context.getString(R.string.application_not_found, componentName.flattenToShortString()), Toast.LENGTH_LONG).show();\n        }\n    }\n\n    private void launchRenameDialog(@NonNull Context ctx) {\n        DialogHelper.makeRenameDialog(ctx, getName(), (dialog, name) -> {\n            // Set new name\n            setName(name);\n            Context context = dialog.getContext();\n            TBApplication app = TBApplication.getApplication(context);\n            app.getDataHandler().renameApp(getUserComponentName(), name);\n            app.behaviour().refreshSearchRecord(AppEntry.this);\n\n            // Show toast message\n            String msg = context.getResources().getString(R.string.app_rename_confirmation, getName());\n            Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();\n        })\n            .setTitle(R.string.title_app_rename)\n            .setNeutralButton(R.string.custom_name_set_default, (dialog, which) -> {\n                Context context = dialog.getContext();\n                String name = null;\n                PackageManager pm = context.getPackageManager();\n                try {\n                    ApplicationInfo applicationInfo = pm.getApplicationInfo(getPackageName(), 0);\n                    name = applicationInfo.loadLabel(pm).toString();\n                } catch (PackageManager.NameNotFoundException ignored) {\n                }\n                if (name != null) {\n                    setName(name);\n                    TBApplication app = TBApplication.getApplication(context);\n                    app.getDataHandler().removeRenameApp(getUserComponentName(), name);\n                    app.behaviour().refreshSearchRecord(AppEntry.this);\n\n                    // Show toast message\n                    String msg = context.getString(R.string.app_rename_confirmation, getName());\n                    Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();\n                }\n\n                dialog.dismiss();\n            })\n            .show();\n    }\n\n    /**\n     * Open an activity displaying details regarding the current package\n     */\n    private void launchAppDetails(Context context, View view) {\n        if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            TBApplication.behaviour(context).beforeLaunchOccurred();\n            view.postDelayed(() -> {\n                Activity activity = Utilities.getActivity(view);\n                if (activity == null)\n                    return;\n                LauncherApps launcher = (LauncherApps) activity.getSystemService(Context.LAUNCHER_APPS_SERVICE);\n                assert launcher != null;\n                Rect bounds = Utilities.getOnScreenRect(view);\n                Bundle opts = Utilities.makeStartActivityOptions(view);\n                launcher.startAppDetailsActivity(componentName, userHandle.getRealHandle(), bounds, opts);\n\n                TBApplication.behaviour(activity).afterLaunchOccurred();\n            }, Behaviour.LAUNCH_DELAY);\n        } else {\n            Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,\n                Uri.fromParts(\"package\", getPackageName(), null));\n            TBApplication.behaviour(context).launchIntent(view, intent);\n        }\n    }\n\n    private void launchAppStore(Context context, View view) {\n        TBApplication.behaviour(context).beforeLaunchOccurred();\n        view.postDelayed(() -> {\n            Activity activity = Utilities.getActivity(view);\n            if (activity == null)\n                return;\n            Rect bound = Utilities.getOnScreenRect(view);\n            Bundle startActivityOptions = Utilities.makeStartActivityOptions(view);\n            Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(\"market://details?id=\" + getPackageName()));\n            try {\n                intent.setSourceBounds(bound);\n                activity.startActivity(intent, startActivityOptions);\n            } catch (ActivityNotFoundException ignored) {\n                intent = new Intent(Intent.ACTION_VIEW, Uri.parse(\"https://play.google.com/store/apps/details?id=\" + getPackageName()));\n                intent.setSourceBounds(bound);\n                activity.startActivity(intent, startActivityOptions);\n            }\n            TBApplication.behaviour(activity).afterLaunchOccurred();\n        }, Behaviour.LAUNCH_DELAY);\n    }\n\n    /**\n     * Open an activity to uninstall the app package\n     */\n    private void launchUninstall(Context context) {\n        Intent intent = new Intent(Intent.ACTION_DELETE,\n            Uri.fromParts(\"package\", getPackageName(), null));\n        context.startActivity(intent);\n    }\n\n    private void hibernate(Context context) {\n        String msg = context.getResources().getString(R.string.toast_hibernate_completed);\n        if (!TBApplication.rootHandler(context).hibernateApp(getPackageName())) {\n            msg = context.getResources().getString(R.string.toast_hibernate_error);\n//        } else {\n//            TBApplication.dataHandler(context).getAppProvider().reload(false);\n        }\n\n        Toast.makeText(context, String.format(msg, getName()), Toast.LENGTH_SHORT).show();\n    }\n\n    public static class AsyncSetEntryIcon extends AsyncSetEntryDrawable<AppEntry> {\n        public AsyncSetEntryIcon(@NonNull ImageView image, int drawFlags, @NonNull AppEntry entryItem) {\n            super(image, drawFlags, entryItem);\n        }\n\n        @Override\n        public Drawable getDrawable(Context context) {\n            return entryItem.getIconDrawable(context);\n        }\n\n        @Override\n        protected void setDrawable(ImageView image, Drawable drawable) {\n            super.setDrawable(image, drawable);\n            if (entryItem.iconInfo.isDynamic)\n                TBApplication.drawableCache(image.getContext()).setCalendar(cacheId);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/entry/CalculatorEntry.java",
    "content": "package rocks.tbog.tblauncher.entry;\n\nimport android.content.Context;\nimport android.text.Spannable;\nimport android.text.SpannableString;\nimport android.text.style.ForegroundColorSpan;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\nimport android.widget.TextView;\nimport android.widget.Toast;\n\nimport androidx.annotation.NonNull;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.result.ResultViewHelper;\nimport rocks.tbog.tblauncher.utils.ClipboardUtils;\nimport rocks.tbog.tblauncher.utils.UIColors;\nimport rocks.tbog.tblauncher.utils.Utilities;\n\npublic final class CalculatorEntry extends SearchEntry {\n    public static final String SCHEME = \"calculator://\";\n\n    public CalculatorEntry(String query) {\n        super(SCHEME + query);\n        setName(query, false);\n    }\n\n    @Override\n    public String getHistoryId() {\n        // Search POJO should not appear in history\n        return \"\";\n    }\n\n    @Override\n    public void displayResult(@NonNull View view, int drawFlags) {\n        Context context = view.getContext();\n        TextView nameView = view.findViewById(android.R.id.text1);\n        nameView.setTextColor(UIColors.getResultTextColor(view.getContext()));\n        if (Utilities.checkFlag(drawFlags, FLAG_DRAW_NAME)) {\n            String text = getName();\n            int pos = text.indexOf(\"=\");\n            if (pos >= 0) {\n                int color = UIColors.getResultHighlightColor(context);\n                SpannableString enriched = new SpannableString(text);\n                enriched.setSpan(\n                    new ForegroundColorSpan(color),\n                    pos + 1,\n                    text.length(),\n                    Spannable.SPAN_INCLUSIVE_EXCLUSIVE\n                );\n                nameView.setText(enriched);\n            } else {\n                nameView.setText(text);\n            }\n            nameView.setVisibility(View.VISIBLE);\n        } else {\n            nameView.setVisibility(View.GONE);\n        }\n\n        ImageView appIcon = view.findViewById(android.R.id.icon);\n        if (Utilities.checkFlag(drawFlags, FLAG_DRAW_ICON)) {\n            ResultViewHelper.setIconColorFilter(appIcon, drawFlags);\n            appIcon.setVisibility(View.VISIBLE);\n            appIcon.setImageResource(R.drawable.ic_functions);\n        } else {\n            appIcon.setImageDrawable(null);\n            appIcon.setVisibility(View.GONE);\n        }\n\n        ResultViewHelper.applyPreferences(drawFlags, nameView, appIcon);\n        if (Utilities.checkFlag(drawFlags, FLAG_DRAW_LIST))\n            ResultViewHelper.applyListRowPreferences((ViewGroup) view);\n    }\n\n    @Override\n    public void doLaunch(@NonNull View v, int flags) {\n        String text = getName();\n        if (!text.isEmpty()) {\n            String result = text.substring(text.indexOf(\"=\") + 1).trim();\n            Context context = v.getContext();\n            ClipboardUtils.setClipboard(context, result);\n            Toast.makeText(context, context.getString(R.string.copy_confirmation, result), Toast.LENGTH_SHORT).show();\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/entry/ContactEntry.java",
    "content": "package rocks.tbog.tblauncher.entry;\n\nimport static rocks.tbog.tblauncher.customicon.ButtonHelper.BTN_ID_MESSAGE;\nimport static rocks.tbog.tblauncher.customicon.ButtonHelper.BTN_ID_OPEN;\nimport static rocks.tbog.tblauncher.customicon.ButtonHelper.BTN_ID_PHONE;\n\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.content.pm.PackageManager;\nimport android.graphics.drawable.ColorDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.net.Uri;\nimport android.text.TextUtils;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageButton;\nimport android.widget.ImageView;\nimport android.widget.TextView;\n\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.StringRes;\nimport androidx.annotation.WorkerThread;\nimport androidx.appcompat.content.res.AppCompatResources;\nimport androidx.core.content.res.ResourcesCompat;\nimport androidx.preference.PreferenceManager;\n\nimport java.io.IOException;\nimport java.io.InputStream;\n\nimport rocks.tbog.tblauncher.BuildConfig;\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.customicon.ButtonHelper;\nimport rocks.tbog.tblauncher.handler.IconsHandler;\nimport rocks.tbog.tblauncher.normalizer.PhoneNormalizer;\nimport rocks.tbog.tblauncher.normalizer.StringNormalizer;\nimport rocks.tbog.tblauncher.result.AsyncSetEntryDrawable;\nimport rocks.tbog.tblauncher.result.ResultHelper;\nimport rocks.tbog.tblauncher.result.ResultViewHelper;\nimport rocks.tbog.tblauncher.ui.LinearAdapter;\nimport rocks.tbog.tblauncher.ui.ListPopup;\nimport rocks.tbog.tblauncher.utils.PackageManagerUtils;\nimport rocks.tbog.tblauncher.utils.PrefCache;\nimport rocks.tbog.tblauncher.utils.UIColors;\nimport rocks.tbog.tblauncher.utils.UserHandleCompat;\nimport rocks.tbog.tblauncher.utils.Utilities;\n\npublic class ContactEntry extends EntryItem {\n    public static final String SCHEME = \"contact://\";\n    private static final int[] RESULT_LAYOUT = {R.layout.item_contact, R.layout.item_grid, R.layout.item_dock};\n    private static final String BTN_ACTION_PHONE = \"phone\";\n    private static final String BTN_ACTION_MESSAGE = \"message\";\n    private static final String BTN_ACTION_OPEN = \"open\";\n    public String lookupKey;\n\n    protected String phone;\n    //phone without special characters\n    public StringNormalizer.Result normalizedPhone;\n    protected Uri iconUri = null;\n\n    // Is this a primary phone?\n    protected boolean primary = false;\n\n    // How many times did we phone this contact?\n    protected int timesContacted = 0;\n\n    // Is this contact starred ?\n    protected boolean starred = false;\n\n    // Is this number a home (local / landline) number? We can't send messages to this.\n    protected boolean homeNumber = false;\n\n    public StringNormalizer.Result normalizedNickname = null;\n\n    protected String nickname = \"\";\n\n    protected ImData imData;\n\n    public ContactEntry(String id) {\n        super(id);\n        if (BuildConfig.DEBUG && !id.startsWith(SCHEME)) {\n            throw new IllegalStateException(\"Invalid \" + ContactEntry.class.getSimpleName() + \" id `\" + id + \"`\");\n        }\n    }\n\n    protected void setNickname(String nickname) {\n        if (nickname != null) {\n            // Set the actual user-friendly name\n            this.nickname = nickname;\n            this.normalizedNickname = StringNormalizer.normalizeWithResult(this.nickname, false);\n        } else {\n            this.nickname = null;\n            this.normalizedNickname = null;\n        }\n    }\n\n    public boolean isPrimary() {\n        return primary;\n    }\n\n    public boolean isStarred() {\n        return starred;\n    }\n\n    public ImData getImData() {\n        return imData;\n    }\n\n    public boolean isHomeNumber() {\n        return homeNumber;\n    }\n\n    public int getTimesContacted() {\n        return timesContacted;\n    }\n\n    public String getPhone() {\n        return phone != null ? phone : \"\";\n    }\n\n    ///////////////////////////////////////////////////////////////////////////////////////////////\n    // Result methods\n    ///////////////////////////////////////////////////////////////////////////////////////////////\n\n    public static int[] getResultLayout() {\n        return RESULT_LAYOUT;\n    }\n\n    @Override\n    public int getResultLayout(int drawFlags) {\n        return Utilities.checkFlag(drawFlags, FLAG_DRAW_LIST) ? RESULT_LAYOUT[0] :\n            (Utilities.checkFlag(drawFlags, FLAG_DRAW_GRID) ? RESULT_LAYOUT[1] :\n                RESULT_LAYOUT[2]);\n    }\n\n    @WorkerThread\n    protected Drawable getIconDrawable(Context ctx) {\n        Drawable drawable = null;\n        if (iconUri != null)\n            try (InputStream inputStream = ctx.getContentResolver().openInputStream(iconUri)) {\n                drawable = Drawable.createFromStream(inputStream, iconUri.toString());\n            } catch (IOException ignored) {\n            }\n        if (drawable == null) {\n            drawable = AppCompatResources.getDrawable(ctx, R.drawable.ic_contact_placeholder);\n            if (drawable == null)\n                drawable = new ColorDrawable(UIColors.getDefaultColor(ctx));\n        }\n        return TBApplication.iconsHandler(ctx).applyContactMask(ctx, drawable);\n    }\n\n    @Override\n    public void displayResult(@NonNull View view, int drawFlags) {\n        if (Utilities.checkFlag(drawFlags, FLAG_DRAW_LIST)) {\n            displayListResult(view, drawFlags);\n            ResultViewHelper.applyListRowPreferences((ViewGroup) view);\n        } else {\n            displayGridResult(view, drawFlags);\n        }\n    }\n\n    private void displayGridResult(@NonNull View view, int drawFlags) {\n        final Context context = view.getContext();\n        // Contact name\n        TextView nameView = view.findViewById(android.R.id.text1);\n        nameView.setTextColor(UIColors.getResultTextColor(context));\n        if (Utilities.checkFlag(drawFlags, FLAG_DRAW_NAME)) {\n            ResultViewHelper.displayHighlighted(relevance, normalizedName, getName(), nameView);\n            nameView.setVisibility(View.VISIBLE);\n        } else {\n            nameView.setText(getName());\n            nameView.setVisibility(View.GONE);\n        }\n\n        // Contact photo\n        ImageView contactIcon = view.findViewById(android.R.id.icon);\n        if (Utilities.checkFlag(drawFlags, FLAG_DRAW_ICON)) {\n            if (PrefCache.modulateContactIcons(context))\n                ResultViewHelper.setIconColorFilter(contactIcon, drawFlags);\n            else\n                ResultViewHelper.removeIconColorFilter(contactIcon);\n            contactIcon.setVisibility(View.VISIBLE);\n            ResultViewHelper.setIconAsync(drawFlags, this, contactIcon, SetContactIconAsync.class, ContactEntry.class);\n        } else {\n            contactIcon.setImageDrawable(null);\n            contactIcon.setVisibility(View.GONE);\n        }\n\n        ResultViewHelper.applyPreferences(drawFlags, nameView, contactIcon);\n    }\n\n    private void displayListResult(@NonNull View view, int drawFlags) {\n        final Context context = view.getContext();\n        // Contact name\n        TextView contactName = view.findViewById(R.id.item_contact_name);\n        contactName.setTextColor(UIColors.getResultTextColor(context));\n        ResultViewHelper.displayHighlighted(relevance, normalizedName, getName(), contactName);\n\n        // Contact phone\n        TextView contactPhone = view.findViewById(R.id.item_contact_phone);\n        if (phone != null) {\n            contactPhone.setVisibility(View.VISIBLE);\n            contactPhone.setTextColor(UIColors.getResultText2Color(context));\n            ResultViewHelper.displayHighlighted(relevance, normalizedPhone, phone, contactPhone);\n            ResultViewHelper.applyResultItemShadow(contactPhone);\n        } else if (getImData() != null && getImData().label != null) {\n            contactPhone.setVisibility(View.VISIBLE);\n            contactPhone.setTextColor(UIColors.getResultText2Color(context));\n            contactPhone.setText(getImData().label);\n            ResultViewHelper.applyResultItemShadow(contactPhone);\n        } else {\n            contactPhone.setVisibility(View.GONE);\n        }\n\n        displayNickname(view);\n\n        // Contact photo\n        ImageView contactIcon = view.findViewById(android.R.id.icon);\n        if (Utilities.checkFlag(drawFlags, FLAG_DRAW_ICON)) {\n            if (PrefCache.modulateContactIcons(context))\n                ResultViewHelper.setIconColorFilter(contactIcon, drawFlags);\n            else\n                ResultViewHelper.removeIconColorFilter(contactIcon);\n            contactIcon.setVisibility(View.VISIBLE);\n            ResultViewHelper.setIconAsync(drawFlags, this, contactIcon, SetContactIconAsync.class, ContactEntry.class);\n        } else {\n            contactIcon.setImageDrawable(null);\n            contactIcon.setVisibility(View.GONE);\n        }\n\n        final PackageManager pm = context.getPackageManager();\n        boolean hasPhone = phone != null && pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY);\n        displayActions(view, hasPhone);\n\n        // App icon\n        {\n            final ImageView appIcon = view.findViewById(android.R.id.icon2);\n            if (getImData() != null) {\n                appIcon.setVisibility(View.VISIBLE);\n                // bypass cache or else the app icon is cached as the contact icon\n                ResultViewHelper.setIconAsync(drawFlags | FLAG_RELOAD | FLAG_DRAW_NO_CACHE, this, appIcon, SetAppIconAsync.class, ContactEntry.class);\n            } else {\n                appIcon.setVisibility(View.GONE);\n            }\n        }\n\n        ResultViewHelper.applyPreferences(drawFlags, contactName, contactPhone, contactIcon);\n    }\n\n    private void displayNickname(View root) {\n        Context context = root.getContext();\n        // Contact nickname\n        TextView contactNickname = root.findViewById(R.id.item_contact_nickname);\n        contactNickname.setTextColor(UIColors.getResultTextColor(context));\n        if (TextUtils.isEmpty(nickname)) {\n            contactNickname.setVisibility(View.GONE);\n        } else {\n            contactNickname.setVisibility(View.VISIBLE);\n            ResultViewHelper.displayHighlighted(relevance, normalizedNickname, nickname, contactNickname);\n            ResultViewHelper.applyResultItemShadow(contactNickname);\n        }\n    }\n\n    private void displayActions(View root, boolean hasPhone) {\n        Context context = root.getContext();\n        final int contactActionColor = UIColors.getContactActionColor(context);\n        // Phone action\n        {\n            ImageButton phoneButton = root.findViewById(R.id.item_contact_action_phone);\n            if (hasPhone) {\n                phoneButton.setVisibility(View.VISIBLE);\n                phoneButton.clearColorFilter();\n                ResultViewHelper.setButtonIconAsync(phoneButton, BTN_ID_PHONE, ctx -> {\n                    Drawable drawable = ResourcesCompat.getDrawable(ctx.getResources(), R.drawable.ic_phone, null);\n                    Utilities.setColorFilterMultiply(drawable, contactActionColor);\n                    return drawable;\n                });\n                phoneButton.setOnClickListener(v -> {\n                    ResultHelper.recordLaunch(this, context);\n                    ResultHelper.launchCall(v.getContext(), v, phone);\n                });\n                phoneButton.setOnLongClickListener(v -> showButtonPopup(v, BTN_ID_PHONE, R.drawable.ic_phone));\n            } else {\n                phoneButton.setVisibility(View.GONE);\n            }\n        }\n\n        // Message action\n        {\n            ImageButton messageButton = root.findViewById(R.id.item_contact_action_message);\n            if (hasPhone && !isHomeNumber()) {\n                messageButton.setVisibility(View.VISIBLE);\n                messageButton.clearColorFilter();\n                ResultViewHelper.setButtonIconAsync(messageButton, BTN_ID_MESSAGE, ctx -> {\n                    Drawable drawable = ResourcesCompat.getDrawable(ctx.getResources(), R.drawable.ic_message, null);\n                    Utilities.setColorFilterMultiply(drawable, contactActionColor);\n                    return drawable;\n                });\n                messageButton.setOnClickListener(v -> {\n                    ResultHelper.recordLaunch(this, context);\n                    ResultHelper.launchMessaging(this, v);\n                });\n                messageButton.setOnLongClickListener(v -> showButtonPopup(v, BTN_ID_MESSAGE, R.drawable.ic_message));\n            } else {\n                messageButton.setVisibility(View.GONE);\n            }\n        }\n\n        // Open action\n        {\n            ImageButton openButton = root.findViewById(R.id.item_contact_action_open);\n            if (getImData() != null) {\n                openButton.setVisibility(View.VISIBLE);\n                openButton.clearColorFilter();\n                ResultViewHelper.setButtonIconAsync(openButton, BTN_ID_OPEN, ctx -> {\n                    Drawable drawable = ResourcesCompat.getDrawable(ctx.getResources(), R.drawable.ic_send, null);\n                    Utilities.setColorFilterMultiply(drawable, contactActionColor);\n                    return drawable;\n                });\n                openButton.setOnClickListener(v -> {\n                    ResultHelper.recordLaunch(this, context);\n                    ResultHelper.launchIm(getImData(), v);\n                });\n                openButton.setOnLongClickListener(v -> showButtonPopup(v, BTN_ID_OPEN, R.drawable.ic_send));\n            } else {\n                openButton.setVisibility(View.GONE);\n            }\n        }\n    }\n\n    @Override\n    public void doLaunch(@NonNull View v, int flags) {\n        Context context = v.getContext();\n        ResultHelper.recordLaunch(this, context);\n\n        SharedPreferences settingPrefs = PreferenceManager.getDefaultSharedPreferences(v.getContext());\n        String btnAction = settingPrefs.getString(\"default-contact-action\", \"\");\n\n        switch (btnAction) {\n            case BTN_ACTION_PHONE:\n                if (phone != null)\n                    ResultHelper.launchCall(context, v, phone);\n                else if (getImData() != null)\n                    ResultHelper.launchIm(getImData(), v);\n                break;\n            case BTN_ACTION_MESSAGE:\n                ResultHelper.launchMessaging(this, v);\n                break;\n            case BTN_ACTION_OPEN:\n                if (getImData() != null)\n                    ResultHelper.launchIm(getImData(), v);\n                else if (phone != null)\n                    ResultHelper.launchCall(context, v, phone);\n                break;\n            default:\n                ResultHelper.launchContactView(this, context, v);\n                break;\n        }\n    }\n\n    @NonNull\n    private static String getButtonIdFromAction(@NonNull String btnPref) {\n        switch (btnPref) {\n            case BTN_ACTION_PHONE:\n                return BTN_ID_PHONE;\n            case BTN_ACTION_MESSAGE:\n                return BTN_ID_MESSAGE;\n            case BTN_ACTION_OPEN:\n                return BTN_ACTION_OPEN;\n            default:\n                return \"\";\n        }\n    }\n\n    private static boolean showButtonPopup(@NonNull View view, @NonNull String buttonId, @DrawableRes int defaultButtonIcon) {\n        final Context context = view.getContext();\n        ListPopup buttonMenu = getButtonPopup(context, buttonId, defaultButtonIcon);\n        return ButtonHelper.showButtonPopup(view, buttonMenu);\n    }\n\n    @NonNull\n    public static ListPopup getButtonPopup(Context ctx, @NonNull String buttonId, @DrawableRes int defaultButtonIcon) {\n        String btnAction = PreferenceManager.getDefaultSharedPreferences(ctx).getString(\"default-contact-action\", \"\");\n        String defaultBtnId = getButtonIdFromAction(btnAction);\n\n        LinearAdapter adapter = new LinearAdapter();\n        adapter.add(new LinearAdapter.Item(ctx, R.string.menu_custom_icon));\n        if (!defaultBtnId.equals(buttonId))\n            adapter.add(new LinearAdapter.Item(ctx, R.string.contact_button_set_default));\n        else\n            adapter.add(new LinearAdapter.Item(ctx, R.string.contact_button_reset_default));\n\n        return ListPopup.create(ctx, adapter).setOnItemClickListener((a, view, pos) -> {\n            LinearAdapter.MenuItem menuItem = ((LinearAdapter) a).getItem(pos);\n            @StringRes int id = 0;\n            if (menuItem instanceof LinearAdapter.Item) {\n                id = ((LinearAdapter.Item) a.getItem(pos)).stringId;\n            }\n            if (id == R.string.menu_custom_icon) {\n                TBApplication.behaviour(ctx).launchCustomIconDialog(buttonId, defaultButtonIcon, () -> {\n                    // force a result refresh to update the icons from all contact buttons\n                    var activity = TBApplication.launcherActivity(ctx);\n                    if (activity != null)\n                        activity.refreshSearchRecords();\n\n                });\n            } else if (id == R.string.contact_button_set_default) {\n                SharedPreferences settingPrefs = PreferenceManager.getDefaultSharedPreferences(ctx);\n                var editor = settingPrefs.edit();\n                switch (buttonId) {\n                    case BTN_ID_PHONE:\n                        editor.putString(\"default-contact-action\", BTN_ACTION_PHONE).apply();\n                        //editor.putBoolean(\"call-contact-on-click\", true).apply();\n                        break;\n                    case BTN_ID_MESSAGE:\n                        editor.putString(\"default-contact-action\", BTN_ACTION_MESSAGE).apply();\n                        break;\n                    case BTN_ID_OPEN:\n                        editor.putString(\"default-contact-action\", BTN_ACTION_OPEN).apply();\n                        break;\n                    default:\n                        editor.putString(\"default-contact-action\", \"\").apply();\n                        break;\n                }\n            } else if (id == R.string.contact_button_reset_default) {\n                PreferenceManager.getDefaultSharedPreferences(ctx)\n                    .edit()\n                    .putString(\"default-contact-action\", \"\")\n                    .apply();\n            }\n        });\n    }\n\n    public static class SetContactIconAsync extends AsyncSetEntryDrawable<ContactEntry> {\n        public SetContactIconAsync(@NonNull ImageView image, int drawFlags, @NonNull ContactEntry contactEntry) {\n            super(image, drawFlags, contactEntry);\n        }\n\n        @Override\n        protected Drawable getDrawable(Context ctx) {\n            return entryItem.getIconDrawable(ctx);\n        }\n    }\n\n    public static class SetAppIconAsync extends AsyncSetEntryDrawable<ContactEntry> {\n        public SetAppIconAsync(@NonNull ImageView image, int drawFlags, @NonNull ContactEntry contactEntry) {\n            super(image, drawFlags, contactEntry);\n        }\n\n        @Override\n        protected Drawable getDrawable(Context context) {\n            IconsHandler iconsHandler = TBApplication.iconsHandler(context);\n            ImData imData = entryItem.getImData();\n            Drawable appDrawable;\n            ComponentName componentName = TBApplication.mimeTypeCache(context).getComponentName(context, imData.getMimeType());\n            if (componentName != null) {\n                appDrawable = iconsHandler.getDrawableIconForPackage(PackageManagerUtils.getLaunchingComponent(context, componentName), UserHandleCompat.CURRENT_USER);\n            } else {\n                // This should never happen, let's just return the generic activity icon\n                appDrawable = context.getPackageManager().getDefaultActivityIcon();\n            }\n            return appDrawable;\n        }\n    }\n\n    // TODO: move to separate class, which package?\n    public static class ImData {\n        private final long id;\n        private final String mimeType;\n        private final String label;\n\n        private String identifier;\n\n        public ImData(String mimeType, long id, String label) {\n            this.mimeType = mimeType;\n            this.id = id;\n            this.label = label;\n        }\n\n        public String getIdentifier() {\n            return identifier;\n        }\n\n        public void setIdentifier(String identifier) {\n            this.identifier = identifier;\n        }\n\n        public String getMimeType() {\n            return mimeType;\n        }\n\n        public long getId() {\n            return id;\n        }\n    }\n\n    public static class Builder {\n        private String name = null;\n        private String phone = null;\n        private String nickname = null;\n        private Uri iconUri = null;\n        private ImData imData = null;\n        private String shortMimeType = null;\n        private String lookupKey = null;\n        private boolean primary = false;\n        private boolean starred = false;\n\n        private long contactId = 0;\n        private long contentId = 0;\n\n        public Builder setContactId(long contactId) {\n            this.contactId = contactId;\n            return this;\n        }\n\n        public Builder setPhone(String phone) {\n            this.phone = phone;\n            return this;\n        }\n\n        public Builder setMimeInfo(long contentId, @NonNull String shortMimeType) {\n            this.contentId = contentId;\n            this.shortMimeType = shortMimeType;\n            return this;\n        }\n\n        public Builder setIconUri(Uri iconUri) {\n            this.iconUri = iconUri;\n            return this;\n        }\n\n        public Builder setPrimary(boolean primary) {\n            this.primary = primary;\n            return this;\n        }\n\n        public Builder setStarred(boolean starred) {\n            this.starred = starred;\n            return this;\n        }\n\n        public Builder setLookupKey(String lookupKey) {\n            this.lookupKey = lookupKey;\n            return this;\n        }\n\n        public Builder setName(@NonNull String name) {\n            this.name = name;\n            return this;\n        }\n\n        public Builder setNickname(@NonNull String nickname) {\n            this.nickname = nickname;\n            return this;\n        }\n\n        public Builder setImData(@NonNull ImData imData) {\n            this.imData = imData;\n            return this;\n        }\n\n        public ContactEntry getContact() {\n            final String entryId;\n            if (shortMimeType != null) {\n                // this is a general contact. No phone number.\n                entryId = SCHEME + contactId + '/' + shortMimeType + '/' + contentId;\n                //entry = new ContactEntry(entryId, lookupKey, icon, primary, 0, starred, false);\n            } else {\n                // phone contact\n                entryId = SCHEME + contactId + '/' + phone;\n                //entry = new ContactEntry(entryId, lookupKey, phone, normalizedPhone, icon, primary, 0, starred, false);\n            }\n\n            ContactEntry entry = new ContactEntry(entryId);\n            entry.lookupKey = lookupKey;\n            if (phone != null) {\n                entry.phone = phone;\n                entry.normalizedPhone = PhoneNormalizer.simplifyPhoneNumber(phone);\n            }\n            if (iconUri != null)\n                entry.iconUri = iconUri;\n            entry.primary = primary;\n            entry.starred = starred;\n            entry.setName(name);\n            entry.setNickname(nickname);\n            if (imData != null)\n                entry.imData = imData;\n\n            return entry;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/entry/DialContactEntry.java",
    "content": "package rocks.tbog.tblauncher.entry;\n\nimport android.content.Context;\nimport android.graphics.drawable.Drawable;\nimport android.view.View;\n\nimport androidx.annotation.NonNull;\n\nimport java.util.List;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.handler.IconsHandler;\nimport rocks.tbog.tblauncher.normalizer.PhoneNormalizer;\nimport rocks.tbog.tblauncher.preference.ContentLoadHelper;\nimport rocks.tbog.tblauncher.ui.LinearAdapter;\nimport rocks.tbog.tblauncher.ui.ListPopup;\nimport rocks.tbog.tblauncher.utils.PrefCache;\nimport rocks.tbog.tblauncher.utils.Utilities;\n\npublic class DialContactEntry extends ContactEntry implements ICustomIconEntry{\n    public static final String SCHEME = ContactEntry.SCHEME + \"dial/\";\n\n    private int customIcon = 0;\n\n    public DialContactEntry() {\n        super(SCHEME);\n    }\n\n    @Override\n    public String getHistoryId() {\n        // Dial number should not appear in history\n        return \"\";\n    }\n\n    @NonNull\n    @Override\n    public String getIconCacheId() {\n        // use same id for any dialed phone\n        return id + \"/ic\" + customIcon;\n    }\n\n    @Override\n    public void setCustomIcon() {\n        customIcon += 1;\n    }\n\n    @Override\n    public void clearCustomIcon() {\n        customIcon = 0;\n    }\n\n    @Override\n    public boolean hasCustomIcon() {\n        return customIcon > 0;\n    }\n\n    @Override\n    protected Drawable getIconDrawable(Context ctx) {\n        if (hasCustomIcon()) {\n            IconsHandler iconsHandler = TBApplication.getApplication(ctx).iconsHandler();\n            Drawable drawable = iconsHandler.getCustomIcon(this);\n            if (drawable != null)\n                return drawable;\n            else\n                iconsHandler.restoreDefaultIcon(this);\n        }\n        return super.getIconDrawable(ctx);\n    }\n\n    @Override\n    protected ListPopup buildPopupMenu(Context context, LinearAdapter adapter, View parentView, int flags) {\n        List<ContentLoadHelper.CategoryItem> categoryTitle = PrefCache.getResultPopupOrder(context);\n        for (ContentLoadHelper.CategoryItem categoryItem : categoryTitle) {\n            final int titleStringId = categoryItem.textId;\n\n            if (titleStringId == R.string.popup_title_customize) {\n                adapter.add(new LinearAdapter.Item(context, R.string.menu_custom_icon));\n            }\n        }\n\n        if (Utilities.checkFlag(flags, LAUNCHED_FROM_QUICK_LIST)) {\n            adapter.add(new LinearAdapter.ItemTitle(context, R.string.menu_popup_title_settings));\n            adapter.add(new LinearAdapter.Item(context, R.string.menu_popup_quick_list_customize));\n        }\n\n        return inflatePopupMenu(context, adapter);\n    }\n\n    @Override\n    protected boolean popupMenuClickHandler(@NonNull final View view, @NonNull LinearAdapter.MenuItem item, int stringId, View parentView) {\n        if (stringId == R.string.menu_custom_icon) {\n            Context ctx = view.getContext();\n            TBApplication.behaviour(ctx).launchCustomIconDialog(this);\n            return true;\n        }\n        return super.popupMenuClickHandler(view, item, stringId, parentView);\n    }\n\n    public void setPhone(String phone) {\n        if (phone != null) {\n            this.phone = phone;\n            this.normalizedPhone = PhoneNormalizer.simplifyPhoneNumber(phone);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/entry/EntryItem.java",
    "content": "package rocks.tbog.tblauncher.entry;\n\nimport android.content.Context;\nimport android.view.View;\n\nimport androidx.annotation.CallSuper;\nimport androidx.annotation.LayoutRes;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.StringRes;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Objects;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.dataprovider.QuickListProvider;\nimport rocks.tbog.tblauncher.normalizer.StringNormalizer;\nimport rocks.tbog.tblauncher.result.ResultHelper;\nimport rocks.tbog.tblauncher.ui.LinearAdapter;\nimport rocks.tbog.tblauncher.ui.ListPopup;\nimport rocks.tbog.tblauncher.utils.DebugInfo;\nimport rocks.tbog.tblauncher.utils.FuzzyScore;\nimport rocks.tbog.tblauncher.utils.Utilities;\n\npublic abstract class EntryItem {\n\n    public static final RelevanceComparator RELEVANCE_COMPARATOR = new RelevanceComparator();\n    public static final NameComparator NAME_COMPARATOR = new NameComparator();\n\n    /**\n     * the layout will be used in a ListView\n     */\n    public static final int FLAG_DRAW_LIST = 0x0001; // 1 << 0\n\n    /**\n     * the layout will be used in a GridView\n     */\n    public static final int FLAG_DRAW_GRID = 0x0002; // 1 << 1\n\n    /**\n     * the layout will be used in a horizontal LinearLayout\n     */\n    public static final int FLAG_DRAW_QUICK_LIST = 0x0004; // 1 << 2\n\n    /**\n     * layout should display an icon\n     */\n    public static final int FLAG_DRAW_ICON = 0x0008; // 1 << 3\n\n    /**\n     * layout may display a badge (shortcut sub-icon) if appropriate\n     */\n    public static final int FLAG_DRAW_ICON_BADGE = 0x0010; // 1 << 4\n\n    /**\n     * layout should display a text/name\n     */\n    public static final int FLAG_DRAW_NAME = 0x0020; // 1 << 5\n\n    /**\n     * layout should display tags\n     */\n    public static final int FLAG_DRAW_TAGS = 0x0040; // 1 << 6\n\n    /**\n     * do not use cache, generate new drawable\n     */\n    public static final int FLAG_DRAW_NO_CACHE = 0x0080; // 1 << 7\n\n    /**\n     * the item will be drawn on a while background\n     */\n    public static final int FLAG_DRAW_WHITE_BG = 0x0100; // 1 << 8\n\n    /**\n     * use cache but also run the load task\n     * Note: used for shortcuts as we don't have a way to cache multiple icons for the same entry id\n     */\n    public static final int FLAG_RELOAD = 0x0200; // 1 << 9\n\n    // Used when generating Popup menu and calling doLaunch\n    public static final int LAUNCHED_FROM_RESULT_LIST = 0x01;\n    public static final int LAUNCHED_FROM_QUICK_LIST = 0x02;\n    public static final int LAUNCHED_FROM_GESTURE = 0x04;\n\n    // Globally unique ID.\n    // Usually starts with provider scheme, e.g. \"app://\" or \"contact://\" to\n    // ensure unique constraint\n    @NonNull\n    public final String id;\n    // normalized name, for faster search\n    public StringNormalizer.Result normalizedName = null;\n    // Name for this Entry, e.g. app name\n    @NonNull\n    private\n    String name = \"\";\n\n    protected final ResultRelevance relevance = new ResultRelevance();\n\n    public EntryItem(@NonNull String id) {\n        this.id = id;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o)\n            return true;\n        if (!(o instanceof EntryItem))\n            return false;\n        EntryItem entryItem = (EntryItem) o;\n        return id.equals(entryItem.id);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(id);\n    }\n\n    @NonNull\n    public String getName() {\n        return name;\n    }\n\n    /**\n     * Set the user-displayable name of this container\n     * <p/>\n     * When this method a searchable version of the name will be generated for the name and stored\n     * as `nameNormalized`. Additionally a mapping from the positions in the searchable name\n     * to the positions in the displayable name will be stored (as `namePositionMap`).\n     *\n     * @param name User-friendly name of this container\n     */\n    public void setName(String name) {\n        if (name != null) {\n            // Set the actual user-friendly name\n            this.name = name;\n            this.normalizedName = StringNormalizer.normalizeWithResult(this.name, false);\n        } else {\n            this.name = \"null\";\n            this.normalizedName = null;\n        }\n    }\n\n    public void setName(String name, boolean generateNormalization) {\n        if (generateNormalization) {\n            setName(name);\n        } else {\n            this.name = name;\n            this.normalizedName = null;\n        }\n    }\n\n    public int getRelevance() {\n        return relevance.getRelevance();\n    }\n\n    public void addResultMatch(@NonNull StringNormalizer.Result normalizedName, @Nullable FuzzyScore.MatchInfo matchInfo) {\n        relevance.addMatchInfo(normalizedName, matchInfo);\n    }\n\n    public void setRelevance(@NonNull StringNormalizer.Result normalizedName, @Nullable FuzzyScore.MatchInfo matchInfo) {\n        relevance.setMatchInfo(normalizedName, matchInfo);\n    }\n\n    public void boostRelevance(int boost) {\n        relevance.boostRelevance(boost);\n    }\n\n    public void resetResultInfo() {\n        relevance.resetRelevance();\n    }\n\n    /**\n     * ID to use in the history\n     * (may be different from the one used in the adapter for display)\n     */\n    public String getHistoryId() {\n        return this.id;\n    }\n\n    public boolean isExcludedFromHistory() {\n        return false;\n    }\n\n    ///////////////////////////////////////////////////////////////////////////////////////////////\n    // Result methods\n    ///////////////////////////////////////////////////////////////////////////////////////////////\n\n    @LayoutRes\n    public abstract int getResultLayout(int drawFlags);\n\n    public abstract void displayResult(@NonNull View view, int drawFlags);\n\n    @NonNull\n    public String getIconCacheId() {\n        return id;\n    }\n\n    public static class RelevanceComparator implements java.util.Comparator<EntryItem> {\n        @Override\n        public int compare(EntryItem lhs, EntryItem rhs) {\n            int difference = rhs.relevance.compareTo(lhs.relevance);\n            if (difference != 0)\n                return difference;\n            return rhs.name.compareTo(lhs.name);\n        }\n\n    }\n\n    public static class NameComparator implements java.util.Comparator<EntryItem> {\n        @Override\n        public int compare(EntryItem lhs, EntryItem rhs) {\n            if (lhs.normalizedName != null && rhs.normalizedName != null)\n                return rhs.normalizedName.compareTo(lhs.normalizedName);\n            return rhs.name.compareTo(lhs.name);\n        }\n\n    }\n\n    /**\n     * Default popup menu implementation, can be overridden by children class to display a more specific menu\n     *\n     * @return an inflated, listener-free PopupMenu\n     */\n    protected ListPopup buildPopupMenu(Context context, LinearAdapter adapter, View parentView, int flags) {\n        adapter.add(new LinearAdapter.ItemTitle(context, R.string.popup_title_hist_fav));\n        adapter.add(new LinearAdapter.Item(context, R.string.menu_remove_history));\n        adapter.add(new LinearAdapter.Item(context, R.string.menu_quick_list_add));\n        if (Utilities.checkFlag(flags, LAUNCHED_FROM_QUICK_LIST)) {\n            adapter.add(new LinearAdapter.ItemTitle(context, R.string.menu_popup_title_settings));\n            adapter.add(new LinearAdapter.Item(context, R.string.menu_popup_quick_list_customize));\n        }\n        return inflatePopupMenu(context, adapter);\n    }\n\n    ListPopup inflatePopupMenu(@NonNull Context context, @NonNull LinearAdapter adapter) {\n        ListPopup menu = ListPopup.create(context, adapter);\n\n//        boolean foundInQuickList = false;\n//        ArrayList<ModRecord> favRecords = TBApplication.dataHandler(context).getFavorites();\n//        for (ModRecord fav : favRecords) {\n//            if (id.equals(fav.record) && fav.isInQuickList()) {\n//                foundInQuickList = true;\n//                break;\n//            }\n//        }\n        QuickListProvider provider = TBApplication.dataHandler(context).getQuickListProvider();\n\n        // get current Quick List content\n        List<? extends EntryItem> list = provider != null ? provider.getPojos() : Collections.emptyList();\n        boolean foundInQuickList = list.contains(this);\n\n        if (foundInQuickList) {\n            // if already in quick list, remove the \"Add to QuickList\" option\n            for (int i = 0; i < adapter.getCount(); i += 1) {\n                LinearAdapter.MenuItem item = adapter.getItem(i);\n                if (item instanceof LinearAdapter.Item) {\n                    if (((LinearAdapter.Item) item).stringId == R.string.menu_quick_list_add)\n                        adapter.remove(item);\n                }\n            }\n        } else {\n            // if not in quick list, remove the \"Remove from QuickList\" option\n            for (int i = 0; i < adapter.getCount(); i += 1) {\n                LinearAdapter.MenuItem item = adapter.getItem(i);\n                if (item instanceof LinearAdapter.Item) {\n                    if (((LinearAdapter.Item) item).stringId == R.string.menu_quick_list_remove)\n                        adapter.remove(item);\n                }\n            }\n        }\n\n        if (DebugInfo.itemRelevance(context)) {\n            String debugTitle = context.getString(R.string.popup_title_debug);\n            int pos = -1;\n            // find title\n            for (int i = 0; i < adapter.getCount(); i += 1) {\n                if (debugTitle.equals(adapter.getItem(i).toString())) {\n                    pos = i + 1;\n                    break;\n                }\n            }\n            // if title not found, add title\n            if (pos == -1) {\n                adapter.add(new LinearAdapter.ItemTitle(debugTitle));\n                pos = adapter.getCount();\n            }\n            // add debug data after title\n            adapter.add(pos, new LinearAdapter.ItemString(\"Relevance: \" + getRelevance()));\n        }\n\n        return menu;\n    }\n\n    /**\n     * How to display the popup menu\n     *\n     * @return a PopupMenu object\n     */\n    @NonNull\n    public ListPopup getPopupMenu(final View parentView, int flags) {\n        final Context context = parentView.getContext();\n        LinearAdapter menuAdapter = new LinearAdapter();\n        ListPopup menu = buildPopupMenu(context, menuAdapter, parentView, flags);\n\n        menu.setOnItemClickListener((adapter, view, position) -> {\n            LinearAdapter.MenuItem item = ((LinearAdapter) adapter).getItem(position);\n            @StringRes int stringId = 0;\n            if (item instanceof LinearAdapter.Item) {\n                stringId = ((LinearAdapter.Item) adapter.getItem(position)).stringId;\n            }\n            popupMenuClickHandler(view, item, stringId, parentView);\n        });\n\n        return menu;\n    }\n\n    @NonNull\n    public ListPopup getPopupMenu(final View parentView) {\n        return getPopupMenu(parentView, LAUNCHED_FROM_RESULT_LIST);\n    }\n\n    /**\n     * Handler for popup menu action.\n     * Default implementation only handle remove from history action.\n     *\n     * @return Works in the same way as onOptionsItemSelected, return true if the action has been handled, false otherwise\n     */\n    @CallSuper\n    boolean popupMenuClickHandler(@NonNull View view, @NonNull LinearAdapter.MenuItem item, @StringRes int stringId, View parentView) {\n        Context context = parentView.getContext();\n        if (R.string.menu_remove_history == stringId) {\n            ResultHelper.removeFromResultsAndHistory(this, context);\n            return true;\n        } else if (R.string.menu_quick_list_add == stringId) {\n            ResultHelper.launchAddToQuickList(context, this);\n            return true;\n        } else if (R.string.menu_quick_list_remove == stringId) {\n            ResultHelper.launchRemoveFromQuickList(context, this);\n            return true;\n        } else if (R.string.menu_popup_quick_list_customize == stringId) {\n            TBApplication.behaviour(context).launchEditQuickListDialog(context);\n            return true;\n        }\n\n//        FullscreenActivity mainActivity = (FullscreenActivity) context;\n//        // Update favorite bar\n//        mainActivity.onFavoriteChange();\n//        mainActivity.launchOccurred();\n//        // Update Search to reflect favorite add, if the \"exclude favorites\" option is active\n//        if (mainActivity.prefs.getBoolean(\"exclude-favorites\", false) && mainActivity.isViewingSearchResults()) {\n//            mainActivity.updateSearchRecords(true);\n//        }\n\n        return false;\n    }\n\n    public void doLaunch(@NonNull View view, int flags) {\n        throw new IllegalStateException(\"No launch action defined for \" + getClass().getSimpleName());\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/entry/EntryWithTags.java",
    "content": "package rocks.tbog.tblauncher.entry;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.collection.ArraySet;\n\nimport java.util.List;\nimport java.util.Objects;\n\nimport rocks.tbog.tblauncher.normalizer.StringNormalizer;\n\npublic abstract class EntryWithTags extends EntryItem {\n    // Tags assigned to this pojo\n    private final ArraySet<TagDetails> tags = new ArraySet<>(0);\n\n    public boolean isHiddenByUser() {\n        return false;\n    }\n\n    public static class TagDetails {\n        @NonNull\n        public final String name;\n        public final StringNormalizer.Result normalized;\n\n        @Override\n        public boolean equals(Object o) {\n            if (this == o)\n                return true;\n            if (o == null || getClass() != o.getClass())\n                return false;\n            TagDetails that = (TagDetails) o;\n            return name.equals(that.name);\n        }\n\n        @Override\n        public int hashCode() {\n            return Objects.hash(name);\n        }\n\n        public TagDetails(@NonNull String name) {\n            this(name, StringNormalizer.normalizeWithResult(name, true));\n        }\n\n        public TagDetails(@NonNull String name, StringNormalizer.Result normalized) {\n            this.name = name;\n            this.normalized = normalized;\n        }\n    }\n\n    EntryWithTags(@NonNull String id) {\n        super(id);\n    }\n\n    @NonNull\n    public ArraySet<TagDetails> getTags() {\n        return tags;\n    }\n\n    public void setTags(@Nullable List<String> tags) {\n        this.tags.clear();\n        if (tags != null) {\n            for (String tag : tags)\n                this.tags.add(new TagDetails(tag));\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/entry/FilterEntry.java",
    "content": "package rocks.tbog.tblauncher.entry;\n\nimport android.view.View;\n\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport rocks.tbog.tblauncher.BuildConfig;\nimport rocks.tbog.tblauncher.R;\n\npublic class FilterEntry extends StaticEntry {\n    public static final String SCHEME = \"filter://\";\n    private View.OnClickListener listener = null;\n    private final String filterScheme;\n\n    public FilterEntry(@NonNull String id, @DrawableRes int icon, String filterScheme) {\n        super(id, icon);\n        if (BuildConfig.DEBUG && !id.startsWith(SCHEME)) {\n            throw new IllegalStateException(\"Invalid \" + FilterEntry.class.getSimpleName() + \" id `\" + id + \"`\");\n        }\n        this.filterScheme = filterScheme;\n    }\n\n    @Override\n    public void displayResult(@NonNull View view, int drawFlags) {\n        super.displayResult(view, drawFlags);\n        // this is used for the toggle animation\n        view.setTag(R.id.tag_actionId, id);\n        view.setTag(R.id.tag_filterText, filterScheme);\n    }\n\n    @Override\n    public void doLaunch(@NonNull View view, int flags) {\n        listener.onClick(view);\n    }\n\n    public void setOnClickListener(@Nullable View.OnClickListener listener) {\n        this.listener = listener;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/entry/ICustomIconEntry.java",
    "content": "package rocks.tbog.tblauncher.entry;\n\npublic interface ICustomIconEntry {\n    void setCustomIcon();\n\n    void clearCustomIcon();\n\n    boolean hasCustomIcon();\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/entry/OpenUrlEntry.java",
    "content": "package rocks.tbog.tblauncher.entry;\n\nimport android.content.ActivityNotFoundException;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.net.Uri;\nimport android.util.Log;\nimport android.view.View;\n\nimport androidx.annotation.NonNull;\n\nimport rocks.tbog.tblauncher.R;\n\npublic final class OpenUrlEntry extends UrlEntry {\n    public static final String SCHEME = \"url://\";\n\n    public OpenUrlEntry(String query, String url) {\n        super(SCHEME + url, url);\n        this.query = query;\n    }\n\n    ///////////////////////////////////////////////////////////////////////////////////////////////\n    // Result methods\n    ///////////////////////////////////////////////////////////////////////////////////////////////\n    @Override\n    protected String getResultText(Context context) {\n        return String.format(context.getString(R.string.ui_item_visit), getName());\n    }\n\n    @Override\n    public void doLaunch(@NonNull View v, int flags) {\n        Context context = v.getContext();\n        Uri uri = Uri.parse(url);\n        Intent search = new Intent(Intent.ACTION_VIEW, uri);\n        search.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n        try {\n            context.startActivity(search);\n        } catch (ActivityNotFoundException e) {\n            Log.w(\"SearchResult\", \"Unable to run search for url: \" + url);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/entry/PlaceholderEntry.java",
    "content": "package rocks.tbog.tblauncher.entry;\n\nimport android.content.Context;\nimport android.graphics.drawable.Drawable;\nimport android.view.View;\n\nimport androidx.annotation.NonNull;\nimport androidx.appcompat.content.res.AppCompatResources;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.ui.LinearAdapter;\nimport rocks.tbog.tblauncher.ui.ListPopup;\nimport rocks.tbog.tblauncher.utils.PrefCache;\nimport rocks.tbog.tblauncher.utils.Utilities;\n\npublic class PlaceholderEntry extends StaticEntry {\n    public final String position;\n\n    public PlaceholderEntry(@NonNull String id, String position) {\n        super(id, R.drawable.ic_loading_arrows);\n        this.position = position;\n    }\n\n    @Override\n    protected ListPopup buildPopupMenu(Context context, LinearAdapter adapter, View parentView, int flags) {\n        if (Utilities.checkFlag(flags, LAUNCHED_FROM_QUICK_LIST)) {\n            adapter.add(new LinearAdapter.ItemTitle(context, R.string.menu_popup_title_settings));\n            adapter.add(new LinearAdapter.Item(context, R.string.menu_popup_quick_list_customize));\n        }\n        return inflatePopupMenu(context, adapter);\n    }\n\n    @Override\n    public Drawable getDefaultDrawable(Context context) {\n        int loadingIconRes = PrefCache.getLoadingIconRes(context);\n        return AppCompatResources.getDrawable(context, loadingIconRes);\n    }\n\n    @Override\n    public void doLaunch(@NonNull View view, int flags) {\n        // do nothing\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/entry/ResultRelevance.java",
    "content": "package rocks.tbog.tblauncher.entry;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.function.Consumer;\n\nimport rocks.tbog.tblauncher.normalizer.StringNormalizer;\nimport rocks.tbog.tblauncher.utils.FuzzyScore;\n\npublic class ResultRelevance implements Comparable<ResultRelevance> {\n\n    private final List<ResultInfo> infoList = Collections.synchronizedList(new ArrayList<>(2));\n    private int scoreBoost = 0;\n\n    public int getRelevance() {\n        synchronized (infoList) {\n            int score = scoreBoost;\n            for (ResultInfo info : infoList)\n                score += info.relevance.score;\n            return score;\n        }\n    }\n\n    public void addMatchInfo(@NonNull StringNormalizer.Result matchedText, @Nullable FuzzyScore.MatchInfo matchInfo) {\n        final ResultInfo resultInfo;\n        if (matchInfo == null)\n            resultInfo = new ResultInfo(matchedText, new FuzzyScore.MatchInfo());\n        else\n            resultInfo = new ResultInfo(matchedText, new FuzzyScore.MatchInfo(matchInfo));\n        infoList.add(resultInfo);\n    }\n\n    public void setMatchInfo(@NonNull StringNormalizer.Result normalizedName, @Nullable FuzzyScore.MatchInfo matchInfo) {\n        resetRelevance();\n        addMatchInfo(normalizedName, matchInfo);\n    }\n\n    public void boostRelevance(int boost) {\n        scoreBoost += boost;\n    }\n\n    public void resetRelevance() {\n        infoList.clear();\n        scoreBoost = 0;\n    }\n\n    public void forEach(Consumer<ResultInfo> action) {\n        synchronized (infoList) {\n            infoList.forEach(action);\n        }\n    }\n\n    @Override\n    public int compareTo(ResultRelevance o) {\n        synchronized (infoList) {\n            int difference = getRelevance() - o.getRelevance();\n            if (difference == 0) {\n                difference = scoreBoost - o.scoreBoost;\n                if (difference == 0) {\n                    StringNormalizer.Result rSource = infoList.size() > 0 ? infoList.get(0).relevanceSource : null;\n                    StringNormalizer.Result o_rSource = o.infoList.size() > 0 ? o.infoList.get(0).relevanceSource : null;\n                    if (rSource != null && o_rSource != null)\n                        return rSource.compareTo(o_rSource);\n                }\n            }\n            return difference;\n        }\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (!(o instanceof ResultRelevance))\n            return false;\n        return compareTo((ResultRelevance) o) == 0;\n    }\n\n    public static class ResultInfo {\n\n        // How relevant is this record? The higher, the most probable it will be displayed\n        @NonNull\n        public final FuzzyScore.MatchInfo relevance;\n        // Pointer to the normalizedName that the above relevance was calculated, used for highlighting\n        @NonNull\n        public final StringNormalizer.Result relevanceSource;\n\n        private ResultInfo(@NonNull StringNormalizer.Result relevanceSource, @NonNull FuzzyScore.MatchInfo relevance) {\n            this.relevance = relevance;\n            this.relevanceSource = relevanceSource;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/entry/SearchEngineEntry.java",
    "content": "package rocks.tbog.tblauncher.entry;\n\nimport android.app.SearchManager;\nimport android.content.ActivityNotFoundException;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.net.Uri;\nimport android.util.Log;\nimport android.view.View;\n\nimport androidx.annotation.NonNull;\nimport androidx.preference.PreferenceManager;\n\nimport java.io.UnsupportedEncodingException;\nimport java.net.URLEncoder;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.ui.LinearAdapter;\n\npublic final class SearchEngineEntry extends UrlEntry {\n    public static final String SCHEME = \"search-engine://\";\n\n    public SearchEngineEntry(String engineName, String engineUrl) {\n        super(SCHEME + engineName, engineUrl);\n        setName(engineName, false);\n    }\n\n    ///////////////////////////////////////////////////////////////////////////////////////////////\n    // Result methods\n    ///////////////////////////////////////////////////////////////////////////////////////////////\n    @Override\n    protected String getResultText(Context context) {\n        return String.format(context.getString(R.string.ui_item_search), getName(), query);\n    }\n\n    @Override\n    protected void buildPopupMenuCategory(Context context, @NonNull LinearAdapter adapter, int titleStringId) {\n        if (titleStringId == R.string.popup_title_hist_fav) {\n            String defaultSearchProvider = PreferenceManager\n                .getDefaultSharedPreferences(context)\n                .getString(\"default-search-provider\", \"Google\");\n            if (!defaultSearchProvider.equals(getName()))\n                adapter.add(new LinearAdapter.Item(context, R.string.search_engine_set_default));\n        }\n        super.buildPopupMenuCategory(context, adapter, titleStringId);\n    }\n\n    @Override\n    protected boolean popupMenuClickHandler(@NonNull View view, @NonNull LinearAdapter.MenuItem item, int stringId, View parentView) {\n        if (stringId == R.string.search_engine_set_default) {\n            PreferenceManager\n                .getDefaultSharedPreferences(view.getContext())\n                .edit()\n                .putString(\"default-search-provider\", getName())\n                .apply();\n            return true;\n        }\n        return super.popupMenuClickHandler(view, item, stringId, parentView);\n    }\n\n    @Override\n    public void doLaunch(@NonNull View v, int flags) {\n        Context context = v.getContext();\n        if (isGoogleSearch(url)) {\n            try {\n                Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);\n                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n                intent.putExtra(SearchManager.QUERY, query); // query contains search string\n                context.startActivity(intent);\n                return;\n            } catch (ActivityNotFoundException e) {\n                // Google app not found, fall back to default method\n            }\n        }\n        String encodedQuery;\n        try {\n            encodedQuery = URLEncoder.encode(query, \"UTF-8\");\n        } catch (UnsupportedEncodingException e) {\n            encodedQuery = URLEncoder.encode(query);\n        }\n        String urlWithQuery = url.replaceAll(\"%s|\\\\{q\\\\}\", encodedQuery);\n        Uri uri = Uri.parse(urlWithQuery);\n        Intent search = new Intent(Intent.ACTION_VIEW, uri);\n        search.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n        try {\n            context.startActivity(search);\n        } catch (ActivityNotFoundException e) {\n            Log.w(\"SearchResult\", \"Unable to run search for url: \" + url);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/entry/SearchEntry.java",
    "content": "package rocks.tbog.tblauncher.entry;\n\nimport android.content.Context;\nimport android.graphics.drawable.Drawable;\nimport android.view.View;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.WorkerThread;\nimport androidx.appcompat.content.res.AppCompatResources;\n\nimport java.util.List;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.handler.IconsHandler;\nimport rocks.tbog.tblauncher.preference.ContentLoadHelper;\nimport rocks.tbog.tblauncher.ui.LinearAdapter;\nimport rocks.tbog.tblauncher.ui.ListPopup;\nimport rocks.tbog.tblauncher.utils.PrefCache;\nimport rocks.tbog.tblauncher.utils.Utilities;\n\npublic abstract class SearchEntry extends EntryItem implements ICustomIconEntry {\n\n    private static final int[] RESULT_LAYOUT = {R.layout.item_builtin, R.layout.item_grid, R.layout.item_dock};\n\n    protected String query;\n    private int customIcon;\n\n    public SearchEntry(String id) {\n        super(id);\n    }\n\n    public void setQuery(@NonNull String query) {\n        this.query = query;\n    }\n\n    @Override\n    public String getHistoryId() {\n        // Search POJO should not appear in history\n        return \"\";\n    }\n\n    @NonNull\n    @Override\n    public String getIconCacheId() {\n        return id + \"/ic\" + customIcon;\n    }\n\n    @Override\n    public void setCustomIcon() {\n        customIcon += 1;\n    }\n\n    @Override\n    public void clearCustomIcon() {\n        customIcon = 0;\n    }\n\n    @Override\n    public boolean hasCustomIcon() {\n        return customIcon > 0;\n    }\n\n    ///////////////////////////////////////////////////////////////////////////////////////////////\n    // Result methods\n    ///////////////////////////////////////////////////////////////////////////////////////////////\n\n    @WorkerThread\n    public Drawable getIconDrawable(Context context) {\n        if (hasCustomIcon()) {\n            IconsHandler iconsHandler = TBApplication.getApplication(context).iconsHandler();\n            Drawable drawable = iconsHandler.getCustomIcon(this);\n            if (drawable != null)\n                return drawable;\n            else\n                iconsHandler.restoreDefaultIcon(this);\n        }\n        return getDefaultDrawable(context);\n    }\n\n    public Drawable getDefaultDrawable(Context context) {\n        return AppCompatResources.getDrawable(context, R.drawable.ic_search);\n    }\n\n    public static int[] getResultLayout() {\n        return RESULT_LAYOUT;\n    }\n\n    @Override\n    public int getResultLayout(int drawFlags) {\n        return Utilities.checkFlag(drawFlags, FLAG_DRAW_LIST) ? RESULT_LAYOUT[0] :\n            (Utilities.checkFlag(drawFlags, FLAG_DRAW_GRID) ? RESULT_LAYOUT[1] :\n                RESULT_LAYOUT[2]);\n    }\n\n    @Override\n    protected ListPopup buildPopupMenu(Context context, LinearAdapter adapter, View parentView, int flags) {\n        List<ContentLoadHelper.CategoryItem> categoryTitle = PrefCache.getResultPopupOrder(context);\n        for (ContentLoadHelper.CategoryItem categoryItem : categoryTitle) {\n            int pos = adapter.getCount();\n            buildPopupMenuCategory(context, adapter, categoryItem.textId);\n            if (pos != adapter.getCount())\n                adapter.add(pos, new LinearAdapter.ItemTitle(context, categoryItem.textId));\n        }\n\n        if (Utilities.checkFlag(flags, LAUNCHED_FROM_QUICK_LIST)) {\n            adapter.add(new LinearAdapter.ItemTitle(context, R.string.menu_popup_title_settings));\n            buildPopupMenuCategory(context, adapter, R.string.menu_popup_title_settings);\n        }\n\n        return inflatePopupMenu(context, adapter);\n    }\n\n    protected void buildPopupMenuCategory(Context context, @NonNull LinearAdapter adapter, int titleStringId) {\n        if (titleStringId == R.string.popup_title_customize) {\n            adapter.add(new LinearAdapter.Item(context, R.string.menu_custom_icon));\n        } else if (titleStringId == R.string.menu_popup_title_settings) {\n            adapter.add(new LinearAdapter.Item(context, R.string.menu_popup_quick_list_customize));\n        }\n    }\n\n    @Override\n    protected boolean popupMenuClickHandler(@NonNull final View view, @NonNull LinearAdapter.MenuItem item, int stringId, View parentView) {\n        if (stringId == R.string.menu_custom_icon) {\n            Context ctx = view.getContext();\n            TBApplication.behaviour(ctx).launchCustomIconDialog(this, null);\n            return true;\n        }\n        return super.popupMenuClickHandler(view, item, stringId, parentView);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/entry/ShortcutEntry.java",
    "content": "package rocks.tbog.tblauncher.entry;\n\nimport android.annotation.TargetApi;\nimport android.content.ActivityNotFoundException;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.pm.LauncherApps;\nimport android.content.pm.PackageManager;\nimport android.content.pm.ResolveInfo;\nimport android.content.pm.ShortcutInfo;\nimport android.graphics.Bitmap;\nimport android.graphics.ColorFilter;\nimport android.graphics.Rect;\nimport android.graphics.drawable.Drawable;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\nimport android.widget.TextView;\nimport android.widget.Toast;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.RequiresApi;\nimport androidx.annotation.WorkerThread;\nimport androidx.appcompat.content.res.AppCompatResources;\n\nimport java.net.URISyntaxException;\nimport java.util.List;\nimport java.util.Locale;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.db.ShortcutRecord;\nimport rocks.tbog.tblauncher.handler.IconsHandler;\nimport rocks.tbog.tblauncher.preference.ContentLoadHelper;\nimport rocks.tbog.tblauncher.result.AsyncSetEntryDrawable;\nimport rocks.tbog.tblauncher.result.ResultViewHelper;\nimport rocks.tbog.tblauncher.shortcut.ShortcutUtil;\nimport rocks.tbog.tblauncher.ui.LinearAdapter;\nimport rocks.tbog.tblauncher.ui.ListPopup;\nimport rocks.tbog.tblauncher.utils.DialogHelper;\nimport rocks.tbog.tblauncher.utils.PrefCache;\nimport rocks.tbog.tblauncher.utils.UIColors;\nimport rocks.tbog.tblauncher.utils.UserHandleCompat;\nimport rocks.tbog.tblauncher.utils.Utilities;\n\n\npublic final class ShortcutEntry extends EntryWithTags {\n\n    public static final String SCHEME = \"shortcut://\";\n    private static final String TAG = \"shortcut\";\n    private static final int[] RESULT_LAYOUT = {R.layout.item_shortcut, R.layout.item_grid_shortcut, R.layout.item_dock_shortcut};\n    @NonNull\n    public final String packageName;\n    @NonNull\n    public final String shortcutData;\n    @Nullable\n    public final ShortcutInfo mShortcutInfo;\n    private final long dbId;\n    protected int customIcon = 0;\n\n    public ShortcutEntry(@NonNull String id, long dbId, @NonNull String packageName, @NonNull String shortcutData) {\n        super(id);\n\n        this.dbId = dbId;\n        this.packageName = packageName;\n        this.shortcutData = shortcutData;\n        mShortcutInfo = null;\n    }\n\n    @RequiresApi(api = Build.VERSION_CODES.N_MR1)\n    public ShortcutEntry(long dbId, @NonNull ShortcutInfo shortcutInfo) {\n        super(ShortcutEntry.SCHEME + shortcutInfo.getId());\n\n        this.dbId = dbId;\n        packageName = shortcutInfo.getPackage();\n        shortcutData = shortcutInfo.getId();\n        mShortcutInfo = shortcutInfo;\n    }\n\n    /**\n     * @return shortcut id generated from ShortcutRecord\n     */\n    public static String generateShortcutId(@NonNull ShortcutRecord rec) {\n        return SCHEME + rec.dbId + \"/\" + rec.packageName.toLowerCase(Locale.ROOT);\n    }\n\n    public static int[] getResultLayout() {\n        return RESULT_LAYOUT;\n    }\n\n    public static void doShortcutLaunch(@NonNull Context context, @NonNull View view, @NonNull String shortcutData) {\n        View potentialIcon = view.findViewById(android.R.id.icon1);\n        Bundle startActivityOptions = Utilities.makeStartActivityOptions(potentialIcon);\n\n        // Non-oreo shortcuts\n        try {\n            Intent intent = Intent.parseUri(shortcutData, Intent.URI_INTENT_SCHEME);\n            Utilities.setIntentSourceBounds(intent, potentialIcon);\n\n            context.startActivity(intent, startActivityOptions);\n        } catch (Exception e) {\n            // Application was just removed?\n            Toast.makeText(context, context.getString(R.string.entry_not_found, shortcutData), Toast.LENGTH_LONG).show();\n        }\n    }\n\n    @TargetApi(Build.VERSION_CODES.O)\n    public static void doOreoLaunch(@NonNull Context context, @NonNull View v, @Nullable ShortcutInfo shortcutInfo) {\n        final LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);\n        assert launcherApps != null;\n\n        // Only the default launcher is allowed to start shortcuts\n        if (!launcherApps.hasShortcutHostPermission()) {\n            Toast.makeText(context, context.getString(R.string.shortcuts_no_host_permission), Toast.LENGTH_LONG).show();\n            return;\n        }\n\n        View potentialIcon = v.findViewById(android.R.id.icon);\n        Bundle startActivityOptions = Utilities.makeStartActivityOptions(potentialIcon);\n        Rect sourceBounds = Utilities.getOnScreenRect(potentialIcon);\n\n        if (shortcutInfo != null) {\n            try {\n                launcherApps.startShortcut(shortcutInfo, sourceBounds, startActivityOptions);\n                return;\n            } catch (ActivityNotFoundException e) {\n                Log.e(TAG, \"startShortcut\", e);\n            }\n        }\n\n        // Application removed? Invalid shortcut? Shortcut to an app on an unmounted SD card?\n        Toast.makeText(context, context.getString(R.string.application_not_found, shortcutInfo), Toast.LENGTH_LONG).show();\n    }\n\n    @WorkerThread\n    public static Drawable getAppDrawable(@NonNull Context context, @NonNull String shortcutData, @NonNull String packageName, @Nullable ShortcutInfo shortcutInfo, boolean isBadge) {\n        Drawable appDrawable = null;\n        final PackageManager packageManager = context.getPackageManager();\n        List<ResolveInfo> activities = null;\n        if (shortcutInfo == null) {\n            try {\n                Intent intent = Intent.parseUri(shortcutData, 0);\n                activities = packageManager.queryIntentActivities(intent, 0);\n            } catch (URISyntaxException e) {\n                Log.e(\"Shortcut\", \"parse `\" + shortcutData + \"`\", e);\n            }\n        }\n\n        final IconsHandler iconsHandler = TBApplication.iconsHandler(context);\n        if (activities != null && !activities.isEmpty()) {\n            ResolveInfo mainPackage = activities.get(0);\n            String packName = mainPackage.activityInfo.applicationInfo.packageName;\n            String actName = mainPackage.activityInfo.name;\n            ComponentName className = new ComponentName(packName, actName);\n            appDrawable = isBadge\n                ? iconsHandler.getDrawableBadgeForPackage(className, UserHandleCompat.CURRENT_USER)\n                : iconsHandler.getDrawableIconForPackage(className, UserHandleCompat.CURRENT_USER);\n        }\n\n        if (appDrawable == null && shortcutInfo != null) {\n            // Can't make sense of the intent URI (Oreo shortcut, or a shortcut from an activity that was removed from an installed app)\n            // Retrieve app icon\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {\n                UserHandleCompat user = new UserHandleCompat(context, shortcutInfo.getUserHandle());\n                ComponentName componentName = shortcutInfo.getActivity();\n                appDrawable = isBadge\n                    ? iconsHandler.getDrawableBadgeForPackage(componentName, user)\n                    : iconsHandler.getDrawableIconForPackage(componentName, user);\n                if (appDrawable == null)\n                    try {\n                        appDrawable = packageManager.getActivityIcon(componentName);\n                    } catch (PackageManager.NameNotFoundException e) {\n                        Log.e(TAG, \"Unable to find activity icon \" + componentName.toString(), e);\n                    }\n\n            }\n        }\n\n        if (appDrawable == null) {\n            try {\n                appDrawable = packageManager.getApplicationIcon(packageName);\n            } catch (PackageManager.NameNotFoundException e) {\n                Log.e(TAG, \"get app shortcut icon\", e);\n                return null;\n            }\n            appDrawable = iconsHandler.getIconPack().applyBackgroundAndMask(context, appDrawable, true);\n        }\n\n        return appDrawable;\n    }\n\n    ///////////////////////////////////////////////////////////////////////////////////////////////\n    // Result methods\n    ///////////////////////////////////////////////////////////////////////////////////////////////\n\n    public static void setIcons(int drawFlags, @NonNull ImageView icon1, Drawable shortcutDrawable, Drawable appDrawable) {\n        if (Utilities.checkFlag(drawFlags, FLAG_DRAW_ICON_BADGE)) {\n            if (icon1.getParent() instanceof View) {\n                ImageView icon2 = ((View) icon1.getParent()).findViewById(android.R.id.icon2);\n                if (shortcutDrawable != null) {\n                    icon2.setImageDrawable(appDrawable);\n                } else {\n                    // If no icon found for this shortcut, use app icon\n                    icon1.setImageDrawable(appDrawable);\n                    icon2.setImageResource(R.drawable.ic_send);\n                }\n            }\n        } else {\n            if (shortcutDrawable == null) {\n                // If no icon found for this shortcut, use app icon\n                icon1.setImageDrawable(appDrawable);\n            }\n        }\n    }\n\n    @NonNull\n    @Override\n    public String getIconCacheId() {\n        return id + customIcon;\n    }\n\n    /**\n     * Oreo shortcuts do not have a real intentUri, instead they have a shortcut id\n     * and the Android system is responsible for safekeeping the Intent\n     */\n    public boolean isOreoShortcut() {\n        return mShortcutInfo != null;\n    }\n\n    public String getOreoId() {\n        // Oreo shortcuts encode their id in the unused intentUri field\n        return shortcutData;\n    }\n\n    public Drawable getIcon(@NonNull Context context) {\n        if (customIcon > 0) {\n            IconsHandler iconsHandler = TBApplication.getApplication(context).iconsHandler();\n            Drawable drawable = iconsHandler.getCustomIcon(this);\n            if (drawable != null)\n                return drawable;\n            else\n                iconsHandler.restoreDefaultIcon(this);\n        }\n\n        Bitmap bitmap = ShortcutUtil.getInitialIcon(context, dbId);\n        if (bitmap == null)\n            return null;\n\n        return TBApplication.iconsHandler(context).applyShortcutMask(context, bitmap);\n    }\n\n    @Override\n    public int getResultLayout(int drawFlags) {\n        return Utilities.checkFlag(drawFlags, FLAG_DRAW_LIST) ? RESULT_LAYOUT[0] :\n            (Utilities.checkFlag(drawFlags, FLAG_DRAW_GRID) ? RESULT_LAYOUT[1] :\n                RESULT_LAYOUT[2]);\n    }\n\n    @Override\n    public void displayResult(@NonNull View view, int drawFlags) {\n        if (Utilities.checkFlag(drawFlags, FLAG_DRAW_LIST)) {\n            displayListResult(view, drawFlags);\n            ResultViewHelper.applyListRowPreferences((ViewGroup) view);\n        } else {\n            displayGridResult(view, drawFlags);\n        }\n    }\n\n    private void displayGridResult(@NonNull View view, int drawFlags) {\n        final Context context = view.getContext();\n\n        drawFlags |= FLAG_RELOAD;\n        TextView nameView = view.findViewById(android.R.id.text1);\n        nameView.setTextColor(UIColors.getResultTextColor(context));\n        if (Utilities.checkFlag(drawFlags, FLAG_DRAW_NAME)) {\n            ResultViewHelper.displayHighlighted(relevance, normalizedName, getName(), nameView);\n            nameView.setVisibility(View.VISIBLE);\n        } else {\n            nameView.setText(getName());\n            nameView.setVisibility(View.GONE);\n        }\n\n        ImageView icon1 = view.findViewById(android.R.id.icon1);\n        ImageView icon2 = view.findViewById(android.R.id.icon2);\n        if (Utilities.checkFlag(drawFlags, FLAG_DRAW_ICON)) {\n            icon1.setVisibility(View.VISIBLE);\n            icon2.setVisibility(View.VISIBLE);\n            ColorFilter colorFilter = ResultViewHelper.setIconColorFilter(icon1, drawFlags);\n            icon2.setColorFilter(colorFilter);\n            ResultViewHelper.setIconAsync(drawFlags, this, icon1, AsyncSetEntryIcon.class, ShortcutEntry.class);\n        } else {\n            icon1.setImageDrawable(null);\n            icon2.setImageDrawable(null);\n            icon1.setVisibility(View.GONE);\n            icon2.setVisibility(View.GONE);\n        }\n\n        ResultViewHelper.applyPreferences(drawFlags, nameView, icon1);\n    }\n\n    private void displayListResult(@NonNull View view, int drawFlags) {\n        drawFlags |= FLAG_RELOAD;\n        Context context = view.getContext();\n\n        TextView shortcutName = view.findViewById(R.id.item_app_name);\n        shortcutName.setTextColor(UIColors.getResultTextColor(context));\n\n        ResultViewHelper.displayHighlighted(relevance, normalizedName, getName(), shortcutName);\n\n        TextView tagsView = view.findViewById(R.id.item_app_tag);\n        tagsView.setTextColor(UIColors.getResultText2Color(context));\n\n        // Hide tags view if tags are empty\n        if (getTags().isEmpty()) {\n            tagsView.setVisibility(View.GONE);\n        } else if (ResultViewHelper.displayHighlighted(relevance, getTags(), tagsView, context)\n            || Utilities.checkFlag(drawFlags, FLAG_DRAW_TAGS)) {\n            tagsView.setVisibility(View.VISIBLE);\n            ResultViewHelper.applyResultItemShadow(tagsView);\n        } else {\n            tagsView.setVisibility(View.GONE);\n        }\n\n        final ImageView shortcutIcon = view.findViewById(android.R.id.icon1);\n        final ImageView appIcon = view.findViewById(android.R.id.icon2);\n\n        if (Utilities.checkFlag(drawFlags, FLAG_DRAW_ICON)) {\n            shortcutIcon.setVisibility(View.VISIBLE);\n            appIcon.setVisibility(View.VISIBLE);\n            ResultViewHelper.setIconAsync(drawFlags, this, shortcutIcon, AsyncSetEntryIcon.class, ShortcutEntry.class);\n            ColorFilter colorFilter = ResultViewHelper.setIconColorFilter(shortcutIcon, drawFlags);\n            appIcon.setColorFilter(colorFilter);\n        } else {\n            shortcutIcon.setImageDrawable(null);\n            appIcon.setImageDrawable(null);\n            shortcutIcon.setVisibility(View.GONE);\n            appIcon.setVisibility(View.GONE);\n        }\n\n        ResultViewHelper.applyPreferences(drawFlags, shortcutName, tagsView, shortcutIcon);\n    }\n\n    @Override\n    public void doLaunch(@NonNull View view, int flags) {\n        Context context = view.getContext();\n        if (isOreoShortcut()) {\n            // Oreo shortcuts\n            doOreoLaunch(context, view, mShortcutInfo);\n        } else {\n            doShortcutLaunch(context, view, shortcutData);\n        }\n    }\n\n    @Override\n    protected ListPopup buildPopupMenu(Context context, LinearAdapter adapter, View parentView, int flags) {\n\n        List<ContentLoadHelper.CategoryItem> categoryTitle = PrefCache.getResultPopupOrder(context);\n\n        for (ContentLoadHelper.CategoryItem categoryItem : categoryTitle) {\n            int titleStringId = categoryItem.textId;\n            if (titleStringId == R.string.popup_title_hist_fav) {\n                adapter.add(new LinearAdapter.ItemTitle(context, R.string.popup_title_hist_fav));\n                adapter.add(new LinearAdapter.Item(context, R.string.menu_remove_history));\n                adapter.add(new LinearAdapter.Item(context, R.string.menu_remove_shortcut));\n                adapter.add(new LinearAdapter.Item(context, R.string.menu_quick_list_add));\n                adapter.add(new LinearAdapter.Item(context, R.string.menu_quick_list_remove));\n            } else if (titleStringId == R.string.popup_title_customize) {\n                adapter.add(new LinearAdapter.ItemTitle(context, R.string.popup_title_customize));\n                if (getTags().isEmpty())\n                    adapter.add(new LinearAdapter.Item(context, R.string.menu_tags_add));\n                else\n                    adapter.add(new LinearAdapter.Item(context, R.string.menu_tags_edit));\n                adapter.add(new LinearAdapter.Item(context, R.string.menu_shortcut_rename));\n                adapter.add(new LinearAdapter.Item(context, R.string.menu_custom_icon));\n            }\n        }\n\n        if (Utilities.checkFlag(flags, LAUNCHED_FROM_QUICK_LIST)) {\n            adapter.add(new LinearAdapter.ItemTitle(context, R.string.menu_popup_title_settings));\n            adapter.add(new LinearAdapter.Item(context, R.string.menu_popup_quick_list_customize));\n        }\n\n        return inflatePopupMenu(context, adapter);\n    }\n\n    @Override\n    boolean popupMenuClickHandler(@NonNull View view, @NonNull LinearAdapter.MenuItem item, int stringId, View parentView) {\n        Context ctx = view.getContext();\n        if (stringId == R.string.menu_remove_shortcut) {\n            TBApplication app = TBApplication.getApplication(ctx);\n            app.getDataHandler().removeShortcut(this);\n            app.behaviour().removeResult(this);\n            //Toast.makeText(ctx, \"Shortcut `\" + getName() + \"` removed.\", Toast.LENGTH_LONG).show();\n            return true;\n        } else if (stringId == R.string.menu_tags_add || stringId == R.string.menu_tags_edit) {\n            TBApplication.behaviour(ctx).launchEditTagsDialog(this);\n            return true;\n        } else if (stringId == R.string.menu_shortcut_rename) {\n            launchRenameDialog(ctx);\n            return true;\n        } else if (stringId == R.string.menu_custom_icon) {\n            TBApplication.behaviour(ctx).launchCustomIconDialog(this);\n            return true;\n        }\n        return super.popupMenuClickHandler(view, item, stringId, parentView);\n    }\n\n    private void launchRenameDialog(@NonNull Context ctx) {\n        DialogHelper.makeRenameDialog(ctx, getName(), (dialog, newName) -> {\n                Context context = dialog.getContext();\n                setName(newName);\n                TBApplication app = TBApplication.getApplication(context);\n                app.getDataHandler().renameShortcut(this, newName);\n                app.behaviour().refreshSearchRecord(ShortcutEntry.this);\n\n                // Show toast message\n                String msg = context.getString(R.string.shortcut_rename_confirmation, getName());\n                Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();\n            })\n            .setTitle(R.string.title_shortcut_rename)\n            .show();\n    }\n\n    public void setCustomIcon() {\n        customIcon += 1;\n    }\n\n    public void clearCustomIcon() {\n        customIcon = 0;\n    }\n\n    public static class AsyncSetEntryIcon extends AsyncSetEntryDrawable<ShortcutEntry> {\n        Drawable subIcon = null;\n\n        public AsyncSetEntryIcon(@NonNull ImageView image, int drawFlags, @NonNull ShortcutEntry shortcutEntry) {\n            super(image, drawFlags, shortcutEntry);\n        }\n\n        @Override\n        public Drawable getDrawable(Context context) {\n            ShortcutEntry shortcutEntry = entryItem;\n            Drawable icon = shortcutEntry.getIcon(context);\n            if (icon == null) {\n                subIcon = AppCompatResources.getDrawable(context, R.drawable.ic_send);\n                return getAppDrawable(context, shortcutEntry.shortcutData, shortcutEntry.packageName, shortcutEntry.mShortcutInfo, false);\n            } else {\n                subIcon = getAppDrawable(context, shortcutEntry.shortcutData, shortcutEntry.packageName, shortcutEntry.mShortcutInfo, true);\n            }\n            return icon;\n        }\n\n        @Override\n        protected void onPostExecute(Drawable drawable) {\n            // get ImageView before calling super\n            ImageView icon1 = getImageView();\n            super.onPostExecute(drawable);\n            if (icon1 != null)\n                setIcons(drawFlags, icon1, drawable, subIcon);\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/entry/StaticEntry.java",
    "content": "package rocks.tbog.tblauncher.entry;\n\nimport android.content.Context;\nimport android.graphics.Color;\nimport android.graphics.drawable.Drawable;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\nimport android.widget.TextView;\nimport android.widget.Toast;\n\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.WorkerThread;\nimport androidx.appcompat.content.res.AppCompatResources;\nimport androidx.core.graphics.drawable.DrawableCompat;\n\nimport java.util.List;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.handler.DataHandler;\nimport rocks.tbog.tblauncher.handler.IconsHandler;\nimport rocks.tbog.tblauncher.preference.ContentLoadHelper;\nimport rocks.tbog.tblauncher.result.AsyncSetEntryDrawable;\nimport rocks.tbog.tblauncher.result.ResultViewHelper;\nimport rocks.tbog.tblauncher.ui.LinearAdapter;\nimport rocks.tbog.tblauncher.ui.ListPopup;\nimport rocks.tbog.tblauncher.utils.DialogHelper;\nimport rocks.tbog.tblauncher.utils.PrefCache;\nimport rocks.tbog.tblauncher.utils.UIColors;\nimport rocks.tbog.tblauncher.utils.Utilities;\n\npublic abstract class StaticEntry extends EntryItem implements ICustomIconEntry {\n\n    private static final int[] RESULT_LAYOUT = {R.layout.item_builtin, R.layout.item_grid, R.layout.item_dock};\n\n    @DrawableRes\n    protected int iconResource;\n    protected int customIcon;\n\n    public StaticEntry(@NonNull String id, @DrawableRes int icon) {\n        super(id);\n        iconResource = icon;\n    }\n\n    @NonNull\n    @Override\n    public String getIconCacheId() {\n        return id + customIcon;\n    }\n\n    @Override\n    protected ListPopup buildPopupMenu(Context context, LinearAdapter adapter, View parentView, int flags) {\n\n        List<ContentLoadHelper.CategoryItem> categoryTitle = PrefCache.getResultPopupOrder(context);\n\n        for (ContentLoadHelper.CategoryItem categoryItem : categoryTitle) {\n            int titleStringId = categoryItem.textId;\n            if (titleStringId == R.string.popup_title_customize) {\n                adapter.add(new LinearAdapter.ItemTitle(context, R.string.popup_title_customize));\n                adapter.add(new LinearAdapter.Item(context, R.string.menu_action_rename));\n                adapter.add(new LinearAdapter.Item(context, R.string.menu_custom_icon));\n            }\n        }\n\n        if (Utilities.checkFlag(flags, LAUNCHED_FROM_QUICK_LIST)) {\n            adapter.add(new LinearAdapter.ItemTitle(context, R.string.menu_popup_title_settings));\n            adapter.add(new LinearAdapter.Item(context, R.string.menu_quick_list_remove));\n            adapter.add(new LinearAdapter.Item(context, R.string.menu_popup_quick_list_customize));\n        }\n\n        return inflatePopupMenu(context, adapter);\n    }\n\n    @Override\n    boolean popupMenuClickHandler(@NonNull View view, @NonNull LinearAdapter.MenuItem item, int stringId, View parentView) {\n        Context ctx = view.getContext();\n        if (stringId == R.string.menu_action_rename) {\n            launchRenameDialog(ctx);\n            return true;\n        } else if (stringId == R.string.menu_custom_icon) {\n            TBApplication.behaviour(ctx).launchCustomIconDialog(this);\n            return true;\n        }\n        return super.popupMenuClickHandler(view, item, stringId, parentView);\n    }\n\n    private void launchRenameDialog(@NonNull Context c) {\n        DialogHelper.makeRenameDialog(c, getName(), (dialog, newName) -> {\n                Context ctx = dialog.getContext();\n\n                // Set new name\n                setName(newName);\n                TBApplication app = TBApplication.getApplication(ctx);\n                app.getDataHandler().renameStaticEntry(this, newName);\n                app.behaviour().refreshSearchRecord(StaticEntry.this);\n\n                // Show toast message\n                String msg = ctx.getString(R.string.entry_rename_confirmation, getName());\n                Toast.makeText(ctx, msg, Toast.LENGTH_SHORT).show();\n            })\n            .setTitle(R.string.title_static_rename)\n            .setNeutralButton(R.string.custom_name_set_default, (dialog, which) -> {\n                Context ctx = dialog.requireContext();\n                TBApplication app = TBApplication.getApplication(ctx);\n                DataHandler dataHandler = app.getDataHandler();\n                // restore default name\n                String name = dataHandler.renameStaticEntry(this, null);\n\n                if (name != null) {\n                    app.behaviour().refreshSearchRecord(StaticEntry.this);\n\n                    // Show toast message\n                    String msg = ctx.getString(R.string.entry_rename_confirmation, getName());\n                    Toast.makeText(ctx, msg, Toast.LENGTH_SHORT).show();\n                }\n\n                dialog.dismiss();\n            })\n            .show();\n    }\n\n    ///////////////////////////////////////////////////////////////////////////////////////////////\n    // Result methods\n    ///////////////////////////////////////////////////////////////////////////////////////////////\n\n    public static int[] getResultLayout() {\n        return RESULT_LAYOUT;\n    }\n\n    @Override\n    public int getResultLayout(int drawFlags) {\n        return Utilities.checkFlag(drawFlags, FLAG_DRAW_LIST) ? RESULT_LAYOUT[0] :\n            (Utilities.checkFlag(drawFlags, FLAG_DRAW_GRID) ? RESULT_LAYOUT[1] :\n                RESULT_LAYOUT[2]);\n    }\n\n    @Override\n    public void displayResult(@NonNull View view, int drawFlags) {\n        TextView nameView = view.findViewById(android.R.id.text1);\n        nameView.setTextColor(UIColors.getResultTextColor(view.getContext()));\n        nameView.setText(getName());\n        if (Utilities.checkFlag(drawFlags, FLAG_DRAW_NAME))\n            nameView.setVisibility(View.VISIBLE);\n        else\n            nameView.setVisibility(View.GONE);\n\n        ImageView appIcon = view.findViewById(android.R.id.icon);\n        if (Utilities.checkFlag(drawFlags, FLAG_DRAW_ICON)) {\n            ResultViewHelper.setIconColorFilter(appIcon, drawFlags);\n            appIcon.setVisibility(View.VISIBLE);\n            ResultViewHelper.setIconAsync(drawFlags, this, appIcon, AsyncSetEntryIcon.class, StaticEntry.class);\n        } else {\n            appIcon.setImageDrawable(null);\n            appIcon.setVisibility(View.GONE);\n        }\n\n        ResultViewHelper.applyPreferences(drawFlags, nameView, appIcon);\n        if (Utilities.checkFlag(drawFlags, FLAG_DRAW_LIST))\n            ResultViewHelper.applyListRowPreferences((ViewGroup) view);\n    }\n\n    @Override\n    public void setCustomIcon() {\n        customIcon += 1;\n    }\n\n    @Override\n    public void clearCustomIcon() {\n        customIcon = 0;\n    }\n\n    @Override\n    public boolean hasCustomIcon() {\n        return customIcon > 0;\n    }\n\n    @Override\n    public boolean isExcludedFromHistory() {\n        return true;\n    }\n\n    @WorkerThread\n    public Drawable getIconDrawable(Context context) {\n        if (hasCustomIcon()) {\n            IconsHandler iconsHandler = TBApplication.getApplication(context).iconsHandler();\n            Drawable drawable = iconsHandler.getCustomIcon(this);\n            if (drawable != null)\n                return drawable;\n            else\n                iconsHandler.restoreDefaultIcon(this);\n        }\n        return getDefaultDrawable(context);\n    }\n\n    public Drawable getDefaultDrawable(@NonNull Context context) {\n        return AppCompatResources.getDrawable(context, iconResource);\n    }\n\n    public static class AsyncSetEntryIcon extends AsyncSetEntryDrawable<StaticEntry> {\n        public AsyncSetEntryIcon(@NonNull ImageView image, int drawFlags, @NonNull StaticEntry staticEntry) {\n            super(image, drawFlags, staticEntry);\n        }\n\n        @Override\n        public Drawable getDrawable(Context context) {\n            Drawable drawable = entryItem.getIconDrawable(context);\n            if (!entryItem.hasCustomIcon()) {\n                drawable = DrawableCompat.wrap(drawable);\n                int color = Utilities.checkFlag(drawFlags, FLAG_DRAW_WHITE_BG) ? Color.BLACK : Color.WHITE;\n                DrawableCompat.setTint(drawable, color);\n            }\n            return drawable;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/entry/TagEntry.java",
    "content": "package rocks.tbog.tblauncher.entry;\n\nimport android.content.Context;\nimport android.graphics.drawable.Drawable;\nimport android.view.View;\n\nimport androidx.annotation.NonNull;\n\nimport rocks.tbog.tblauncher.BuildConfig;\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.TagsManager;\nimport rocks.tbog.tblauncher.drawable.CodePointDrawable;\nimport rocks.tbog.tblauncher.searcher.TagSearcher;\nimport rocks.tbog.tblauncher.ui.LinearAdapter;\nimport rocks.tbog.tblauncher.utils.DialogHelper;\n\npublic class TagEntry extends StaticEntry {\n    public static final String SCHEME = \"tag://\";\n\n    public TagEntry(@NonNull String id) {\n        super(id, 0);\n        if (BuildConfig.DEBUG && !id.startsWith(SCHEME)) {\n            throw new IllegalStateException(\"Invalid \" + TagEntry.class.getSimpleName() + \" id `\" + id + \"`\");\n        }\n    }\n\n    @Override\n    public void setName(String name) {\n        if (name != null) {\n            if (!id.endsWith(name))\n                throw new IllegalStateException(\"tags can't have the display name different from the tag name\");\n            super.setName(name);\n        } else {\n            super.setName(id.substring(SCHEME.length()));\n        }\n    }\n\n    @Override\n    boolean popupMenuClickHandler(@NonNull View view, @NonNull LinearAdapter.MenuItem item, int stringId, View parentView) {\n        if (stringId == R.string.menu_action_rename) {\n            Context ctx = view.getContext();\n            launchRenameDialog(ctx);\n            return true;\n        }\n        return super.popupMenuClickHandler(view, item, stringId, parentView);\n    }\n\n    @Override\n    public void doLaunch(@NonNull View v, int flags) {\n        if (TBApplication.activityInvalid(v))\n            return;\n        Context ctx = v.getContext();\n        TBApplication.quickList(ctx).toggleSearch(v, getName(), TagSearcher.class);\n    }\n\n    @Override\n    public void displayResult(@NonNull View view, int drawFlags) {\n        super.displayResult(view, drawFlags);\n        view.setTag(R.id.tag_actionId, id);\n    }\n\n    @Override\n    public Drawable getDefaultDrawable(Context context) {\n        return new CodePointDrawable(getName());\n    }\n\n    private void launchRenameDialog(@NonNull Context c) {\n        DialogHelper.makeRenameDialog(c, getName(), (dialog, newName) -> {\n            Context ctx = dialog.getContext();\n\n            String oldName = getName();\n\n            TBApplication app = TBApplication.getApplication(ctx);\n            app.tagsHandler().renameTag(oldName, newName);\n            app.behaviour().refreshSearchRecord(TagEntry.this);\n\n            // update providers and refresh views\n            TagsManager.afterChangesMade(ctx);\n        })\n            .setTitle(R.string.title_rename_tag)\n            .setHint(R.string.hint_rename_tag)\n            .show();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/entry/UrlEntry.java",
    "content": "package rocks.tbog.tblauncher.entry;\n\nimport android.content.Context;\nimport android.content.pm.PackageManager;\nimport android.graphics.drawable.Drawable;\nimport android.text.Spannable;\nimport android.text.SpannableString;\nimport android.text.style.ForegroundColorSpan;\nimport android.util.Pair;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\nimport android.widget.TextView;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport java.util.ArrayList;\n\nimport rocks.tbog.tblauncher.result.AsyncSetEntryDrawable;\nimport rocks.tbog.tblauncher.result.ResultViewHelper;\nimport rocks.tbog.tblauncher.utils.UIColors;\nimport rocks.tbog.tblauncher.utils.Utilities;\n\npublic abstract class UrlEntry extends SearchEntry {\n\n    public final String url;\n    private static final ArrayList<Pair<String, String>> APP4URL;\n\n    static {\n        APP4URL = new ArrayList<>(5);\n        APP4URL.add(new Pair<>(\"https://encrypted.google.com\", \"com.google.android.googlequicksearchbox\"));\n        APP4URL.add(new Pair<>(\"https://play.google.com/store\", \"com.android.vending\"));\n        APP4URL.add(new Pair<>(\"https://start.duckduckgo.com\", \"com.duckduckgo.mobile.android\"));\n        APP4URL.add(new Pair<>(\"https://www.google.com/maps\", \"com.google.android.apps.maps\"));\n        APP4URL.add(new Pair<>(\"https://www.youtube.com\", \"com.google.android.youtube\"));\n    }\n\n    public UrlEntry(@NonNull String id, @NonNull String url) {\n        super(id);\n        this.url = url;\n    }\n\n    @Override\n    public String getHistoryId() {\n        // Search POJO should not appear in history\n        return \"\";\n    }\n\n    @Nullable\n    protected static Drawable getApplicationIconForUrl(@NonNull Context context, @Nullable String url) {\n        if (url == null || url.isEmpty())\n            return null;\n        for (Pair<String, String> pair : APP4URL) {\n            if (url.startsWith(pair.first)) {\n                try {\n                    return context.getPackageManager().getApplicationIcon(pair.second);\n                } catch (PackageManager.NameNotFoundException ignored) {\n                }\n            }\n\n        }\n        return null;\n    }\n\n    protected static boolean isGoogleSearch(String url) {\n        return url.startsWith(\"https://encrypted.google.com\");\n    }\n\n    ///////////////////////////////////////////////////////////////////////////////////////////////\n    // Result methods\n    ///////////////////////////////////////////////////////////////////////////////////////////////\n    protected abstract String getResultText(Context context);\n\n    @Override\n    public Drawable getDefaultDrawable(Context context) {\n        Drawable appIcon = getApplicationIconForUrl(context, url);\n        if (appIcon != null)\n            return appIcon;\n        return super.getDefaultDrawable(context);\n    }\n\n    @Override\n    public void displayResult(@NonNull View view, int drawFlags) {\n        Context context = view.getContext();\n        TextView nameView = view.findViewById(android.R.id.text1);\n        nameView.setTextColor(UIColors.getResultTextColor(view.getContext()));\n        if (Utilities.checkFlag(drawFlags, FLAG_DRAW_NAME)) {\n            String text = getResultText(context);\n            int pos = text.lastIndexOf(query);\n            if (pos >= 0) {\n                int color = UIColors.getResultHighlightColor(context);\n                SpannableString enriched = new SpannableString(text);\n                enriched.setSpan(\n                    new ForegroundColorSpan(color),\n                    pos,\n                    pos + query.length(),\n                    Spannable.SPAN_INCLUSIVE_EXCLUSIVE\n                );\n                nameView.setText(enriched);\n            } else {\n                nameView.setText(text);\n            }\n            nameView.setVisibility(View.VISIBLE);\n        } else {\n            nameView.setVisibility(View.GONE);\n        }\n\n        ImageView appIcon = view.findViewById(android.R.id.icon);\n        if (Utilities.checkFlag(drawFlags, FLAG_DRAW_ICON)) {\n            ResultViewHelper.setIconColorFilter(appIcon, drawFlags);\n            appIcon.setVisibility(View.VISIBLE);\n            ResultViewHelper.setIconAsync(drawFlags, this, appIcon, AsyncSetUrlEntryIcon.class, UrlEntry.class);\n        } else {\n            appIcon.setImageDrawable(null);\n            appIcon.setVisibility(View.GONE);\n        }\n\n        ResultViewHelper.applyPreferences(drawFlags, nameView, appIcon);\n        if (Utilities.checkFlag(drawFlags, FLAG_DRAW_LIST))\n            ResultViewHelper.applyListRowPreferences((ViewGroup) view);\n    }\n\n    public static class AsyncSetUrlEntryIcon extends AsyncSetEntryDrawable<UrlEntry> {\n        public AsyncSetUrlEntryIcon(@NonNull ImageView image, int drawFlags, @NonNull UrlEntry urlEntry) {\n            super(image, drawFlags, urlEntry);\n        }\n\n        @Override\n        public Drawable getDrawable(Context context) {\n            return entryItem.getIconDrawable(context);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/handler/AppsHandler.java",
    "content": "package rocks.tbog.tblauncher.handler;\n\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.util.Log;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.WorkerThread;\n\nimport java.util.ArrayDeque;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.dataprovider.AppCacheProvider;\nimport rocks.tbog.tblauncher.db.AppRecord;\nimport rocks.tbog.tblauncher.db.DBHelper;\nimport rocks.tbog.tblauncher.entry.AppEntry;\nimport rocks.tbog.tblauncher.utils.Timer;\nimport rocks.tbog.tblauncher.utils.UserHandleCompat;\nimport rocks.tbog.tblauncher.utils.Utilities;\n\npublic class AppsHandler {\n    private static final String TAG = AppsHandler.class.getSimpleName();\n    private final TBApplication mApplication;\n    private final HashMap<String, AppEntry> mAppsCache = new HashMap<>();\n    private boolean mIsLoaded = false;\n    private final ArrayDeque<Runnable> mAfterLoadedTasks = new ArrayDeque<>(2);\n\n    public AppsHandler(TBApplication application) {\n        this.mApplication = application;\n        loadFromDB(false);\n    }\n\n    public void loadFromDB(boolean wait) {\n        Log.d(TAG, \"loadFromDB(wait= \" + wait + \" )\");\n\n        synchronized (this) {\n            mIsLoaded = false;\n        }\n\n        final Timer timer = Timer.startMilli();\n        final HashMap<String, AppEntry> apps = new HashMap<>();\n        final Runnable load = () -> {\n            TagsHandler tagsHandler = mApplication.tagsHandler();\n            Context context = getContext();\n            Map<String, AppRecord> dbApps = DBHelper.getAppsData(context);\n            apps.clear();\n\n            // convert from AppRecord to AppEntry\n            for (AppRecord rec : dbApps.values()) {\n                AppEntry appEntry = record2app(context, rec);\n                apps.put(appEntry.id, appEntry);\n            }\n            setTagsForApps(apps.values(), tagsHandler);\n        };\n\n        final Runnable apply = () -> {\n            synchronized (AppsHandler.this) {\n                mAppsCache.clear();\n                mAppsCache.putAll(apps);\n                mIsLoaded = true;\n\n                timer.stop();\n                Log.d(\"time\", \"Time to load all DB apps: \" + timer);\n\n                // run and remove tasks\n                Runnable task;\n                while (null != (task = mAfterLoadedTasks.poll()))\n                    task.run();\n            }\n        };\n\n        if (wait) {\n            load.run();\n            apply.run();\n        } else\n            Utilities.runAsync((t) -> load.run(), (t) -> apply.run());\n    }\n\n    public void runWhenLoaded(@NonNull Runnable task) {\n        synchronized (this) {\n            if (mIsLoaded)\n                task.run();\n            else\n                mAfterLoadedTasks.add(task);\n        }\n    }\n\n    @WorkerThread\n    public static void setTagsForApps(@NonNull Collection<AppEntry> apps, @NonNull TagsHandler tagsHandler) {\n        tagsHandler.runWhenLoaded(() -> {\n            Utilities.runAsync(() -> {\n                Log.d(TAG, \"set \" + apps.size() + \" cached app(s) tags\");\n                for (AppEntry appEntry : apps)\n                    appEntry.setTags(tagsHandler.getTags(appEntry.id));\n            });\n        });\n    }\n\n    @NonNull\n    private static AppEntry record2app(@NonNull Context context, @NonNull AppRecord rec) {\n        UserHandleCompat user = UserHandleCompat.fromComponentName(context, rec.componentName);\n        ComponentName cn = UserHandleCompat.unflattenComponentName(rec.componentName);\n        AppEntry appEntry = new AppEntry(cn, user);\n\n        if (rec.hasCustomName())\n            appEntry.setName(rec.displayName);\n        else\n            appEntry.setName(user.getBadgedLabelForUser(context, rec.displayName));\n        if (rec.hasCustomIcon())\n            appEntry.setCustomIcon(rec.dbId);\n\n        return appEntry;\n    }\n\n    private Context getContext() {\n        return mApplication;\n    }\n\n    /**\n     * Get an unmodifiable collection with the applications\n     * @return an empty list if not loaded yet\n     */\n    @NonNull\n    public Collection<AppEntry> getAllApps() {\n        synchronized (AppsHandler.this) {\n            if (!mIsLoaded)\n                return Collections.emptyList();\n            return Collections.unmodifiableCollection(mAppsCache.values());\n        }\n    }\n\n    /**\n     * Get an ArrayList of the application collection.\n     * `AppEntry.resetRelevance` is called before returning list\n     * @return a new instance of ArrayList with all apps\n     */\n    @NonNull\n    public ArrayList<AppEntry> getApplications() {\n        ArrayList<AppEntry> records = new ArrayList<>(mAppsCache.size());\n        synchronized (AppsHandler.this) {\n            if (mIsLoaded) {\n                for (AppEntry appEntry : mAppsCache.values()) {\n                    appEntry.resetResultInfo();\n                    records.add(appEntry);\n                }\n            }\n        }\n        return records;\n    }\n\n    public AppCacheProvider getCacheProvider() {\n        return new AppCacheProvider(this);\n    }\n\n    @NonNull\n    public Map<String, AppRecord> getAppRecords(@NonNull Context context) {\n        return DBHelper.getAppsData(context);\n    }\n\n    public void updateAppCache(@Nullable ArrayList<AppRecord> insertOrUpdate, @Nullable ArrayList<AppRecord> remove) {\n        if (insertOrUpdate != null && insertOrUpdate.size() > 0) {\n            DBHelper.insertOrUpdateApps(getContext(), insertOrUpdate);\n        }\n        if (remove != null && remove.size() > 0) {\n            DBHelper.deleteApps(getContext(), remove);\n        }\n    }\n\n    public void setAppCache(@Nullable ArrayList<AppEntry> list) {\n        if (list == null || list.isEmpty())\n            return;\n        synchronized (AppsHandler.this) {\n            mIsLoaded = true;\n            mAppsCache.clear();\n            for (AppEntry appEntry : list)\n                mAppsCache.put(appEntry.id, appEntry);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/handler/DataHandler.java",
    "content": "package rocks.tbog.tblauncher.handler;\n\nimport android.app.Application;\nimport android.app.KeyguardManager;\nimport android.content.BroadcastReceiver;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.IntentFilter;\nimport android.content.ServiceConnection;\nimport android.content.SharedPreferences;\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.os.Build;\nimport android.os.Handler;\nimport android.os.IBinder;\nimport android.os.Looper;\nimport android.util.Log;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.WorkerThread;\nimport androidx.core.app.ActivityCompat;\nimport androidx.core.content.ContextCompat;\nimport androidx.preference.PreferenceManager;\n\nimport java.util.ArrayDeque;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.Iterator;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.LinkedBlockingQueue;\nimport java.util.concurrent.ThreadPoolExecutor;\nimport java.util.concurrent.TimeUnit;\n\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.TBLauncherActivity;\nimport rocks.tbog.tblauncher.dataprovider.ActionProvider;\nimport rocks.tbog.tblauncher.dataprovider.AppProvider;\nimport rocks.tbog.tblauncher.dataprovider.CalculatorProvider;\nimport rocks.tbog.tblauncher.dataprovider.ContactsProvider;\nimport rocks.tbog.tblauncher.dataprovider.DialProvider;\nimport rocks.tbog.tblauncher.dataprovider.FilterProvider;\nimport rocks.tbog.tblauncher.dataprovider.IProvider;\nimport rocks.tbog.tblauncher.dataprovider.ModProvider;\nimport rocks.tbog.tblauncher.dataprovider.Provider;\nimport rocks.tbog.tblauncher.dataprovider.QuickListProvider;\nimport rocks.tbog.tblauncher.dataprovider.SearchProvider;\nimport rocks.tbog.tblauncher.dataprovider.ShortcutsProvider;\nimport rocks.tbog.tblauncher.dataprovider.TagsProvider;\nimport rocks.tbog.tblauncher.db.AppRecord;\nimport rocks.tbog.tblauncher.db.DBHelper;\nimport rocks.tbog.tblauncher.db.ModRecord;\nimport rocks.tbog.tblauncher.db.ShortcutRecord;\nimport rocks.tbog.tblauncher.db.ValuedHistoryRecord;\nimport rocks.tbog.tblauncher.entry.AppEntry;\nimport rocks.tbog.tblauncher.entry.EntryItem;\nimport rocks.tbog.tblauncher.entry.ShortcutEntry;\nimport rocks.tbog.tblauncher.entry.StaticEntry;\nimport rocks.tbog.tblauncher.searcher.Searcher;\nimport rocks.tbog.tblauncher.shortcut.ShortcutUtil;\nimport rocks.tbog.tblauncher.utils.Timer;\nimport rocks.tbog.tblauncher.utils.Utilities;\n\npublic class DataHandler extends BroadcastReceiver\n    implements SharedPreferences.OnSharedPreferenceChangeListener {\n    final static private String TAG = \"DataHandler\";\n\n    public static final ExecutorService EXECUTOR_PROVIDERS;\n\n    static {\n        /*\n         corePoolSize: the number of threads to keep in the pool.\n         maximumPoolSize: the maximum number of threads to allow in the pool.\n         keepAliveTime: if the pool currently has more than corePoolSize threads, excess threads will be terminated if they have been idle for more than keepAliveTime.\n         unit: the time unit for the keepAliveTime argument. Can be NANOSECONDS, MILLISECONDS, SECONDS, MINUTES, HOURS and DAYS.\n         workQueue: the queue used for holding tasks before they are executed. Default choices are SynchronousQueue for multi-threaded pools and LinkedBlockingQueue for single-threaded pools.\n        */\n        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(\n            1, 1, 1, TimeUnit.SECONDS,\n            new LinkedBlockingQueue<>());\n        threadPoolExecutor.allowCoreThreadTimeOut(true);\n\n        EXECUTOR_PROVIDERS = threadPoolExecutor;\n    }\n\n    /**\n     * Package the providers reside in\n     */\n    final static private String PROVIDER_PREFIX = IProvider.class.getPackage().getName() + \".\";\n    /**\n     * List all known complex providers, that are defined as Android services\n     */\n    final static private List<String> PROVIDER_NAMES = Arrays.asList(\n        \"app\"\n        , \"contacts\"\n        , \"shortcuts\"\n    );\n\n    @NonNull\n    private final Application mApplication;\n    private String currentQuery;\n    private final Map<String, ProviderEntry> providers = new LinkedHashMap<>(); // preserve insert order\n    private boolean mFullLoadOverSent = false;\n    private final ArrayDeque<Runnable> mAfterLoadOverTasks = new ArrayDeque<>(2);\n    private final Timer mTimer = new Timer();\n\n    /**\n     * Initialize all providers\n     */\n    public DataHandler(@NonNull Application app) {\n        // Make sure we are in the context of the main application\n        // (otherwise we might receive an exception about broadcast listeners not being able\n        //  to bind to services)\n        mApplication = app;\n        Context ctx = app.getApplicationContext();\n\n        mTimer.start();\n\n        IntentFilter intentFilter = new IntentFilter(TBLauncherActivity.LOAD_OVER);\n\n        ActivityCompat.registerReceiver(ctx, this, intentFilter, ContextCompat.RECEIVER_NOT_EXPORTED);\n\n        sendBroadcast(ctx, TBLauncherActivity.START_LOAD, TAG);\n\n        // Monitor changes for service preferences (to automatically start and stop services)\n        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);\n        prefs.registerOnSharedPreferenceChangeListener(this);\n\n        // add DB providers\n        basicProviders();\n\n        // add providers that may be toggled by preferences\n        toggleableProviders(prefs);\n\n        // start STEP_1 providers\n        for (ProviderEntry entry : providers.values()) {\n            if (entry.provider == null)\n                return;\n            if (IProvider.LOAD_STEP_1 == entry.provider.getLoadStep() && !entry.provider.isLoaded()) {\n                entry.provider.reload(false);\n            }\n        }\n\n        // Connect to complex providers\n        // Those are the complex providers, that are defined as Android services\n        // to survive even if the app's UI is killed\n        // (this way, we don't need to reload the app list as often)\n        for (String providerName : PROVIDER_NAMES) {\n            if (prefs.getBoolean(\"enable-\" + providerName, true)) {\n                this.connectToProvider(providerName, 0);\n            }\n        }\n    }\n\n    public static void sendBroadcast(@NonNull Context context, @NonNull String action, @Nullable String data) {\n        Intent msg = new Intent(action)\n            .setPackage(context.getPackageName())\n            .putExtra(TBLauncherActivity.INTENT_DATA, data);\n        context.sendBroadcast(msg);\n    }\n\n    @NonNull\n    public Context getContext() {\n        return mApplication.getApplicationContext();\n    }\n\n    /*\n     * Some basic providers are defined directly, as we don't need the overhead of a service\n     * for them. These providers don't expose a service connection, and you can't bind / unbind\n     * to them dynamically.\n     */\n    private void basicProviders() {\n        Context context = mApplication;\n        // Filters\n        {\n            ProviderEntry providerEntry = new ProviderEntry();\n            providerEntry.provider = new FilterProvider(context);\n            providers.put(\"filters\", providerEntry);\n        }\n\n        // Actions\n        {\n            ProviderEntry providerEntry = new ProviderEntry();\n            providerEntry.provider = new ActionProvider(context);\n            providers.put(\"actions\", providerEntry);\n        }\n\n        // Tag provider\n        {\n            ProviderEntry providerEntry = new ProviderEntry();\n            providerEntry.provider = new TagsProvider(context);\n            providers.put(\"tags\", providerEntry);\n        }\n\n        // Favorites\n        {\n            ProviderEntry providerEntry = new ProviderEntry();\n            providerEntry.provider = new ModProvider(context);\n            providers.put(\"mods\", providerEntry);\n        }\n\n        // QuickList\n        {\n            ProviderEntry providerEntry = new ProviderEntry();\n            providerEntry.provider = new QuickListProvider(context);\n            providers.put(\"quickList\", providerEntry);\n        }\n    }\n\n    private void toggleableProviders(SharedPreferences prefs) {\n        final Context context = getContext();\n\n        // Search engine provider,\n        {\n            String providerName = \"search\";\n            if (prefs.getBoolean(\"enable-search\", true) ||\n                prefs.getBoolean(\"enable-url\", true)) {\n                ProviderEntry providerEntry = new ProviderEntry();\n                providerEntry.provider = new SearchProvider(context, prefs);\n                providers.put(providerName, providerEntry);\n            } else {\n                providers.remove(providerName);\n            }\n        }\n\n        // Calculator provider, may be toggled by preference\n        {\n            String providerName = \"calculator\";\n            if (prefs.getBoolean(\"enable-\" + providerName, true)) {\n                ProviderEntry providerEntry = new ProviderEntry();\n                providerEntry.provider = new CalculatorProvider();\n                providers.put(providerName, providerEntry);\n            } else {\n                providers.remove(providerName);\n            }\n        }\n\n        // Dial phone provider, may be toggled by preference\n        {\n            String providerName = \"dial\";\n            if (prefs.getBoolean(\"enable-\" + providerName, true)) {\n                ProviderEntry providerEntry = new ProviderEntry();\n                providerEntry.provider = new DialProvider();\n                providers.put(providerName, providerEntry);\n            } else {\n                providers.remove(providerName);\n            }\n        }\n    }\n\n    @Override\n    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {\n        if (key != null && key.startsWith(\"enable-\")) {\n            String providerName = key.substring(7);\n            if (PROVIDER_NAMES.contains(providerName)) {\n                if (sharedPreferences.getBoolean(key, true)) {\n                    this.connectToProvider(providerName, 0);\n                } else {\n                    this.disconnectFromProvider(providerName);\n                }\n            }\n        }\n    }\n\n    /**\n     * Generate an intent that can be used to start or stop the given provider\n     *\n     * @param name The name of the provider\n     * @return Android intent for this provider\n     */\n    private Intent providerName2Intent(@NonNull Context context, String name) {\n        // Build expected fully-qualified provider class name\n        StringBuilder className = new StringBuilder(50);\n        className.append(PROVIDER_PREFIX);\n        className.append(Character.toUpperCase(name.charAt(0)));\n        className.append(name.substring(1).toLowerCase(Locale.ROOT));\n        className.append(\"Provider\");\n\n        // Try to create reflection class instance for class name\n        try {\n            return new Intent(context, Class.forName(className.toString()));\n        } catch (ClassNotFoundException e) {\n            e.printStackTrace();\n            return null;\n        }\n    }\n\n    /**\n     * Require the data handler to be connected to the data provider with the given name\n     *\n     * @param name Data provider name (i.e.: `ContactsProvider` → `\"contacts\"`)\n     */\n    private void connectToProvider(final String name, final int counter) {\n        final Context context = getContext();\n\n        // Do not continue if this provider has already been connected to\n        if (this.providers.containsKey(name)) {\n            return;\n        }\n\n        Log.v(TAG, \"Connecting to \" + name);\n\n\n        // Find provider class for the given service name\n        final Intent intent = this.providerName2Intent(context, name);\n        if (intent == null) {\n            return;\n        }\n\n        if (!startService(context, intent, name, counter))\n            return;\n\n        final ProviderEntry entry = new ProviderEntry();\n        // Add empty provider object to list of providers\n        this.providers.put(name, entry);\n\n        // Connect and bind to provider service\n        context.bindService(intent, new ServiceConnection() {\n            @Override\n            public void onServiceConnected(ComponentName className, IBinder service) {\n                Log.i(TAG, \"onServiceConnected \" + className);\n\n                // We've bound to LocalService, cast the IBinder and get LocalService instance\n                Provider<?>.LocalBinder binder = (Provider<?>.LocalBinder) service;\n                IProvider<?> provider = binder.getService();\n\n                // Update provider info so that it contains something useful\n                entry.provider = provider;\n                entry.connection = this;\n\n                if (provider.isLoaded()) {\n                    handleProviderLoaded();\n                }\n            }\n\n            @Override\n            public void onServiceDisconnected(ComponentName arg0) {\n                Log.i(TAG, \"onServiceDisconnected \" + arg0);\n            }\n        }, Context.BIND_AUTO_CREATE);\n    }\n\n    private boolean startService(Context context, Intent intent, String name, int counter) {\n        try {\n            // Send \"start service\" command first so that the service can run independently\n            // of the activity\n            context.startService(intent);\n        } catch (IllegalStateException e) {\n            // When KISS is the default launcher,\n            // the system will try to start KISS in the background after a reboot\n            // however at this point we're not allowed to start services, and an IllegalStateException will be thrown\n            // We'll then add a broadcast receiver for the next time the user turns his screen on\n            // (or passes the lockscreen) to retry at this point\n            // https://github.com/Neamar/KISS/issues/1130\n            // https://github.com/Neamar/KISS/issues/1154\n            Log.w(TAG, \"Unable to start service for \" + name + \". KISS is probably not in the foreground. Service will automatically be started when KISS gets to the foreground.\");\n\n            if (counter > 20) {\n                Log.e(TAG, \"Already tried and failed twenty times to start service. Giving up.\");\n                return false;\n            }\n\n            // Add a receiver to get notified next time the screen is on\n            // or next time the users successfully dismisses his lock screen\n            IntentFilter intentFilter = new IntentFilter();\n            intentFilter.addAction(Intent.ACTION_SCREEN_ON);\n            intentFilter.addAction(Intent.ACTION_USER_PRESENT);\n            ActivityCompat.registerReceiver(context, new BroadcastReceiver() {\n                @Override\n                public void onReceive(final Context context, Intent intent) {\n                    // Is there a lockscreen still visible to the user?\n                    // If yes, we can't start background services yet, so we'll need to wait until we get ACTION_USER_PRESENT\n                    KeyguardManager myKM = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);\n                    assert myKM != null;\n                    boolean isPhoneLocked = myKM.isKeyguardLocked();\n                    if (!isPhoneLocked) {\n                        context.unregisterReceiver(this);\n                        final Handler handler = new Handler(Looper.getMainLooper());\n                        // Even when all the stars are aligned,\n                        // starting the service needs to be slightly delayed because the Intent is fired *before* the app is considered in the foreground.\n                        // Each new release of Android manages to make the developer life harder.\n                        // Can't wait for the next one.\n                        handler.postDelayed(new Runnable() {\n                            @Override\n                            public void run() {\n                                Log.i(TAG, \"Screen turned on or unlocked, retrying to start background services\");\n                                connectToProvider(name, counter + 1);\n                            }\n                        }, 10);\n                    }\n                }\n            }, intentFilter, ContextCompat.RECEIVER_EXPORTED);\n\n            // Stop here for now, the Receiver will re-trigger the whole flow when services can be started.\n            return false;\n        }\n        return true;\n    }\n\n    /**\n     * Terminate any connection between the data handler and the data provider with the given name\n     *\n     * @param name Data provider name (i.e.: `AppProvider` → `\"app\"`)\n     */\n    private void disconnectFromProvider(String name) {\n        final Context context = getContext();\n\n        // Skip already disconnected services\n        ProviderEntry entry = this.providers.get(name);\n        if (entry == null) {\n            return;\n        }\n\n        // Disconnect from provider service\n        context.unbindService(entry.connection);\n\n        // Stop provider service\n        context.stopService(new Intent(context, entry.provider.getClass()));\n\n        // Remove provider from list\n        this.providers.remove(name);\n    }\n\n    private boolean allProvidersHaveLoaded() {\n        final Context context = getContext();\n\n        for (ProviderEntry entry : this.providers.values())\n            if (entry.provider == null || !entry.provider.isLoaded())\n                return false;\n        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);\n        for (String providerName : PROVIDER_NAMES) {\n            if (prefs.getBoolean(\"enable-\" + providerName, true)) {\n                if (!providers.containsKey(providerName))\n                    return false;\n            }\n        }\n        return true;\n    }\n\n    private boolean providersHaveLoaded(int step) {\n        for (ProviderEntry entry : this.providers.values()) {\n            if (entry.provider == null) {\n                return false;\n            }\n            if (step == entry.provider.getLoadStep() && !entry.provider.isLoaded()) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    /**\n     * Called when some event occurred that makes us believe that all data providers\n     * might be ready now\n     */\n    private void handleProviderLoaded() {\n        if (mFullLoadOverSent) {\n            return;\n        }\n\n        for (int step : IProvider.LOAD_STEPS) {\n            boolean stepLoaded = true;\n            for (ProviderEntry entry : this.providers.values()) {\n                if (entry.provider == null)\n                    return;\n                if (step == entry.provider.getLoadStep() && !entry.provider.isLoaded()) {\n                    stepLoaded = false;\n                    entry.provider.reload(false);\n                }\n            }\n            if (!stepLoaded)\n                return;\n        }\n\n        if (!allProvidersHaveLoaded())\n            return;\n\n        mTimer.stop();\n        Log.v(TAG, \"Time to load all providers: \" + mTimer);\n\n        mFullLoadOverSent = true;\n\n        final Context context = getContext();\n        // Broadcast the fact that the new providers list is ready\n        try {\n            context.unregisterReceiver(this);\n            sendBroadcast(context, TBLauncherActivity.FULL_LOAD_OVER, TAG);\n        } catch (IllegalArgumentException e) {\n            Log.e(TAG, \"send FULL_LOAD_OVER\", e);\n        }\n    }\n\n    @Override\n    public void onReceive(Context context, Intent intent) {\n        // A provider finished loading and contacted us\n        this.handleProviderLoaded();\n    }\n\n    public void appendDebugText(StringBuilder text) {\n        text.append(\"Providers: \");\n        boolean first = true;\n        for (int step : IProvider.LOAD_STEPS) {\n            if (first)\n                first = false;\n            else\n                text.append(\", \");\n\n            if (providersHaveLoaded(step))\n                text.append(\"S#\")   // step number\n                    .append(step);\n        }\n        if (mFullLoadOverSent)\n            text.append(\",done in \")\n                .append(mTimer);\n        text.append(\"\\n\");\n\n        ArrayList<ProviderEntry> sortedProviders = new ArrayList<>(providers.size());\n        sortedProviders.addAll(providers.values());\n        Collections.sort(sortedProviders, (o1, o2) -> {\n            Timer t1 = o1.provider == null ? null : o1.provider.getLoadDuration();\n            Timer t2 = o2.provider == null ? null : o2.provider.getLoadDuration();\n            return Timer.STOP_TIME_COMPARATOR.compare(t1, t2);\n        });\n\n        first = true;\n        for (ProviderEntry entry : sortedProviders) {\n            if (entry.provider == null)\n                continue;\n            if (entry.provider.isLoaded()) {\n                Timer timer = entry.provider.getLoadDuration();\n                if (timer == null)\n                    continue;\n                if (first)\n                    first = false;\n                else\n                    text.append(\" | \");\n                text.append(entry.provider.getLoadStep())\n                    .append(\".\")\n                    .append(entry.provider.getClass().getSimpleName())\n                    .append(\":\")\n                    .append(timer);\n            }\n        }\n        text.append(\"\\n\");\n    }\n\n    /**\n     * Get records for this query.\n     *\n     * @param query    query to run\n     * @param searcher the searcher currently running\n     */\n    @WorkerThread\n    public void requestResults(String query, Searcher searcher) {\n        currentQuery = query;\n        for (Map.Entry<String, ProviderEntry> setEntry : this.providers.entrySet()) {\n            if (searcher.isCancelled())\n                break;\n            IProvider<?> provider = setEntry.getValue().provider;\n            if (provider == null || !provider.isLoaded()) {\n                Context context = searcher.getContext();\n                // if the apps provider has not finished yet, return the cached ones\n                if (\"app\".equals(setEntry.getKey()) && context != null)\n                    provider = TBApplication.appsHandler(context).getCacheProvider();\n                else\n                    continue;\n            }\n            // Retrieve results for query:\n            provider.requestResults(query, searcher);\n        }\n    }\n\n    /**\n     * Get records for this query.\n     *\n     * @param searcher the searcher currently running\n     */\n    public void requestAllRecords(Searcher searcher) {\n        for (ProviderEntry entry : this.providers.values()) {\n            if (entry.provider == null)\n                continue;\n\n            List<? extends EntryItem> pojos = entry.provider.getPojos();\n            if (pojos == null)\n                continue;\n            boolean accept = searcher.addResult(pojos.toArray(new EntryItem[0]));\n            // if searcher will not accept any more results, exit\n            if (!accept)\n                break;\n        }\n    }\n\n    @NonNull\n    public static DBHelper.HistoryMode getHistoryMode(String historyMode) {\n        switch (historyMode) {\n            case \"frecency\":\n                return DBHelper.HistoryMode.FRECENCY;\n            case \"frequency\":\n                return DBHelper.HistoryMode.FREQUENCY;\n            case \"adaptive\":\n                return DBHelper.HistoryMode.ADAPTIVE;\n            default:\n                return DBHelper.HistoryMode.RECENCY;\n        }\n    }\n\n    /**\n     * Return previously selected items.<br />\n     * May return null if no items were ever selected (app first use)<br />\n     * May return an empty set if the providers are not done building records,\n     * in this case it is probably a good idea to call this function 500ms after\n     *\n     * @param itemCount          max number of items to retrieve, total number may be less (search or calls are not returned for instance)\n     * @param historyMode        Recency vs Frecency vs Frequency vs Adaptive\n     * @param sortHistory        Sort history entries alphabetically\n     * @param itemsToExcludeById Items to exclude from history by their id\n     * @return pojos in recent history\n     */\n    public List<EntryItem> getHistory(int itemCount, DBHelper.HistoryMode historyMode,\n                                      boolean sortHistory, Set<String> itemsToExcludeById) {\n        // Max sure that we get enough items, regardless of how many may be excluded\n        int extendedItemCount = itemCount + itemsToExcludeById.size();\n\n        // Read history\n        final Context context = getContext();\n        List<ValuedHistoryRecord> ids = DBHelper.getHistory(context, extendedItemCount, historyMode);\n\n        // Pre-allocate array slots that are likely to be used\n        ArrayList<EntryItem> history = new ArrayList<>(ids.size());\n\n        // Find associated items\n        for (int i = 0; i < ids.size(); i++) {\n            // Ask all providers if they know this id\n            EntryItem pojo = getPojo(ids.get(i).record);\n\n            if (pojo == null)\n                continue;\n\n            if (itemsToExcludeById.contains(pojo.id))\n                continue;\n\n            history.add(pojo);\n        }\n\n        // sort the list if needed\n        if (sortHistory)\n            Collections.sort(history, Comparator.comparing(EntryItem::getName));\n\n        // enforce item count after the sort operation\n        if (history.size() > itemCount)\n            history.subList(itemCount, history.size()).clear();\n\n        return history;\n    }\n\n    public boolean addShortcut(ShortcutRecord record) {\n        final Context context = getContext();\n\n        Log.d(TAG, \"Adding shortcut \" + record.displayName + \" for \" + record.packageName);\n        if (DBHelper.insertShortcut(context, record)) {\n            ShortcutsProvider provider = getShortcutsProvider();\n            if (provider != null)\n                provider.reload(true);\n            return true;\n        }\n        return false;\n    }\n\n    public void removeShortcut(ShortcutEntry shortcut) {\n        final Context context = getContext();\n\n        // Also remove shortcut from mods\n        removeFromMods(shortcut);\n        DBHelper.removeShortcut(context, shortcut);\n\n        if (shortcut.mShortcutInfo != null) {\n            ShortcutUtil.removeShortcut(context, shortcut.mShortcutInfo);\n        }\n\n        if (this.getShortcutsProvider() != null) {\n            this.getShortcutsProvider().reload(true);\n        }\n    }\n\n    public void removeShortcuts(String packageName) {\n        final Context context = getContext();\n\n        if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {\n            return;\n        }\n\n        // Remove all shortcuts from mods for given package name\n        List<ShortcutRecord> shortcutsList = DBHelper.getShortcutsNoIcons(context, packageName);\n        for (ShortcutRecord shortcut : shortcutsList) {\n            String id = ShortcutEntry.generateShortcutId(shortcut);\n            EntryItem entry = getPojo(id);\n            if (entry != null)\n                removeFromMods(entry);\n        }\n\n        DBHelper.removeShortcuts(context, packageName);\n\n        if (this.getShortcutsProvider() != null) {\n            this.getShortcutsProvider().reload(true);\n        }\n    }\n\n    public boolean addToHidden(AppEntry entry) {\n        final Context context = getContext();\n        return DBHelper.setAppHidden(context, entry.getUserComponentName());\n    }\n\n    public boolean removeFromHidden(AppEntry entry) {\n        final Context context = getContext();\n        return DBHelper.removeAppHidden(context, entry.getUserComponentName());\n    }\n\n    @Nullable\n    public ContactsProvider getContactsProvider() {\n        ProviderEntry entry = this.providers.get(\"contacts\");\n        return (entry != null) ? ((ContactsProvider) entry.provider) : null;\n    }\n\n    @Nullable\n    public ShortcutsProvider getShortcutsProvider() {\n        ProviderEntry entry = this.providers.get(\"shortcuts\");\n        return (entry != null) ? ((ShortcutsProvider) entry.provider) : null;\n    }\n\n    @Nullable\n    public AppProvider getAppProvider() {\n        ProviderEntry entry = this.providers.get(\"app\");\n        return (entry != null) ? ((AppProvider) entry.provider) : null;\n    }\n\n    @Nullable\n    public ModProvider getModProvider() {\n        ProviderEntry entry = this.providers.get(\"mods\");\n        return (entry != null) ? ((ModProvider) entry.provider) : null;\n    }\n\n    @Nullable\n    public FilterProvider getFilterProvider() {\n        ProviderEntry entry = this.providers.get(\"filters\");\n        return (entry != null) ? ((FilterProvider) entry.provider) : null;\n    }\n\n    @Nullable\n    public ActionProvider getActionProvider() {\n        ProviderEntry entry = this.providers.get(\"actions\");\n        return (entry != null) ? ((ActionProvider) entry.provider) : null;\n    }\n\n    @Nullable\n    public TagsProvider getTagsProvider() {\n        ProviderEntry entry = this.providers.get(\"tags\");\n        return (entry != null) ? ((TagsProvider) entry.provider) : null;\n    }\n\n    @Nullable\n    public QuickListProvider getQuickListProvider() {\n        ProviderEntry entry = this.providers.get(\"quickList\");\n        return (entry != null) ? ((QuickListProvider) entry.provider) : null;\n    }\n\n    /**\n     * Return a list of records that have modifications (custom icon, name or flags)\n     *\n     * @return list of {@link ModRecord}\n     */\n    @NonNull\n    public List<ModRecord> getMods() {\n        final Context context = getContext();\n        return DBHelper.getMods(context);\n    }\n\n    public void removeFromMods(EntryItem entry) {\n        final Context context = getContext();\n\n        if (DBHelper.removeMod(context, entry.id)) {\n            ModProvider modProvider = getModProvider();\n            if (modProvider != null)\n                modProvider.reload(true);\n        }\n    }\n\n    /**\n     * Insert specified ID (probably a pojo.id) into history\n     *\n     * @param id pojo.id of item to record\n     */\n    public void addToHistory(String id) {\n        if (id.isEmpty()) {\n            return;\n        }\n        final Context context = getContext();\n        DBHelper.insertHistory(context, currentQuery, id);\n    }\n\n    @Nullable\n    public EntryItem getPojo(@NonNull String id) {\n        // Ask all providers if they know this id\n        for (ProviderEntry entry : this.providers.values()) {\n            if (entry.provider != null && entry.provider.mayFindById(id)) {\n                return entry.provider.findById(id);\n            }\n        }\n\n        return null;\n    }\n\n    public void renameApp(String componentName, String newName) {\n        final Context context = getContext();\n        DBHelper.setCustomAppName(context, componentName, newName);\n    }\n\n    /**\n     * Rename an action or a tag in the DB, refresh providers, update {@link EntryItem}\n     *\n     * @param entry   static entry to operate on\n     * @param newName new name or null to restore default\n     * @return The name after we rename or null in case of error\n     */\n    @Nullable\n    public String renameStaticEntry(@NonNull StaticEntry entry, @Nullable String newName) {\n        final Context context = getContext();\n\n        final String entryId = entry.id;\n        if (newName == null) {\n            // we need to restore the default name\n            DBHelper.removeCustomStaticEntryName(context, entryId);\n            String name = null;\n\n            {\n                ActionProvider actionProvider = getActionProvider();\n                if (actionProvider != null && actionProvider.mayFindById(entryId))\n                    name = actionProvider.getDefaultName(entryId);\n            }\n            {\n                FilterProvider filterProvider = getFilterProvider();\n                if (filterProvider != null && filterProvider.mayFindById(entryId))\n                    name = filterProvider.getDefaultName(entryId);\n            }\n\n            if (name != null) {\n                entry.setName(name);\n            } else {\n                // can't find the default name. Reload providers and hope to get the name\n                reloadProviders();\n            }\n\n            return name;\n        } else {\n            DBHelper.setCustomStaticEntryName(context, entryId, newName);\n            return newName;\n        }\n    }\n\n    public void removeRenameApp(String componentName, String defaultName) {\n        final Context context = getContext();\n        DBHelper.removeCustomAppName(context, componentName, defaultName);\n    }\n\n    public void setCachedAppIcon(String componentName, Bitmap bitmap) {\n        byte[] array = Utilities.bitmapToByteArray(bitmap);\n        if (array == null) {\n            Log.e(TAG, \"bitmapToByteArray failed for `\" + componentName + \"` with bitmap \" + bitmap);\n            return;\n        }\n        final Context context = getContext();\n        if (!DBHelper.setCachedAppIcon(context, componentName, array)) {\n            Log.w(TAG, \"setCachedAppIcon failed for `\" + componentName + \"` with bitmap \" + bitmap);\n        }\n    }\n\n    @Nullable\n    public AppRecord setCustomAppIcon(String componentName, Bitmap bitmap) {\n        byte[] array = Utilities.bitmapToByteArray(bitmap);\n        if (array == null) {\n            Log.e(TAG, \"bitmapToByteArray failed for `\" + componentName + \"` with bitmap \" + bitmap);\n            return null;\n        }\n        final Context context = getContext();\n        return DBHelper.setCustomAppIcon(context, componentName, array);\n    }\n\n    public void setCustomStaticEntryIcon(String entryId, Bitmap bitmap) {\n        final Context context = getContext();\n        byte[] array = Utilities.bitmapToByteArray(bitmap);\n        if (array != null) {\n            DBHelper.setCustomStaticEntryIcon(context, entryId, array);\n            // reload provider to make sure we're up to date\n            ModProvider modProvider = getModProvider();\n            if (modProvider != null)\n                modProvider.reload(true);\n        }\n    }\n\n    public void setCustomButtonIcon(String buttonId, Bitmap bitmap) {\n        final Context context = getContext();\n        byte[] array = Utilities.bitmapToByteArray(bitmap);\n        if (array != null) {\n            DBHelper.setCustomStaticEntryIcon(context, buttonId, array);\n            // we expect calling function to refresh buttons\n        }\n    }\n\n    public Bitmap getCachedAppIcon(String componentName) {\n        final Context context = getContext();\n        byte[] bytes = DBHelper.getCachedAppIcon(context, componentName);\n        if (bytes == null)\n            return null;\n        return BitmapFactory.decodeByteArray(bytes, 0, bytes.length);\n    }\n\n    public Bitmap getCustomAppIcon(String componentName) {\n        final Context context = getContext();\n        byte[] bytes = DBHelper.getCustomAppIcon(context, componentName);\n        if (bytes == null)\n            return null;\n        return BitmapFactory.decodeByteArray(bytes, 0, bytes.length);\n    }\n\n    public AppRecord removeCustomAppIcon(String componentName) {\n        final Context context = getContext();\n        return DBHelper.removeCustomAppIcon(context, componentName);\n    }\n\n    public void removeCustomStaticEntryIcon(String entryId) {\n        final Context context = getContext();\n        DBHelper.removeCustomStaticEntryIcon(context, entryId);\n    }\n\n    public void removeCustomButtonIcon(String buttonId) {\n        final Context context = getContext();\n        DBHelper.removeMod(context, buttonId);\n    }\n\n    public Bitmap getCustomEntryIconById(@NonNull String entryId) {\n        final Context context = getContext();\n        byte[] bytes = DBHelper.getCustomFavIcon(context, entryId);\n        if (bytes == null)\n            return null;\n        return BitmapFactory.decodeByteArray(bytes, 0, bytes.length);\n    }\n\n    public void renameShortcut(ShortcutEntry shortcutEntry, String newName) {\n        final Context context = getContext();\n        DBHelper.renameShortcut(context, shortcutEntry, newName);\n    }\n\n    public void onProviderRecreated(Provider<? extends EntryItem> provider) {\n        mFullLoadOverSent = false;\n        final Context context = getContext();\n\n        IntentFilter intentFilter = new IntentFilter(TBLauncherActivity.LOAD_OVER);\n        ActivityCompat.registerReceiver(context, this, intentFilter, ContextCompat.RECEIVER_NOT_EXPORTED);\n\n        sendBroadcast(context, TBLauncherActivity.START_LOAD, provider.getClass().getSimpleName());\n\n        // reload providers for the next steps\n        for (int step : IProvider.LOAD_STEPS) {\n            if (step <= provider.getLoadStep())\n                continue;\n            for (ProviderEntry entry : this.providers.values()) {\n                if (entry.provider != null && step == entry.provider.getLoadStep())\n                    entry.provider.setDirty();\n            }\n        }\n    }\n\n    /**\n     * Reload all providers with load step equal or greater\n     *\n     * @param loadStep to compare\n     */\n    public void reloadProviders(int loadStep) {\n        mFullLoadOverSent = false;\n        final Context context = getContext();\n        mTimer.start();\n\n        IntentFilter intentFilter = new IntentFilter(TBLauncherActivity.LOAD_OVER);\n        ActivityCompat.registerReceiver(context, this, intentFilter, ContextCompat.RECEIVER_NOT_EXPORTED);\n\n        sendBroadcast(context, TBLauncherActivity.START_LOAD, \"reload_\" + loadStep);\n\n        for (int step : IProvider.LOAD_STEPS) {\n            if (step < loadStep)\n                continue;\n            for (ProviderEntry entry : providers.values()) {\n                if (entry.provider != null && step == entry.provider.getLoadStep())\n                    entry.provider.reload(true);\n            }\n        }\n    }\n\n    public void reloadProviders() {\n        mFullLoadOverSent = false;\n        final Context context = getContext();\n        mTimer.start();\n\n        IntentFilter intentFilter = new IntentFilter(TBLauncherActivity.LOAD_OVER);\n        ActivityCompat.registerReceiver(context, this, intentFilter, ContextCompat.RECEIVER_NOT_EXPORTED);\n\n        sendBroadcast(context, TBLauncherActivity.START_LOAD, \"reload\");\n\n        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);\n        toggleableProviders(prefs);\n\n        for (String providerName : PROVIDER_NAMES) {\n            if (prefs.getBoolean(\"enable-\" + providerName, true)) {\n                connectToProvider(providerName, 0);\n            }\n        }\n\n        for (int step : IProvider.LOAD_STEPS) {\n            for (ProviderEntry entry : providers.values()) {\n                if (entry.provider != null && step == entry.provider.getLoadStep())\n                    entry.provider.reload(true);\n            }\n        }\n    }\n\n    public void checkServices() {\n        final Context context = getContext();\n        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);\n        for (String providerName : PROVIDER_NAMES) {\n            if (!providers.containsKey(providerName) && prefs.getBoolean(\"enable-\" + providerName, true)) {\n                reloadProviders();\n                break;\n            }\n        }\n    }\n\n    public void setQuickList(Iterable<String> records) {\n        final Context context = getContext();\n\n        List<ModRecord> oldFav = getMods();\n        int pos = 1;\n        for (String record : records) {\n            // remove from oldFav the current record\n            for (Iterator<ModRecord> iterator = oldFav.iterator(); iterator.hasNext(); ) {\n                ModRecord modRecord = iterator.next();\n                if (modRecord.record.equals(record))\n                    iterator.remove();\n            }\n            String position = String.format(\"%08x\", pos);\n            if (!DBHelper.updateQuickListPosition(context, record, position)) {\n                ModRecord modRecord = new ModRecord();\n                modRecord.record = record;\n                modRecord.addFlags(ModRecord.FLAG_SHOW_IN_QUICK_LIST);\n                modRecord.position = position;\n                DBHelper.setMod(context, modRecord);\n            }\n            pos += 11;\n        }\n\n        // keep only entries that have mods and remove from quick list flag from oldFav\n        for (ModRecord modRecord : oldFav) {\n            if (modRecord.isInQuickList()) {\n                modRecord.clearFlags(ModRecord.FLAG_SHOW_IN_QUICK_LIST);\n                if (modRecord.canBeCulled())\n                    DBHelper.removeMod(context, modRecord.record);\n                else\n                    DBHelper.setMod(context, modRecord);\n            } else if (modRecord.canBeCulled()) {\n                DBHelper.removeMod(context, modRecord.record);\n            }\n        }\n\n        // refresh relevant providers\n        {\n            IProvider<?> provider = getModProvider();\n            if (provider != null)\n                provider.reload(true);\n        }\n        {\n            IProvider<?> provider = getTagsProvider();\n            if (provider != null)\n                provider.reload(true);\n        }\n        {\n            IProvider<?> provider = getQuickListProvider();\n            if (provider != null)\n                provider.reload(true);\n        }\n    }\n\n    public boolean fullLoadOverSent() {\n        return mFullLoadOverSent;\n    }\n\n    public void runAfterLoadOver(@NonNull Runnable task) {\n        synchronized (this) {\n            if (mFullLoadOverSent)\n                task.run();\n            else\n                mAfterLoadOverTasks.add(task);\n        }\n    }\n\n    public void executeAfterLoadOverTasks() {\n        synchronized (this) {\n            checkServices();\n            if (!mFullLoadOverSent) {\n                Log.e(TAG, \"executeAfterLoadOverTasks called before mFullLoadOverSent==true\");\n                return;\n            }\n            Log.d(TAG, \"executeAfterLoadOverTasks size=\" + mAfterLoadOverTasks.size());\n            // run and remove tasks\n            int count = 0;\n            Runnable task;\n            while (null != (task = mAfterLoadOverTasks.poll())) {\n                task.run();\n                count += 1;\n            }\n            Log.d(TAG, \"executeAfterLoadOverTasks count=\" + count);\n        }\n    }\n\n    static final class ProviderEntry {\n        public IProvider<?> provider = null;\n        ServiceConnection connection = null;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/handler/IconsHandler.java",
    "content": "package rocks.tbog.tblauncher.handler;\n\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.SharedPreferences;\nimport android.content.pm.ApplicationInfo;\nimport android.content.pm.PackageInfo;\nimport android.content.pm.PackageManager;\nimport android.content.pm.PackageManager.NameNotFoundException;\nimport android.content.pm.ResolveInfo;\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.graphics.Canvas;\nimport android.graphics.Path;\nimport android.graphics.drawable.BitmapDrawable;\nimport android.graphics.drawable.ColorDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.os.Build;\nimport android.util.Log;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.WorkerThread;\nimport androidx.core.content.pm.PackageInfoCompat;\nimport androidx.core.content.res.ResourcesCompat;\nimport androidx.preference.PreferenceManager;\n\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.List;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.TBLauncherActivity;\nimport rocks.tbog.tblauncher.WorkAsync.RunnableTask;\nimport rocks.tbog.tblauncher.db.AppRecord;\nimport rocks.tbog.tblauncher.drawable.DrawableUtils;\nimport rocks.tbog.tblauncher.drawable.TextDrawable;\nimport rocks.tbog.tblauncher.entry.AppEntry;\nimport rocks.tbog.tblauncher.entry.ContactEntry;\nimport rocks.tbog.tblauncher.entry.DialContactEntry;\nimport rocks.tbog.tblauncher.entry.SearchEntry;\nimport rocks.tbog.tblauncher.entry.ShortcutEntry;\nimport rocks.tbog.tblauncher.entry.StaticEntry;\nimport rocks.tbog.tblauncher.icons.DrawableInfo;\nimport rocks.tbog.tblauncher.icons.IconPack;\nimport rocks.tbog.tblauncher.icons.IconPackXML;\nimport rocks.tbog.tblauncher.icons.SystemIconPack;\nimport rocks.tbog.tblauncher.utils.Timer;\nimport rocks.tbog.tblauncher.utils.UIColors;\nimport rocks.tbog.tblauncher.utils.UISizes;\nimport rocks.tbog.tblauncher.utils.UserHandleCompat;\nimport rocks.tbog.tblauncher.utils.Utilities;\n\n/**\n * Inspired from http://stackoverflow.com/questions/31490630/how-to-load-icon-from-icon-pack\n */\n\npublic class IconsHandler {\n\n    private static final String TAG = \"IconsHandler\";\n    // map with available icons packs\n    private final HashMap<String, String> mIconPackNames = new HashMap<>();\n    private final Context ctx;\n\n    private int mContactsShape = DrawableUtils.SHAPE_NONE;\n    private int mShortcutsShape = DrawableUtils.SHAPE_NONE;\n    private IconPackXML mIconPack = null;\n    private SystemIconPack mSystemPack = new SystemIconPack();\n    private boolean mForceAdaptive;\n    private boolean mForceShape;\n    private boolean mContactPackMask;\n    private boolean mShortcutPackMask;\n    private boolean mShortcutBadgePackMask;\n    private RunnableTask mLoadIconsPackTask = null;\n\n    public IconsHandler(Context ctx) {\n        super();\n        this.ctx = ctx;\n\n        loadAvailableIconsPacks();\n        onPrefChanged(PreferenceManager.getDefaultSharedPreferences(ctx));\n    }\n\n    /**\n     * Set values from preferences\n     */\n    public void onPrefChanged(SharedPreferences pref) {\n        loadIconsPack(pref.getString(\"icons-pack\", null));\n        mSystemPack.setAdaptiveShape(getAdaptiveShape(pref, \"adaptive-shape\"));\n        mForceAdaptive = pref.getBoolean(\"force-adaptive\", true);\n        mForceShape = pref.getBoolean(\"force-shape\", true);\n\n        mContactPackMask = pref.getBoolean(\"contact-pack-mask\", true);\n        mContactsShape = getAdaptiveShape(pref, \"contacts-shape\");\n\n        mShortcutPackMask = pref.getBoolean(\"shortcut-pack-mask\", true);\n        mShortcutsShape = getAdaptiveShape(pref, \"shortcut-shape\");\n\n        mShortcutBadgePackMask = pref.getBoolean(\"shortcut-pack-badge-mask\", true);\n    }\n\n    private static int getAdaptiveShape(SharedPreferences pref, String key) {\n        try {\n            return Integer.parseInt(pref.getString(key, null));\n        } catch (Exception ignored) {\n        }\n        return DrawableUtils.SHAPE_NONE;\n    }\n\n    /**\n     * Parse icons pack metadata\n     *\n     * @param packageName Android package ID of the package to parse\n     */\n    private void loadIconsPack(@Nullable String packageName) {\n        // system icons, nothing to do\n        if (packageName == null || packageName.equalsIgnoreCase(\"default\")) {\n            mIconPack = null;\n            return;\n        }\n\n        // don't reload the icon pack\n        if (mIconPack == null\n            || !mIconPack.getPackPackageName().equals(packageName)\n            || (mLoadIconsPackTask == null && !mIconPack.isLoaded())) {\n            if (mLoadIconsPackTask != null) {\n                mLoadIconsPackTask.cancel();\n                mLoadIconsPackTask = null;\n            }\n            final IconPackXML iconPack = TBApplication.iconPackCache(ctx).getIconPack(packageName);\n\n            // timer start\n            Timer timer = Timer.startMilli();\n\n            Log.i(TAG, \"[start] loading default icon pack: \" + packageName);\n            // set the current icon pack\n            mIconPack = iconPack;\n            // start async loading\n            mLoadIconsPackTask = Utilities.runAsync((task) -> {\n                if (task == mLoadIconsPackTask)\n                    iconPack.load(ctx.getPackageManager());\n                if (iconPack.isLoaded()) {\n                    PackageInfo packageInfo;\n                    try {\n                        packageInfo = ctx.getPackageManager().getPackageInfo(iconPack.getPackPackageName(), 0);\n                    } catch (NameNotFoundException ignored) {\n                        packageInfo = null;\n                    }\n                    long version = packageInfo != null ? PackageInfoCompat.getLongVersionCode(packageInfo) : 0;\n                    TBApplication.dataHandler(ctx).runAfterLoadOver(() -> Utilities.runAsync(() -> cacheAppIcons(version)));\n                }\n            }, (task) -> {\n                // timer end\n                timer.stop();\n\n                if (!task.isCancelled() && task == mLoadIconsPackTask) {\n                    Log.i(TAG, \"[end] loading default icon pack: \" + packageName);\n\n                    Log.i(\"time\", timer + \" to load icon pack \" + packageName);\n                    mLoadIconsPackTask = null;\n                    TBLauncherActivity activity = TBApplication.launcherActivity(ctx);\n                    if (activity != null) {\n                        activity.refreshSearchRecords();\n                        activity.queueDockReload();\n                    }\n                }\n            });\n        }\n    }\n\n    private void cacheAppIcons(long cacheVersion) {\n        if (mIconPack == null) {\n            Log.e(TAG, \"mIconPack==null and we want to cache icons?\");\n            return;\n        }\n        if (!mIconPack.isLoaded()) {\n            Log.e(TAG, \"icon pack `\" + mIconPack.getPackPackageName() + \"` not loaded and we want to cache icons?\");\n            return;\n        }\n        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);\n        final long version = prefs.getLong(\"cached-app-icons-version\", -1);\n        final String packName = prefs.getString(\"cached-app-icons-pack\", \"\");\n\n        // check icon pack name and version\n        if (version == cacheVersion && packName.equals(mIconPack.getPackPackageName())) {\n            Log.i(TAG, \"cached app icons `\" + packName + \"` v\" + version + \" found. Skip cache build.\");\n            return;\n        }\n\n        // we add it to the run queue to make sure we run it synchronized\n        TBApplication.appsHandler(ctx).runWhenLoaded(() -> {\n            Collection<AppEntry> appEntries = TBApplication.appsHandler(ctx).getAllApps();\n            DataHandler dataHandler = TBApplication.dataHandler(ctx);\n            // build the cache\n            for (AppEntry appEntry : appEntries) {\n                Drawable drawable = getDrawableIconForPackage(appEntry.componentName, UserHandleCompat.CURRENT_USER);\n                Bitmap bitmap = getIconBitmap(ctx, drawable);\n                dataHandler.setCachedAppIcon(appEntry.getUserComponentName(), bitmap);\n            }\n\n            // save icon pack name and version\n            prefs.edit()\n                .putLong(\"cached-app-icons-version\", cacheVersion)\n                .putString(\"cached-app-icons-pack\", mIconPack.getPackPackageName())\n                .apply();\n\n            Log.i(TAG, \"cached app icons changed from \" +\n                \"`\" + packName + \"` v\" + version + \" to \" +\n                \"`\" + mIconPack.getPackPackageName() + \"` v\" + cacheVersion);\n\n        });\n    }\n\n    public void resetCachedAppIcons() {\n        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);\n        prefs.edit()\n            .putLong(\"cached-app-icons-version\", 0)\n            .putString(\"cached-app-icons-pack\", \"\")\n            .apply();\n    }\n\n    /**\n     * Get or generate icon for an app\n     */\n    @WorkerThread\n    @NonNull\n    public IconInfo getIconForPackage(ComponentName componentName, UserHandleCompat userHandle) {\n        IconInfo icon = new IconInfo();\n        // check the icon pack for a resource\n        if (mIconPack != null) {\n            // just checking will make this thread wait for the icon pack to load\n            if (!mIconPack.isLoaded()) {\n                String componentString = componentName.toString();\n                if (mLoadIconsPackTask == null) {\n                    Log.w(TAG, \"icon pack `\" + mIconPack.getPackPackageName() + \"` not loaded, reload\");\n                    SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(ctx);\n                    loadIconsPack(pref.getString(\"icons-pack\", null));\n                    return icon.setCachedAppIcon(getCachedAppIcon(componentString));\n                }\n\n                Drawable cachedIcon = getCachedAppIcon(componentString);\n                if (cachedIcon != null) {\n                    Log.i(TAG, \"icon pack `\" + mIconPack.getPackPackageName() + \"` not loaded, cached icon used\");\n                    return icon.setCachedAppIcon(cachedIcon);\n                }\n\n                Log.w(TAG, \"icon pack `\" + mIconPack.getPackPackageName() + \"` not loaded, wait\");\n                try {\n                    mLoadIconsPackTask.wait();\n                } catch (Exception ignored) {\n                }\n                if (!mIconPack.isLoaded()) {\n                    Log.e(TAG, \"icon pack `\" + mIconPack.getPackPackageName() + \"` waiting failed to load\");\n                    return icon;\n                }\n            }\n            String componentString = componentName.toString();\n            DrawableInfo info = mIconPack.getComponentDrawable(componentString);\n            if (info != null && info.isDynamic())\n                icon.setDynamic();\n            Drawable drawable = mIconPack.getDrawable(info);\n            if (drawable != null) {\n                if (DrawableUtils.isAdaptiveIconDrawable(drawable) || mForceAdaptive) {\n                    int shape = mSystemPack.getAdaptiveShape();\n                    drawable = DrawableUtils.applyIconMaskShape(ctx, drawable, shape, true);\n                    return icon.setAdaptiveIcon(drawable);\n                } else {\n                    //drawable = mIconPack.applyBackgroundAndMask(ctx, drawable, false);\n                    return icon.setFitInside(false).setNonAdaptiveIcon(drawable);\n                }\n            }\n        }\n\n        // if icon pack doesn't have the drawable, use system drawable\n        Drawable systemIcon = mSystemPack.getComponentDrawable(ctx, componentName, userHandle);\n        if (systemIcon == null)\n            return icon;\n\n        if (mSystemPack.isComponentDynamic(componentName))\n            icon.setDynamic();\n\n        // if the icon pack has a mask, use that instead of the adaptive shape\n        if (mIconPack != null && mIconPack.hasMask() && !mForceShape) {\n            Drawable drawable = mIconPack.applyBackgroundAndMask(ctx, systemIcon, false);\n            return icon.setPackMask().setFitInside(false).setNonAdaptiveIcon(drawable);\n        }\n\n        boolean fitInside = mForceAdaptive || !mForceShape;\n        Drawable drawable = mSystemPack.applyBackgroundAndMask(ctx, systemIcon, fitInside);\n        return icon.setFitInside(fitInside).setNonAdaptiveIcon(drawable);\n    }\n\n    /**\n     * Get or generate icon for an app\n     */\n    @WorkerThread\n    @Nullable\n    public Drawable getDrawableIconForPackage(ComponentName componentName, UserHandleCompat userHandle) {\n        IconInfo icon = getIconForPackage(componentName, userHandle);\n        return icon.getDrawable();\n    }\n\n    /**\n     * Get or generate icon to use as a badge for an app\n     */\n    @WorkerThread\n    public Drawable getDrawableBadgeForPackage(ComponentName componentName, UserHandleCompat userHandle) {\n        // check the icon pack for a resource\n        if (mIconPack != null) {\n            // just checking will make this thread wait for the icon pack to load\n            if (!mIconPack.isLoaded())\n                return null;\n            String componentString = componentName.toString();\n            DrawableInfo info = mIconPack.getComponentDrawable(componentString);\n            Drawable drawable = mIconPack.getDrawable(info);\n            if (drawable != null) {\n                if (DrawableUtils.isAdaptiveIconDrawable(drawable) || mForceAdaptive) {\n                    int shape = mSystemPack.getAdaptiveShape();\n                    return DrawableUtils.applyIconMaskShape(ctx, drawable, shape, true);\n                } else\n                    return mIconPack.applyBackgroundAndMask(ctx, drawable, false);\n            }\n        }\n\n        // if icon pack doesn't have the drawable, use system drawable\n        Drawable systemIcon = mSystemPack.getComponentDrawable(ctx, componentName, userHandle);\n        if (systemIcon == null)\n            return null;\n\n        // if the icon pack has a mask, use that instead of the adaptive shape\n        if (mShortcutBadgePackMask && mIconPack != null && mIconPack.hasMask())\n            return mIconPack.applyBackgroundAndMask(ctx, systemIcon, false);\n\n        boolean fitInside = mForceAdaptive || !mForceShape;\n        return mSystemPack.applyBackgroundAndMask(ctx, systemIcon, fitInside);\n    }\n\n    /**\n     * Scan for installed icons packs\n     */\n    private void loadAvailableIconsPacks() {\n        PackageManager pm = ctx.getPackageManager();\n\n        List<ResolveInfo> launcherThemes = pm.queryIntentActivities(new Intent(\"org.adw.launcher.THEMES\"), PackageManager.GET_META_DATA);\n\n        for (ResolveInfo ri : launcherThemes) {\n            String packageName = ri.activityInfo.packageName;\n            try {\n                ApplicationInfo ai = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);\n                String name = pm.getApplicationLabel(ai).toString();\n                mIconPackNames.put(packageName, name);\n            } catch (NameNotFoundException e) {\n                // shouldn't happen\n                Log.e(TAG, \"Unable to find package \" + packageName, e);\n            }\n        }\n    }\n\n    public HashMap<String, String> getIconPackNames() {\n        return mIconPackNames;\n    }\n\n    @Nullable\n    public IconPackXML getCustomIconPack() {\n        return mIconPack;\n    }\n\n    @NonNull\n    public SystemIconPack getSystemIconPack() {\n        return mSystemPack;\n    }\n\n    @NonNull\n    public IconPack<?> getIconPack() {\n        return mIconPack != null ? mIconPack : mSystemPack;\n    }\n\n    public Drawable getCustomIcon(StaticEntry staticEntry) {\n        Bitmap bitmap = TBApplication.dataHandler(ctx).getCustomEntryIconById(staticEntry.id);\n        if (bitmap != null)\n            return new BitmapDrawable(ctx.getResources(), bitmap);\n\n        Log.e(TAG, \"Unable to get custom icon for \" + staticEntry.id);\n        return null;\n    }\n\n    public Drawable getCustomIcon(SearchEntry searchEntry) {\n        Bitmap bitmap = TBApplication.dataHandler(ctx).getCustomEntryIconById(searchEntry.id);\n        if (bitmap != null)\n            return new BitmapDrawable(ctx.getResources(), bitmap);\n\n        Log.e(TAG, \"Unable to get custom icon for \" + searchEntry.id);\n        return null;\n    }\n\n    public Drawable getCustomIcon(ShortcutEntry shortcutEntry) {\n        Bitmap bitmap = TBApplication.dataHandler(ctx).getCustomEntryIconById(shortcutEntry.id);\n        if (bitmap != null)\n            return new BitmapDrawable(ctx.getResources(), bitmap);\n\n        Log.e(TAG, \"Unable to get custom icon for \" + shortcutEntry.id);\n        return null;\n    }\n\n    public Drawable getCustomIcon(ContactEntry contactEntry) {\n        Bitmap bitmap = TBApplication.dataHandler(ctx).getCustomEntryIconById(contactEntry.id);\n        if (bitmap != null)\n            return new BitmapDrawable(ctx.getResources(), bitmap);\n\n        Log.e(TAG, \"Unable to get custom icon for \" + contactEntry.id);\n        return null;\n    }\n\n    @Nullable\n    public Drawable getButtonIcon(@NonNull String buttonId) {\n        Bitmap bitmap = TBApplication.dataHandler(ctx).getCustomEntryIconById(buttonId);\n        if (bitmap != null)\n            return new BitmapDrawable(ctx.getResources(), bitmap);\n        return null;\n    }\n\n    @WorkerThread\n    public Drawable getCachedAppIcon(String componentName) {\n        Bitmap bitmap = TBApplication.dataHandler(ctx).getCachedAppIcon(componentName);\n        if (bitmap != null)\n            return new BitmapDrawable(ctx.getResources(), bitmap);\n\n        Log.e(TAG, \"Unable to get cached app icon for \" + componentName);\n        return null;\n    }\n\n    @WorkerThread\n    public Drawable getCustomIcon(String componentName) {\n        Bitmap bitmap = TBApplication.dataHandler(ctx).getCustomAppIcon(componentName);\n        if (bitmap != null)\n            return new BitmapDrawable(ctx.getResources(), bitmap);\n\n        Log.e(TAG, \"Unable to get custom icon for \" + componentName);\n        return null;\n    }\n\n    private static Bitmap getIconBitmap(Context ctx, Drawable drawable) {\n        if (drawable instanceof TextDrawable) {\n            int size = UISizes.getResultIconSize(ctx);\n            return DrawableUtils.drawableToBitmap(drawable, size, size);\n        }\n        return Utilities.drawableToBitmap(drawable);\n    }\n\n    public void changeIcon(AppEntry appEntry, Drawable drawable) {\n        Bitmap bitmap = getIconBitmap(ctx, drawable);\n        TBApplication app = TBApplication.getApplication(ctx);\n        AppRecord appRecord = app.getDataHandler().setCustomAppIcon(appEntry.getUserComponentName(), bitmap);\n\n        app.drawableCache().cacheDrawable(appEntry.getIconCacheId(), null);\n        appEntry.setCustomIcon(appRecord.dbId);\n        app.drawableCache().cacheDrawable(appEntry.getIconCacheId(), drawable);\n    }\n\n    public void changeIcon(ShortcutEntry shortcutEntry, Drawable drawable) {\n        Bitmap bitmap = getIconBitmap(ctx, drawable);\n        TBApplication app = TBApplication.getApplication(ctx);\n        app.getDataHandler().setCustomStaticEntryIcon(shortcutEntry.id, bitmap);\n\n        app.drawableCache().cacheDrawable(shortcutEntry.getIconCacheId(), null);\n        shortcutEntry.setCustomIcon();\n        app.drawableCache().cacheDrawable(shortcutEntry.getIconCacheId(), drawable);\n    }\n\n    public void changeIcon(StaticEntry staticEntry, Drawable drawable) {\n        Bitmap bitmap = getIconBitmap(ctx, drawable);\n        TBApplication app = TBApplication.getApplication(ctx);\n        app.getDataHandler().setCustomStaticEntryIcon(staticEntry.id, bitmap);\n\n        app.drawableCache().cacheDrawable(staticEntry.getIconCacheId(), null);\n        staticEntry.setCustomIcon();\n        app.drawableCache().cacheDrawable(staticEntry.getIconCacheId(), drawable);\n    }\n\n    public void changeIcon(SearchEntry searchEntry, Drawable drawable) {\n        Bitmap bitmap = getIconBitmap(ctx, drawable);\n        TBApplication app = TBApplication.getApplication(ctx);\n        app.getDataHandler().setCustomStaticEntryIcon(searchEntry.id, bitmap);\n\n        app.drawableCache().cacheDrawable(searchEntry.getIconCacheId(), null);\n        searchEntry.setCustomIcon();\n        app.drawableCache().cacheDrawable(searchEntry.getIconCacheId(), drawable);\n    }\n\n    public void changeIcon(DialContactEntry dialContactEntry, Drawable drawable) {\n        Bitmap bitmap = getIconBitmap(ctx, drawable);\n        TBApplication app = TBApplication.getApplication(ctx);\n        app.getDataHandler().setCustomStaticEntryIcon(DialContactEntry.SCHEME, bitmap);\n\n        app.drawableCache().cacheDrawable(dialContactEntry.getIconCacheId(), null);\n        dialContactEntry.setCustomIcon();\n        app.drawableCache().cacheDrawable(dialContactEntry.getIconCacheId(), drawable);\n    }\n\n    public void changeIcon(@NonNull String buttonId, Drawable drawable) {\n        Bitmap bitmap = getIconBitmap(ctx, drawable);\n        TBApplication app = TBApplication.getApplication(ctx);\n        app.getDataHandler().setCustomButtonIcon(buttonId, bitmap);\n        // we expect calling function to refresh buttons\n        app.drawableCache().cacheDrawable(buttonId, drawable);\n    }\n\n    public void restoreDefaultIcon(AppEntry appEntry) {\n        TBApplication app = TBApplication.getApplication(ctx);\n        app.getDataHandler().removeCustomAppIcon(appEntry.getUserComponentName());\n\n        app.drawableCache().cacheDrawable(appEntry.getIconCacheId(), null);\n        appEntry.clearCustomIcon();\n    }\n\n    public void restoreDefaultIcon(ShortcutEntry shortcutEntry) {\n        TBApplication app = TBApplication.getApplication(ctx);\n        app.getDataHandler().removeCustomStaticEntryIcon(shortcutEntry.id);\n\n        app.drawableCache().cacheDrawable(shortcutEntry.getIconCacheId(), null);\n        shortcutEntry.clearCustomIcon();\n    }\n\n    public void restoreDefaultIcon(StaticEntry staticEntry) {\n        TBApplication app = TBApplication.getApplication(ctx);\n        app.getDataHandler().removeCustomStaticEntryIcon(staticEntry.id);\n\n        app.drawableCache().cacheDrawable(staticEntry.getIconCacheId(), null);\n        staticEntry.clearCustomIcon();\n    }\n\n    public void restoreDefaultIcon(SearchEntry searchEntry) {\n        TBApplication app = TBApplication.getApplication(ctx);\n        app.getDataHandler().removeCustomStaticEntryIcon(searchEntry.id);\n\n        app.drawableCache().cacheDrawable(searchEntry.getIconCacheId(), null);\n        searchEntry.clearCustomIcon();\n    }\n\n    public void restoreDefaultIcon(DialContactEntry dialContactEntry) {\n        TBApplication app = TBApplication.getApplication(ctx);\n        app.getDataHandler().removeCustomStaticEntryIcon(DialContactEntry.SCHEME);\n\n        app.drawableCache().cacheDrawable(dialContactEntry.getIconCacheId(), null);\n        dialContactEntry.clearCustomIcon();\n    }\n\n    public void restoreDefaultIcon(@NonNull String buttonId) {\n        TBApplication app = TBApplication.getApplication(ctx);\n        app.getDataHandler().removeCustomButtonIcon(buttonId);\n\n        app.drawableCache().cacheDrawable(buttonId, null);\n    }\n\n    public Drawable applyContactMask(@NonNull Context ctx, @NonNull Drawable drawable) {\n        if (!mContactPackMask)\n            return DrawableUtils.applyIconMaskShape(ctx, drawable, mContactsShape, false);\n        if (mIconPack != null && mIconPack.hasMask())\n            return mIconPack.applyBackgroundAndMask(ctx, drawable, false);\n        // if pack has no mask, make it a circle\n        int size = ctx.getResources().getDimensionPixelSize(R.dimen.icon_size);\n        Bitmap b = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);\n        Canvas c = new Canvas(b);\n        Path path = new Path();\n        int h = size / 2;\n        path.addCircle(h, h, h, Path.Direction.CCW);\n        c.clipPath(path);\n        drawable.setBounds(0, 0, c.getWidth(), c.getHeight());\n        drawable.draw(c);\n        return new BitmapDrawable(ctx.getResources(), b);\n    }\n\n    public Drawable applyShortcutMask(@NonNull Context ctx, Bitmap bitmap) {\n        Drawable drawable = new BitmapDrawable(ctx.getResources(), bitmap);\n        if (!mShortcutPackMask)\n            return DrawableUtils.applyIconMaskShape(ctx, drawable, mShortcutsShape, true);\n        if (mIconPack != null && mIconPack.hasMask())\n            return mIconPack.applyBackgroundAndMask(ctx, drawable, false);\n        return drawable;\n    }\n\n    @NonNull\n    public Drawable getDefaultActivityIcon(Context context) {\n        Resources resources = context.getResources();\n\n        int iconId = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)\n            ? android.R.drawable.sym_def_app_icon\n            : android.R.mipmap.sym_def_app_icon;\n\n        Drawable d = null;\n        try {\n            d = ResourcesCompat.getDrawable(resources, iconId, context.getTheme());\n        } catch (Resources.NotFoundException ignored) {\n        }\n\n        return (d == null) ? new ColorDrawable(UIColors.getDefaultColor(context)) : d;\n    }\n\n    public static class IconInfo {\n        private Drawable drawable = null;\n        private boolean isDynamic = false;\n        private Boolean fitInside = null;\n\n        public void setDynamic() {\n            isDynamic = true;\n        }\n\n        public boolean isDynamic() {\n            return isDynamic;\n        }\n\n        public IconInfo setCachedAppIcon(Drawable cachedAppIcon) {\n            drawable = cachedAppIcon;\n            return this;\n        }\n\n        public Drawable getDrawable() {\n            return drawable;\n        }\n\n        public IconInfo setAdaptiveIcon(Drawable drawable) {\n            this.drawable = drawable;\n            return this;\n        }\n\n        public IconInfo setNonAdaptiveIcon(Drawable drawable) {\n            this.drawable = drawable;\n            return this;\n        }\n\n        public IconInfo setPackMask() {\n            return this;\n        }\n\n        public IconInfo setFitInside(boolean fitInside) {\n            this.fitInside = fitInside;\n            return this;\n        }\n\n        public Boolean getFitInside() {\n            return fitInside;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/handler/TagsHandler.java",
    "content": "package rocks.tbog.tblauncher.handler;\n\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.SharedPreferences;\nimport android.content.pm.PackageManager;\nimport android.content.pm.ResolveInfo;\nimport android.content.res.Resources;\nimport android.os.Build;\nimport android.util.Log;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.preference.PreferenceManager;\n\nimport java.util.ArrayDeque;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.dataprovider.TagsProvider;\nimport rocks.tbog.tblauncher.db.DBHelper;\nimport rocks.tbog.tblauncher.entry.AppEntry;\nimport rocks.tbog.tblauncher.entry.EntryItem;\nimport rocks.tbog.tblauncher.entry.EntryWithTags;\nimport rocks.tbog.tblauncher.entry.TagEntry;\nimport rocks.tbog.tblauncher.ui.ListPopup;\nimport rocks.tbog.tblauncher.ui.TagsMenuUtils;\nimport rocks.tbog.tblauncher.utils.PrefOrderedListHelper;\nimport rocks.tbog.tblauncher.utils.Timer;\nimport rocks.tbog.tblauncher.utils.UserHandleCompat;\nimport rocks.tbog.tblauncher.utils.Utilities;\n\npublic class TagsHandler {\n    private static final String TAG = TagsHandler.class.getSimpleName();\n    private final TBApplication mApplication;\n    // HashMap with EntryItem id as key and an ArrayList of tags for each\n    private final HashMap<String, List<String>> mTagsCache = new HashMap<>();\n    private boolean mIsLoaded = false;\n    private final ArrayDeque<Runnable> mAfterLoadedTasks = new ArrayDeque<>(2);\n\n    public TagsHandler(TBApplication application) {\n        mApplication = application;\n        loadFromDB(false);\n    }\n\n    @Nullable\n    public void loadFromDB(boolean wait) {\n        Log.d(TAG, \"loadFromDB(wait= \" + wait + \" )\");\n\n        synchronized (this) {\n            mIsLoaded = false;\n        }\n        final Timer timer = Timer.startMilli();\n        final HashMap<String, List<String>> tags = new HashMap<>();\n        final Runnable load = () -> {\n            Map<String, List<String>> dbTags = DBHelper.loadTags(getContext());\n            tags.clear();\n            tags.putAll(dbTags);\n        };\n        final Runnable apply = () -> {\n            if (tags.isEmpty()) {\n                mTagsCache.clear();\n                addDefaultAliases();\n                mTagsCache.put(\".\", Collections.singletonList(\"\"));\n                DBHelper.addTags(getContext(), mTagsCache);\n                tags.putAll(mTagsCache);\n            }\n            synchronized (TagsHandler.this) {\n                mTagsCache.clear();\n                mTagsCache.putAll(tags);\n                mIsLoaded = true;\n\n                timer.stop();\n                Log.d(\"time\", \"Time to load all tags: \" + timer);\n\n                // run and remove tasks\n                Runnable task;\n                while (null != (task = mAfterLoadedTasks.poll()))\n                    task.run();\n            }\n        };\n        if (wait) {\n            load.run();\n            apply.run();\n        } else {\n            Utilities.runAsync(\n                    (t) -> load.run(),\n                    (t) -> apply.run());\n        }\n    }\n\n    public void runWhenLoaded(@NonNull Runnable task) {\n        synchronized (this) {\n            if (mIsLoaded)\n                task.run();\n            else\n                mAfterLoadedTasks.add(task);\n        }\n    }\n\n    private Context getContext() {\n        return mApplication;\n    }\n\n    public void addTag(EntryItem entry, String tag) {\n        // add to db\n        DBHelper.addTag(getContext(), tag, entry);\n        // add to cache\n        List<String> tags = mTagsCache.get(entry.id);\n        if (tags == null)\n            mTagsCache.put(entry.id, tags = new ArrayList<>());\n        tags.add(tag);\n    }\n\n    private boolean removeTag(String entryId, String tag) {\n        boolean changesMade = false;\n        // remove from DB\n        if (DBHelper.removeTag(getContext(), tag, entryId) > 0)\n            changesMade = true;\n        // remove from cache\n        List<String> tags = mTagsCache.get(entryId);\n        if (tags != null) {\n            tags.remove(tag);\n            changesMade = true;\n        }\n        return changesMade;\n    }\n\n    public boolean removeTag(String tag) {\n        List<EntryWithTags> entries = getEntries(tag);\n        for (EntryWithTags entry : entries) {\n            if (removeTag(entry.id, tag)) {\n                entry.setTags(getTags(entry.id));\n                return true;\n            }\n        }\n        return false;\n    }\n\n    @NonNull\n    public List<String> getTags(String entryId) {\n        List<String> tags = mTagsCache.get(entryId);\n        if (tags == null) {\n            return Collections.emptyList();\n        }\n        return Collections.unmodifiableList(tags);\n    }\n\n    /**\n     * Get tags currently used\n     *\n     * @return a set of tags\n     */\n    @NonNull\n    public Set<String> getValidTags() {\n        Set<String> tags = new HashSet<>();\n        DataHandler dataHandler = TBApplication.dataHandler(getContext());\n        for (Map.Entry<String, List<String>> entry : mTagsCache.entrySet()) {\n            EntryItem entryItem = dataHandler.getPojo(entry.getKey());\n            if (entryItem != null)\n                tags.addAll(entry.getValue());\n        }\n        tags.remove(\"\");\n        return tags;\n    }\n\n    /**\n     * Filter out entry ids not found from each set. Remove unused tags from the map.\n     *\n     * @param context used for getting DataHandler to check if entryIds are found.\n     * @param tags    map with tag names as keys.\n     */\n    public static void validateTags(@NonNull Context context, Map<String, Set<String>> tags) {\n        tags.remove(\"\");\n        DataHandler dataHandler = TBApplication.dataHandler(context);\n        for (Iterator<Map.Entry<String, Set<String>>> iteratorTagsMap = tags.entrySet().iterator(); iteratorTagsMap.hasNext(); ) {\n            Map.Entry<String, Set<String>> tagsMapEntry = iteratorTagsMap.next();\n            String tagName = tagsMapEntry.getKey();\n            Set<String> entryIdSet = tagsMapEntry.getValue();\n            for (Iterator<String> iteratorEntryId = entryIdSet.iterator(); iteratorEntryId.hasNext(); ) {\n                String entryId = iteratorEntryId.next();\n                EntryItem entryItem = dataHandler.getPojo(entryId);\n                if (entryItem == null)\n                    iteratorEntryId.remove();\n            }\n            if (tagsMapEntry.getValue().isEmpty()) {\n                Log.i(TAG, \"Dropped tag `\" + tagName + \"`\");\n                iteratorTagsMap.remove();\n            }\n        }\n    }\n\n    /**\n     * Get all tags from DB, even if not used\n     *\n     * @return a set of tags\n     */\n    @NonNull\n    public Set<String> getAllTags() {\n        Set<String> allTags = new HashSet<>();\n        for (List<String> tags : mTagsCache.values()) {\n            allTags.addAll(tags);\n        }\n        allTags.remove(\"\");\n        return allTags;\n    }\n\n    @NonNull\n    public List<String> getValidEntryIds(String tagName) {\n        ArrayList<String> ids = new ArrayList<>();\n        DataHandler dataHandler = TBApplication.dataHandler(getContext());\n        for (Map.Entry<String, List<String>> entry : mTagsCache.entrySet()) {\n            if (entry.getValue().contains(tagName)) {\n                EntryItem entryItem = dataHandler.getPojo(entry.getKey());\n                if (entryItem != null)\n                    ids.add(entryItem.id);\n            }\n        }\n        return ids;\n    }\n\n    @NonNull\n    public List<String> getAllEntryIds(String tagName) {\n        ArrayList<String> ids = new ArrayList<>();\n        for (Map.Entry<String, List<String>> entry : mTagsCache.entrySet()) {\n            if (entry.getValue().contains(tagName))\n                ids.add(entry.getKey());\n        }\n        return ids;\n    }\n\n    @NonNull\n    public List<EntryWithTags> getEntries(String tagName) {\n        ArrayList<EntryWithTags> entries = new ArrayList<>();\n        DataHandler dataHandler = TBApplication.dataHandler(getContext());\n        for (Map.Entry<String, List<String>> mapEntry : mTagsCache.entrySet()) {\n            if (mapEntry.getValue().contains(tagName)) {\n                EntryItem entryItem = dataHandler.getPojo(mapEntry.getKey());\n                if (entryItem instanceof EntryWithTags)\n                    entries.add((EntryWithTags) entryItem);\n            }\n        }\n        return entries;\n    }\n\n    @NonNull\n    public ListPopup getTagsMenu(Context ctx) {\n        SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(ctx);\n        ArrayList<String> tagList;\n        List<String> tagOrder = PrefOrderedListHelper.getOrderedList(pref, \"tags-menu-list\", \"tags-menu-order\");\n        if (tagOrder.isEmpty()) {\n            tagList = new ArrayList<>(5);\n            TagsHandler tagsHandler = TBApplication.tagsHandler(ctx);\n            Set<String> validTags = tagsHandler.getValidTags();\n            for (String tagName : validTags) {\n                if (tagList.size() >= 5)\n                    break;\n                tagList.add(tagName);\n            }\n        } else {\n            tagList = new ArrayList<>(tagOrder.size());\n            for (String orderValue : tagOrder)\n                tagList.add(PrefOrderedListHelper.getOrderedValueName(orderValue));\n        }\n        return TagsMenuUtils.createTagsMenu(ctx, tagList);\n    }\n\n    @NonNull\n    public Collection<String> getAllEntryIds() {\n        return Collections.unmodifiableSet(mTagsCache.keySet());\n    }\n\n    private void addDefaultAliases() {\n        Context context = getContext();\n        final PackageManager pm = context.getPackageManager();\n        final Resources res = context.getResources();\n\n        // keep all changes here and apply them after we do all the checks\n        Map<String, List<String>> pendingTags = new HashMap<>();\n\n        String phoneApp = getApp(pm, Intent.ACTION_DIAL);\n        if (phoneApp != null) {\n            String phoneAlias = res.getString(R.string.alias_phone);\n            addAliasesToEntry(phoneAlias, phoneApp, pendingTags);\n        }\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {\n            String contactApp = getAppByCategory(pm, Intent.CATEGORY_APP_CONTACTS);\n            if (contactApp != null) {\n                String contactAlias = res.getString(R.string.alias_contacts);\n                addAliasesToEntry(contactAlias, contactApp, pendingTags);\n            }\n\n            String browserApp = getAppByCategory(pm, Intent.CATEGORY_APP_BROWSER);\n            if (browserApp != null) {\n                String webAlias = res.getString(R.string.alias_web);\n                addAliasesToEntry(webAlias, browserApp, pendingTags);\n            }\n\n            String mailApp = getAppByCategory(pm, Intent.CATEGORY_APP_EMAIL);\n            if (mailApp != null) {\n                String mailAlias = res.getString(R.string.alias_mail);\n                addAliasesToEntry(mailAlias, mailApp, pendingTags);\n            }\n\n            String marketApp = getAppByCategory(pm, Intent.CATEGORY_APP_MARKET);\n            if (marketApp != null) {\n                String marketAlias = res.getString(R.string.alias_market);\n                addAliasesToEntry(marketAlias, marketApp, pendingTags);\n            }\n\n            String messagingApp = getAppByCategory(pm, Intent.CATEGORY_APP_MESSAGING);\n            if (messagingApp != null) {\n                String messagingAlias = res.getString(R.string.alias_messaging);\n                addAliasesToEntry(messagingAlias, messagingApp, pendingTags);\n            }\n\n            String clockApp = getClockApp(pm);\n            if (clockApp != null) {\n                String clockAlias = res.getString(R.string.alias_clock);\n                addAliasesToEntry(clockAlias, clockApp, pendingTags);\n            }\n        }\n\n        // apply all pending changes in the cache\n        for (Map.Entry<String, List<String>> entry : pendingTags.entrySet()) {\n            String entryId = entry.getKey();\n            List<String> tags = mTagsCache.get(entryId);\n            if (tags == null)\n                mTagsCache.put(entryId, tags = new ArrayList<>());\n            tags.addAll(entry.getValue());\n        }\n    }\n\n    private void addAliasesToEntry(String aliases, String entryId, Map<String, List<String>> pendingTags) {\n        //add aliases only if they haven't overridden by the user (not in db)\n        if (!mTagsCache.containsKey(entryId)) {\n            //aliases.replace(\",\", \" \")\n            String[] arr = aliases.split(\",\");\n            List<String> tags = pendingTags.get(entryId);\n            if (tags == null)\n                pendingTags.put(entryId, tags = new ArrayList<>());\n            tags.addAll(Arrays.asList(arr));\n        }\n    }\n\n    private String getApp(PackageManager pm, String action) {\n        Intent lookingFor = new Intent(action, null);\n        return getApp(pm, lookingFor);\n    }\n\n    private String getAppByCategory(PackageManager pm, String category) {\n        Intent lookingFor = new Intent(Intent.ACTION_MAIN, null);\n        lookingFor.addCategory(category);\n        return getApp(pm, lookingFor);\n    }\n\n    private String getApp(PackageManager pm, Intent lookingFor) {\n        List<ResolveInfo> list = pm.queryIntentActivities(lookingFor, 0);\n        if (list.size() == 0) {\n            return null;\n        } else {\n            String packageName = list.get(0).activityInfo.applicationInfo.packageName;\n            String className = list.get(0).activityInfo.name;\n\n            UserHandleCompat user = UserHandleCompat.CURRENT_USER;\n            return AppEntry.generateAppId(packageName, className, user);\n        }\n    }\n\n    private String getClockApp(PackageManager pm) {\n        Intent alarmClockIntent = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LAUNCHER);\n\n        // Known clock implementations\n        // See http://stackoverflow.com/questions/3590955/intent-to-launch-the-clock-application-on-android\n        String[][] clockImpls = {\n                // Nexus\n                {\"com.android.deskclock\", \"com.android.deskclock.DeskClock\"},\n                // Samsung\n                {\"com.sec.android.app.clockpackage\", \"com.sec.android.app.clockpackage.ClockPackage\"},\n                // HTC\n                {\"com.htc.android.worldclock\", \"com.htc.android.worldclock.WorldClockTabControl\"},\n                // Standard Android\n                {\"com.android.deskclock\", \"com.android.deskclock.AlarmClock\"},\n                // New Android versions\n                {\"com.google.android.deskclock\", \"com.android.deskclock.AlarmClock\"},\n                // Froyo\n                {\"com.google.android.deskclock\", \"com.android.deskclock.DeskClock\"},\n                // Motorola\n                {\"com.motorola.blur.alarmclock\", \"com.motorola.blur.alarmclock.AlarmClock\"},\n                // Sony\n                {\"com.sonyericsson.organizer\", \"com.sonyericsson.organizer.Organizer_WorldClock\"},\n                // ASUS Tablets\n                {\"com.asus.deskclock\", \"com.asus.deskclock.DeskClock\"}\n        };\n\n        UserHandleCompat user = UserHandleCompat.CURRENT_USER;\n        for (String[] clockImpl : clockImpls) {\n            String packageName = clockImpl[0];\n            String className = clockImpl[1];\n            try {\n                ComponentName cn = new ComponentName(packageName, className);\n\n                pm.getActivityInfo(cn, PackageManager.GET_META_DATA);\n                alarmClockIntent.setComponent(cn);\n\n                return AppEntry.generateAppId(cn, user);\n            } catch (PackageManager.NameNotFoundException ignored) {\n                // Try next suggestion, this one does not exists on the phone.\n            }\n        }\n\n        return null;\n    }\n\n    public void setTags(EntryWithTags entry, Set<String> tags) {\n        if (tags == null || tags.isEmpty()) {\n            ArrayList<String> tagsToRemove = new ArrayList<>(getTags(entry.id));\n            for (String tag : tagsToRemove)\n                removeTag(entry.id, tag);\n        } else {\n            List<String> oldTags = DBHelper.loadTags(getContext(), entry.id);\n\n            // tags that need to be removed\n            {\n                ArrayList<String> tagsToRemove = new ArrayList<>();\n                for (String tag : oldTags)\n                    if (!tags.contains(tag))\n                        tagsToRemove.add(tag);\n                for (String tag : tagsToRemove)\n                    removeTag(entry.id, tag);\n            }\n\n            // add new tags\n            for (String tag : tags) {\n                if (oldTags.contains(tag))\n                    continue;\n                addTag(entry, tag);\n            }\n        }\n        entry.setTags(getTags(entry.id));\n    }\n\n    public boolean renameTag(String tagName, String newName) {\n        // rename tags from mTagsCache\n        DataHandler dataHandler = mApplication.getDataHandler();\n        for (Map.Entry<String, List<String>> entry : mTagsCache.entrySet()) {\n            int pos = entry.getValue().indexOf(tagName);\n            if (pos >= 0) {\n                entry.getValue().set(pos, newName);\n                EntryItem entryItem = dataHandler.getPojo(entry.getKey());\n                if (entryItem instanceof EntryWithTags)\n                    ((EntryWithTags) entryItem).setTags(entry.getValue());\n            }\n        }\n\n        // rename tags from tags menu\n        SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getContext());\n        SharedPreferences.Editor editor = pref.edit();\n        HashSet<String> tagsMenuSet = new HashSet<>(pref.getStringSet(\"tags-menu-list\", Collections.emptySet()));\n        if (tagsMenuSet.remove(tagName)) {\n            tagsMenuSet.add(newName);\n            editor.putStringSet(\"tags-menu-list\", tagsMenuSet);\n        }\n        int order = -1;\n        HashSet<String> tagsMenuOrderSet = new HashSet<>(pref.getStringSet(\"tags-menu-order\", Collections.emptySet()));\n        for (Iterator<String> iterator = tagsMenuOrderSet.iterator(); iterator.hasNext(); ) {\n            String orderedValue = iterator.next();\n            String value = PrefOrderedListHelper.getOrderedValueName(orderedValue);\n            if (value.equals(tagName)) {\n                order = PrefOrderedListHelper.getOrderedValueIndex(orderedValue);\n                iterator.remove();\n                break;\n            }\n        }\n        if (order >= 0) {\n            tagsMenuOrderSet.add(PrefOrderedListHelper.makeOrderedValue(newName, order));\n            editor.putStringSet(\"tags-menu-order\", tagsMenuOrderSet);\n        }\n\n        editor.apply();\n\n        // rename tag from favorites\n        TagEntry tagEntry = null;\n        TagEntry newEntry = null;\n        TagsProvider tagsProvider = dataHandler.getTagsProvider();\n        if (tagsProvider != null) {\n            tagEntry = tagsProvider.getTagEntry(tagName);\n            if (tagEntry.hasCustomIcon()) {\n                newEntry = tagsProvider.getTagEntry(newName);\n            }\n        }\n\n        // rename tags from database\n        return DBHelper.renameTag(getContext(), tagName, newName, tagEntry, newEntry) > 0;\n    }\n\n    /**\n     * Remove all tags from the Entry.\n     * We keep the DB as is, maybe later we'll reinstall the app.\n     *\n     * @param entryId what Entry\n     */\n    public void removeAllTags(String entryId) {\n        // remove from cache\n        List<String> tags = mTagsCache.remove(entryId);\n        // remove from DB\n//        if (tags != null) {\n//            for (String tag : tags)\n//                DBHelper.removeTag(getContext(), tag, entryId);\n//        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/icons/CalendarDrawable.java",
    "content": "package rocks.tbog.tblauncher.icons;\n\nimport android.annotation.SuppressLint;\nimport android.content.res.Resources;\nimport android.graphics.drawable.Drawable;\n\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.core.content.res.ResourcesCompat;\n\nimport java.util.Arrays;\nimport java.util.Calendar;\n\npublic class CalendarDrawable extends DrawableInfo {\n    private final int[] drawableForDay;\n    private final boolean[] drawableIdCached;\n\n    protected CalendarDrawable(@NonNull String drawableName) {\n        super(drawableName);\n        drawableForDay = new int[31];\n        drawableIdCached = new boolean[31];\n        Arrays.fill(drawableIdCached, false);\n    }\n\n    @SuppressLint(\"DiscouragedApi\")\n    @Override\n    @DrawableRes\n    public int getDrawableResId(@NonNull IconPackXML iconPack) {\n        int dayOfMonthIdx = Calendar.getInstance().get(Calendar.DAY_OF_MONTH) - 1;\n        return getDayDrawableId(iconPack, dayOfMonthIdx);\n    }\n\n    @SuppressLint(\"DiscouragedApi\")\n    @DrawableRes\n    private int getDayDrawableId(@NonNull IconPackXML iconPack, int dayOfMonthIdx) {\n        Resources res = iconPack.getResources();\n        if (res == null)\n            return drawableForDay[dayOfMonthIdx];\n        if (!drawableIdCached[dayOfMonthIdx]) {\n            String drawableName = getDrawableName() + (1 + dayOfMonthIdx);\n            drawableForDay[dayOfMonthIdx] = res.getIdentifier(drawableName, \"drawable\", iconPack.getPackPackageName());\n            drawableIdCached[dayOfMonthIdx] = true;\n        }\n\n        return drawableForDay[dayOfMonthIdx];\n    }\n\n    @Override\n    public boolean isDynamic() {\n        return true;\n    }\n\n    @Nullable\n    @Override\n    public Drawable getDrawable(@NonNull IconPackXML iconPack, @Nullable Resources.Theme theme) {\n        Resources res = iconPack.getResources();\n        if (res == null)\n            return null;\n        int dayOfMonthIdx = Calendar.getInstance().get(Calendar.DAY_OF_MONTH) - 1;\n        int drawableId = getDayDrawableId(iconPack, dayOfMonthIdx);\n        try {\n            return ResourcesCompat.getDrawable(res, drawableId, theme);\n        } catch (Resources.NotFoundException ignored) {\n            return null;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/icons/DrawableInfo.java",
    "content": "package rocks.tbog.tblauncher.icons;\n\nimport android.content.res.Resources;\nimport android.graphics.drawable.Drawable;\n\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport java.util.Objects;\n\npublic abstract class DrawableInfo {\n    @NonNull\n    private final String drawableName;\n\n    protected DrawableInfo(@NonNull String drawableName) {\n        this.drawableName = drawableName;\n    }\n\n    @NonNull\n    public String getDrawableName() {\n        return drawableName;\n    }\n\n    public boolean isDynamic() {\n        return false;\n    }\n\n    @DrawableRes\n    public abstract int getDrawableResId(@NonNull IconPackXML iconPack);\n\n    @Nullable\n    public abstract Drawable getDrawable(@NonNull IconPackXML iconPack, @Nullable Resources.Theme theme);\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o)\n            return true;\n        if (!(o instanceof DrawableInfo))\n            return false;\n        DrawableInfo that = (DrawableInfo) o;\n        return drawableName.equals(that.drawableName);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(drawableName);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/icons/IconPack.java",
    "content": "package rocks.tbog.tblauncher.icons;\n\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.pm.PackageManager;\nimport android.graphics.drawable.Drawable;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport java.util.Collection;\n\nimport rocks.tbog.tblauncher.utils.UserHandleCompat;\n\npublic interface IconPack<DrawableInfo> {\n\n    @NonNull\n    String getPackPackageName();\n\n    void load(PackageManager packageManager);\n    boolean isLoaded();\n\n    @Nullable\n    DrawableInfo getComponentDrawable(@NonNull Context ctx, @NonNull ComponentName componentName, @NonNull UserHandleCompat userHandle);\n\n    boolean isComponentDynamic(@NonNull ComponentName componentName);\n\n    @NonNull\n    Drawable applyBackgroundAndMask(@NonNull Context ctx, @NonNull Drawable defaultBitmap, boolean fitInside);\n\n    @NonNull\n    Collection<DrawableInfo> getDrawableList();\n\n    @Nullable\n    Drawable getDrawable(@Nullable DrawableInfo drawableInfo);\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/icons/IconPackCache.java",
    "content": "package rocks.tbog.tblauncher.icons;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.collection.LruCache;\n\nimport java.lang.ref.SoftReference;\nimport java.util.HashMap;\n\nimport rocks.tbog.tblauncher.TBApplication;\n\npublic class IconPackCache {\n    private final SoftReferenceCache<String, IconPackXML> mCache = new SoftReferenceCache<>();\n\n    @NonNull\n    public IconPackXML getIconPack(String packageName) {\n        IconPackXML pack = mCache.get(packageName);\n        if (pack == null) {\n            pack = new IconPackXML(packageName);\n            mCache.put(packageName, pack);\n        }\n        return pack;\n    }\n\n    public void clearCache(TBApplication app) {\n        mCache.evictAll();\n        IconPackXML customIconPack = app.iconsHandler().getCustomIconPack();\n        if (customIconPack != null)\n            mCache.put(customIconPack.getPackPackageName(), customIconPack);\n    }\n\n\n    /**\n     * SoftReferenceCache\n     *\n     * @param <K> The type of the key's.\n     * @param <V> The type of the value's.\n     */\n    static class SoftReferenceCache<K, V> {\n        private final HashMap<K, SoftReference<V>> mCache = new HashMap<>();\n\n        /**\n         * Put a new item in the cache. This item can be gone after a GC run.\n         *\n         * @param key   The key of the value.\n         * @param value The value to store.\n         */\n        public void put(K key, V value) {\n            mCache.put(key, new SoftReference<>(value));\n        }\n\n        /**\n         * Retrieve a value from the cache (if available).\n         *\n         * @param key The key to look for.\n         * @return The value if it's found. Return null if the key-value pair is not stored yet or the GC has removed the value from memory.\n         */\n        @Nullable\n        public V get(K key) {\n            V value = null;\n\n            SoftReference<V> reference = mCache.get(key);\n\n            if (reference != null) {\n                value = reference.get();\n            }\n\n            if (value == null)\n                mCache.remove(key);\n\n            return value;\n        }\n\n        public void evictAll() {\n            mCache.clear();\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/icons/IconPackXML.java",
    "content": "package rocks.tbog.tblauncher.icons;\n\nimport android.annotation.SuppressLint;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.pm.PackageManager;\nimport android.content.res.Resources;\nimport android.content.res.XmlResourceParser;\nimport android.graphics.Bitmap;\nimport android.graphics.Canvas;\nimport android.graphics.Matrix;\nimport android.graphics.Paint;\nimport android.graphics.PorterDuff;\nimport android.graphics.PorterDuffXfermode;\nimport android.graphics.drawable.AdaptiveIconDrawable;\nimport android.graphics.drawable.BitmapDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.os.Build;\nimport android.text.TextUtils;\nimport android.util.ArrayMap;\nimport android.util.Log;\nimport android.util.Pair;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.collection.ArraySet;\n\nimport org.xmlpull.v1.XmlPullParser;\nimport org.xmlpull.v1.XmlPullParserException;\nimport org.xmlpull.v1.XmlPullParserFactory;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.LinkedHashSet;\nimport java.util.Map;\nimport java.util.Random;\n\nimport rocks.tbog.tblauncher.drawable.DrawableUtils;\nimport rocks.tbog.tblauncher.utils.UserHandleCompat;\nimport rocks.tbog.tblauncher.utils.Utilities;\n\npublic class IconPackXML implements IconPack<DrawableInfo> {\n    private final static String TAG = IconPackXML.class.getSimpleName();\n    private final Map<String, ArraySet<DrawableInfo>> drawablesByComponent = new ArrayMap<>(0);\n    private final LinkedHashSet<DrawableInfo> drawableList = new LinkedHashSet<>(0);\n    // instance of a resource object of an icon pack\n    private Resources packResources;\n    // package name of the icons pack\n    @NonNull\n    private final String iconPackPackageName;\n    // list of back images available on an icons pack\n    private final ArrayList<DrawableInfo> backImages = new ArrayList<>();\n    // bitmap mask of an icons pack\n    private DrawableInfo maskImage = null;\n    // front image of an icons pack\n    private DrawableInfo frontImage = null;\n    // scale factor of an icons pack\n    private float factor = 1.0f;\n\n    private final Random random = new Random();\n    private final Matrix matScale = new Matrix();\n\n    private boolean loaded;\n\n    public IconPackXML(@NonNull String packageName) {\n        iconPackPackageName = packageName;\n        loaded = false;\n    }\n\n    @Override\n    public synchronized boolean isLoaded() {\n        return loaded;\n    }\n\n    @Override\n    public synchronized void load(PackageManager packageManager) {\n        if (loaded)\n            return;\n        try {\n            packResources = packageManager.getResourcesForApplication(iconPackPackageName);\n        } catch (PackageManager.NameNotFoundException e) {\n            Log.e(TAG, \"get icon pack resources\" + iconPackPackageName, e);\n        }\n\n        parseAppFilterXML();\n        loaded = true;\n    }\n\n    public synchronized void loadDrawables(PackageManager packageManager) {\n        if (!loaded)\n            load(packageManager);\n        try {\n            packResources = packageManager.getResourcesForApplication(iconPackPackageName);\n        } catch (PackageManager.NameNotFoundException e) {\n            Log.e(TAG, \"get icon pack resources\" + iconPackPackageName, e);\n        }\n\n        parseDrawableXML();\n    }\n\n    public boolean hasMask() {\n        return maskImage != null;\n    }\n\n    @NonNull\n    @Override\n    public Collection<DrawableInfo> getDrawableList() {\n        return Collections.unmodifiableCollection(drawableList);\n    }\n\n    @Override\n    @Nullable\n    public DrawableInfo getComponentDrawable(@NonNull Context ctx, @NonNull ComponentName componentName, @NonNull UserHandleCompat userHandle) {\n        return getComponentDrawable(componentName.toString());\n    }\n\n    @Override\n    public boolean isComponentDynamic(@NonNull ComponentName componentName) {\n        return getCalendarDrawable(componentName.toString()) != null;\n    }\n\n    @Nullable\n    private CalendarDrawable getCalendarDrawable(@Nullable String componentName) {\n        ArraySet<DrawableInfo> drawables = drawablesByComponent.get(componentName);\n        if (drawables != null)\n            for (DrawableInfo info : drawables)\n                if (info instanceof CalendarDrawable)\n                    return (CalendarDrawable) info;\n        return null;\n    }\n\n    @Nullable\n    public DrawableInfo getComponentDrawable(String componentName) {\n        CalendarDrawable calendar = getCalendarDrawable(componentName);\n        if (calendar != null)\n            return calendar;\n        ArraySet<DrawableInfo> drawables = drawablesByComponent.get(componentName);\n        return drawables != null ? drawables.valueAt(0) : null;\n    }\n\n    @Nullable\n    @Override\n    public Drawable getDrawable(@Nullable DrawableInfo drawableInfo) {\n        if (drawableInfo != null) {\n            return drawableInfo.getDrawable(this, null);\n        }\n        return null;\n    }\n\n    @NonNull\n    private Bitmap getBitmap(@NonNull DrawableInfo drawableInfo) {\n        Drawable drawable = getDrawable(drawableInfo);\n        return Utilities.drawableToBitmap(drawable);\n    }\n\n    @NonNull\n    @Override\n    public Drawable applyBackgroundAndMask(@NonNull Context ctx, @NonNull Drawable systemIcon, boolean fitInside) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            if (systemIcon instanceof AdaptiveIconDrawable)\n                systemIcon = DrawableUtils.applyIconMaskShape(ctx, systemIcon, DrawableUtils.SHAPE_SQUARE, fitInside);\n        }\n\n        if (systemIcon instanceof BitmapDrawable) {\n            return generateBitmap((BitmapDrawable) systemIcon);\n        }\n\n        Bitmap bitmap;\n        if (systemIcon.getIntrinsicWidth() <= 0 || systemIcon.getIntrinsicHeight() <= 0)\n            bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); // Single color bitmap will be created of 1x1 pixel\n        else\n            bitmap = Bitmap.createBitmap(systemIcon.getIntrinsicWidth(), systemIcon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);\n        systemIcon.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight());\n        systemIcon.draw(new Canvas(bitmap));\n        return generateBitmap(new BitmapDrawable(ctx.getResources(), bitmap));\n    }\n\n    @NonNull\n    private BitmapDrawable generateBitmap(@NonNull BitmapDrawable defaultBitmap) {\n\n        // if no support images in the icon pack return the bitmap itself\n        if (backImages.size() == 0) {\n            return defaultBitmap;\n        }\n\n        // select a random background image\n        int backImageInd = random.nextInt(backImages.size());\n        Bitmap backImage = getBitmap(backImages.get(backImageInd));\n        int w = backImage.getWidth();\n        int h = backImage.getHeight();\n\n        // create a bitmap for the result\n        Bitmap result = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);\n        Canvas canvas = new Canvas(result);\n        canvas.setDensity(Bitmap.DENSITY_NONE);\n\n        // draw the background first\n        canvas.drawBitmap(backImage, 0, 0, null);\n\n        // scale original icon\n        Bitmap scaledBitmap = Bitmap.createScaledBitmap(defaultBitmap.getBitmap(), (int) (w * factor), (int) (h * factor), false);\n        scaledBitmap.setDensity(Bitmap.DENSITY_NONE);\n\n        int offsetLeft = (w - scaledBitmap.getWidth()) / 2;\n        int offsetTop = (h - scaledBitmap.getHeight()) / 2;\n        if (maskImage != null) {\n            // draw the scaled bitmap with mask\n            Bitmap mask = getBitmap(maskImage);\n\n            // paint the bitmap with mask into the result\n            Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);\n            paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));\n            canvas.drawBitmap(scaledBitmap, offsetLeft, offsetTop, null);\n            matScale.setScale(w / (float) mask.getWidth(), h / (float) mask.getHeight());\n            canvas.drawBitmap(mask, matScale, paint);\n            paint.setXfermode(null);\n        } else { // draw the scaled bitmap without mask\n            canvas.drawBitmap(scaledBitmap, offsetLeft, offsetTop, null);\n        }\n\n        // paint the front\n        if (frontImage != null) {\n            canvas.drawBitmap(getBitmap(frontImage), 0, 0, null);\n        }\n\n        return new BitmapDrawable(packResources, result);\n    }\n\n    @SuppressLint(\"DiscouragedApi\")\n    private void parseDrawableXML() {\n        XmlResourceParser xpp = null;\n        // search drawable.xml into icons pack apk resource folder\n        @SuppressLint(\"DiscouragedApi\")\n        int drawableXmlId = packResources.getIdentifier(\"drawable\", \"xml\", iconPackPackageName);\n        if (drawableXmlId > 0) {\n            xpp = packResources.getXml(drawableXmlId);\n        }\n        if (xpp == null)\n            return;\n        try {\n            int eventType = xpp.getEventType();\n            while (eventType != XmlPullParser.END_DOCUMENT) {\n                if (eventType == XmlPullParser.START_TAG) {\n                    int attrCount = xpp.getAttributeCount();\n                    switch (xpp.getName()) {\n                        case \"item\":\n                            for (int attrIdx = 0; attrIdx < attrCount; attrIdx += 1) {\n                                String attrName = xpp.getAttributeName(attrIdx);\n                                if (attrName.equals(\"drawable\")) {\n                                    String drawableName = xpp.getAttributeValue(attrIdx);\n                                    if (!TextUtils.isEmpty(drawableName)) {\n                                        drawableList.add(new LazyLoadDrawable(drawableName));\n                                    }\n                                }\n                            }\n                            break;\n                        case \"category\":\n                            break;\n                        default:\n                            Log.d(TAG, \"ignored \" + xpp.getName());\n                    }\n                }\n                eventType = xpp.next();\n            }\n        } catch (XmlPullParserException | IOException e) {\n            Log.e(TAG, \"parsing drawable.xml\", e);\n        } finally {\n            xpp.close();\n        }\n\n    }\n\n    @NonNull\n    private Pair<XmlPullParser, InputStream> findAppFilterXml() throws XmlPullParserException {\n        XmlPullParser parser = null;\n        InputStream inputStream = null;\n        // search appfilter.xml in icon pack's apk resource folder for xml files\n        try {\n            inputStream = packResources.getAssets().open(\"appfilter.xml\");\n            XmlPullParserFactory factory = XmlPullParserFactory.newInstance();\n            parser = factory.newPullParser();\n            parser.setInput(inputStream, \"UTF-8\");\n        } catch (Exception e) {\n            if (inputStream != null) {\n                try {\n                    inputStream.close();\n                } catch (IOException ignored) {\n                }\n            }\n            inputStream = null;\n            // Catch any exception since we want to fall back to parsing the xml/resource in all cases\n            @SuppressLint(\"DiscouragedApi\")\n            int appFilterIdXml = packResources.getIdentifier(\"appfilter\", \"xml\", iconPackPackageName);\n            if (appFilterIdXml > 0) {\n                parser = packResources.getXml(appFilterIdXml);\n            }\n        }\n\n        if (parser == null) {\n            // search appfilter.xml in icon pack's apk resource folder for raw files (supporting icon pack studio)\n            @SuppressLint(\"DiscouragedApi\")\n            int appFilterIdRaw = packResources.getIdentifier(\"appfilter\", \"raw\", iconPackPackageName);\n            if (appFilterIdRaw > 0) {\n                inputStream = packResources.openRawResource(appFilterIdRaw);\n                XmlPullParserFactory factory = XmlPullParserFactory.newInstance();\n                parser = factory.newPullParser();\n                parser.setInput(inputStream, \"UTF-8\");\n            }\n        }\n        return Pair.create(parser, inputStream);\n    }\n\n    @SuppressLint(\"DiscouragedApi\")\n    private void parseAppFilterXML() {\n        if (packResources == null)\n            return;\n\n        XmlPullParser xpp = null;\n        InputStream inputStream = null;\n        Map<String, CalendarDrawable> calendarDrawablesByPrefix = new ArrayMap<>(0);\n        try {\n            var appFilterXml = findAppFilterXml();\n            xpp = appFilterXml.first;\n            inputStream = appFilterXml.second;\n            if (xpp != null) {\n                int eventType = xpp.getEventType();\n                while (eventType != XmlPullParser.END_DOCUMENT) {\n                    if (eventType == XmlPullParser.START_TAG) {\n                        String componentName = null;\n                        String drawableName = null;\n                        int drawableId;\n\n                        switch (xpp.getName()) {\n                            //parse <iconback> xml tags used as background of generated icons\n                            case \"iconback\":\n                                for (int i = 0; i < xpp.getAttributeCount(); i++) {\n                                    if (xpp.getAttributeName(i).startsWith(\"img\")) {\n                                        drawableName = xpp.getAttributeValue(i);\n                                        drawableId = packResources.getIdentifier(drawableName, \"drawable\", iconPackPackageName);\n                                        if (drawableId != 0)\n                                            backImages.add(new SimpleDrawable(drawableName, drawableId));\n                                    }\n                                }\n                                break;\n                            //parse <iconmask> xml tags used as mask of generated icons\n                            case \"iconmask\":\n                                if (xpp.getAttributeCount() > 0 && xpp.getAttributeName(0).equals(\"img1\")) {\n                                    drawableName = xpp.getAttributeValue(0);\n                                    drawableId = packResources.getIdentifier(drawableName, \"drawable\", iconPackPackageName);\n                                    if (drawableId != 0)\n                                        maskImage = new SimpleDrawable(drawableName, drawableId);\n                                }\n                                break;\n                            //parse <iconupon> xml tags used as front image of generated icons\n                            case \"iconupon\":\n                                if (xpp.getAttributeCount() > 0 && xpp.getAttributeName(0).equals(\"img1\")) {\n                                    drawableName = xpp.getAttributeValue(0);\n                                    drawableId = packResources.getIdentifier(drawableName, \"drawable\", iconPackPackageName);\n                                    if (drawableId != 0)\n                                        frontImage = new SimpleDrawable(drawableName, drawableId);\n                                }\n                                break;\n                            //parse <scale> xml tags used as scale factor of original bitmap icon\n                            case \"scale\":\n                                if (xpp.getAttributeCount() > 0 && xpp.getAttributeName(0).equals(\"factor\"))\n                                    factor = Float.parseFloat(xpp.getAttributeValue(0));\n                                break;\n                            //parse <item> xml tags for custom icons\n                            case \"item\":\n                                for (int i = 0; i < xpp.getAttributeCount(); i++) {\n                                    if (xpp.getAttributeName(i).equals(\"component\")) {\n                                        componentName = xpp.getAttributeValue(i);\n                                    } else if (xpp.getAttributeName(i).equals(\"drawable\")) {\n                                        drawableName = xpp.getAttributeValue(i);\n                                    }\n                                }\n\n                                if (!TextUtils.isEmpty(drawableName) && !TextUtils.isEmpty(componentName)) {\n                                    DrawableInfo drawableInfo = new LazyLoadDrawable(drawableName);\n                                    drawableList.add(drawableInfo);\n                                    ArraySet<DrawableInfo> infoSet = drawablesByComponent.get(componentName);\n                                    if (infoSet == null)\n                                        drawablesByComponent.put(componentName, infoSet = new ArraySet<>(1));\n                                    infoSet.add(drawableInfo);\n                                }\n                                break;\n                            case \"calendar\":\n                                String prefix = null;\n\n                                for (int i = 0; i < xpp.getAttributeCount(); i++) {\n                                    if (xpp.getAttributeName(i).equals(\"component\")) {\n                                        componentName = xpp.getAttributeValue(i);\n                                    } else if (xpp.getAttributeName(i).equals(\"prefix\")) {\n                                        prefix = xpp.getAttributeValue(i);\n                                    }\n                                }\n\n                                if (!TextUtils.isEmpty(prefix) && !TextUtils.isEmpty(componentName)) {\n                                    CalendarDrawable calendarDrawable = calendarDrawablesByPrefix.get(prefix);\n                                    if (calendarDrawable == null) {\n                                        calendarDrawable = new CalendarDrawable(prefix);\n                                        calendarDrawablesByPrefix.put(prefix, calendarDrawable);\n                                    }\n\n                                    ArraySet<DrawableInfo> infoSet = drawablesByComponent.get(componentName);\n                                    if (infoSet == null)\n                                        drawablesByComponent.put(componentName, infoSet = new ArraySet<>(1));\n                                    infoSet.add(calendarDrawable);\n                                }\n                                break;\n                            default:\n                                // ignore\n                                break;\n                        }\n                    }\n                    eventType = xpp.next();\n                }\n            }\n        } catch (Exception e) {\n            Log.e(TAG, \"Error parsing appfilter.xml \", e);\n        } finally {\n            if (xpp instanceof XmlResourceParser) {\n                ((XmlResourceParser) xpp).close();\n            } else if (inputStream != null) {\n                try {\n                    inputStream.close();\n                } catch (IOException e) {\n                    Log.e(TAG, \"Error closing appfilter.xml \", e);\n                }\n            }\n        }\n    }\n\n\n    @NonNull\n    @Override\n    public String getPackPackageName() {\n        return iconPackPackageName;\n    }\n\n    @Nullable\n    public Resources getResources() {\n        return packResources;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/icons/LazyLoadDrawable.java",
    "content": "package rocks.tbog.tblauncher.icons;\n\nimport android.annotation.SuppressLint;\nimport android.content.res.Resources;\nimport android.graphics.drawable.Drawable;\n\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.core.content.res.ResourcesCompat;\n\npublic class LazyLoadDrawable extends DrawableInfo {\n\n    @DrawableRes\n    private int drawableId = 0;\n    private boolean drawableIdCached = false;\n\n    protected LazyLoadDrawable(@NonNull String drawableName) {\n        super(drawableName);\n    }\n\n    @SuppressLint(\"DiscouragedApi\")\n    @Override\n    @DrawableRes\n    public int getDrawableResId(@NonNull IconPackXML iconPack) {\n        Resources res = iconPack.getResources();\n        if (res == null)\n            return drawableId;\n        if (!drawableIdCached) {\n            drawableId = res.getIdentifier(getDrawableName(), \"drawable\", iconPack.getPackPackageName());\n            drawableIdCached = true;\n        }\n        return drawableId;\n    }\n\n    @SuppressLint(\"DiscouragedApi\")\n    @Nullable\n    @Override\n    public Drawable getDrawable(@NonNull IconPackXML iconPack, @Nullable Resources.Theme theme) {\n        Resources res = iconPack.getResources();\n        if (res == null)\n            return null;\n        if (!drawableIdCached) {\n            drawableId = res.getIdentifier(getDrawableName(), \"drawable\", iconPack.getPackPackageName());\n            drawableIdCached = true;\n        }\n        try {\n            return ResourcesCompat.getDrawable(res, drawableId, theme);\n        } catch (Resources.NotFoundException ignored) {\n            return null;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/icons/SimpleDrawable.java",
    "content": "package rocks.tbog.tblauncher.icons;\n\nimport android.content.res.Resources;\nimport android.graphics.drawable.Drawable;\n\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.core.content.res.ResourcesCompat;\n\npublic class SimpleDrawable extends DrawableInfo {\n    @DrawableRes\n    private final int drawableId;\n\n    public SimpleDrawable(@NonNull String drawableName, @DrawableRes int drawableId) {\n        super(drawableName);\n        this.drawableId = drawableId;\n    }\n\n    @Override\n    @DrawableRes\n    public int getDrawableResId(@NonNull IconPackXML iconPack) {\n        return drawableId;\n    }\n\n    @Nullable\n    @Override\n    public Drawable getDrawable(@NonNull IconPackXML iconPack, @Nullable Resources.Theme theme) {\n        Resources res = iconPack.getResources();\n        if (res == null)\n            return null;\n        try {\n            return ResourcesCompat.getDrawable(res, drawableId, theme);\n        } catch (Resources.NotFoundException ignored) {\n            return null;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/icons/SystemIconPack.java",
    "content": "package rocks.tbog.tblauncher.icons;\n\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.pm.LauncherActivityInfo;\nimport android.content.pm.LauncherApps;\nimport android.content.pm.PackageManager;\nimport android.graphics.drawable.Drawable;\nimport android.os.Build;\nimport android.util.Log;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.List;\n\nimport rocks.tbog.tblauncher.drawable.DrawableUtils;\nimport rocks.tbog.tblauncher.utils.GoogleCalendarIcon;\nimport rocks.tbog.tblauncher.utils.UserHandleCompat;\n\npublic class SystemIconPack implements IconPack<Drawable> {\n\n    private static final String TAG = SystemIconPack.class.getSimpleName();\n    private int mAdaptiveShape = DrawableUtils.SHAPE_NONE;\n\n    @NonNull\n    @Override\n    public String getPackPackageName() {\n        return \"default\";\n    }\n\n    @Override\n    public boolean isLoaded() {\n        return true;\n    }\n\n    @Override\n    public void load(PackageManager packageManager) {\n    }\n\n    public int getAdaptiveShape() {\n        return mAdaptiveShape;\n    }\n\n    public void setAdaptiveShape(int shape) {\n        mAdaptiveShape = shape;\n    }\n\n    @Nullable\n    @Override\n    public Drawable getComponentDrawable(@NonNull Context ctx, @NonNull ComponentName componentName, @NonNull UserHandleCompat userHandle) {\n        Drawable drawable = null;\n        if (isComponentDynamic(componentName)) {\n            drawable = GoogleCalendarIcon.getDrawable(ctx, componentName.getClassName());\n        }\n        if (drawable == null) {\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n                LauncherApps launcher = (LauncherApps) ctx.getSystemService(Context.LAUNCHER_APPS_SERVICE);\n                assert launcher != null;\n                List<LauncherActivityInfo> icons = launcher.getActivityList(componentName.getPackageName(), userHandle.getRealHandle());\n                for (LauncherActivityInfo info : icons) {\n                    if (info.getComponentName().equals(componentName)) {\n                        drawable = info.getBadgedIcon(0);\n                        break;\n                    }\n                }\n\n                // This should never happen, let's just return the first icon\n                if (drawable == null && !icons.isEmpty())\n                    drawable = icons.get(0).getBadgedIcon(0);\n            }\n        }\n\n        if (drawable == null) {\n            try {\n                drawable = ctx.getPackageManager().getActivityIcon(componentName);\n            } catch (PackageManager.NameNotFoundException e) {\n                Log.e(TAG, \"Unable to find activity icon \" + componentName.toString(), e);\n            }\n        }\n\n        if (drawable == null) {\n            try {\n                drawable = ctx.getPackageManager().getApplicationIcon(componentName.getPackageName());\n            } catch (PackageManager.NameNotFoundException e) {\n                Log.e(TAG, \"Unable to find app icon \" + componentName.toString(), e);\n            }\n        }\n\n        if (drawable == null)\n            Log.e(TAG, \"Unable to find component drawable \" + componentName.toString());\n\n        return drawable;\n    }\n\n    @Override\n    public boolean isComponentDynamic(@NonNull ComponentName componentName) {\n        return GoogleCalendarIcon.GOOGLE_CALENDAR.equals(componentName.getPackageName());\n    }\n\n    @NonNull\n    @Override\n    public Drawable applyBackgroundAndMask(@NonNull Context ctx, @NonNull Drawable icon, boolean fitInside) {\n        return DrawableUtils.applyIconMaskShape(ctx, icon, mAdaptiveShape, fitInside);\n    }\n\n    @NonNull\n    @Override\n    public Collection<Drawable> getDrawableList() {\n        return Collections.emptyList();\n    }\n\n    @Nullable\n    @Override\n    public Drawable getDrawable(@Nullable Drawable drawable) {\n        return drawable;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/loader/LoadAppEntry.java",
    "content": "package rocks.tbog.tblauncher.loader;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.pm.ApplicationInfo;\nimport android.content.pm.LauncherActivityInfo;\nimport android.content.pm.LauncherApps;\nimport android.content.pm.PackageManager;\nimport android.content.pm.ResolveInfo;\nimport android.os.Build;\nimport android.os.UserManager;\nimport android.util.Log;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\n\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.db.AppRecord;\nimport rocks.tbog.tblauncher.entry.AppEntry;\nimport rocks.tbog.tblauncher.handler.AppsHandler;\nimport rocks.tbog.tblauncher.utils.Timer;\nimport rocks.tbog.tblauncher.utils.UserHandleCompat;\n\npublic class LoadAppEntry extends LoadEntryItem<AppEntry> {\n\n    public LoadAppEntry(Context context) {\n        super(context);\n    }\n\n    @NonNull\n    @Override\n    public String getScheme() {\n        return AppEntry.SCHEME;\n    }\n\n    @Override\n    protected ArrayList<AppEntry> doInBackground(Void param) {\n        SystemAppLoader loader = new SystemAppLoader(context.get());\n        //List<AppEntry> currentApplications = TBApplication.dataHandler(context.get()).getApplications();\n\n        // timer start\n        Timer timer = Timer.startMilli();\n        // function to time\n        ArrayList<AppEntry> apps = loader.getAppList();\n        // timer end\n        timer.stop();\n\n        Log.i(\"time\", timer + \" to list apps\");\n        return apps;\n    }\n\n    public static class SystemAppLoader {\n        private Map<String, AppRecord> dbApps = null;\n        private ArrayList<AppRecord> pendingChanges = null;\n        @Nullable\n        private final Context ctx;\n\n        SystemAppLoader(@Nullable Context context) {\n            ctx = context;\n        }\n\n        @NonNull\n        public ArrayList<AppEntry> getAppList() {\n            ArrayList<AppEntry> apps = new ArrayList<>(0);\n\n            if (ctx == null) {\n                return apps;\n            }\n\n            AppsHandler appsHandler = TBApplication.appsHandler(ctx);\n\n            dbApps = appsHandler.getAppRecords(ctx);\n            pendingChanges = new ArrayList<>(0);\n\n            if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n                UserManager manager = (UserManager) ctx.getSystemService(Context.USER_SERVICE);\n                LauncherApps launcher = (LauncherApps) ctx.getSystemService(Context.LAUNCHER_APPS_SERVICE);\n                if (manager != null && launcher != null) {\n                    // Handle multi-profile support introduced in Android 5 (#542)\n                    for (android.os.UserHandle profile : manager.getUserProfiles()) {\n                        UserHandleCompat user = new UserHandleCompat(manager.getSerialNumberForUser(profile), profile);\n                        List<LauncherActivityInfo> activityList = launcher.getActivityList(null, profile);\n                        apps.ensureCapacity(apps.size() + activityList.size());\n                        Log.i(\"App\", \"getActivityList(\" + profile + \") found \" + activityList.size() + \" app(s)\");\n                        for (LauncherActivityInfo activityInfo : activityList) {\n                            ApplicationInfo appInfo = activityInfo.getApplicationInfo();\n\n                            String displayName = activityInfo.getLabel().toString();\n                            if (displayName.equals(appInfo.packageName))\n                                displayName = activityInfo.getName();\n\n                            AppEntry app = processApp(displayName, appInfo.packageName, activityInfo.getName(), user);\n\n                            apps.add(app);\n                        }\n                    }\n                }\n            } else {\n                PackageManager manager = ctx.getPackageManager();\n\n                Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);\n                mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);\n\n                List<ResolveInfo> activityList = manager.queryIntentActivities(mainIntent, 0);\n                apps.ensureCapacity(apps.size() + activityList.size());\n                Log.i(\"App\", \"queryIntentActivities found \" + activityList.size() + \" app(s)\");\n                for (ResolveInfo info : activityList) {\n                    UserHandleCompat user = UserHandleCompat.CURRENT_USER;\n                    ApplicationInfo appInfo = info.activityInfo.applicationInfo;\n\n                    String displayName = info.loadLabel(manager).toString();\n                    AppEntry app = processApp(displayName, appInfo.packageName, info.activityInfo.name, user);\n\n                    apps.add(app);\n                }\n            }\n\n            Log.i(\"App\", \"LoadAppPojos found \" + apps.size() + \" app(s)\");\n\n            // add new apps to database\n            appsHandler.updateAppCache(pendingChanges, null);\n            pendingChanges.clear();\n\n            for (Map.Entry<String, AppRecord> entry : dbApps.entrySet()) {\n                AppRecord rec = entry.getValue();\n                if (rec.isFlagSet(AppRecord.FLAG_VALIDATED))\n                    continue;\n                pendingChanges.add(rec);\n            }\n\n            // remove apps from database\n            appsHandler.updateAppCache(null, pendingChanges);\n            pendingChanges = null;\n            dbApps = null;\n\n            AppsHandler.setTagsForApps(apps, TBApplication.tagsHandler(ctx));\n\n            return apps;\n        }\n\n        @NonNull\n        private AppEntry processApp(String appName, String packageName, String activityName, UserHandleCompat user) {\n            String componentName = user.getUserComponentName(packageName, activityName);\n            AppRecord rec = dbApps.get(componentName);\n            if (rec == null) {\n                rec = new AppRecord();\n                rec.componentName = componentName;\n                rec.displayName = appName;\n                pendingChanges.add(rec);\n            }\n            if (!rec.hasCustomName() && !appName.equals(rec.displayName)) {\n                rec.displayName = appName;\n                pendingChanges.add(rec);\n            }\n\n            rec.addFlags(AppRecord.FLAG_VALIDATED);\n\n            AppEntry app = new AppEntry(packageName, activityName, user);\n\n            if (rec.hasCustomName())\n                app.setName(rec.displayName);\n            else\n                app.setName(user.getBadgedLabelForUser(ctx, appName));\n            if (rec.hasCustomIcon())\n                app.setCustomIcon(rec.dbId);\n            app.setHiddenByUser(rec.isHidden());\n\n            return app;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/loader/LoadCacheApps.java",
    "content": "package rocks.tbog.tblauncher.loader;\n\nimport android.content.Context;\nimport android.util.Log;\n\nimport androidx.annotation.NonNull;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.concurrent.CountDownLatch;\n\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.entry.AppEntry;\nimport rocks.tbog.tblauncher.handler.AppsHandler;\nimport rocks.tbog.tblauncher.utils.Timer;\n\npublic class LoadCacheApps extends LoadEntryItem<AppEntry> {\n    private final static String TAG = \"LCApps\";\n    private final AppsHandler appsHandler;\n\n    public LoadCacheApps(Context context) {\n        super(context);\n        TBApplication app = TBApplication.getApplication(context);\n        // call this here in case the AppsHandler is not yet loaded\n        appsHandler = app.appsHandler();\n    }\n\n    @NonNull\n    @Override\n    public String getScheme() {\n        return AppEntry.SCHEME;\n    }\n\n    @Override\n    protected ArrayList<AppEntry> doInBackground(Void param) {\n        Log.d(TAG, \"doInBackground\");\n        final Context context = this.context.get();\n        // timer start\n        Timer timer = Timer.startMilli();\n\n        final CountDownLatch latch = new CountDownLatch(1);\n        // notify that the tags are loaded\n        appsHandler.runWhenLoaded(latch::countDown);\n        // wait for the tags to load\n        try {\n            latch.await();\n        } catch (InterruptedException e) {\n            Log.e(TAG, \"waiting for TagsHandler\", e);\n        }\n\n        // function to time\n        final ArrayList<AppEntry> pojos;\n        if (context != null)\n            pojos = getApps(context, appsHandler);\n        else\n            pojos = new ArrayList<>(0);\n\n        // timer end\n        timer.stop();\n\n        Log.i(\"time\", timer + \" to load (\" + pojos.size() + \") cached apps\");\n        return pojos;\n    }\n\n    @NonNull\n    private static ArrayList<AppEntry> getApps(@NonNull Context context, @NonNull AppsHandler appsHandler) {\n        Collection<AppEntry> appEntries = appsHandler.getAllApps();\n        Log.d(TAG, \"appsHandler.getAllApps.size=\" + appEntries.size());\n        if (appEntries.isEmpty()) {\n            // cache is empty, load system apps now\n            LoadAppEntry.SystemAppLoader loader = new LoadAppEntry.SystemAppLoader(context);\n            return loader.getAppList();\n        }\n        return new ArrayList<>(appEntries);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/loader/LoadContactsEntry.java",
    "content": "package rocks.tbog.tblauncher.loader;\n\nimport android.content.ContentResolver;\nimport android.content.ContentUris;\nimport android.content.Context;\nimport android.database.Cursor;\nimport android.net.Uri;\nimport android.provider.ContactsContract;\nimport android.util.Log;\n\nimport androidx.annotation.NonNull;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport rocks.tbog.tblauncher.MimeTypeCache;\nimport rocks.tbog.tblauncher.Permission;\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.entry.ContactEntry;\nimport rocks.tbog.tblauncher.utils.MimeTypeUtils;\nimport rocks.tbog.tblauncher.utils.Timer;\n\npublic class LoadContactsEntry extends LoadEntryItem<ContactEntry> {\n\n    private static final String TAG = \"LoadContacts\";\n\n    public LoadContactsEntry(Context context) {\n        super(context);\n    }\n\n    @NonNull\n    @Override\n    public String getScheme() {\n        return ContactEntry.SCHEME;\n    }\n\n    @Override\n    protected ArrayList<ContactEntry> doInBackground(Void param) {\n        Timer timer = Timer.startNano();\n\n        ArrayList<ContactEntry> contacts = new ArrayList<>();\n\n        Context ctx = context.get();\n        if (ctx == null) {\n            return contacts;\n        }\n\n        // Skip if we don't have permission to list contacts yet:(\n        if (!Permission.checkPermission(ctx, Permission.PERMISSION_READ_CONTACTS)) {\n            return contacts;\n        }\n\n        // Skip if we don't have any mime types to be shown\n        Set<String> mimeTypes = MimeTypeUtils.getActiveMimeTypes(ctx);\n        if (mimeTypes.isEmpty()) {\n            return contacts;\n        }\n\n        final ContentResolver contentResolver = ctx.getContentResolver();\n        Map<String, BasicContact> basicContacts = loadBasicContacts(contentResolver);\n        Map<Long, BasicRawContact> basicRawContacts = loadRawContacts(contentResolver);\n\n        // Retrieve contacts' nicknames\n        Cursor nickCursor = contentResolver.query(\n            ContactsContract.Data.CONTENT_URI,\n            new String[]{\n                ContactsContract.CommonDataKinds.Nickname.NAME,\n                ContactsContract.Data.LOOKUP_KEY},\n            ContactsContract.Data.MIMETYPE + \"=?\",\n            new String[]{ContactsContract.CommonDataKinds.Nickname.CONTENT_ITEM_TYPE},\n            null);\n\n        if (nickCursor != null) {\n            if (nickCursor.getCount() > 0) {\n                int lookupKeyIndex = nickCursor.getColumnIndex(ContactsContract.Data.LOOKUP_KEY);\n                int nickNameIndex = nickCursor.getColumnIndex(ContactsContract.CommonDataKinds.Nickname.NAME);\n                while (nickCursor.moveToNext()) {\n                    String lookupKey = nickCursor.getString(lookupKeyIndex);\n                    String nick = nickCursor.getString(nickNameIndex);\n\n                    if (nick != null && lookupKey != null) {\n                        BasicContact basicContact = basicContacts.get(lookupKey);\n                        if (basicContact != null) {\n                            basicContact.setNickName(nick);\n                        }\n                    }\n                }\n            }\n            nickCursor.close();\n        }\n\n        // get mime type labels\n        Map<String, String> mimeLabels = TBApplication.mimeTypeCache(ctx).getUniqueLabels(ctx, mimeTypes);\n\n        // Query all mime types\n        for (String mimeType : mimeTypes) {\n            Timer timerMimeType = Timer.startNano();\n            int sizeBefore = contacts.size();\n            if (ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE.equals(mimeType)) {\n                contacts.addAll(createPhoneContacts(contentResolver, basicContacts, basicRawContacts));\n            } else {\n                String mimeLabel = mimeLabels.get(mimeType);\n                contacts.addAll(createGenericContacts(mimeType, basicContacts, basicRawContacts, mimeLabel));\n            }\n            int sizeAfter = contacts.size();\n            Log.i(\"time\", timerMimeType + \" to list \" + (sizeAfter - sizeBefore) + \" contact(s) for \" + mimeType);\n        }\n\n        Log.i(\"time\", timer + \" to list \" + contacts.size() + \" contact(s)\");\n        return contacts;\n    }\n\n    @NonNull\n    private static Map<String, BasicContact> loadBasicContacts(@NonNull ContentResolver contentResolver) {\n        // Run query\n        Cursor contactCursor = contentResolver.query(\n            ContactsContract.CommonDataKinds.Phone.CONTENT_URI,\n            new String[]{\n                ContactsContract.Contacts.LOOKUP_KEY,\n                ContactsContract.Contacts._ID,\n                ContactsContract.Contacts.DISPLAY_NAME_PRIMARY,\n                ContactsContract.Contacts.PHOTO_ID,\n                ContactsContract.Contacts.PHOTO_URI},\n            null, null, null);\n\n        if (contactCursor == null)\n            return Collections.emptyMap();\n\n        if (contactCursor.getCount() == 0) {\n            contactCursor.close();\n            return Collections.emptyMap();\n        }\n\n        // Query basic contact information and keep in memory to prevent duplicates\n        Map<String, BasicContact> basicContacts = new HashMap<>(contactCursor.getCount());\n\n        int lookupIndex = contactCursor.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY);\n        int contactIdIndex = contactCursor.getColumnIndex(ContactsContract.Contacts._ID);\n        int displayNameIndex = contactCursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME_PRIMARY);\n        int photoIdIndex = contactCursor.getColumnIndex(ContactsContract.Contacts.PHOTO_ID);\n        int photoUriIndex = contactCursor.getColumnIndex(ContactsContract.Contacts.PHOTO_URI);\n        while (contactCursor.moveToNext()) {\n            BasicContact basicContact = new BasicContact(\n                contactCursor.getString(lookupIndex),\n                contactCursor.getLong(contactIdIndex),\n                contactCursor.getString(displayNameIndex),\n                contactCursor.getString(photoIdIndex),\n                contactCursor.getString(photoUriIndex)\n            );\n            basicContacts.put(basicContact.getLookupKey(), basicContact);\n        }\n        contactCursor.close();\n\n        return basicContacts;\n    }\n\n    private static Map<Long, BasicRawContact> loadRawContacts(@NonNull ContentResolver contentResolver) {\n        // Query raw contact information and keep in memory to prevent duplicates\n        Cursor rawContactCursor = contentResolver.query(\n            ContactsContract.RawContacts.CONTENT_URI,\n            new String[]{ContactsContract.RawContacts._ID,\n                ContactsContract.RawContacts.ACCOUNT_TYPE,\n                ContactsContract.RawContacts.STARRED},\n            null, null, null);\n        if (rawContactCursor == null) {\n            return Collections.emptyMap();\n        }\n        if (rawContactCursor.getCount() == 0) {\n            rawContactCursor.close();\n            return Collections.emptyMap();\n        }\n        Map<Long, BasicRawContact> basicRawContacts = new HashMap<>(rawContactCursor.getCount());\n        int rawContactIdIndex = rawContactCursor.getColumnIndex(ContactsContract.RawContacts._ID);\n        int accountTypeIndex = rawContactCursor.getColumnIndex(ContactsContract.RawContacts.ACCOUNT_TYPE);\n        int starredIndex = rawContactCursor.getColumnIndex(ContactsContract.RawContacts.STARRED);\n        while (rawContactCursor.moveToNext()) {\n            BasicRawContact basicRawContact = new BasicRawContact(\n                rawContactCursor.getLong(rawContactIdIndex),\n                rawContactCursor.getString(accountTypeIndex),\n                rawContactCursor.getInt(starredIndex) != 0\n            );\n            basicRawContacts.put(basicRawContact.getId(), basicRawContact);\n        }\n        rawContactCursor.close();\n        return basicRawContacts;\n    }\n\n    private static ArrayList<ContactEntry> createPhoneContacts(@NonNull ContentResolver contentResolver, Map<String, BasicContact> basicContacts, Map<Long, BasicRawContact> basicRawContacts) {\n        // Query all phone numbers\n        Cursor phoneCursor = contentResolver.query(\n            ContactsContract.CommonDataKinds.Phone.CONTENT_URI,\n            new String[]{ContactsContract.Contacts.LOOKUP_KEY,\n                ContactsContract.CommonDataKinds.Phone.RAW_CONTACT_ID,\n                ContactsContract.CommonDataKinds.Phone.NUMBER,\n                ContactsContract.CommonDataKinds.Phone.IS_PRIMARY}, null, null, null);\n\n        // Prevent duplicates by keeping in memory encountered contacts.\n        Map<String, Set<ContactEntry>> mapContacts = new HashMap<>();\n\n        if (phoneCursor != null) {\n            if (phoneCursor.getCount() > 0) {\n                int lookupIndex = phoneCursor.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY);\n                int rawContactIdIndex = phoneCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.RAW_CONTACT_ID);\n                int numberIndex = phoneCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);\n                int isPrimaryIndex = phoneCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.IS_PRIMARY);\n\n                while (phoneCursor.moveToNext()) {\n                    String lookupKey = phoneCursor.getString(lookupIndex);\n                    BasicContact basicContact = basicContacts.get(lookupKey);\n                    long rawContactId = phoneCursor.getLong(rawContactIdIndex);\n                    BasicRawContact basicRawContact = basicRawContacts.get(rawContactId);\n\n                    if (basicContact != null && basicRawContact != null) {\n                        String phone = phoneCursor.getString(numberIndex);\n                        if (phone == null) {\n                            phone = \"\";\n                        }\n\n                        ContactEntry contact = new ContactEntry.Builder()\n                            .setContactId(basicContact.getContactId())\n                            .setPhone(phone)\n                            .setPrimary(phoneCursor.getInt(isPrimaryIndex) != 0)\n                            .setLookupKey(lookupKey)\n                            .setStarred(basicRawContact.isStarred())\n                            .setIconUri(basicContact.getIcon())\n                            .setName(basicContact.getDisplayName())\n                            .setNickname(basicContact.getNickName())\n                            .getContact();\n\n                        addContactToMap(contact, mapContacts);\n                    }\n                }\n            }\n            phoneCursor.close();\n        }\n\n        return getFilteredContacts(mapContacts, contact -> contact.normalizedPhone);\n    }\n\n    @NonNull\n    private List<ContactEntry> createGenericContacts(String mimeType, Map<String, BasicContact> basicContacts, Map<Long, BasicRawContact> basicRawContacts, String mimeLabel) {\n        // Prevent duplicates by keeping in memory encountered contacts.\n        Map<String, Set<ContactEntry>> mapContacts = new HashMap<>();\n\n        List<String> columns = new ArrayList<>();\n        columns.add(ContactsContract.Data.LOOKUP_KEY);\n        columns.add(ContactsContract.Data.RAW_CONTACT_ID);\n        columns.add(ContactsContract.Data._ID);\n        columns.add(ContactsContract.Data.IS_PRIMARY);\n\n        Context ctx = context.get();\n        if (ctx == null) {\n            Log.w(TAG, \"null context in createGenericContacts\");\n            return Collections.emptyList();\n        }\n\n        final MimeTypeCache mimeTypeCache = TBApplication.mimeTypeCache(ctx);\n        String detailColumn = mimeTypeCache.getDetailColumn(ctx, mimeType);\n        if (detailColumn != null && !columns.contains(detailColumn)) {\n            columns.add(detailColumn);\n        }\n\n        // Query all entries by mimeType\n        Cursor mimeTypeCursor = ctx.getContentResolver().query(\n            ContactsContract.Data.CONTENT_URI,\n            columns.toArray(new String[]{}),\n            ContactsContract.Data.MIMETYPE + \"= ?\",\n            new String[]{mimeType}, null);\n        if (mimeTypeCursor != null) {\n            if (mimeTypeCursor.getCount() > 0) {\n                int lookupIndex = mimeTypeCursor.getColumnIndex(ContactsContract.Data.LOOKUP_KEY);\n                int rawContactIdIndex = mimeTypeCursor.getColumnIndex(ContactsContract.Data.RAW_CONTACT_ID);\n                int idIndex = mimeTypeCursor.getColumnIndex(ContactsContract.Data._ID);\n                int isPrimaryIndex = mimeTypeCursor.getColumnIndex(ContactsContract.Data.IS_PRIMARY);\n                int detailColumnIndex = -1;\n                if (detailColumn != null) {\n                    detailColumnIndex = mimeTypeCursor.getColumnIndex(detailColumn);\n                }\n                while (mimeTypeCursor.moveToNext()) {\n                    String lookupKey = mimeTypeCursor.getString(lookupIndex);\n                    BasicContact basicContact = basicContacts.get(lookupKey);\n                    long rawContactId = mimeTypeCursor.getLong(rawContactIdIndex);\n                    BasicRawContact basicRawContact = basicRawContacts.get(rawContactId);\n\n                    if (basicContact != null && basicRawContact != null) {\n                        long id = mimeTypeCursor.getLong(idIndex);\n                        String label = null;\n                        if (detailColumnIndex >= 0) {\n                            label = mimeTypeCursor.getString(detailColumnIndex);\n                        }\n                        if (label == null) {\n                            label = mimeTypeCache.getLabel(ctx, mimeType);\n                        }\n\n                        ContactEntry.ImData imData = new ContactEntry.ImData(mimeType, id, mimeLabel);\n                        imData.setIdentifier(label);\n\n                        ContactEntry contact = new ContactEntry.Builder()\n                            .setContactId(basicContact.getContactId())\n                            .setMimeInfo(id, MimeTypeUtils.getShortMimeType(mimeType))\n                            .setPrimary(mimeTypeCursor.getInt(isPrimaryIndex) != 0)\n                            .setLookupKey(lookupKey)\n                            .setStarred(basicRawContact.isStarred())\n                            .setIconUri(basicContact.getIcon())\n                            .setName(basicContact.getDisplayName())\n                            .setNickname(basicContact.getNickName())\n                            .setImData(imData)\n                            .getContact();\n\n                        addContactToMap(contact, mapContacts);\n                    }\n                }\n            }\n            mimeTypeCursor.close();\n        }\n\n        return getFilteredContacts(mapContacts, contact -> contact.getImData().getIdentifier());\n    }\n\n    /**\n     * add contact to mapContacts, grouped by lookup key\n     *\n     * @param contact\n     * @param mapContacts\n     */\n    private static void addContactToMap(@NonNull ContactEntry contact, @NonNull Map<String, Set<ContactEntry>> mapContacts) {\n        Set<ContactEntry> mimeTypes = mapContacts.get(contact.lookupKey);\n        if (mimeTypes == null) {\n            mimeTypes = new HashSet<>(1);\n            mapContacts.put(contact.lookupKey, mimeTypes);\n        }\n        mimeTypes.add(contact);\n    }\n\n    /**\n     * Filter all contacts dependent of fields.\n     * Return primary contacts if available.\n     * If no primary contacts are available all contacts are returned.\n     *\n     * @param mapContacts all contacts grouped by lookup key\n     * @param idSupplier  id supplier for identifying duplicates\n     * @return filtered contacts\n     */\n    private static ArrayList<ContactEntry> getFilteredContacts(Map<String, Set<ContactEntry>> mapContacts, IdSupplier idSupplier) {\n        ArrayList<ContactEntry> contacts = new ArrayList<>();\n        // Add phone numbers\n        for (Set<ContactEntry> mappedContacts : mapContacts.values()) {\n            // Find primary phone and add this one.\n            boolean hasPrimary = false;\n            for (ContactEntry contact : mappedContacts) {\n                if (contact.isPrimary()) {\n                    contacts.add(contact);\n                    hasPrimary = true;\n                    break;\n                }\n            }\n\n            // If no primary available, add all (excluding duplicates).\n            if (!hasPrimary) {\n                HashSet<Object> added = new HashSet<>(mappedContacts.size());\n                for (ContactEntry contact : mappedContacts) {\n                    Object id = idSupplier.getId(contact);\n                    if (id == null) {\n                        contacts.add(contact);\n                    } else if (!added.contains(id)) {\n                        added.add(id);\n                        contacts.add(contact);\n                    }\n                }\n            }\n        }\n        return contacts;\n    }\n\n    // TODO: move to separate class, which package?\n    @FunctionalInterface\n    public interface IdSupplier {\n        Object getId(ContactEntry contact);\n    }\n\n    // TODO: move to separate class, which package?\n    private static class BasicContact {\n        private final String lookupKey;\n        private final long contactId;\n        private final String displayName;\n        private final String photoId;\n        private final String photoUri;\n        private String nickName;\n        private String mimeTypeLabel;\n\n        private BasicContact(String lookupKey, long contactId, String displayName, String photoId, String photoUri) {\n            this.lookupKey = lookupKey;\n            this.contactId = contactId;\n            this.displayName = displayName;\n            this.photoId = photoId;\n            this.photoUri = photoUri;\n        }\n\n        public String getLookupKey() {\n            return lookupKey;\n        }\n\n        public long getContactId() {\n            return contactId;\n        }\n\n        public String getDisplayName() {\n            return displayName;\n        }\n\n        public String getNickName() {\n            return nickName;\n        }\n\n        public void setNickName(String nickName) {\n            this.nickName = nickName;\n        }\n\n        public void setLabel(String label) {\n            mimeTypeLabel = label;\n        }\n\n        public Uri getIcon() {\n            if (photoUri != null) {\n                return Uri.parse(photoUri);\n            }\n            if (photoId != null) {\n                return ContentUris.withAppendedId(ContactsContract.Data.CONTENT_URI,\n                    Long.parseLong(photoId));\n\n            }\n            return null;\n        }\n    }\n\n    // TODO: move to separate class, which package?\n    private static class BasicRawContact {\n        private final long id;\n        private final String accountType;\n        private final boolean starred;\n\n        private BasicRawContact(long id, String accountType, boolean starred) {\n            this.id = id;\n            this.accountType = accountType;\n            this.starred = starred;\n        }\n\n        public long getId() {\n            return id;\n        }\n\n        public String getAccountType() {\n            return accountType;\n        }\n\n        public boolean isStarred() {\n            return starred;\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/loader/LoadEntryItem.java",
    "content": "package rocks.tbog.tblauncher.loader;\n\nimport android.content.Context;\n\nimport androidx.annotation.NonNull;\n\nimport java.lang.ref.WeakReference;\nimport java.util.ArrayList;\n\nimport rocks.tbog.tblauncher.handler.DataHandler;\nimport rocks.tbog.tblauncher.WorkAsync.AsyncTask;\nimport rocks.tbog.tblauncher.WorkAsync.TaskRunner;\nimport rocks.tbog.tblauncher.dataprovider.Provider;\nimport rocks.tbog.tblauncher.entry.EntryItem;\n\npublic abstract class LoadEntryItem<T extends EntryItem> extends AsyncTask<Void, ArrayList<T>> {\n\n    final WeakReference<Context> context;\n    private WeakReference<Provider<T>> weakProvider;\n\n    LoadEntryItem(Context context) {\n        super();\n        this.context = new WeakReference<>(context);\n    }\n\n    public void setProvider(Provider<T> provider) {\n        this.weakProvider = new WeakReference<>(provider);\n    }\n\n    @NonNull\n    public abstract String getScheme();\n\n    protected abstract ArrayList<T> doInBackground(Void param);\n\n    protected void onPostExecute(ArrayList<T> result) {\n        Provider<T> provider = weakProvider.get();\n        if (provider != null) {\n            provider.loadOver(result);\n        }\n    }\n\n    public void execute() {\n        TaskRunner.executeOnExecutor(DataHandler.EXECUTOR_PROVIDERS, this);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/loader/LoadShortcutsEntryItem.java",
    "content": "package rocks.tbog.tblauncher.loader;\n\nimport android.content.Context;\nimport android.content.pm.LauncherApps;\nimport android.content.pm.LauncherApps.ShortcutQuery;\nimport android.content.pm.ShortcutInfo;\nimport android.os.Build;\nimport android.os.Process;\n\nimport androidx.annotation.NonNull;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\n\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.db.DBHelper;\nimport rocks.tbog.tblauncher.db.ModRecord;\nimport rocks.tbog.tblauncher.db.ShortcutRecord;\nimport rocks.tbog.tblauncher.entry.ShortcutEntry;\nimport rocks.tbog.tblauncher.handler.TagsHandler;\n\npublic class LoadShortcutsEntryItem extends LoadEntryItem<ShortcutEntry> {\n\n    private final TagsHandler tagsHandler;\n    private final LauncherApps mLauncherApps;\n\n    public LoadShortcutsEntryItem(Context context) {\n        super(context);\n        tagsHandler = TBApplication.tagsHandler(context);\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            mLauncherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);\n        } else {\n            mLauncherApps = null;\n        }\n    }\n\n    @NonNull\n    @Override\n    public String getScheme() {\n        return ShortcutEntry.SCHEME;\n    }\n\n    @Override\n    protected ArrayList<ShortcutEntry> doInBackground(Void arg) {\n        Context ctx = context.get();\n        if (ctx == null) {\n            return new ArrayList<>();\n        }\n\n        final HashMap<String, ModRecord> favorites;\n        {\n            ArrayList<ModRecord> favList = DBHelper.getMods(ctx);\n            favorites = new HashMap<>();\n            for (ModRecord fav : favList)\n                favorites.put(fav.record, fav);\n        }\n\n        List<ShortcutRecord> records = DBHelper.getShortcutsNoIcons(ctx);\n        ArrayList<ShortcutEntry> pojos = new ArrayList<>(records.size());\n\n        HashMap<String, ShortcutRecord> oreoMap = new HashMap<>();\n\n        for (ShortcutRecord shortcutRecord : records) {\n            if (shortcutRecord.isOreo()) {\n                oreoMap.put(shortcutRecord.infoData, shortcutRecord);\n                continue;\n            }\n\n            final String id = ShortcutEntry.generateShortcutId(shortcutRecord);\n            final ShortcutEntry pojo = new ShortcutEntry(id, shortcutRecord.dbId, shortcutRecord.packageName, shortcutRecord.infoData);\n\n            pojo.setName(shortcutRecord.displayName);\n\n            ModRecord modRecord = favorites.get(pojo.id);\n            if (modRecord != null && modRecord.hasCustomIcon())\n                pojo.setCustomIcon();\n\n            pojos.add(pojo);\n        }\n\n        if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {\n            List<ShortcutInfo> shortcutInfos = null;\n\n            ShortcutQuery q = new ShortcutQuery();\n            if (TBApplication.getApplication(ctx).preferences().getBoolean(\"shortcut-dynamic-in-results\", false)) {\n                q.setQueryFlags(ShortcutQuery.FLAG_MATCH_PINNED\n                    | ShortcutQuery.FLAG_MATCH_MANIFEST\n                    | ShortcutQuery.FLAG_MATCH_DYNAMIC\n                    | ShortcutQuery.FLAG_MATCH_CACHED);\n            } else {\n                q.setQueryFlags(ShortcutQuery.FLAG_MATCH_PINNED);\n            }\n\n            if (mLauncherApps.hasShortcutHostPermission())\n                shortcutInfos = mLauncherApps.getShortcuts(q, Process.myUserHandle());\n            if (shortcutInfos == null) {\n                shortcutInfos = Collections.emptyList();\n            }\n\n            for (ShortcutInfo shortcutInfo : shortcutInfos) {\n                ShortcutRecord record = oreoMap.remove(shortcutInfo.getId());\n                long dbId = 0;\n                String name = null;\n                if (record != null) {\n                    dbId = record.dbId;\n                    name = record.displayName;\n                }\n                // if no name found, try the shortcut text\n                if (name == null || name.isEmpty()) {\n                    CharSequence label = shortcutInfo.getLongLabel();\n                    if (label != null)\n                        name = label.toString();\n                }\n                // if no name found, try the shortcut title\n                if (name == null || name.isEmpty()) {\n                    CharSequence label = shortcutInfo.getShortLabel();\n                    if (label != null)\n                        name = label.toString();\n                }\n                ShortcutEntry pojo = new ShortcutEntry(dbId, shortcutInfo);\n                pojo.setName(name);\n\n                ModRecord modRecord = favorites.get(pojo.id);\n                if (modRecord != null && modRecord.hasCustomIcon())\n                    pojo.setCustomIcon();\n\n                pojos.add(pojo);\n            }\n\n            // clear remaining shortcuts\n            for (ShortcutRecord record : oreoMap.values()) {\n                DBHelper.removeShortcut(ctx, record.dbId);\n                //tagsHandler.removeAllTags(ShortcutEntry.SCHEME + record.infoData);\n            }\n        }\n\n        tagsHandler.runWhenLoaded(() -> {\n            for (ShortcutEntry shortcutEntry : pojos)\n                shortcutEntry.setTags(tagsHandler.getTags(shortcutEntry.id));\n        });\n\n        return pojos;\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/normalizer/IntSequenceBuilder.java",
    "content": "package rocks.tbog.tblauncher.normalizer;\n\n/**\n * Simple integer sequence class that allows adding individual elements and exporting those\n * elements to an integer array.\n * <p/>\n * Created by Alexander Schlarb on 17.08.15.\n */\nclass IntSequenceBuilder {\n    private int[] data;\n    private int size;\n\n\n    /**\n     * @param capacity The initial size of the internal storage array\n     */\n    public IntSequenceBuilder(int capacity) {\n        // Create new storage array of requested size\n        this.data = new int[capacity];\n        this.size = 0;\n    }\n\n\n    /**\n     * Add a new element to this builder\n     *\n     * @param element The value of the element to add\n     */\n    public void add(int element) {\n        // Resize storage array larger if required\n        if ((this.size + 1) >= this.data.length) {\n            int[] data = this.data;\n            this.data = new int[(this.data.length * 3) / 2 + 1];\n            System.arraycopy(data, 0, this.data, 0, this.size);\n        }\n\n        // Add element to storage array\n        this.data[this.size] = element;\n\n        // Increment stored element number counter\n        this.size++;\n    }\n\n\n    /**\n     * Export an array with the current data stored in this builder\n     *\n     * @return Copy of the elements of the internal storage array\n     */\n    public int[] toArray() {\n        // Copy the actual number of stored elements to a new array\n        int[] data = new int[this.size];\n        System.arraycopy(this.data, 0, data, 0, this.size);\n\n        return data;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/normalizer/PhoneNormalizer.java",
    "content": "package rocks.tbog.tblauncher.normalizer;\n\npublic class PhoneNormalizer {\n    public static StringNormalizer.Result simplifyPhoneNumber(String phoneNumber) {\n        // This is done manually for performance reason,\n        // But the algorithm is just a regexp replacement of \"[-.():/ ]\" with \"\"\n\n        int numCodePoints = Character.codePointCount(phoneNumber, 0, phoneNumber.length());\n        IntSequenceBuilder codePoints = new IntSequenceBuilder(numCodePoints);\n        IntSequenceBuilder resultMap = new IntSequenceBuilder(numCodePoints);\n\n        int i = 0;\n        for (int iterCodePoint = 0; iterCodePoint < numCodePoints; iterCodePoint += 1) {\n            int c = Character.codePointAt(phoneNumber, i);\n\n            if (c != ' ' && c != '-' && c != '.' && c != '(' && c != ')' && c != ':' && c != '/') {\n                codePoints.add(c);\n                resultMap.add(i);\n            }\n            i += Character.charCount(c);\n        }\n\n        return new StringNormalizer.Result(phoneNumber.length(), codePoints.toArray(), resultMap.toArray());\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/normalizer/StringNormalizer.java",
    "content": "package rocks.tbog.tblauncher.normalizer;\n\nimport java.nio.CharBuffer;\nimport java.text.Normalizer;\nimport java.util.Arrays;\n\n/**\n * String utils to handle accented characters for search and highlighting\n */\npublic class StringNormalizer {\n    private StringNormalizer() {\n    }\n\n    /**\n     * Make the given string easier to compare by performing a number of simplifications on it\n     * <p/>\n     * 1. Decompose combination characters into their respective parts (see below)\n     * 2. Strip all combining character marks (see below)\n     * 3. Strip some other common-but-not-very-useful characters (such as dashes)\n     * 4. Lower-case the string\n     * <p/>\n     * Combination characters are characters that (essentially) have the same meaning as one or\n     * more other, more common, characters. Examples for these include:\n     * Roman numerals (`Ⅱ` → `II`) and half-width katakana (`ﾐ` → `ミ`)\n     * <p/>\n     * Combining character marks are diacritics and other extra strokes that are often found as\n     * part of many characters in non-English roman scripts. Examples for these include:\n     * Diaereses (`ë` → `e`), acutes (`á` → `a`) and macrons (`ō` → `o`)\n     *\n     * @param input         string input, with accents and anything else you can think of\n     * @param makeLowercase make all characters lowercase\n     * @return normalized string and list that maps each result string position to its source\n     * string position\n     */\n    public static Result normalizeWithResult(CharSequence input, boolean makeLowercase) {\n        int numCodePoints = Character.codePointCount(input, 0, input.length());\n        IntSequenceBuilder codePoints = new IntSequenceBuilder(numCodePoints);\n        IntSequenceBuilder resultMap = new IntSequenceBuilder(numCodePoints);\n        CharBuffer buffer = CharBuffer.allocate(2);\n        int i = 0;\n        for (int iterCodePoint = 0; iterCodePoint < numCodePoints; iterCodePoint += 1) {\n            int codepoint = Character.codePointAt(input, i);\n            String decomposedCharString;\n            // Is it within the basic latin range?\n            // If so, we can skip the expensive call to Normalizer.normalize\n            if (codepoint < 'z') {\n                // Ascii range, no need to normalize!\n                // Add directly if it's not a dash\n                // (HYPHEN-MINUS is the only character before 'z' in one of the\n                //  NON_SPACING_MARK / COMBINING_SPACING_MARK / DASH_PUNCTUATION\n                //  category, so we can skip the Character.getType() and explicitly check for it)\n                if (codepoint != '-') {\n                    codePoints.add(makeLowercase ? Character.toLowerCase(codepoint) : codepoint);\n                    resultMap.add(i);\n                }\n            } else {\n                // Otherwise, we'll need to normalize the code point to a letter and potential accentuation\n                buffer.put(Character.toChars(codepoint));\n                buffer.flip();\n                decomposedCharString = Normalizer.normalize(buffer, Normalizer.Form.NFKD);\n                buffer.clear();\n\n                // `inputChar` codepoint may be decomposed to four (or maybe even more) new code points\n                int decomposedCharOffset = 0;\n                while (decomposedCharOffset < decomposedCharString.length()) {\n                    int resultChar = decomposedCharString.codePointAt(decomposedCharOffset);\n\n                    // Skip characters for some unicode character classes, including:\n                    //  * combining characters produced by the NFKD normalizer above\n                    //  * dashes\n                    // See the method's description for more information\n                    switch (Character.getType(resultChar)) {\n                        case Character.NON_SPACING_MARK:\n                        case Character.COMBINING_SPACING_MARK:\n                            // Some combining character found\n                            // See http://www.fileformat.info/info/unicode/category/Mn/list.htm\n                            // And http://www.fileformat.info/info/unicode/category/Mc/list.htm\n                            break;\n\n                        case Character.DASH_PUNCTUATION:\n                            // We skip dashes too\n                            // (standard HYPHEN-MINUS was skipped above, but dashes are a large family!)\n                            // see http://www.fileformat.info/info/unicode/category/Pd/list.htm\n                            break;\n\n                        default:\n                            codePoints.add(makeLowercase ? Character.toLowerCase(resultChar) : resultChar);\n                            resultMap.add(i);\n                    }\n\n                    decomposedCharOffset += Character.charCount(resultChar);\n                }\n            }\n\n            i += Character.charCount(codepoint);\n        }\n\n        return new Result(input.length(), codePoints.toArray(), resultMap.toArray());\n    }\n\n    public static class Result implements Comparable<Result> {\n        private final int originalInputLastCharPosition;\n        public final int[] codePoints;\n        private final int[] mapPositions;\n\n        Result(final int originalInputLastCharPosition,\n               final int[] codePoints, final int[] mapPositions) {\n            if (codePoints.length != mapPositions.length)\n                throw new IllegalStateException(\"Each codepoint needs a mapped position\");\n            this.originalInputLastCharPosition = originalInputLastCharPosition;\n            this.codePoints = codePoints;\n            this.mapPositions = mapPositions;\n        }\n\n        public int length() {\n            return this.codePoints.length;\n        }\n\n        /**\n         * Map a position in the normalized string to a position in the original string\n         *\n         * @param position Position in normalized string\n         * @return Position in non-normalized string\n         */\n        public int mapPosition(int position) {\n            if (position < mapPositions.length)\n                return mapPositions[position];\n            // We are behind the last character, return the position of the end of the original input\n            return originalInputLastCharPosition;\n        }\n\n        @Override\n        public int compareTo(Result that) {\n            // this optimization is usually worthwhile, and can always be added\n            if (this == that)\n                return 0;\n            if (that == null)\n                return 1;\n\n            int minLength = Math.min(this.codePoints.length, that.codePoints.length);\n            for (int i = 0; i < minLength; i += 1) {\n                final int cmp = Character.toLowerCase(this.codePoints[i]) - Character.toLowerCase(that.codePoints[i]);\n                if (cmp != 0)\n                    return cmp;\n            }\n\n            if (this.codePoints.length != that.codePoints.length)\n                return this.codePoints.length - that.codePoints.length;\n\n            // equal\n            return 0;\n        }\n\n        @Override\n        public boolean equals(Object o) {\n            if (this == o)\n                return true;\n            if (!(o instanceof Result))\n                return false;\n\n            Result result = (Result) o;\n\n            return Arrays.equals(codePoints, result.codePoints);\n        }\n\n        @Override\n        public int hashCode() {\n            return Arrays.hashCode(codePoints);\n        }\n\n        @Override\n        public String toString() {\n            // Since we stripped all combining Unicode characters in the\n            // normalization function there should be no combining character\n            // remaining in the string and the composed and decomposed\n            // versions of the string should be equivalent. This also means\n            // we do not need to convert the string back to composed Unicode\n            // before returning it.\n            StringBuilder sb = new StringBuilder(codePoints.length);\n            for (int codePoint : codePoints)\n                sb.appendCodePoint(codePoint);\n            return sb.toString();\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/preference/BaseListPreferenceDialog.java",
    "content": "package rocks.tbog.tblauncher.preference;\n\nimport android.os.Bundle;\n\nimport androidx.appcompat.app.AlertDialog;\nimport androidx.preference.ListPreferenceDialogFragmentCompat;\n\nimport rocks.tbog.tblauncher.utils.DialogHelper;\n\npublic class BaseListPreferenceDialog extends ListPreferenceDialogFragmentCompat {\n\n    public static BaseListPreferenceDialog newInstance(String key) {\n        final BaseListPreferenceDialog fragment = new BaseListPreferenceDialog();\n        final Bundle b = new Bundle(1);\n        b.putString(ARG_KEY, key);\n        fragment.setArguments(b);\n        return fragment;\n    }\n\n    @Override\n    protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {\n        super.onPrepareDialogBuilder(builder);\n        DialogHelper.setCustomTitle(builder, getPreference().getDialogTitle());\n    }\n\n    @Override\n    public void onStart() {\n        super.onStart();\n        DialogHelper.setButtonBarBackground(requireDialog());\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/preference/BaseMultiSelectListPreferenceDialog.java",
    "content": "package rocks.tbog.tblauncher.preference;\n\nimport android.os.Bundle;\n\nimport androidx.appcompat.app.AlertDialog;\nimport androidx.preference.MultiSelectListPreferenceDialogFragmentCompat;\n\nimport rocks.tbog.tblauncher.utils.DialogHelper;\n\npublic class BaseMultiSelectListPreferenceDialog extends MultiSelectListPreferenceDialogFragmentCompat {\n\n    public static BaseMultiSelectListPreferenceDialog newInstance(String key) {\n        final BaseMultiSelectListPreferenceDialog fragment = new BaseMultiSelectListPreferenceDialog();\n        final Bundle b = new Bundle(1);\n        b.putString(ARG_KEY, key);\n        fragment.setArguments(b);\n        return fragment;\n    }\n\n    @Override\n    protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {\n        super.onPrepareDialogBuilder(builder);\n        DialogHelper.setCustomTitle(builder, getPreference().getDialogTitle());\n    }\n\n    @Override\n    public void onStart() {\n        super.onStart();\n        DialogHelper.setButtonBarBackground(requireDialog());\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/preference/BasePreferenceDialog.java",
    "content": "package rocks.tbog.tblauncher.preference;\n\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.ViewParent;\n\nimport androidx.annotation.NonNull;\nimport androidx.appcompat.app.AlertDialog;\nimport androidx.lifecycle.Lifecycle;\nimport androidx.lifecycle.LifecycleOwner;\nimport androidx.lifecycle.LifecycleRegistry;\nimport androidx.preference.PreferenceDialogFragmentCompat;\n\nimport rocks.tbog.tblauncher.utils.DialogHelper;\n\npublic abstract class BasePreferenceDialog extends PreferenceDialogFragmentCompat {\n    private View mDialogView = null;\n    private DialogLifecycleOwner mDialogLifecycleOwner = new DialogLifecycleOwner();\n\n    public LifecycleOwner getDialogLifecycleOwner() {\n        return mDialogLifecycleOwner;\n    }\n\n    @Override\n    protected void onBindDialogView(View view) {\n        super.onBindDialogView(view);\n        mDialogView = view;\n        mDialogLifecycleOwner.onCreate();\n    }\n\n    @Override\n    protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {\n        super.onPrepareDialogBuilder(builder);\n        DialogHelper.setCustomTitle(builder, getPreference().getDialogTitle());\n    }\n\n    @Override\n    public void onStart() {\n        super.onStart();\n\n        // hack to have the LinearLayout weight work\n        ViewParent parent = mDialogView != null ? mDialogView.getParent() : null;\n        while (parent instanceof ViewGroup) {\n            ViewGroup layout = (ViewGroup) parent;\n            ViewGroup.LayoutParams params = layout.getLayoutParams();\n            if (params.height != ViewGroup.LayoutParams.MATCH_PARENT) {\n                params.width = ViewGroup.LayoutParams.MATCH_PARENT;\n                params.height = ViewGroup.LayoutParams.MATCH_PARENT;\n                layout.setLayoutParams(params);\n            }\n            if (layout.getId() == android.R.id.content)\n                break;\n            parent = parent.getParent();\n        }\n\n        DialogHelper.setButtonBarBackground(requireDialog());\n        mDialogLifecycleOwner.onStart();\n    }\n\n    @Override\n    public void onStop() {\n        super.onStop();\n        mDialogLifecycleOwner.onStop();\n    }\n\n    @Override\n    public void onDestroyView() {\n        mDialogView = null;\n        super.onDestroyView();\n        mDialogLifecycleOwner.onDestroy();\n    }\n\n    @Override\n    public void onResume() {\n        super.onResume();\n        mDialogLifecycleOwner.onResume();\n    }\n\n    @Override\n    public void onPause() {\n        super.onPause();\n        mDialogLifecycleOwner.onPause();\n    }\n\n    protected static class DialogLifecycleOwner implements LifecycleOwner {\n        LifecycleRegistry lifecycleRegistry;\n\n        public DialogLifecycleOwner() {\n            lifecycleRegistry = new LifecycleRegistry(this);\n        }\n\n        public void onCreate() {\n            lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);\n        }\n\n        public void onStart() {\n            lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);\n        }\n\n        public void onResume() {\n            lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);\n        }\n\n        public void onPause() {\n            lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE);\n        }\n\n        public void onStop() {\n            lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);\n        }\n\n        public void onDestroy() {\n            lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);\n        }\n\n        @NonNull\n        @Override\n        public Lifecycle getLifecycle() {\n            return lifecycleRegistry;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/preference/ConfirmDialog.java",
    "content": "package rocks.tbog.tblauncher.preference;\n\nimport android.annotation.SuppressLint;\nimport android.app.Activity;\nimport android.app.Dialog;\nimport android.app.admin.DevicePolicyManager;\nimport android.content.Context;\nimport android.content.DialogInterface;\nimport android.content.Intent;\nimport android.content.SharedPreferences;\nimport android.os.Bundle;\nimport android.os.Looper;\nimport android.util.Log;\nimport android.view.View;\nimport android.widget.TextView;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.WorkerThread;\nimport androidx.appcompat.app.AlertDialog;\nimport androidx.preference.PreferenceGroup;\nimport androidx.preference.PreferenceManager;\n\nimport rocks.tbog.tblauncher.DeviceAdmin;\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.SettingsActivity;\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.TBLauncherActivity;\nimport rocks.tbog.tblauncher.WorkAsync.TaskRunner;\nimport rocks.tbog.tblauncher.db.XmlExport;\nimport rocks.tbog.tblauncher.utils.FileUtils;\nimport rocks.tbog.tblauncher.utils.UITheme;\nimport rocks.tbog.tblauncher.utils.Utilities;\n\npublic class ConfirmDialog extends BasePreferenceDialog {\n\n    private static final String TAG = \"Dialog\";\n\n    public static ConfirmDialog newInstance(String key) {\n        ConfirmDialog fragment = new ConfirmDialog();\n        final Bundle b = new Bundle(1);\n        b.putString(ARG_KEY, key);\n        fragment.setArguments(b);\n\n        return fragment;\n    }\n\n    @SuppressLint(\"ApplySharedPref\")\n    @Override\n    public void onDialogClosed(boolean positiveResult) {\n        if (!positiveResult)\n            return;\n        CustomDialogPreference preference = (CustomDialogPreference) getPreference();\n        final String key = preference.getKey();\n\n        switch (key) {\n            case \"device-admin\": {\n                final Context context = requireContext();\n                if (DeviceAdmin.isAdminActive(context)) {\n                    DeviceAdmin.removeActiveAdmin(context);\n                } else {\n                    Activity activity = requireActivity();\n                    Intent intent = new Intent();\n                    intent.setAction(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);\n                    intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, DeviceAdmin.getAdminComponent(context));\n                    intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION, getString(R.string.device_admin_explanation));\n                    activity.startActivityForResult(intent, SettingsActivity.ENABLE_DEVICE_ADMIN);\n                }\n                break;\n            }\n            case \"generate-theme-simple\": {\n                UITheme.applyColorsThemeSimple(requireContext());\n                break;\n            }\n            case \"generate-theme-highlight\": {\n                UITheme.applyColorsThemeHighlight(requireContext());\n                break;\n            }\n            case \"reset-matrix\": {\n                final Context context = requireContext();\n                preference\n                        .getPreferenceManager()\n                        .getSharedPreferences()\n                        .edit()\n                        .remove(\"icon-scale-red\")\n                        .remove(\"icon-scale-green\")\n                        .remove(\"icon-scale-blue\")\n                        .remove(\"icon-scale-alpha\")\n                        .remove(\"icon-hue\")\n                        .remove(\"icon-contrast\")\n                        .remove(\"icon-brightness\")\n                        .remove(\"icon-saturation\")\n                        .commit();\n\n                PreferenceManager.setDefaultValues(context, R.xml.preferences, true);\n                TBApplication.drawableCache(context).clearCache();\n                TBLauncherActivity launcherActivity = TBApplication.launcherActivity(context);\n                if (launcherActivity != null) {\n                    launcherActivity.refreshSearchRecords();\n                    launcherActivity.queueDockReload();\n                }\n                break;\n            }\n            case \"reset-preferences\":\n                preference.getPreferenceManager().getSharedPreferences().edit().clear().commit();\n                PreferenceManager.setDefaultValues(requireContext(), R.xml.preferences, true);\n                PreferenceManager.setDefaultValues(requireContext(), R.xml.preference_features, true);\n                break;\n            case \"reset-cached-app-icons\":\n                TBApplication.iconsHandler(getContext()).resetCachedAppIcons();\n                break;\n            case \"exit-app\":\n                //getActivity().finishAffinity();\n                System.exit(0);\n                break;\n            case \"reset-default-launcher\":\n                TBApplication.resetDefaultLauncherAndOpenChooser(requireContext());\n                break;\n            case \"export-tags\":\n                FileUtils.sendSettingsFile(requireActivity(), \"tags\");\n                break;\n            case \"export-modifications\":\n                FileUtils.sendSettingsFile(requireActivity(), \"modifications\");\n                break;\n            case \"export-apps\":\n                FileUtils.sendSettingsFile(requireActivity(), \"applications\");\n                break;\n            case \"export-interface\":\n                FileUtils.sendSettingsFile(requireActivity(), \"interface\");\n                break;\n            case \"export-preferences\":\n                FileUtils.sendSettingsFile(requireActivity(), \"settings\");\n                break;\n            case \"export-widgets\":\n                FileUtils.sendSettingsFile(requireActivity(), \"widgets\");\n                break;\n            case \"export-history\":\n                FileUtils.sendSettingsFile(requireActivity(), \"history\");\n                break;\n            case \"export-backup\":\n                FileUtils.sendSettingsFile(requireActivity(), \"backup\");\n                break;\n            case \"unlimited-search-cap\": {\n                SharedPreferences pref = preference.getPreferenceManager().getSharedPreferences();\n                pref.edit().putInt(\"result-search-cap\", 0).apply();\n                break;\n            }\n            default:\n                Log.w(TAG, \"Unexpected key `\" + key + \"`\");\n        }\n    }\n\n    @Override\n    protected void onBindDialogView(View view) {\n        super.onBindDialogView(view);\n        CustomDialogPreference preference = (CustomDialogPreference) getPreference();\n        final String key = preference.getKey();\n\n        switch (key) {\n            case \"device-admin\":\n                ((TextView) view.findViewById(android.R.id.text1)).setText(R.string.device_admin_disable);\n                ((TextView) view.findViewById(android.R.id.text2)).setVisibility(View.GONE);\n                break;\n            case \"generate-theme-simple\":\n            case \"generate-theme-highlight\":\n                ((TextView) view.findViewById(android.R.id.text1)).setText(R.string.generate_theme_confirm);\n                ((TextView) view.findViewById(android.R.id.text2)).setText(R.string.generate_theme_description);\n                break;\n            case \"reset-matrix\":\n                ((TextView) view.findViewById(android.R.id.text1)).setText(R.string.reset_matrix_confirm);\n                ((TextView) view.findViewById(android.R.id.text2)).setText(R.string.reset_matrix_description);\n                break;\n            case \"reset-preferences\":\n                ((TextView) view.findViewById(android.R.id.text1)).setText(R.string.reset_preferences_confirm);\n                ((TextView) view.findViewById(android.R.id.text2)).setText(R.string.reset_preferences_description);\n                break;\n            case \"reset-cached-app-icons\":\n                ((TextView) view.findViewById(android.R.id.text1)).setText(R.string.reset_cached_app_icons_confirm);\n                ((TextView) view.findViewById(android.R.id.text2)).setText(R.string.reset_cached_app_icons_description);\n                break;\n            case \"crash-app\":\n                throw new IllegalStateException(\"Debug crash\");\n            case \"exit-app\":\n                ((TextView) view.findViewById(android.R.id.text1)).setText(R.string.exit_the_app_confirm);\n                ((TextView) view.findViewById(android.R.id.text2)).setText(R.string.exit_the_app_description);\n                break;\n            case \"reset-default-launcher\":\n                ((TextView) view.findViewById(android.R.id.text1)).setText(R.string.reset_default_launcher_confirm);\n                ((TextView) view.findViewById(android.R.id.text2)).setText(R.string.reset_default_launcher_description);\n                break;\n            case \"export-tags\":\n            case \"export-modifications\":\n            case \"export-apps\":\n            case \"export-interface\":\n            case \"export-widgets\":\n            case \"export-history\":\n            case \"export-backup\":\n                ((TextView) view.findViewById(android.R.id.text1)).setText(R.string.export_xml);\n                ((TextView) view.findViewById(android.R.id.text2)).setText(R.string.export_description);\n                break;\n        }\n    }\n\n    @WorkerThread\n    @SuppressLint(\"RestrictedApi\")\n    private static PreferenceGroup loadAllPreferences(@NonNull Context context) {\n        boolean looperCreated = false;\n        if (Looper.myLooper() == null) {\n            //because inflateFromResource needs a looper and we don't have one, we make one\n            Looper.prepare();\n            looperCreated = true;\n        }\n\n        // load the preference XML\n        PreferenceManager manager = new PreferenceManager(context);\n        PreferenceGroup root = manager.inflateFromResource(context, R.xml.preferences, null);\n        // add `R.xml.preference_features` to rootPreference even if it means we'll get some duplicated key errors\n        // it's easier to handle only one root\n        manager.inflateFromResource(context, R.xml.preference_features, root.findPreference(\"feature-holder\"));\n\n        // we don't need the looper anymore\n        if (looperCreated) {\n            Looper.myLooper().quitSafely();\n            Looper.loop();\n        }\n        return root;\n    }\n\n    @Override\n    public void onStart() {\n        super.onStart();\n        CustomDialogPreference preference = (CustomDialogPreference) getPreference();\n        TaskRunner.AsyncRunnable asyncWrite = null;\n        final String key = preference.getKey();\n\n        switch (key) {\n            case \"device-admin\": {\n                if (!DeviceAdmin.isAdminActive(requireContext())) {\n                    ((AlertDialog) requireDialog()).getButton(DialogInterface.BUTTON_POSITIVE).performClick();\n                }\n                break;\n            }\n            case \"export-tags\":\n                asyncWrite = t -> {\n                    final Activity activity = Utilities.getActivity(getContext());\n                    if (activity != null)\n                        FileUtils.writeSettingsFile(activity, \"tags\", w -> XmlExport.tagsXml(activity, w));\n                };\n                break;\n            case \"export-modifications\":\n                asyncWrite = t -> {\n                    final Activity activity = Utilities.getActivity(getContext());\n                    if (activity != null)\n                        FileUtils.writeSettingsFile(activity, \"modifications\", w -> XmlExport.modificationsXml(activity, w));\n                };\n                break;\n            case \"export-apps\":\n                asyncWrite = t -> {\n                    final Activity activity = Utilities.getActivity(getContext());\n                    if (activity != null)\n                        FileUtils.writeSettingsFile(activity, \"applications\", w -> XmlExport.applicationsXml(activity, w));\n                };\n                break;\n            case \"export-interface\": {\n                asyncWrite = t -> {\n                    final Activity activity = Utilities.getActivity(getContext());\n                    if (activity != null) {\n                        final PreferenceGroup rootPreference = loadAllPreferences(activity);\n                        FileUtils.writeSettingsFile(activity, \"interface\", w -> XmlExport.interfaceXml(rootPreference, w));\n                    }\n                };\n                break;\n            }\n            case \"export-preferences\": {\n                asyncWrite = t -> {\n                    final Activity activity = Utilities.getActivity(getContext());\n                    if (activity != null) {\n                        final PreferenceGroup rootPreference = loadAllPreferences(activity);\n                        FileUtils.writeSettingsFile(activity, \"settings\", w -> XmlExport.preferencesXml(rootPreference, w));\n                    }\n                };\n                break;\n            }\n            case \"export-widgets\": {\n                asyncWrite = t -> {\n                    final Activity activity = Utilities.getActivity(getContext());\n                    if (activity != null)\n                        FileUtils.writeSettingsFile(activity, \"widgets\", w -> XmlExport.widgetsXml(activity, w));\n                };\n                break;\n            }\n            case \"export-history\": {\n                asyncWrite = t -> {\n                    final Activity activity = Utilities.getActivity(getContext());\n                    if (activity != null)\n                        FileUtils.writeSettingsFile(activity, \"history\", w -> XmlExport.historyXml(activity, w));\n                };\n                break;\n            }\n            case \"export-backup\": {\n                asyncWrite = t -> {\n                    final Activity activity = Utilities.getActivity(getContext());\n                    if (activity != null) {\n                        final PreferenceGroup rootPreference = loadAllPreferences(activity);\n                        FileUtils.writeSettingsFile(activity, \"backup\", w -> XmlExport.backupXml(rootPreference, w));\n                    }\n                };\n                break;\n            }\n        }\n        if (asyncWrite != null) {\n            {\n                Dialog dialog = getDialog();\n                // disable positive button while we generate the file\n                if (dialog instanceof AlertDialog)\n                    ((AlertDialog) dialog).getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(false);\n            }\n            Utilities.runAsync(getLifecycle(), asyncWrite, (t) -> {\n                Activity activity = Utilities.getActivity(getContext());\n                Dialog dialog = getDialog();\n                // enable positive button after we generate the file\n                if (activity != null && dialog instanceof AlertDialog)\n                    ((AlertDialog) dialog).getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(true);\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/preference/ContentLoadHelper.java",
    "content": "package rocks.tbog.tblauncher.preference;\n\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.graphics.drawable.Drawable;\nimport android.text.SpannableString;\nimport android.util.Log;\nimport android.util.Pair;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.StringRes;\nimport androidx.core.graphics.drawable.DrawableCompat;\nimport androidx.preference.MultiSelectListPreference;\nimport androidx.preference.Preference;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.SettingsActivity;\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.dataprovider.TagsProvider;\nimport rocks.tbog.tblauncher.drawable.TextDrawable;\nimport rocks.tbog.tblauncher.entry.ActionEntry;\nimport rocks.tbog.tblauncher.entry.FilterEntry;\nimport rocks.tbog.tblauncher.entry.StaticEntry;\nimport rocks.tbog.tblauncher.entry.TagEntry;\nimport rocks.tbog.tblauncher.handler.TagsHandler;\nimport rocks.tbog.tblauncher.utils.PrefOrderedListHelper;\nimport rocks.tbog.tblauncher.utils.UIColors;\nimport rocks.tbog.tblauncher.utils.Utilities;\n\npublic class ContentLoadHelper {\n    public static final CategoryItem[] RESULT_POPUP_CATEGORIES = {\n        new CategoryItem(R.string.popup_title_shortcut_dynamic, \"dyn_shortcut\"),\n        new CategoryItem(R.string.popup_title_hist_fav, \"prefs\"),\n        new CategoryItem(R.string.popup_title_customize, \"customize\"),\n        new CategoryItem(R.string.popup_title_link, \"links\"),\n        new CategoryItem(R.string.popup_title_debug, \"debug\"),\n    };\n\n    public static OrderedMultiSelectListData generateResultPopupContent(@NonNull Context context, @NonNull SharedPreferences sharedPreferences) {\n        final HashSet<String> values = new HashSet<>(RESULT_POPUP_CATEGORIES.length);\n        // get default values\n        for (CategoryItem categoryItem : RESULT_POPUP_CATEGORIES) {\n            categoryItem.updateText(context);\n            values.add(categoryItem.value);\n        }\n\n        // get values from previous order\n        final ArrayList<String> orderedValues;\n        {\n            Set<String> order = sharedPreferences.getStringSet(\"result-popup-order\", Collections.emptySet());\n            orderedValues = new ArrayList<>(order);\n            Collections.sort(orderedValues);\n        }\n\n        // sync current categories with previous order\n        ArrayList<String> newOrder = new ArrayList<>(RESULT_POPUP_CATEGORIES.length);\n        for (String orderValue : orderedValues) {\n            String valueName = PrefOrderedListHelper.getOrderedValueName(orderValue);\n            if (values.remove(valueName))\n                newOrder.add(valueName);\n        }\n        for (CategoryItem categoryItem : RESULT_POPUP_CATEGORIES) {\n            if (values.remove(categoryItem.value))\n                newOrder.add(categoryItem.value);\n        }\n\n        // make new order values\n        orderedValues.clear();\n        orderedValues.addAll(PrefOrderedListHelper.getOrderedArrayList(newOrder));\n\n        // initialize entries using the ordered values\n        CharSequence[] entries = new CharSequence[orderedValues.size()];\n        CharSequence[] entryValues = new CharSequence[orderedValues.size()];\n        for (int i = 0; i < orderedValues.size(); i += 1) {\n            String orderValue = orderedValues.get(i);\n            String value = PrefOrderedListHelper.getOrderedValueName(orderValue);\n            for (CategoryItem categoryItem : RESULT_POPUP_CATEGORIES) {\n                if (categoryItem.value.equals(value)) {\n                    entries[i] = categoryItem.text;\n                    entryValues[i] = categoryItem.value;\n                    break;\n                }\n            }\n        }\n\n        return new OrderedMultiSelectListData(entries, entryValues, null, orderedValues);\n    }\n\n    public static OrderedMultiSelectListData generateTagsMenuContent(@NonNull Context context, @NonNull SharedPreferences sharedPreferences) {\n        TagsHandler tagsHandler = TBApplication.tagsHandler(context);\n        Set<String> validTags = tagsHandler.getValidTags();\n\n        TagsProvider tagsProvider = TBApplication.dataHandler(context).getTagsProvider();\n\n        Set<String> tagsMenuListValues = sharedPreferences.getStringSet(\"tags-menu-list\", Collections.emptySet());\n\n        ArrayList<String> prefEntries = new ArrayList<>(validTags);\n        // make sure we have the selected values as entries (so the user can remove them)\n        for (String tagName : tagsMenuListValues) {\n            if (!validTags.contains(tagName))\n                prefEntries.add(0, tagName);\n        }\n        // sort entries\n        Collections.sort(prefEntries, String.CASE_INSENSITIVE_ORDER);\n\n        int layoutDirection = context.getResources().getConfiguration().getLayoutDirection();\n        int size = context.getResources().getDimensionPixelSize(R.dimen.icon_preview_size);\n\n        // set preference entries\n        int count = prefEntries.size();\n        CharSequence[] entries = new CharSequence[count];\n        for (int idx = 0; idx < count; idx += 1) {\n            String tagName = prefEntries.get(idx);\n            if (tagsProvider != null) {\n                TagEntry tagEntry = tagsProvider.getTagEntry(tagName);\n                Drawable tagIcon = tagEntry.getIconDrawable(context);\n                tagIcon.setBounds(0, 0, size, size);\n\n                SpannableString name = Utilities.addDrawableBeforeString(tagName, tagIcon, layoutDirection);\n                entries[idx] = name;\n            } else {\n                entries[idx] = tagName;\n            }\n        }\n\n        // set preference values\n        CharSequence[] entryValues = prefEntries.toArray(new String[0]);\n\n\n        // set default values if we need them\n        HashSet<String> defaultValues = new HashSet<>();\n        for (String tagName : validTags) {\n            if (defaultValues.size() >= 5)\n                break;\n            defaultValues.add(tagName);\n        }\n\n        Set<String> orderedValues = sharedPreferences.getStringSet(\"tags-menu-order\", null);\n        return new OrderedMultiSelectListData(entries, entryValues, defaultValues, orderedValues);\n    }\n\n    public static Pair<CharSequence[], CharSequence[]> generateStaticEntryList(@NonNull Context context, @NonNull List<StaticEntry> entryToShowList) {\n        final int size = entryToShowList.size();\n        final CharSequence[] entries = new CharSequence[size];\n        final CharSequence[] entryValues = new CharSequence[size];\n\n        int layoutDirection = context.getResources().getConfiguration().getLayoutDirection();\n        int iconSize = context.getResources().getDimensionPixelSize(R.dimen.icon_preview_size);\n        int tintColor = UIColors.getThemeColor(context, com.google.android.material.R.attr.colorAccent);\n\n        for (int idx = 0; idx < size; idx++) {\n            StaticEntry entry = entryToShowList.get(idx);\n            if (entry instanceof TagEntry) {\n                Drawable tagIcon = entry.getIconDrawable(context);\n                if (tagIcon instanceof TextDrawable)\n                    ((TextDrawable) tagIcon).setTextColor(tintColor);\n                tagIcon.setBounds(0, 0, iconSize, iconSize);\n                SpannableString name = Utilities.addDrawableBeforeString(entry.getName(), tagIcon, layoutDirection);\n                entries[idx] = name;\n            } else if (entry instanceof ActionEntry || entry instanceof FilterEntry) {\n                Drawable iconAction = entry.getDefaultDrawable(context);\n                if (iconAction == null) {\n                    entries[idx] = entry.getName();\n                } else {\n                    DrawableCompat.setTint(iconAction, tintColor);\n                    iconAction.setBounds(0, 0, iconSize, iconSize);\n\n                    SpannableString name = Utilities.addDrawableBeforeString(entry.getName(), iconAction, layoutDirection);\n                    entries[idx] = name;\n                }\n            } else {\n                entries[idx] = entry.getName();\n            }\n            entryValues[idx] = entry.id;\n        }\n        return new Pair<>(entries, entryValues);\n    }\n\n    public static void setMultiListValues(@Nullable Preference preference, @NonNull Pair<CharSequence[], CharSequence[]> values, @Nullable Set<String> selected) {\n        if (!(preference instanceof MultiSelectListPreference))\n            return;\n        MultiSelectListPreference multiSelectList = (MultiSelectListPreference) preference;\n        multiSelectList.setEntryValues(values.first);\n        multiSelectList.setEntries(values.second);\n        if (selected != null)\n            multiSelectList.setValues(selected);\n    }\n\n    public static class CategoryItem {\n        /**\n         * String resource used when inflating the popup menu.\n         * Currently we use this to generate the preference menu as well\n         */\n        @StringRes\n        public final int textId;\n\n        /**\n         * Value stored in the preference. Must not change with language.\n         */\n        public final String value;\n\n        /**\n         * String to be used for the preference menu.\n         */\n        private String text = null;\n\n        public CategoryItem(int textId, String value) {\n            this.textId = textId;\n            this.value = value;\n        }\n\n        /**\n         * Using context generate the string for the preference menu\n         *\n         * @param context so we can get the string from the resource id\n         */\n        public void updateText(@NonNull Context context) {\n            text = context.getString(textId);\n        }\n    }\n\n    public static class OrderedMultiSelectListData {\n        private final CharSequence[] entries;\n        private final CharSequence[] entryValues;\n        private final Set<String> defaultValues;\n        private final ArrayList<String> orderedValues;\n\n        public OrderedMultiSelectListData(CharSequence[] entries, CharSequence[] entryValues, Set<String> defaultValues, @Nullable Collection<String> orderedValues) {\n            this.entries = entries;\n            this.entryValues = entryValues;\n            this.defaultValues = defaultValues;\n\n            if (orderedValues == null || orderedValues.isEmpty()) {\n                // if no order found\n                this.orderedValues = PrefOrderedListHelper.getOrderedArrayList(entryValues);\n            } else {\n                this.orderedValues = new ArrayList<>(orderedValues);\n                // sort entries\n                Collections.sort(this.orderedValues);\n            }\n        }\n\n        public void reloadOrderedValues(@NonNull SharedPreferences sharedPreferences, @NonNull SettingsActivity.SettingsFragment settings, String orderKey) {\n            orderedValues.clear();\n            orderedValues.addAll(sharedPreferences.getStringSet(orderKey, Collections.emptySet()));\n            Collections.sort(orderedValues);\n            setOrderedListValues(settings.findPreference(orderKey));\n        }\n\n        public void setMultiListValues(@Nullable Preference preference) {\n            if (!(preference instanceof MultiSelectListPreference))\n                return;\n            MultiSelectListPreference multiSelectList = (MultiSelectListPreference) preference;\n\n            if (entries != null)\n                multiSelectList.setEntries(entries);\n            if (entryValues != null)\n                multiSelectList.setEntryValues(entryValues);\n            if (defaultValues != null && multiSelectList.getValues().isEmpty())\n                multiSelectList.setValues(defaultValues);\n\n            Log.d(\"pref\", \"setMultiListValues \" + preference.getKey() + \"\\n entries=\" + Arrays.toString(entries) + \"\\n values=\" + Arrays.toString(entryValues));\n        }\n\n        public void setOrderedListValues(@Nullable Preference preference) {\n            if (!(preference instanceof MultiSelectListPreference))\n                return;\n            MultiSelectListPreference listPref = (MultiSelectListPreference) preference;\n\n            ArrayList<CharSequence> orderedEntries = new ArrayList<>(orderedValues.size());\n            ArrayList<CharSequence> orderedEntryValues = new ArrayList<>(orderedValues.size());\n            for (String orderedValue : orderedValues) {\n                String value = PrefOrderedListHelper.getOrderedValueName(orderedValue);\n                for (int i = 0; i < entryValues.length; i += 1) {\n                    if (entryValues[i].equals(value)) {\n                        orderedEntries.add(entries[i]);\n                        orderedEntryValues.add(entryValues[i]);\n                        break;\n                    }\n                }\n            }\n\n            listPref.setEntries(orderedEntries.toArray(new CharSequence[0]));\n            listPref.setEntryValues(orderedEntryValues.toArray(new CharSequence[0]));\n            Log.d(\"pref\", \"setOrderedListValues \" + listPref.getKey() + \"\\n entries=\" + orderedEntries + \"\\n values=\" + orderedValues);\n        }\n\n        public List<String> getOrderedListValues() {\n            return orderedValues;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/preference/CustomDialogPreference.java",
    "content": "package rocks.tbog.tblauncher.preference;\n\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport android.graphics.drawable.Drawable;\nimport android.util.AttributeSet;\nimport android.view.View;\nimport android.widget.ImageView;\nimport android.widget.TextView;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.preference.PreferenceViewHolder;\n\nimport java.util.Collections;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.utils.PrefCache;\nimport rocks.tbog.tblauncher.utils.UIColors;\nimport rocks.tbog.tblauncher.utils.UISizes;\n\npublic class CustomDialogPreference extends androidx.preference.DialogPreference {\n\n    private Object mValue = null;\n\n    public CustomDialogPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {\n        super(context, attrs, defStyleAttr, defStyleRes);\n    }\n\n    public CustomDialogPreference(Context context, AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n    }\n\n    public CustomDialogPreference(Context context, AttributeSet attrs) {\n        super(context, attrs);\n    }\n\n    public CustomDialogPreference(Context context) {\n        super(context);\n    }\n\n    public Object getValue() {\n        return mValue;\n    }\n\n    public void setValue(Object value) {\n        mValue = value;\n    }\n\n    public boolean persistValue() {\n        Object value = getValue();\n        if (value instanceof String)\n            return persistString((String) value);\n        else if (value instanceof Integer)\n            return persistInt((Integer) value);\n        else if (value instanceof Float)\n            return persistFloat((Float) value);\n        return false;\n    }\n\n    public boolean persistValueIfAllowed() {\n        if (callChangeListener(getValue())) {\n            return persistValue();\n        }\n        return false;\n    }\n\n    public boolean persistValueIfAllowed(Object value) {\n        if (callChangeListener(value)) {\n            setValue(value);\n            return persistValue();\n        }\n        return false;\n    }\n\n    @Override\n    protected void onSetInitialValue(@Nullable Object defaultValue) {\n        if (getValue() == null)\n            setValue(defaultValue);\n        persistValue();\n    }\n\n    @Override\n    protected Object onGetDefaultValue(TypedArray a, int index) {\n        try {\n            return a.getInteger(index, 0);\n        } catch (UnsupportedOperationException e) {\n            try {\n                return a.getFloat(index, 0f);\n            } catch (UnsupportedOperationException ignored) {\n                return a.getString(index);\n            }\n        }\n    }\n\n    @Override\n    public void onBindViewHolder(@NonNull PreferenceViewHolder holder) {\n        super.onBindViewHolder(holder);\n        final String key = getKey();\n        final var pref = getSharedPreferences();\n        final var prefMap = pref != null ? pref.getAll() : Collections.emptyMap();\n        Object value = prefMap.get(key);\n\n        {\n            View view = holder.findViewById(R.id.prefAmountPreview);\n            if (view instanceof TextView) {\n                String text = null;\n                if (value instanceof Integer) {\n                    Integer amount = ((Integer) value);\n                    text = amount > 0 ? (\"+\" + amount) : amount.toString();\n                    if (key.contains(\"-scale\"))\n                        text += \"%\";\n                }\n                ((TextView) view).setText(text);\n                return;\n            }\n        }\n        {\n            View view = holder.findViewById(R.id.prefColorPreview);\n            if (view instanceof ImageView) {\n                int color = 0xFFffffff;\n                if (value instanceof Integer)\n                    color = (int) value | 0xFF000000;\n\n                Context ctx = getContext();\n                float radius = ctx.getResources().getDimension(R.dimen.color_preview_radius);\n                int border = UISizes.dp2px(ctx, 1);\n                Drawable drawable = UIColors.getPreviewDrawable(color, border, radius);\n                ((ImageView) view).setImageDrawable(drawable);\n                return;\n            }\n        }\n        {\n            View view = holder.findViewById(R.id.prefAlphaPreview);\n            if (view instanceof TextView) {\n                String text = null;\n                if (value instanceof Integer)\n                    text = ((Integer) value) * 100 / 255 + \"%\";\n                ((TextView) view).setText(text);\n                return;\n            }\n        }\n        {\n            View view = holder.findViewById(R.id.prefSizePreview);\n            if (view instanceof TextView) {\n                if (value instanceof Float) {\n                    float size = (float) value;\n                    ((TextView) view).setText(view.getResources().getString(R.string.size_float, size));\n                } else {\n                    int size = -1;\n                    if (\"result-search-cap\".equals(key))\n                        size = PrefCache.getResultSearcherCap(getContext());\n                    else if (value instanceof Integer)\n                        size = (int) value;\n                    ((TextView) view).setText(view.getResources().getString(R.string.size, size));\n                }\n            }\n        }\n        {\n            View view = holder.findViewById(R.id.prefShadowPreview);\n            if (view instanceof TextView) {\n                // used for shadow\n                Object value2 = prefMap.get(key.replace(\"-dx\", \"-dy\"));\n                Object value3 = prefMap.get(key.replace(\"-dx\", \"-radius\"));\n                if (value instanceof Float && value2 instanceof Float && value3 instanceof Float) {\n                    float v1 = (float) value;\n                    float v2 = (float) value2;\n                    float v3 = (float) value3;\n                    ((TextView) view).setText(view.getResources().getString(R.string.shadow_preview, v1, v2, v3));\n                } else {\n                    ((TextView) view).setText(\"\");\n                }\n            }\n        }\n        {\n            View view = holder.findViewById(R.id.prefOffsetPreview);\n            if (view instanceof TextView) {\n                // used for shadow\n                Object value2 = prefMap.get(key.replace(\"-dx\", \"-dy\"));\n                if (value instanceof Float && value2 instanceof Float) {\n                    float v1 = (float) value;\n                    float v2 = (float) value2;\n                    ((TextView) view).setText(view.getResources().getString(R.string.offset_preview, v1, v2));\n                } else {\n                    ((TextView) view).setText(\"\");\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/preference/EditAddResetEditor.java",
    "content": "package rocks.tbog.tblauncher.preference;\n\nimport android.app.Application;\nimport android.app.Dialog;\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.view.View;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.WorkerThread;\nimport androidx.fragment.app.FragmentActivity;\nimport androidx.fragment.app.FragmentManager;\nimport androidx.lifecycle.AndroidViewModel;\n\nimport rocks.tbog.tblauncher.utils.Utilities;\n\npublic abstract class EditAddResetEditor extends AndroidViewModel {\n    private FragmentManager mFragmentManager = null;\n    protected FragmentManager getFragmentManager() {\n        return mFragmentManager;\n    }\n    public EditAddResetEditor(@NonNull Application application) {\n        super(application);\n    }\n\n    public void loadDefaults(@NonNull Context context)\n    {\n        Utilities.runAsync(()-> loadDefaultsInternal(context));\n    }\n\n    public void loadData(@NonNull Context context, @NonNull SharedPreferences prefs)\n    {\n        Utilities.runAsync(()-> loadDataInternal(context, prefs));\n    }\n\n    @WorkerThread\n    public abstract void loadDefaultsInternal(@NonNull Context context);\n    @WorkerThread\n    public abstract void loadDataInternal(@NonNull Context context, @NonNull SharedPreferences prefs);\n    public abstract void applyChanges(@NonNull Context context);\n    public abstract void bindEditView(@NonNull View view);\n    public abstract void bindAddView(@NonNull View view);\n\n    public void onStartLifecycle(@NonNull Dialog dialog, @NonNull BasePreferenceDialog owner) {\n        FragmentActivity activity = owner.getActivity();\n        if (activity != null)\n            mFragmentManager = activity.getSupportFragmentManager();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/preference/EditAddResetPreferenceDialog.java",
    "content": "package rocks.tbog.tblauncher.preference;\n\nimport android.app.Dialog;\nimport android.content.Context;\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.view.View;\nimport android.widget.Toast;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.preference.PreferenceManager;\n\nimport java.util.Arrays;\n\npublic abstract class EditAddResetPreferenceDialog extends BasePreferenceDialog {\n    private static final String TAG = EditAddResetPreferenceDialog.class.getSimpleName();\n\n    protected EditAddResetEditor mEditor = null;\n\n    @Nullable\n    public static <T extends EditAddResetPreferenceDialog> T newInstance(String key, Class<T> clazz) {\n        T fragment;\n        try {\n            fragment = clazz.newInstance();\n        } catch (ReflectiveOperationException e) {\n            Log.e(TAG, \"no constructor?\", e);\n            return null;\n        }\n        final Bundle b = new Bundle(1);\n        b.putString(ARG_KEY, key);\n        fragment.setArguments(b);\n\n        return fragment;\n    }\n\n    @NonNull\n    protected abstract String[] getEditAddResetKeys();\n\n    @Nullable\n    protected abstract EditAddResetEditor newEditor();\n\n    @NonNull\n    @Override\n    public Dialog onCreateDialog(Bundle savedInstanceState) {\n        mEditor = newEditor();\n        return super.onCreateDialog(savedInstanceState);\n    }\n\n    @Override\n    public void onDialogClosed(boolean positiveResult) {\n        if (!positiveResult)\n            return;\n        if (mEditor != null)\n            mEditor.applyChanges(requireContext());\n    }\n\n    @Override\n    protected void onBindDialogView(View view) {\n        super.onBindDialogView(view);\n\n        if (mEditor == null)\n            return;\n\n        Context context = requireContext();\n        String key = getPreference().getKey();\n        int keyIndex = Arrays.asList(getEditAddResetKeys()).indexOf(key);\n        switch (keyIndex) {\n            case 0: // edit\n                mEditor.loadData(context, PreferenceManager.getDefaultSharedPreferences(context));\n                mEditor.bindEditView(view);\n                break;\n            case 1: // add\n                mEditor.loadData(context, PreferenceManager.getDefaultSharedPreferences(context));\n                mEditor.bindAddView(view);\n                break;\n            case 2: // reset\n                mEditor.loadDefaults(context);\n                mEditor.bindEditView(view);\n                break;\n            default:\n                Toast.makeText(context, \"`\" + key + \"` not found\", Toast.LENGTH_SHORT).show();\n                break;\n        }\n    }\n\n    @Override\n    public void onStart() {\n        super.onStart();\n        if (mEditor != null)\n            mEditor.onStartLifecycle(requireDialog(), this);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/preference/EditSearchEnginesPreferenceDialog.java",
    "content": "package rocks.tbog.tblauncher.preference;\n\nimport android.app.Application;\nimport android.app.Dialog;\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.graphics.Paint;\nimport android.graphics.Typeface;\nimport android.text.Spannable;\nimport android.text.SpannableStringBuilder;\nimport android.text.TextUtils;\nimport android.text.style.UnderlineSpan;\nimport android.view.View;\nimport android.widget.Adapter;\nimport android.widget.ArrayAdapter;\nimport android.widget.CheckedTextView;\nimport android.widget.EditText;\nimport android.widget.ListView;\nimport android.widget.TextView;\nimport android.widget.Toast;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.collection.ArraySet;\nimport androidx.lifecycle.LiveData;\nimport androidx.lifecycle.MutableLiveData;\nimport androidx.lifecycle.ViewModelProvider;\nimport androidx.preference.PreferenceManager;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.Objects;\nimport java.util.Set;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.dataprovider.SearchProvider;\nimport rocks.tbog.tblauncher.ui.ListPopup;\nimport rocks.tbog.tblauncher.ui.dialog.EditTextDialog;\nimport rocks.tbog.tblauncher.utils.SimpleTextWatcher;\nimport rocks.tbog.tblauncher.utils.ViewHolderAdapter;\nimport rocks.tbog.tblauncher.utils.ViewHolderListAdapter;\n\npublic class EditSearchEnginesPreferenceDialog extends EditAddResetPreferenceDialog {\n    @Nullable\n    public static EditSearchEnginesPreferenceDialog newInstance(String key) {\n        return EditAddResetPreferenceDialog.newInstance(key, EditSearchEnginesPreferenceDialog.class);\n    }\n\n    @Nullable\n    @Override\n    protected EditAddResetEditor newEditor() {\n        return new ViewModelProvider(this).get(EditSearchEngines.class);\n    }\n\n    @NonNull\n    protected String[] getEditAddResetKeys() {\n        return new String[]{\"edit-search-engines\", \"add-search-engine\", \"reset-search-engines\"};\n    }\n\n    public static class EditSearchEngines extends EditAddResetEditor {\n        private final MutableLiveData<ArrayList<SearchEngineInfo>> searchEngineInfoList = new MutableLiveData<>();\n        private final MutableLiveData<String> defaultProviderName = new MutableLiveData<>();\n        private final MutableLiveData<String> addSearchEngineName = new MutableLiveData<>();\n        private final MutableLiveData<String> addSearchEngineUrl = new MutableLiveData<>();\n\n        public EditSearchEngines(@NonNull Application application) {\n            super(application);\n        }\n\n        public LiveData<ArrayList<SearchEngineInfo>> getSearchEngineInfoList() {\n            return searchEngineInfoList;\n        }\n\n        public LiveData<String> getDefaultProviderName() {\n            return defaultProviderName;\n        }\n\n        public void setDefaultProviderName(String name) {\n            defaultProviderName.setValue(name);\n        }\n\n        public void updateSearchEngineInfoList(SearchEngineInfo info) {\n            ArrayList<SearchEngineInfo> arrayList = searchEngineInfoList.getValue();\n            if (arrayList == null || arrayList.contains(info))\n                searchEngineInfoList.setValue(arrayList);\n        }\n\n        @Override\n        public void loadDefaultsInternal(@NonNull Context context) {\n            final ArrayList<SearchEngineInfo> list = new ArrayList<>(0);\n            Set<String> defaultSearchProviders = SearchProvider.getDefaultSearchProviders(context);\n            list.ensureCapacity(defaultSearchProviders.size());\n            for (String searchProvider : defaultSearchProviders) {\n                SearchEngineInfo searchEngineInfo = new SearchEngineInfo(searchProvider);\n                searchEngineInfo.selected = true;\n                list.add(searchEngineInfo);\n            }\n            Collections.sort(list, Comparator.comparing(lhs -> lhs.provider));\n\n            searchEngineInfoList.postValue(list);\n            defaultProviderName.postValue(\"Google\");\n        }\n\n        @Override\n        public void loadDataInternal(@NonNull Context context, @NonNull SharedPreferences prefs) {\n            final ArrayList<SearchEngineInfo> list = new ArrayList<>(0);\n            // load search engines\n            Set<String> availableSearchProviders = SearchProvider.getAvailableSearchProviders(context, prefs);\n            Set<String> selectedProviderNames = SearchProvider.getSelectedProviderNames(context, prefs);\n\n            list.ensureCapacity(availableSearchProviders.size());\n\n            for (String searchProvider : availableSearchProviders) {\n                SearchEngineInfo searchEngineInfo = new SearchEngineInfo(searchProvider);\n                searchEngineInfo.selected = selectedProviderNames.contains(searchEngineInfo.name);\n                list.add(searchEngineInfo);\n            }\n            Collections.sort(list, Comparator.comparing(lhs -> lhs.provider));\n            searchEngineInfoList.postValue(list);\n\n            // get default search engine name\n            String providerName = prefs.getString(\"default-search-provider\", null);\n            if (providerName == null || providerName.isEmpty())\n                defaultProviderName.postValue(list.isEmpty() ? \"\" : list.get(0).name);\n            else\n                defaultProviderName.postValue(providerName);\n        }\n\n        public void applyChanges(@NonNull Context context) {\n            SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);\n\n            Set<String> availableProviders = new ArraySet<>();\n            Set<String> selectedProviderNames = new ArraySet<>();\n\n            String addName = addSearchEngineName.getValue();\n            String addUrl = addSearchEngineUrl.getValue();\n            if (addName != null && addUrl != null) {\n                String name = SearchProvider.sanitizeProviderName(addName).trim();\n                String url = SearchProvider.sanitizeProviderUrl(addUrl).trim();\n                if (!name.isEmpty() && !url.isEmpty()) {\n                    String searchProvider = SearchProvider.makeProvider(name, url);\n                    availableProviders.add(searchProvider);\n                    selectedProviderNames.add(name);\n                }\n            }\n\n            ArrayList<SearchEngineInfo> searchEngineList = getSearchEngineInfoList().getValue();\n            if (searchEngineList != null) {\n                for (SearchEngineInfo searchEngineInfo : searchEngineList) {\n                    if (searchEngineInfo.action == SearchEngineInfo.Action.DELETE)\n                        continue;\n                    availableProviders.add(SearchProvider.makeProvider(searchEngineInfo.name, searchEngineInfo.url));\n                    if (searchEngineInfo.selected)\n                        selectedProviderNames.add(searchEngineInfo.name);\n                }\n            }\n\n            prefs.edit()\n                .putStringSet(\"available-search-providers\", availableProviders)\n                .putStringSet(\"selected-search-provider-names\", selectedProviderNames)\n                .putString(\"default-search-provider\", getDefaultProviderName().getValue())\n                .apply();\n\n            TBApplication.dataHandler(context).reloadProviders();\n        }\n\n        public void bindEditView(@NonNull View view) {\n            ListView listView = view.findViewById(android.R.id.list);\n\n            listView.setOnItemClickListener((list, itemView, position, id) -> {\n                Adapter adapter = list.getAdapter();\n                if (adapter instanceof SearchEngineAdapter) {\n                    SearchEngineAdapter searchEngineAdapter = (SearchEngineAdapter) adapter;\n\n                    SearchEngineInfo info = searchEngineAdapter.getItem(position);\n                    info.selected = !info.selected;\n                    updateSearchEngineInfoList(info);\n                }\n            });\n\n            listView.setOnItemLongClickListener((list, itemView, position, id) -> {\n                Adapter adapter = list.getAdapter();\n                if (!(adapter instanceof SearchEngineAdapter))\n                    return false;\n                SearchEngineAdapter searchEngineAdapter = (SearchEngineAdapter) adapter;\n                SearchEngineInfo info = searchEngineAdapter.getItem(position);\n                if (info.action == SearchEngineInfo.Action.DELETE) {\n                    String provider = SearchProvider.makeProvider(info.name, info.url);\n                    info.action = info.provider.equals(provider) ? SearchEngineInfo.Action.NONE : SearchEngineInfo.Action.RENAME;\n                    updateSearchEngineInfoList(info);\n                } else {\n                    Context ctx = list.getContext();\n                    ArrayAdapter<ListPopup.Item> arrayAdapter = new ArrayAdapter<>(ctx, android.R.layout.simple_list_item_1);\n                    ListPopup popup = ListPopup.create(ctx, arrayAdapter);\n                    if (!info.name.equals(getDefaultProviderName().getValue()) && info.selected)\n                        arrayAdapter.add(new ListPopup.Item(ctx, R.string.search_engine_set_default));\n                    arrayAdapter.add(new ListPopup.Item(ctx, R.string.menu_action_rename));\n                    arrayAdapter.add(new ListPopup.Item(ctx, R.string.search_engine_edit_url));\n                    arrayAdapter.add(new ListPopup.Item(ctx, R.string.menu_action_delete));\n                    popup.setOnItemClickListener((popupAdapter, popupItemView, popupPosition) -> {\n                        Object object = popupAdapter.getItem(popupPosition);\n                        if (!(object instanceof ListPopup.Item))\n                            return;\n                        ListPopup.Item item = (ListPopup.Item) object;\n                        if (item.stringId == R.string.search_engine_set_default) {\n                            setDefaultProviderName(info.name);\n                        } else if (item.stringId == R.string.menu_action_rename) {\n                            launchRenameDialog(listView.getContext(), info);\n                        } else if (item.stringId == R.string.search_engine_edit_url) {\n                            launchEditUrlDialog(listView.getContext(), info);\n                        } else if (item.stringId == R.string.menu_action_delete) {\n                            info.action = SearchEngineInfo.Action.DELETE;\n                            updateSearchEngineInfoList(info);\n                        }\n                    });\n                    popup.setModal(true);\n                    popup.setDimAmount(0.5f);\n                    popup.show(itemView);\n                }\n                return true;\n            });\n        }\n\n        public void bindAddView(@NonNull View view) {\n            EditText editText;\n            {\n                String name = addSearchEngineName.getValue();\n                editText = view.findViewById(android.R.id.text1);\n                if (!TextUtils.isEmpty(name))\n                    editText.setText(name);\n                editText.addTextChangedListener(new SimpleTextWatcher() {\n                    @Override\n                    public void onTextChanged(String newValue) {\n                        addSearchEngineName.setValue(newValue);\n                    }\n                });\n            }\n            {\n                String urlValue = addSearchEngineUrl.getValue();\n                editText = view.findViewById(android.R.id.text2);\n                if (!TextUtils.isEmpty(urlValue))\n                    editText.setText(urlValue);\n                editText.addTextChangedListener(new SimpleTextWatcher() {\n                    @Override\n                    public void onTextChanged(String newValue) {\n                        addSearchEngineUrl.setValue(newValue);\n                    }\n                });\n            }\n        }\n\n        private void launchRenameDialog(Context ctx, SearchEngineInfo info) {\n            new EditTextDialog.Builder(ctx)\n                .setTitle(R.string.title_rename_search_engine)\n                .setInitialText(info.name)\n                .setConfirmListener(R.string.menu_action_rename, name -> {\n                    String newName = name != null ? SearchProvider.sanitizeProviderName(name.toString()).trim() : null;\n                    boolean isValid = !TextUtils.isEmpty(newName);\n                    ArrayList<SearchEngineInfo> searchEngineList = getSearchEngineInfoList().getValue();\n                    if (isValid && searchEngineList != null) {\n                        for (SearchEngineInfo searchEngineInfo : searchEngineList) {\n                            if (searchEngineInfo == info)\n                                continue;\n                            if (SearchProvider.getProviderName(searchEngineInfo.provider).equals(newName) || searchEngineInfo.name.equals(newName)) {\n                                isValid = false;\n                                break;\n                            }\n                        }\n                    }\n                    if (!isValid) {\n                        Toast.makeText(ctx, ctx.getString(R.string.invalid_rename_search_engine, newName), Toast.LENGTH_LONG).show();\n                        return;\n                    }\n\n                    // Set new name\n                    if (TextUtils.equals(defaultProviderName.getValue(), info.name))\n                        setDefaultProviderName(newName);\n                    info.name = newName;\n                    info.action = SearchProvider.getProviderName(info.provider).equals(info.name) ? SearchEngineInfo.Action.NONE : SearchEngineInfo.Action.RENAME;\n                    updateSearchEngineInfoList(info);\n                })\n                .setNegativeButton(android.R.string.cancel, null)\n                .getDialog()\n                .show(getFragmentManager(), \"rename\");\n        }\n\n        private void launchEditUrlDialog(Context ctx, SearchEngineInfo info) {\n            new EditTextDialog.Builder(ctx)\n                .setTitle(R.string.title_edit_url_search_engine)\n                .setHint(info.name)\n                .setInitialText(info.url)\n                .setConfirmListener(R.string.confirm_edit_url_search_engine, (newName) -> {\n                    if (newName == null)\n                        return;\n                    // Set new name\n                    info.url = SearchProvider.sanitizeProviderUrl(newName.toString()).trim();\n                    if (info.url.equals(SearchProvider.getProviderUrl(info.provider))) {\n                        info.action = SearchProvider.getProviderName(info.provider).equals(info.name) ? SearchEngineInfo.Action.NONE : SearchEngineInfo.Action.RENAME;\n                    } else {\n                        info.action = SearchEngineInfo.Action.RENAME;\n                    }\n\n                    updateSearchEngineInfoList(info);\n                })\n                .setNegativeButton(android.R.string.cancel, null)\n                .getDialog()\n                .show(getFragmentManager(), \"edit_url\");\n        }\n\n        public void onStartLifecycle(@NonNull Dialog dialog, @NonNull BasePreferenceDialog owner) {\n            super.onStartLifecycle(dialog, owner);\n            ListView listView = dialog.findViewById(android.R.id.list);\n            if (listView != null) {\n                ArrayList<SearchEngineInfo> list = getSearchEngineInfoList().getValue();\n                if (list == null)\n                    list = new ArrayList<>();\n                SearchEngineAdapter adapter = new SearchEngineAdapter(list);\n                listView.setAdapter(adapter);\n                getSearchEngineInfoList().observe(owner, adapter::replaceItems);\n            }\n        }\n\n    }\n\n    public static class SearchEngineInfo {\n        @NonNull\n        public final String provider;\n        public String name;\n        public String url;\n        public boolean selected;\n        public Action action = Action.NONE;\n\n        public enum Action {NONE, DELETE, RENAME}\n\n        public SearchEngineInfo(@NonNull String searchProvider) {\n            provider = searchProvider;\n            name = SearchProvider.getProviderName(searchProvider);\n            url = SearchProvider.getProviderUrl(searchProvider);\n        }\n\n        @Override\n        public boolean equals(Object o) {\n            if (this == o)\n                return true;\n            if (o == null || getClass() != o.getClass())\n                return false;\n            SearchEngineInfo that = (SearchEngineInfo) o;\n            return selected == that.selected &&\n                provider.equals(that.provider) &&\n                Objects.equals(name, that.name) &&\n                Objects.equals(url, that.url) &&\n                action == that.action;\n        }\n\n        @Override\n        public int hashCode() {\n            return Objects.hash(provider, name, url, selected, action);\n        }\n    }\n\n    public static class SearchEngineAdapter extends ViewHolderListAdapter<SearchEngineInfo, TagViewHolder> {\n\n        SearchEngineAdapter(@NonNull ArrayList<SearchEngineInfo> list) {\n            super(TagViewHolder.class, android.R.layout.simple_list_item_checked, list);\n        }\n\n        @Override\n        protected int getItemViewTypeLayout(int viewType) {\n            if (viewType == 1)\n                return android.R.layout.simple_list_item_1;\n            return super.getItemViewTypeLayout(viewType);\n        }\n\n        public int getItemViewType(int position) {\n            return getItem(position).action == SearchEngineInfo.Action.DELETE ? 1 : 0;\n        }\n\n        public int getViewTypeCount() {\n            return 2;\n        }\n\n        public void replaceItems(Collection<? extends SearchEngineInfo> list) {\n            if (list != mList) {\n                mList.clear();\n                mList.addAll(list);\n            }\n            notifyDataSetChanged();\n        }\n    }\n\n    public static class TagViewHolder extends ViewHolderAdapter.ViewHolder<SearchEngineInfo> {\n        private final TextView text1View;\n\n        public TagViewHolder(View itemView) {\n            super(itemView);\n            text1View = itemView.findViewById(android.R.id.text1);\n            text1View.setLines(2);\n        }\n\n        @Override\n        protected void setContent(SearchEngineInfo content, int position, @NonNull ViewHolderAdapter<SearchEngineInfo, ? extends ViewHolderAdapter.ViewHolder<SearchEngineInfo>> adapter) {\n            SpannableStringBuilder enhancedText = new SpannableStringBuilder();\n            enhancedText\n                .append(content.name)\n                .setSpan(new UnderlineSpan(), 0, content.name.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);\n            enhancedText\n                .append(\"\\n\")\n                .append(content.url);\n            text1View.setText(enhancedText);\n            text1View.setTypeface(null, content.action == SearchEngineInfo.Action.RENAME ? Typeface.BOLD : Typeface.NORMAL);\n            if (text1View instanceof CheckedTextView)\n                ((CheckedTextView) text1View).setChecked(content.selected);\n            if (content.action == SearchEngineInfo.Action.DELETE) {\n                text1View.setPaintFlags(text1View.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/preference/EditSearchHintPreferenceDialog.java",
    "content": "package rocks.tbog.tblauncher.preference;\n\nimport android.app.Application;\nimport android.app.Dialog;\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.graphics.Paint;\nimport android.graphics.Typeface;\nimport android.text.TextUtils;\nimport android.view.View;\nimport android.widget.Adapter;\nimport android.widget.ArrayAdapter;\nimport android.widget.CheckedTextView;\nimport android.widget.EditText;\nimport android.widget.ListView;\nimport android.widget.TextView;\nimport android.widget.Toast;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.collection.ArraySet;\nimport androidx.lifecycle.MutableLiveData;\nimport androidx.lifecycle.ViewModelProvider;\nimport androidx.preference.PreferenceManager;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.Set;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.ui.ListPopup;\nimport rocks.tbog.tblauncher.utils.DialogHelper;\nimport rocks.tbog.tblauncher.utils.SimpleTextWatcher;\nimport rocks.tbog.tblauncher.utils.ViewHolderAdapter;\nimport rocks.tbog.tblauncher.utils.ViewHolderListAdapter;\n\npublic class EditSearchHintPreferenceDialog extends EditAddResetPreferenceDialog {\n\n    @Nullable\n    public static EditSearchHintPreferenceDialog newInstance(String key) {\n        return EditAddResetPreferenceDialog.newInstance(key, EditSearchHintPreferenceDialog.class);\n    }\n\n    @Nullable\n    @Override\n    protected EditAddResetEditor newEditor() {\n        return new ViewModelProvider(this).get(EditSearchHint.class);\n    }\n\n    @NonNull\n    protected String[] getEditAddResetKeys() {\n        return new String[]{\"edit-search-hint\", \"add-search-hint\", \"reset-search-hint\"};\n    }\n\n    public static class EditSearchHint extends EditAddResetEditor {\n\n        private final MutableLiveData<ArrayList<SearchHintInfo>> searchHintList = new MutableLiveData<>();\n        private final MutableLiveData<String> addHintName = new MutableLiveData<>();\n\n        public EditSearchHint(@NonNull Application application) {\n            super(application);\n        }\n\n        public void updateSearchHintList(SearchHintInfo info) {\n            ArrayList<SearchHintInfo> arrayList = searchHintList.getValue();\n            if (arrayList == null || arrayList.contains(info))\n                searchHintList.setValue(arrayList);\n        }\n\n        public void applyChanges(@NonNull Context context) {\n            SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);\n\n            Set<String> availableHints = new ArraySet<>();\n            Set<String> selectedHints = new ArraySet<>();\n\n            String mAddHint = addHintName.getValue();\n            if (mAddHint != null) {\n                String name = mAddHint.trim();\n                availableHints.add(name);\n                selectedHints.add(name);\n            }\n\n            ArrayList<SearchHintInfo> searchHintInfoList = searchHintList.getValue();\n            if (searchHintInfoList != null) {\n                for (SearchHintInfo hintInfo : searchHintInfoList) {\n                    if (hintInfo.action == SearchHintInfo.Action.DELETE)\n                        continue;\n                    availableHints.add(hintInfo.text);\n                    if (hintInfo.selected)\n                        selectedHints.add(hintInfo.text);\n                }\n            }\n\n            prefs.edit()\n                .putStringSet(\"available-search-hints\", availableHints)\n                .putStringSet(\"selected-search-hints\", selectedHints)\n                .apply();\n\n            TBApplication.dataHandler(context).reloadProviders();\n        }\n\n        public void bindEditView(@NonNull View view) {\n            ListView listView = view.findViewById(android.R.id.list);\n\n            listView.setOnItemClickListener((list, itemView, position, id) -> {\n                Adapter adapter = list.getAdapter();\n                if (adapter instanceof SearchHintAdapter) {\n                    SearchHintInfo info = ((SearchHintAdapter) adapter).getItem(position);\n                    info.selected = !info.selected;\n                    updateSearchHintList(info);\n                }\n            });\n\n            listView.setOnItemLongClickListener((list, itemView, position, id) -> {\n                Adapter adapter = list.getAdapter();\n                if (!(adapter instanceof SearchHintAdapter))\n                    return false;\n                SearchHintInfo info = ((SearchHintAdapter) adapter).getItem(position);\n                if (info.action == SearchHintInfo.Action.DELETE) {\n                    info.action = info.text.equals(info.hint) ? SearchHintInfo.Action.NONE : SearchHintInfo.Action.RENAME;\n                    updateSearchHintList(info);\n                } else {\n                    Context ctx = list.getContext();\n                    ArrayAdapter<ListPopup.Item> arrayAdapter = new ArrayAdapter<>(ctx, android.R.layout.simple_list_item_1);\n                    arrayAdapter.add(new ListPopup.Item(ctx, R.string.menu_action_rename));\n                    arrayAdapter.add(new ListPopup.Item(ctx, R.string.menu_action_delete));\n                    ListPopup.create(ctx, arrayAdapter)\n                        .setOnItemClickListener((popupAdapter, popupItemView, popupPosition) -> {\n                            Object object = popupAdapter.getItem(popupPosition);\n                            if (!(object instanceof ListPopup.Item))\n                                return;\n                            ListPopup.Item item = (ListPopup.Item) object;\n                            if (item.stringId == R.string.menu_action_rename) {\n                                launchRenameDialog(listView.getContext(), info);\n                            } else if (item.stringId == R.string.menu_action_delete) {\n                                info.action = SearchHintInfo.Action.DELETE;\n                                updateSearchHintList(info);\n                            }\n                        })\n                        .setModal(true)\n                        .setDimAmount(0.5f)\n                        .show(itemView);\n                }\n                return true;\n            });\n        }\n\n        public void bindAddView(@NonNull View view) {\n            String name = addHintName.getValue();\n            EditText editText = view.findViewById(android.R.id.text1);\n            if (!TextUtils.isEmpty(name))\n                editText.setText(name);\n            editText.addTextChangedListener(new SimpleTextWatcher() {\n                @Override\n                public void onTextChanged(String newValue) {\n                    addHintName.setValue(newValue);\n                }\n            });\n        }\n\n        private void launchRenameDialog(@NonNull Context ctx, @NonNull SearchHintInfo info) {\n            DialogHelper.makeRenameDialog(ctx, info.text, (dialog, newName) -> {\n                    boolean isValid = true;\n                    ArrayList<SearchHintInfo> searchHintInfoList = searchHintList.getValue();\n                    if (searchHintInfoList != null) {\n                        for (SearchHintInfo hintInfo : searchHintInfoList) {\n                            if (info.equals(hintInfo))\n                                continue;\n                            if (hintInfo.hint.equals(newName) || hintInfo.text.equals(newName)) {\n                                isValid = false;\n                                break;\n                            }\n                        }\n                    }\n                    if (!isValid) {\n                        Toast.makeText(ctx, ctx.getString(R.string.invalid_rename_search_engine, newName), Toast.LENGTH_LONG).show();\n                        return;\n                    }\n\n                    // Set new name\n                    info.text = newName;\n                    info.action = info.hint.equals(info.text) ? SearchHintInfo.Action.NONE : SearchHintInfo.Action.RENAME;\n\n                    updateSearchHintList(info);\n                })\n                .setTitle(R.string.title_rename_search_hint)\n                .setNegativeButton(android.R.string.cancel, null)\n                .getDialog()\n                .show(getFragmentManager(), \"rename\");\n        }\n\n        public void onStartLifecycle(@NonNull Dialog dialog, @NonNull BasePreferenceDialog owner) {\n            super.onStartLifecycle(dialog, owner);\n            ListView listView = dialog.findViewById(android.R.id.list);\n            if (listView != null) {\n                ArrayList<SearchHintInfo> list = searchHintList.getValue();\n                if (list == null)\n                    list = new ArrayList<>();\n                SearchHintAdapter adapter = new SearchHintAdapter(list);\n                listView.setAdapter(adapter);\n                searchHintList.observe(owner, adapter::replaceItems);\n            }\n        }\n\n        public void loadDataInternal(@NonNull Context context, @NonNull SharedPreferences prefs) {\n            ArrayList<SearchHintInfo> list = new ArrayList<>(0);\n            Set<String> availableHints = prefs.getStringSet(\"available-search-hints\", null);\n            if (availableHints == null) {\n                availableHints = new ArraySet<>();\n                Collections.addAll(availableHints, context.getResources().getStringArray(R.array.defaultSearchHints));\n            }\n            Set<String> selectedHints = prefs.getStringSet(\"selected-search-hints\", null);\n            if (selectedHints == null) {\n                selectedHints = new ArraySet<>(availableHints);\n            }\n\n            list.ensureCapacity(availableHints.size());\n\n            for (String hint : availableHints) {\n                SearchHintInfo hintInfo = new SearchHintInfo(hint);\n                hintInfo.selected = selectedHints.contains(hintInfo.hint);\n                list.add(hintInfo);\n            }\n            Collections.sort(list, Comparator.comparing(lhs -> lhs.hint));\n            searchHintList.postValue(list);\n        }\n\n        public void loadDefaultsInternal(@NonNull Context context) {\n            ArrayList<SearchHintInfo> list = new ArrayList<>(0);\n            String[] defaultSearchHints = context.getResources().getStringArray(R.array.defaultSearchHints);\n            list.ensureCapacity(defaultSearchHints.length);\n            for (String searchHint : defaultSearchHints) {\n                SearchHintInfo searchEngineInfo = new SearchHintInfo(searchHint);\n                searchEngineInfo.selected = true;\n                list.add(searchEngineInfo);\n            }\n            Collections.sort(list, Comparator.comparing(lhs -> lhs.hint));\n            searchHintList.postValue(list);\n        }\n\n    }\n\n    public static class SearchHintInfo {\n        @NonNull\n        public final String hint;\n        public String text;\n        public boolean selected;\n        public Action action = Action.NONE;\n\n        public enum Action {NONE, DELETE, RENAME}\n\n        public SearchHintInfo(@NonNull String hintText) {\n            hint = hintText;\n            text = hintText;\n        }\n\n        @Override\n        public boolean equals(Object o) {\n            if (this == o)\n                return true;\n            if (o == null || getClass() != o.getClass())\n                return false;\n            SearchHintInfo that = (SearchHintInfo) o;\n            return selected == that.selected &&\n                action == that.action &&\n                hint.equals(that.hint) &&\n                text.equals(that.text);\n        }\n\n        @Override\n        public int hashCode() {\n            return Objects.hash(hint, text, selected, action);\n        }\n    }\n\n    public static class SearchHintAdapter extends ViewHolderListAdapter<SearchHintInfo, SearchHintVH> {\n\n        public SearchHintAdapter(@NonNull List<SearchHintInfo> list) {\n            super(SearchHintVH.class, android.R.layout.simple_list_item_checked, list);\n        }\n\n        @Override\n        public int getItemViewTypeLayout(int viewType) {\n            if (viewType == 1)\n                return android.R.layout.simple_list_item_1;\n            return super.getItemViewTypeLayout(viewType);\n        }\n\n        public int getItemViewType(int position) {\n            return getItem(position).action == SearchHintInfo.Action.DELETE ? 1 : 0;\n        }\n\n        public int getViewTypeCount() {\n            return 2;\n        }\n\n        public List<SearchHintInfo> getItems() {\n            return mList;\n        }\n\n        public void replaceItems(Collection<? extends SearchHintInfo> list) {\n            if (list != mList) {\n                mList.clear();\n                mList.addAll(list);\n            }\n            notifyDataSetChanged();\n        }\n    }\n\n    public static class SearchHintVH extends ViewHolderAdapter.ViewHolder<SearchHintInfo> {\n        private final TextView text1View;\n\n        public SearchHintVH(View view) {\n            super(view);\n            text1View = view.findViewById(android.R.id.text1);\n        }\n\n        @Override\n        public void setContent(SearchHintInfo content, int position, @NonNull ViewHolderAdapter<SearchHintInfo, ? extends ViewHolderAdapter.ViewHolder<SearchHintInfo>> adapter) {\n            text1View.setText(content.text);\n            text1View.setTypeface(null, content.action == SearchHintInfo.Action.RENAME ? Typeface.BOLD : Typeface.NORMAL);\n            if (text1View instanceof CheckedTextView)\n                ((CheckedTextView) text1View).setChecked(content.selected);\n            if (content.action == SearchHintInfo.Action.DELETE) {\n                text1View.setPaintFlags(text1View.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/preference/IconListPreferenceDialog.java",
    "content": "package rocks.tbog.tblauncher.preference;\n\nimport android.content.Context;\nimport android.content.DialogInterface;\nimport android.content.res.ColorStateList;\nimport android.content.res.Resources;\nimport android.content.res.TypedArray;\nimport android.graphics.Color;\nimport android.graphics.drawable.ColorDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.graphics.drawable.StateListDrawable;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.util.StateSet;\nimport android.util.TypedValue;\nimport android.view.View;\nimport android.widget.ArrayAdapter;\nimport android.widget.CheckedTextView;\nimport android.widget.ListAdapter;\nimport android.widget.TextView;\n\nimport androidx.annotation.LayoutRes;\nimport androidx.annotation.NonNull;\nimport androidx.appcompat.app.AlertDialog;\nimport androidx.preference.ListPreference;\nimport androidx.preference.PreferenceDialogFragmentCompat;\n\nimport com.google.android.material.R;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport rocks.tbog.tblauncher.drawable.DrawableUtils;\nimport rocks.tbog.tblauncher.utils.DialogHelper;\nimport rocks.tbog.tblauncher.utils.Utilities;\nimport rocks.tbog.tblauncher.utils.ViewHolderAdapter;\nimport rocks.tbog.tblauncher.utils.ViewHolderListAdapter;\n\npublic class IconListPreferenceDialog extends PreferenceDialogFragmentCompat {\n\n    private static final String SAVE_STATE_INDEX = \"IconListPreferenceDialog.index\";\n    private static final String SAVE_STATE_ENTRIES = \"IconListPreferenceDialog.entries\";\n    private static final String SAVE_STATE_ENTRY_VALUES = \"IconListPreferenceDialog.entryValues\";\n\n    private int mClickedDialogEntryIndex = -1;\n    private CharSequence[] mEntries;\n    private CharSequence[] mEntryValues;\n\n    public static IconListPreferenceDialog newInstance(String key) {\n        final IconListPreferenceDialog fragment = new IconListPreferenceDialog();\n        final Bundle b = new Bundle(1);\n        b.putString(ARG_KEY, key);\n        fragment.setArguments(b);\n        return fragment;\n    }\n\n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        if (savedInstanceState == null) {\n            final ListPreference preference = getListPreference();\n\n            if (preference.getEntries() == null || preference.getEntryValues() == null) {\n                throw new IllegalStateException(\"ListPreference requires an entries array and an entryValues array.\");\n            }\n\n            mClickedDialogEntryIndex = preference.findIndexOfValue(preference.getValue());\n            mEntries = preference.getEntries();\n            mEntryValues = preference.getEntryValues();\n        } else {\n            mClickedDialogEntryIndex = savedInstanceState.getInt(SAVE_STATE_INDEX, 0);\n            mEntries = savedInstanceState.getCharSequenceArray(SAVE_STATE_ENTRIES);\n            mEntryValues = savedInstanceState.getCharSequenceArray(SAVE_STATE_ENTRY_VALUES);\n        }\n    }\n\n    @Override\n    public void onSaveInstanceState(@NonNull Bundle outState) {\n        super.onSaveInstanceState(outState);\n        outState.putInt(SAVE_STATE_INDEX, mClickedDialogEntryIndex);\n        outState.putCharSequenceArray(SAVE_STATE_ENTRIES, mEntries);\n        outState.putCharSequenceArray(SAVE_STATE_ENTRY_VALUES, mEntryValues);\n    }\n\n    @Override\n    protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {\n        super.onPrepareDialogBuilder(builder);\n        ArrayList<IconEntry> list = new ArrayList<>(mEntries.length);\n        for (int i = 0; i < mEntries.length; i++) {\n            list.add(new IconEntry(mEntries[i], mEntryValues[i]));\n        }\n\n        final ListAdapter listAdapter;\n        {\n            final Bundle args = getArguments();\n            String key = args != null ? args.getString(ARG_KEY) : null;\n            final int listItemLayout = getItemLayout(builder.getContext());\n            if (key != null && key.endsWith(\"-shape\"))\n                listAdapter = new IconAdapter(ShapeViewHolder.class, listItemLayout, list);\n            else if (\"icons-pack\".equals(key))\n                listAdapter = new IconAdapter(PackViewHolder.class, listItemLayout, list);\n            else\n                listAdapter = new ArrayAdapter<>(getContext(), listItemLayout, list);\n        }\n\n        builder.setSingleChoiceItems(\n            listAdapter,\n            mClickedDialogEntryIndex,\n            (dialog, which) -> {\n                mClickedDialogEntryIndex = which;\n\n                // Clicking on an item simulates the positive button click\n                IconListPreferenceDialog.this.onClick(dialog, DialogInterface.BUTTON_POSITIVE);\n                // and dismisses the dialog.\n                dialog.dismiss();\n            });\n\n        builder.setPositiveButton(null, null);\n        DialogHelper.setCustomTitle(builder, getPreference().getDialogTitle());\n    }\n\n    @Override\n    public void onStart() {\n        super.onStart();\n        DialogHelper.setButtonBarBackground(requireDialog());\n    }\n\n    private ListPreference getListPreference() {\n        return (ListPreference) getPreference();\n    }\n\n    @LayoutRes\n    private static int getItemLayout(@NonNull Context context) {\n        @LayoutRes final int layout;\n        final Resources.Theme theme = context.getTheme();\n        TypedValue res = new TypedValue();\n        boolean found = theme.resolveAttribute(R.attr.singleChoiceItemLayout, res, true);\n        if (found && res.resourceId != 0) {\n            layout = res.resourceId;\n        } else {\n            TypedArray a = theme.obtainStyledAttributes(R.style.MaterialAlertDialog_MaterialComponents, new int[]{R.attr.singleChoiceItemLayout});\n            layout = a.getResourceId(0, android.R.layout.simple_list_item_checked);\n            a.recycle();\n        }\n        return layout;\n    }\n\n    @Override\n    public void onDialogClosed(boolean positiveResult) {\n        if (positiveResult && mClickedDialogEntryIndex >= 0) {\n            String value = mEntryValues[mClickedDialogEntryIndex].toString();\n            final ListPreference preference = getListPreference();\n            if (preference.callChangeListener(value)) {\n                preference.setValue(value);\n            }\n        }\n    }\n\n    private static class IconEntry {\n        private final CharSequence name;\n        private final CharSequence value;\n\n        public IconEntry(CharSequence name, CharSequence value) {\n            this.name = name;\n            this.value = value;\n        }\n    }\n\n    private static class IconAdapter extends ViewHolderListAdapter<IconEntry, ViewHolderAdapter.ViewHolder<IconEntry>> {\n        protected IconAdapter(@NonNull Class<? extends ViewHolder<IconEntry>> viewHolderClass, int listItemLayout, @NonNull List<IconEntry> list) {\n            super(viewHolderClass, listItemLayout, list);\n        }\n    }\n\n    public static class ShapeViewHolder extends ViewHolderAdapter.ViewHolder<IconEntry> {\n        private final static int[] STATE_CHECKED = new int[]{android.R.attr.state_checked};\n\n        final TextView textView;\n        int defaultColor = 0;\n        int checkedColor = 0;\n        int size;\n\n        public ShapeViewHolder(View view) {\n            super(view);\n            textView = view.findViewById(android.R.id.text1);\n\n            final Context context = view.getContext();\n\n            // get color from theme\n            {\n                final Resources.Theme theme = context.getTheme();\n                TypedValue res = new TypedValue();\n                if (theme.resolveAttribute(R.attr.colorControlActivated, res, true)) {\n                    if (res.type >= TypedValue.TYPE_FIRST_COLOR_INT && res.type <= TypedValue.TYPE_LAST_COLOR_INT) {\n                        checkedColor = res.data;\n                    }\n                }\n                if (theme.resolveAttribute(R.attr.colorControlNormal, res, true)) {\n                    if (res.type >= TypedValue.TYPE_FIRST_COLOR_INT && res.type <= TypedValue.TYPE_LAST_COLOR_INT) {\n                        defaultColor = res.data;\n                    }\n                }\n            }\n\n            if (defaultColor == 0 || checkedColor == 0) {\n                ColorStateList textColorList = null;\n                if (textView instanceof CheckedTextView) {\n                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n                        textColorList = ((CheckedTextView) textView).getCheckMarkTintList();\n                    }\n                }\n                if (textColorList == null)\n                    textColorList = textView.getTextColors();\n                defaultColor = textColorList.getDefaultColor();\n                checkedColor = textColorList.getColorForState(STATE_CHECKED, defaultColor);\n            }\n\n            size = context.getResources().getDimensionPixelSize(rocks.tbog.tblauncher.R.dimen.icon_preview_size);\n        }\n\n        @Override\n        protected void setContent(IconEntry content, int position, @NonNull ViewHolderAdapter<IconEntry, ? extends ViewHolderAdapter.ViewHolder<IconEntry>> adapter) {\n            // set icon async\n            Utilities.setViewAsync(textView,\n                context -> {\n                    Drawable drawable = new ColorDrawable(defaultColor);\n                    for (int shape : DrawableUtils.SHAPE_LIST) {\n                        if (Integer.toString(shape).equals(content.value.toString())) {\n                            Drawable shapedDrawable;\n                            if (shape == DrawableUtils.SHAPE_NONE) {\n                                shapedDrawable = new ColorDrawable(Color.TRANSPARENT);\n                            } else {\n                                StateListDrawable listDrawable = new StateListDrawable();\n                                Drawable checkedDrawable = new ColorDrawable(checkedColor);\n                                listDrawable.addState(STATE_CHECKED, DrawableUtils.applyIconMaskShape(context, checkedDrawable, shape));\n                                listDrawable.addState(StateSet.WILD_CARD, DrawableUtils.applyIconMaskShape(context, drawable, shape));\n\n                                shapedDrawable = listDrawable;\n                            }\n                            drawable = shapedDrawable;\n                            break;\n                        }\n                    }\n                    return drawable;\n                }, (view, drawable) -> {\n                    if (!(view instanceof TextView))\n                        return;\n                    TextView textView = (TextView) view;\n\n                    // compound drawables need a size\n                    drawable.setBounds(0, 0, size, size);\n\n                    // get relative because that's where the checkmark can be\n                    Drawable[] cd = textView.getCompoundDrawablesRelative();\n                    // set compound drawable\n                    textView.setCompoundDrawablesRelative(cd[0], cd[1], drawable, cd[3]);\n                });\n\n            // set text\n            textView.setText(content.name);\n        }\n    }\n\n    public static class PackViewHolder extends ViewHolderAdapter.ViewHolder<IconEntry> {\n        final TextView textView;\n        int size;\n\n        public PackViewHolder(View view) {\n            super(view);\n            textView = view.findViewById(android.R.id.text1);\n            final Context context = view.getContext();\n            size = context.getResources().getDimensionPixelSize(rocks.tbog.tblauncher.R.dimen.icon_preview_size);\n        }\n\n        @Override\n        protected void setContent(IconEntry content, int position, @NonNull ViewHolderAdapter<IconEntry, ? extends ViewHolderAdapter.ViewHolder<IconEntry>> adapter) {\n            // set icon async\n            Utilities.setViewAsync(textView,\n                ctx -> {\n                    try {\n                        return ctx.getPackageManager().getApplicationIcon(content.value.toString());\n                    } catch (Exception ignored) {\n                    }\n                    return new ColorDrawable(Color.TRANSPARENT);\n                },\n                (view, drawable) -> {\n                    if (!(view instanceof TextView))\n                        return;\n                    drawable.setBounds(0, 0, size, size);\n                    TextView textView = (TextView) view;\n                    // get relative because that's where the checkmark can be\n                    Drawable[] cd = textView.getCompoundDrawablesRelative();\n                    // set compound drawable\n                    textView.setCompoundDrawablesRelative(cd[0], cd[1], drawable, cd[3]);\n                });\n\n            // set text\n            textView.setText(content.name);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/preference/MarginDialog.java",
    "content": "package rocks.tbog.tblauncher.preference;\n\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.view.View;\nimport android.widget.TextView;\n\nimport androidx.lifecycle.MediatorLiveData;\nimport androidx.lifecycle.MutableLiveData;\nimport androidx.preference.DialogPreference;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.ui.CustomizeMarginView;\nimport rocks.tbog.tblauncher.utils.UIColors;\nimport rocks.tbog.tblauncher.utils.UISizes;\n\npublic class MarginDialog extends BasePreferenceDialog {\n    private static final String TAG = MarginDialog.class.getSimpleName();\n    private final MutableLiveData<Float> offsetX = new MutableLiveData<>();\n    private final MutableLiveData<Float> offsetY = new MutableLiveData<>();\n\n    public static MarginDialog newInstance(String key) {\n        MarginDialog fragment = new MarginDialog();\n        final Bundle b = new Bundle(1);\n        b.putString(ARG_KEY, key);\n        fragment.setArguments(b);\n\n        return fragment;\n    }\n\n    @Override\n    public void onDialogClosed(boolean positiveResult) {\n        if (!positiveResult)\n            return;\n        DialogPreference dialogPreference = getPreference();\n        if (!(dialogPreference instanceof CustomDialogPreference))\n            return;\n        CustomDialogPreference preference = (CustomDialogPreference) dialogPreference;\n\n        // save data when user clicked OK\n        final var offsetXValue = offsetX.getValue();\n        if (offsetXValue != null)\n            preference.persistValueIfAllowed(offsetXValue);\n\n        SharedPreferences sharedPreferences = preference.getSharedPreferences();\n        var editor = sharedPreferences != null ? sharedPreferences.edit() : null;\n        if (editor != null) {\n            final var offsetYValue = offsetY.getValue();\n            if (offsetYValue != null) {\n                final String key = preference.getKey().replace(\"-dx\", \"-dy\");\n                editor.putFloat(key, offsetYValue);\n            }\n\n            editor.apply();\n        }\n    }\n\n    @Override\n    protected void onBindDialogView(View root) {\n        super.onBindDialogView(root);\n\n        DialogPreference preference = getPreference();\n        final String keyX = preference.getKey();\n        if (!keyX.endsWith(\"-dx\"))\n            throw new IllegalStateException(\"pref key `\" + keyX + \"` must end with `-dx`\");\n        final String keyY = keyX.replace(\"-dx\", \"-dy\");\n\n        SharedPreferences sharedPreferences = preference.getSharedPreferences();\n        if (sharedPreferences == null) {\n            Log.e(TAG, \"getSharedPreferences == null for preference `\" + keyX + \"`\");\n            return;\n        }\n        var prefMap = sharedPreferences.getAll();\n\n        CustomizeMarginView viewXY = root.findViewById(R.id.viewXY);\n        TextView textValueXY = root.findViewById(R.id.textValueXY);\n\n        // initialize LiveData\n        {\n            var value = prefMap.get(keyX);\n            offsetX.setValue(value instanceof Float ? (float) value : 0f);\n        }\n        {\n            var value = prefMap.get(keyY);\n            offsetY.setValue(value instanceof Float ? (float) value : 0f);\n        }\n\n        final Context ctx = requireContext();\n\n        {\n            Float dx = offsetX.getValue();\n            Float dy = offsetY.getValue();\n            if (dx == null)\n                dx = 0f;\n            if (dy == null)\n                dy = 0f;\n            viewXY.setOffsetValues(UISizes.dp2px_float(ctx, dx), UISizes.dp2px_float(ctx, dy));\n        }\n\n        // initialize preview\n        {\n            int color1;\n            int color2;\n            switch (keyX) {\n                case \"result-list-margin-offset-dx\":\n                    color1 = UIColors.getResultListBackground(ctx);\n                    color2 = UIColors.getResultListRipple(ctx);\n                    final var margin = UISizes.getResultListMargin(ctx);\n                    viewXY.setMarginParameters(margin.exactCenterX(), margin.exactCenterY());\n                    break;\n                default:\n                    color1 = 0;\n                    color2 = 0;\n                    break;\n            }\n            if (color1 != 0 || color2 != 0) {\n                color1 = UIColors.setAlpha(color1, 0xFF);\n                color2 = UIColors.setAlpha(color2, 0xFF);\n                viewXY.setPreviewColors(color1, color2);\n            }\n        }\n\n        viewXY.setOnOffsetChanged((dx, dy) -> {\n            offsetX.postValue(UISizes.px2dp_float(viewXY.getContext(), dx));\n            offsetY.postValue(UISizes.px2dp_float(viewXY.getContext(), dy));\n        });\n\n        MediatorLiveData<LiveMarginParameters> dataMerge = new MediatorLiveData<>();\n        dataMerge.addSource(offsetX, aFloat -> dataMerge.setValue(new LiveMarginParameters(aFloat, offsetY.getValue())));\n        dataMerge.addSource(offsetY, aFloat -> dataMerge.setValue(new LiveMarginParameters(offsetX.getValue(), aFloat)));\n\n        dataMerge.observe(getDialogLifecycleOwner(), marginParameters -> {\n            final float dx = marginParameters.dx;\n            final float dy = marginParameters.dy;\n            textValueXY.setText(getResources().getString(R.string.value_float_xy, dx, dy));\n        });\n    }\n\n    private static class LiveMarginParameters {\n        Float dx;\n        Float dy;\n\n        public LiveMarginParameters(Float dx, Float dy) {\n            this.dx = dx;\n            this.dy = dy;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/preference/MultiDependencies.java",
    "content": "package rocks.tbog.tblauncher.preference;\n\nimport android.util.AttributeSet;\nimport android.util.Log;\n\nimport androidx.preference.CheckBoxPreference;\nimport androidx.preference.Preference;\n\nimport java.lang.reflect.Method;\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic abstract class MultiDependencies {\n    private static final String TAG = \"MDep\";\n    private static final String NS = \"http://tbog.rocks/res/pref\";\n    private static final Method PREFERENCE_METHOD_REGISTER_DEPENDENT;\n    private static final Method PREFERENCE_METHOD_UNREGISTER_DEPENDENT;\n\n    private final Preference host;\n    private final Map<String, Boolean> dependencies = new HashMap<>();\n\n\n    static {\n        final Class<Preference> prefClass = Preference.class;\n        Method registerMethod = null;\n        Method unregisterMethod = null;\n        try {\n            registerMethod = prefClass.getDeclaredMethod(\"registerDependent\", Preference.class);\n            registerMethod.setAccessible(true);\n            unregisterMethod = prefClass.getDeclaredMethod(\"unregisterDependent\", Preference.class);\n            unregisterMethod.setAccessible(true);\n        } catch (Throwable t) {\n            Log.w(TAG, \"make methods from \" + prefClass + \" accessible\", t);\n        }\n        PREFERENCE_METHOD_REGISTER_DEPENDENT = registerMethod;\n        PREFERENCE_METHOD_UNREGISTER_DEPENDENT = unregisterMethod;\n    }\n\n    //We have to get access to the 'findPreferenceInHierarchy' function\n    //from the extended preference, because this function is protected\n    protected abstract Preference findPreferenceInHierarchy(String key);\n\n    public MultiDependencies(Preference host, AttributeSet attrs) {\n\n        this.host = host;\n\n        final String dependencyString = getAttributeStringValue(attrs, NS, \"dependencies\", null);\n\n        if (dependencyString != null) {\n            String[] dependencies = dependencyString.split(\",\");\n            for (String dependency : dependencies) {\n                this.dependencies.put(dependency.trim(), false);\n            }\n        }\n    }\n\n    public void register() {\n        if (hasDependencies())\n            registerDependencies();\n    }\n\n    public void unregister() {\n        unregisterDependencies();\n    }\n\n    public void onDependencyChanged(Preference dependency, boolean disableDependent) {\n        setDependencyState(dependency.getKey(), !disableDependent);\n        setHostState();\n    }\n\n    private void setDependencyState(String key, boolean enabled) {\n        if (dependencies.containsKey(key))\n            dependencies.put(key, enabled);\n    }\n\n    private static String getAttributeStringValue(AttributeSet attrs, String namespace, String name, String defaultValue) {\n        String value = attrs.getAttributeValue(namespace, name);\n        if (value == null)\n            value = defaultValue;\n        return value;\n    }\n\n    private void registerDependencies() {\n        for (final Map.Entry<String, Boolean> entry : dependencies.entrySet()) {\n            final Preference preference = findPreferenceInHierarchy(entry.getKey());\n\n            if (preference != null) {\n                try {\n                    PREFERENCE_METHOD_REGISTER_DEPENDENT.invoke(preference, host);\n                } catch (final Exception e) {\n                    Log.e(TAG, \"registerDependent on (\" + host.getClass() + \") \" + host);\n                }\n\n                boolean enabled = preference.isEnabled();\n                if (preference instanceof CheckBoxPreference) {\n                    enabled &= ((CheckBoxPreference) preference).isChecked();\n                }\n\n                setDependencyState(preference.getKey(), enabled);\n            }\n        }\n        setHostState();\n    }\n\n    private void unregisterDependencies() {\n        for (final Map.Entry<String, Boolean> entry : dependencies.entrySet()) {\n            final Preference preference = findPreferenceInHierarchy(entry.getKey());\n\n            if (preference != null) {\n                try {\n                    PREFERENCE_METHOD_UNREGISTER_DEPENDENT.invoke(preference, host);\n                } catch (final Exception e) {\n                    Log.e(TAG, \"unregisterDependent on (\" + host.getClass() + \") \" + host);\n                }\n\n                boolean enabled = preference.isEnabled();\n                if (preference instanceof CheckBoxPreference) {\n                    enabled &= ((CheckBoxPreference) preference).isChecked();\n                }\n\n                setDependencyState(preference.getKey(), enabled);\n            }\n        }\n    }\n\n    private void setHostState() {\n        boolean enabled = true;\n        for (Map.Entry<String, Boolean> entry : dependencies.entrySet()) {\n            if (!entry.getValue()) {\n                enabled = false;\n                break;\n            }\n        }\n        host.setEnabled(enabled);\n    }\n\n    public boolean hasDependencies() {\n        return dependencies.size() > 0;\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/preference/MultiDependenciesSwitchPreference.java",
    "content": "package rocks.tbog.tblauncher.preference;\n\nimport android.content.Context;\nimport android.util.AttributeSet;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.preference.Preference;\nimport androidx.preference.SwitchPreference;\n\npublic class MultiDependenciesSwitchPreference extends SwitchPreference {\n\n    MultiDependencies multiDependencies;\n\n    public MultiDependenciesSwitchPreference(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {\n        super(context, attrs, defStyleAttr, defStyleRes);\n        initMultiDep(attrs);\n    }\n\n    public MultiDependenciesSwitchPreference(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n        initMultiDep(attrs);\n    }\n\n    public MultiDependenciesSwitchPreference(@NonNull Context context, @Nullable AttributeSet attrs) {\n        super(context, attrs);\n        initMultiDep(attrs);\n    }\n\n    public MultiDependenciesSwitchPreference(@NonNull Context context) {\n        this(context, null);\n    }\n\n    private void initMultiDep(@Nullable AttributeSet attrs) {\n        multiDependencies = new MultiDependencies(this, attrs) {\n            @Override\n            protected Preference findPreferenceInHierarchy(String key) {\n                return MultiDependenciesSwitchPreference.this.findPreferenceInHierarchy(key);\n            }\n        };\n    }\n\n    @Override\n    public void onAttached() {\n        super.onAttached();\n        multiDependencies.register();\n    }\n\n    @Override\n    public void onDetached() {\n        multiDependencies.unregister();\n        super.onDetached();\n    }\n\n    @Override\n    protected void onPrepareForRemoval() {\n        multiDependencies.unregister();\n        super.onPrepareForRemoval();\n    }\n\n    @Override\n    public void onDependencyChanged(@NonNull Preference dependency, boolean disableDependent) {\n        if(multiDependencies.hasDependencies())\n            multiDependencies.onDependencyChanged(dependency, disableDependent);\n        else super.onDependencyChanged(dependency, disableDependent);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/preference/OrderListPreferenceDialog.java",
    "content": "package rocks.tbog.tblauncher.preference;\n\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.view.View;\nimport android.widget.TextView;\n\nimport androidx.annotation.NonNull;\nimport androidx.appcompat.app.AlertDialog;\nimport androidx.preference.MultiSelectListPreference;\nimport androidx.preference.PreferenceDialogFragmentCompat;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Objects;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.utils.DialogHelper;\nimport rocks.tbog.tblauncher.utils.PrefOrderedListHelper;\nimport rocks.tbog.tblauncher.utils.ViewHolderAdapter;\nimport rocks.tbog.tblauncher.utils.ViewHolderListAdapter;\n\npublic class OrderListPreferenceDialog extends PreferenceDialogFragmentCompat {\n\n    private static final String SAVE_STATE_VALUES = \"OrderListPreferenceDialogFragment.values\";\n    private static final String SAVE_STATE_CHANGED = \"OrderListPreferenceDialogFragment.changed\";\n    private static final String SAVE_STATE_ENTRIES = \"OrderListPreferenceDialogFragment.entries\";\n    private static final String SAVE_STATE_ENTRY_VALUES = \"OrderListPreferenceDialogFragment.entryValues\";\n\n    protected final HashSet<String> mNewValues = new HashSet<>();\n    boolean mPreferenceChanged;\n    protected CharSequence[] mEntries;\n    protected CharSequence[] mEntryValues;\n\n    public static OrderListPreferenceDialog newInstance(String key) {\n        final OrderListPreferenceDialog fragment = new OrderListPreferenceDialog();\n        final Bundle b = new Bundle(2);\n        b.putString(ARG_KEY, key);\n        fragment.setArguments(b);\n        return fragment;\n    }\n\n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        if (savedInstanceState == null) {\n            final MultiSelectListPreference preference = getListPreference();\n\n            if (preference.getEntries() == null || preference.getEntryValues() == null) {\n                throw new IllegalStateException(\"OrderListPreferenceDialog requires an entries array and an entryValues array.\");\n            }\n\n            mNewValues.clear();\n            mNewValues.addAll(preference.getValues());\n            mPreferenceChanged = false;\n            mEntries = preference.getEntries();\n            mEntryValues = preference.getEntryValues();\n        } else {\n            mNewValues.clear();\n            mNewValues.addAll(savedInstanceState.getStringArrayList(SAVE_STATE_VALUES));\n            mPreferenceChanged = savedInstanceState.getBoolean(SAVE_STATE_CHANGED, false);\n            mEntries = savedInstanceState.getCharSequenceArray(SAVE_STATE_ENTRIES);\n            mEntryValues = savedInstanceState.getCharSequenceArray(SAVE_STATE_ENTRY_VALUES);\n        }\n\n        Log.d(\"pref\", \"OrderListPreferenceDialog \" + getPreference().getKey() + \"\\n entries=\" + Arrays.toString(mEntries) + \"\\n values=\" + Arrays.toString(mEntryValues));\n    }\n\n    @Override\n    public void onSaveInstanceState(@NonNull Bundle outState) {\n        super.onSaveInstanceState(outState);\n        outState.putStringArrayList(SAVE_STATE_VALUES, new ArrayList<>(mNewValues));\n        outState.putBoolean(SAVE_STATE_CHANGED, mPreferenceChanged);\n        outState.putCharSequenceArray(SAVE_STATE_ENTRIES, mEntries);\n        outState.putCharSequenceArray(SAVE_STATE_ENTRY_VALUES, mEntryValues);\n    }\n\n    private MultiSelectListPreference getListPreference() {\n        return (MultiSelectListPreference) getPreference();\n    }\n\n    protected ArrayList<ListEntry> generateEntryList() {\n        final int entryCount = mEntryValues.length;\n        ArrayList<ListEntry> entryArrayList = new ArrayList<>(entryCount);\n        for (int i = 0; i < entryCount; i += 1) {\n            ListEntry listEntry = new ListEntry(mEntries[i], mEntryValues[i].toString());\n            entryArrayList.add(listEntry);\n        }\n        return entryArrayList;\n    }\n\n    @Override\n    protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {\n        super.onPrepareDialogBuilder(builder);\n        DialogHelper.setCustomTitle(builder, getPreference().getDialogTitle());\n\n        if (mEntryValues.length != mEntries.length)\n            throw new IllegalStateException(\"mEntryValues.length=\" + mEntryValues.length + \" mEntries.length=\" + mEntries.length);\n\n        ArrayList<ListEntry> entryArrayList = generateEntryList();\n        EntryAdapter entryAdapter = new EntryAdapter(EntryViewHolder.class, entryArrayList);\n        builder.setAdapter(entryAdapter, null);\n\n        entryAdapter.mOnMoveUpListener = (adapter, view, position) -> {\n            if (position <= 0)\n                return;\n\n            List<ListEntry> list = adapter.getList();\n            ListEntry entry = list.remove(position);\n            list.add(position - 1, entry);\n            adapter.notifyDataSetChanged();\n\n            generateNewValues(list);\n        };\n\n        entryAdapter.mOnMoveDownListener = (adapter, view, position) -> {\n            if ((position + 1) >= adapter.getCount())\n                return;\n\n            List<ListEntry> list = adapter.getList();\n            ListEntry entry = list.remove(position);\n            list.add(position + 1, entry);\n            adapter.notifyDataSetChanged();\n\n            generateNewValues(list);\n        };\n    }\n\n    @Override\n    public void onStart() {\n        super.onStart();\n        DialogHelper.setButtonBarBackground(requireDialog());\n    }\n\n    protected void generateNewValues(List<ListEntry> list) {\n        mPreferenceChanged = mEntryValues.length != list.size();\n        mNewValues.clear();\n        int ord = 0;\n        for (ListEntry entry : list) {\n            if ((ord >= mEntryValues.length) || !mEntryValues[ord].equals(entry.value))\n                mPreferenceChanged = true;\n            mNewValues.add(PrefOrderedListHelper.makeOrderedValue(entry.value, ord++));\n        }\n    }\n\n    @Override\n    public void onDialogClosed(boolean positiveResult) {\n        if (positiveResult && mPreferenceChanged) {\n            final MultiSelectListPreference preference = getListPreference();\n            Log.d(\"pref\", \"onDialogClosed \" + preference.getKey() + \"\\n mNewValues=\" + mNewValues);\n            if (preference.callChangeListener(mNewValues)) {\n                preference.setEntryValues(mNewValues.toArray(new CharSequence[0]));\n                preference.setValues(mNewValues);\n            }\n        }\n        mPreferenceChanged = false;\n    }\n\n    public static class ListEntry {\n        final CharSequence name;\n        final String value;\n\n        public ListEntry(@NonNull CharSequence name, @NonNull String value) {\n            this.name = name;\n            this.value = value;\n        }\n\n        @Override\n        public boolean equals(Object o) {\n            if (this == o)\n                return true;\n            if (o == null || getClass() != o.getClass())\n                return false;\n            ListEntry entry = (ListEntry) o;\n            return name.equals(entry.name) && value.equals(entry.value);\n        }\n\n        @Override\n        public int hashCode() {\n            return Objects.hash(name, value);\n        }\n    }\n\n    private static class EntryAdapter extends ViewHolderListAdapter<ListEntry, EntryViewHolder> {\n\n        private OnItemClickListener mOnMoveUpListener = null;\n        private OnItemClickListener mOnMoveDownListener = null;\n\n        public interface OnItemClickListener {\n            void onClick(EntryAdapter adapter, View view, int position);\n        }\n\n        protected EntryAdapter(@NonNull Class<? extends EntryViewHolder> viewHolderClass, @NonNull List<ListEntry> list) {\n            super(viewHolderClass, R.layout.order_list_item, list);\n        }\n\n        public List<ListEntry> getList() {\n            return mList;\n        }\n    }\n\n    public static class EntryViewHolder extends ViewHolderAdapter.ViewHolder<ListEntry> {\n        TextView textView;\n        View btnUp;\n        View btnDown;\n\n        protected EntryViewHolder(View view) {\n            super(view);\n            textView = view.findViewById(android.R.id.text1);\n            btnUp = view.findViewById(android.R.id.button1);\n            btnDown = view.findViewById(android.R.id.button2);\n        }\n\n        @Override\n        protected void setContent(ListEntry content, int position, @NonNull ViewHolderAdapter<ListEntry, ? extends ViewHolderAdapter.ViewHolder<ListEntry>> adapter) {\n            EntryAdapter entryAdapter = (EntryAdapter) adapter;\n\n            textView.setText(content.name);\n\n            if (position == 0)\n                btnUp.setVisibility(View.INVISIBLE);\n            else {\n                btnUp.setVisibility(View.VISIBLE);\n                btnUp.setOnClickListener(v -> {\n                    if (entryAdapter.mOnMoveUpListener == null)\n                        return;\n                    int pos = entryAdapter.getList().indexOf(content);\n                    if (pos != -1)\n                        entryAdapter.mOnMoveUpListener.onClick(entryAdapter, v, pos);\n                });\n            }\n\n            if (position == (adapter.getCount() - 1))\n                btnDown.setVisibility(View.INVISIBLE);\n            else {\n                btnDown.setVisibility(View.VISIBLE);\n                btnDown.setOnClickListener(v -> {\n                    if (entryAdapter.mOnMoveDownListener == null)\n                        return;\n                    int pos = entryAdapter.getList().indexOf(content);\n                    if (pos != -1)\n                        entryAdapter.mOnMoveDownListener.onClick(entryAdapter, v, pos);\n                });\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/preference/PreferenceColorDialog.java",
    "content": "package rocks.tbog.tblauncher.preference;\n\nimport android.annotation.SuppressLint;\nimport android.content.Context;\nimport android.os.Bundle;\nimport android.transition.Fade;\nimport android.transition.TransitionManager;\nimport android.transition.TransitionSet;\nimport android.util.Log;\nimport android.util.TypedValue;\nimport android.view.ContextThemeWrapper;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.animation.AccelerateInterpolator;\n\nimport androidx.annotation.NonNull;\nimport androidx.asynclayoutinflater.view.AsyncLayoutInflater;\nimport androidx.constraintlayout.widget.ConstraintLayout;\nimport androidx.constraintlayout.widget.ConstraintSet;\nimport androidx.preference.DialogPreference;\n\nimport net.mm2d.color.chooser.ColorChooserDialog;\nimport net.mm2d.color.chooser.ColorChooserView;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.databinding.Mm2dCcColorChooserBinding;\nimport rocks.tbog.tblauncher.utils.Timer;\nimport rocks.tbog.tblauncher.utils.UIColors;\n\npublic class PreferenceColorDialog extends BasePreferenceDialog {\n    private static final String TAG = PreferenceColorDialog.class.getSimpleName();\n    private ColorChooserView mChooseView = null;\n    private int mInitialTab;\n    private int mInitialColor;\n\n    public static PreferenceColorDialog newInstance(String key) {\n        final PreferenceColorDialog fragment = new PreferenceColorDialog();\n        final Bundle b = new Bundle(1);\n        b.putString(ARG_KEY, key);\n        fragment.setArguments(b);\n\n        return fragment;\n    }\n\n    @Override\n    public void onDialogClosed(boolean positiveResult) {\n        if (!positiveResult)\n            return;\n\n        DialogPreference dialogPreference = getPreference();\n        if (!(dialogPreference instanceof CustomDialogPreference))\n            return;\n        CustomDialogPreference preference = (CustomDialogPreference) dialogPreference;\n        if (mChooseView != null) {\n            preference.setValue(mChooseView.getColor());\n        }\n\n        preference.persistValueIfAllowed();\n    }\n\n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        // the DialogPreference has no value set, get the one from SharedPreferences\n        DialogPreference dialogPreference = getPreference();\n        int color = dialogPreference.getSharedPreferences().getInt(dialogPreference.getKey(), UIColors.COLOR_DEFAULT);\n        if (dialogPreference instanceof CustomDialogPreference) {\n            CustomDialogPreference preference = (CustomDialogPreference) dialogPreference;\n            preference.setValue(color);\n        }\n\n        if (savedInstanceState != null) {\n            mInitialTab = savedInstanceState.getInt(ColorChooserDialog.KEY_INITIAL_TAB, ColorChooserDialog.TAB_PALETTE);\n            mInitialColor = savedInstanceState.getInt(ColorChooserDialog.KEY_INITIAL_COLOR, color);\n        } else {\n            mInitialTab = ColorChooserDialog.TAB_PALETTE;\n            mInitialColor = color;\n        }\n    }\n\n    @Override\n    protected View onCreateDialogView(@NonNull Context context) {\n        Log.d(TAG, \"onCreateDialogView start\");\n        var t = Timer.startNano();\n\n        TypedValue outValue = new TypedValue();\n        context.getTheme().resolveAttribute(com.google.android.material.R.attr.alertDialogTheme, outValue, true);\n        var themeWrapper = new ContextThemeWrapper(context, outValue.resourceId);\n\n        var inflater = LayoutInflater.from(themeWrapper);\n        @SuppressLint(\"InflateParams\")\n        View view = inflater.inflate(R.layout.dialog_preference_color_chooser, null, false);\n\n        final ConstraintLayout rootLayout = view instanceof ConstraintLayout ? (ConstraintLayout) view : null;\n        if (rootLayout == null)\n            return view;\n\n        new AsyncLayoutInflater(themeWrapper).inflate(R.layout.mm2d_cc_color_chooser, rootLayout, (v, resid, parent) -> {\n            mChooseView = Mm2dCcColorChooserBinding.bind(v).getRoot();\n            mChooseView.setId(View.generateViewId());\n            mChooseView.setVisibility(View.GONE);\n            rootLayout.addView(mChooseView);\n            t.stop();\n            Log.d(TAG, \"onCreateDialogView finished \" + t);\n\n            // initialize color-chooser\n            mChooseView.setCurrentItem(mInitialTab);\n            mChooseView.init(mInitialColor);\n            mChooseView.setWithAlpha(getPreference().getKey().endsWith(\"-argb\"));\n\n            // set new constraints to hide loading and place the button bar below the color-chooser\n            var constraintSet = new ConstraintSet();\n            constraintSet.clone(rootLayout);\n            constraintSet.setVisibility(R.id.iconLoadingBar, ConstraintSet.GONE);\n            constraintSet.setVisibility(mChooseView.getId(), ConstraintSet.VISIBLE);\n            constraintSet.connect(mChooseView.getId(), ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP);\n            constraintSet.connect(mChooseView.getId(), ConstraintSet.BOTTOM, R.id.buttonPanel, ConstraintSet.TOP);\n            constraintSet.connect(R.id.buttonPanel, ConstraintSet.TOP, mChooseView.getId(), ConstraintSet.BOTTOM);\n            constraintSet.constrainDefaultHeight(mChooseView.getId(), ConstraintSet.MATCH_CONSTRAINT_WRAP);\n            constraintSet.constrainHeight(mChooseView.getId(), ConstraintSet.MATCH_CONSTRAINT);\n\n            // set transition without the button bar\n            var transition = new TransitionSet();\n            transition.setOrdering(TransitionSet.ORDERING_TOGETHER);\n            transition.addTransition(new Fade(Fade.OUT));\n            transition.addTransition(new Fade(Fade.IN));\n            transition.setInterpolator(new AccelerateInterpolator());\n            transition.excludeTarget(R.id.buttonPanel, true);\n\n            // start the transition\n            TransitionManager.beginDelayedTransition(parent, transition);\n            constraintSet.applyTo(rootLayout);\n        });\n\n        return rootLayout;\n    }\n\n    @Override\n    public void onSaveInstanceState(@NonNull Bundle outState) {\n        super.onSaveInstanceState(outState);\n        outState.putInt(ColorChooserDialog.KEY_INITIAL_TAB, mInitialTab = mChooseView.getCurrentItem());\n        outState.putInt(ColorChooserDialog.KEY_INITIAL_COLOR, mInitialColor = mChooseView.getColor());\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/preference/PreviewImagePreference.java",
    "content": "package rocks.tbog.tblauncher.preference;\n\nimport android.annotation.SuppressLint;\nimport android.app.Activity;\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android.view.View;\nimport android.widget.GridView;\n\nimport androidx.preference.PreferenceViewHolder;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\nimport rocks.tbog.tblauncher.handler.DataHandler;\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.db.DBHelper;\nimport rocks.tbog.tblauncher.entry.EntryItem;\nimport rocks.tbog.tblauncher.result.EntryAdapter;\nimport rocks.tbog.tblauncher.result.LoadDataForAdapter;\nimport rocks.tbog.tblauncher.utils.UIColors;\nimport rocks.tbog.tblauncher.utils.Utilities;\n\npublic class PreviewImagePreference extends androidx.preference.DialogPreference {\n\n    public PreviewImagePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {\n        super(context, attrs, defStyleAttr, defStyleRes);\n    }\n\n    public PreviewImagePreference(Context context, AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n    }\n\n    public PreviewImagePreference(Context context, AttributeSet attrs) {\n        super(context, attrs);\n    }\n\n    public PreviewImagePreference(Context context) {\n        super(context);\n    }\n\n    @SuppressLint(\"ClickableViewAccessibility\")\n    @Override\n    public void onBindViewHolder(PreferenceViewHolder holder) {\n        super.onBindViewHolder(holder);\n\n        View view = holder.findViewById(R.id.grid);\n        if (!(view instanceof GridView))\n            return;\n        GridView gridView = (GridView) view;\n        // disable touch\n        gridView.setOnTouchListener((v, event) -> true);\n        Utilities.setVerticalScrollbarThumbDrawable(gridView, null);\n        Context ctx = gridView.getContext();\n\n        int background = UIColors.getResultListBackground(getSharedPreferences());\n        gridView.setBackgroundColor(background);\n\n        ArrayList<EntryItem> list = new ArrayList<>();\n        int drawFlags = EntryItem.FLAG_DRAW_GRID | EntryItem.FLAG_DRAW_ICON | EntryItem.FLAG_DRAW_ICON_BADGE | EntryItem.FLAG_DRAW_NO_CACHE;\n        EntryAdapter adapter = new EntryAdapter(list, drawFlags);\n        gridView.setAdapter(adapter);\n        new LoadDataForAdapter(adapter, () -> {\n            Activity activity = Utilities.getActivity(ctx);\n            if (activity == null)\n                return null;\n            DataHandler dataHandler = TBApplication.dataHandler(activity);\n            List<EntryItem> data = dataHandler.getHistory(9, DBHelper.HistoryMode.RECENCY, false, Collections.emptySet());\n            if (data instanceof ArrayList)\n                return (ArrayList<EntryItem>) data;\n            return new ArrayList<>(data);\n        }).execute();\n\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/preference/QuickListPreferenceDialog.java",
    "content": "package rocks.tbog.tblauncher.preference;\n\nimport android.os.Bundle;\nimport android.view.View;\n\nimport rocks.tbog.tblauncher.quicklist.EditQuickList;\n\npublic class QuickListPreferenceDialog extends BasePreferenceDialog {\n\n    private final EditQuickList mEditor = new EditQuickList();\n\n    public static QuickListPreferenceDialog newInstance(String key) {\n        QuickListPreferenceDialog fragment = new QuickListPreferenceDialog();\n        final Bundle b = new Bundle(1);\n        b.putString(ARG_KEY, key);\n        fragment.setArguments(b);\n\n        return fragment;\n    }\n\n    @Override\n    public void onDialogClosed(boolean positiveResult) {\n        if (!positiveResult)\n            return;\n        mEditor.applyChanges(requireContext());\n    }\n\n    @Override\n    protected void onBindDialogView(View view) {\n        super.onBindDialogView(view);\n\n        mEditor.bindView(view);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/preference/SeekBarChangeListener.java",
    "content": "package rocks.tbog.tblauncher.preference;\n\nimport android.widget.SeekBar;\nimport android.widget.TextView;\n\nimport rocks.tbog.tblauncher.R;\n\npublic abstract class SeekBarChangeListener<T> implements SeekBar.OnSeekBarChangeListener {\n    protected final int offset;\n    protected final TextView textView;\n    protected final ValueChanged<T> listener;\n\n    interface ValueChanged<T> {\n        void valueChanged(T newValue);\n    }\n\n    public SeekBarChangeListener(int offset, TextView textView, ValueChanged<T> listener) {\n        this.offset = offset;\n        this.textView = textView;\n        this.listener = listener;\n    }\n\n    @Override\n    public void onStartTrackingTouch(SeekBar seekBar) {\n        // do nothing\n    }\n\n    public static class ProgressChangedInt extends SeekBarChangeListener<Integer> {\n\n        public ProgressChangedInt(int offset, TextView textView, ValueChanged<Integer> listener) {\n            super(offset, textView, listener);\n        }\n\n        @Override\n        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {\n            final int newValue = progress + offset;\n            textView.setText(textView.getResources().getString(R.string.value, newValue));\n        }\n\n        @Override\n        public void onStopTrackingTouch(SeekBar seekBar) {\n            int progress = seekBar.getProgress();\n            progress += offset;\n            listener.valueChanged(progress);\n        }\n    }\n\n    public static class ProgressChangedFloat extends SeekBarChangeListener<Float> {\n        protected float incrementBy;\n\n        public ProgressChangedFloat(int offset, float incrementBy, TextView textView, ValueChanged<Float> listener) {\n            super(offset, textView, listener);\n            this.incrementBy = incrementBy;\n        }\n\n        @Override\n        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {\n            final float newValue = (progress + offset) * incrementBy;\n            textView.setText(textView.getResources().getString(R.string.value_float, newValue));\n        }\n\n        @Override\n        public void onStopTrackingTouch(SeekBar seekBar) {\n            float progress = seekBar.getProgress();\n            progress = (progress + offset) * incrementBy;\n            listener.valueChanged(progress);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/preference/ShadowDialog.java",
    "content": "package rocks.tbog.tblauncher.preference;\n\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.view.View;\nimport android.widget.SeekBar;\nimport android.widget.TextView;\n\nimport androidx.lifecycle.MediatorLiveData;\nimport androidx.lifecycle.MutableLiveData;\nimport androidx.preference.DialogPreference;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.ui.CustomizeShadowView;\nimport rocks.tbog.tblauncher.utils.UIColors;\nimport rocks.tbog.tblauncher.utils.UISizes;\n\npublic class ShadowDialog extends BasePreferenceDialog {\n    private static final String TAG = ShadowDialog.class.getSimpleName();\n    private final MutableLiveData<Float> offsetX = new MutableLiveData<>();\n    private final MutableLiveData<Float> offsetY = new MutableLiveData<>();\n    private final MutableLiveData<Float> radius = new MutableLiveData<>();\n\n    public static ShadowDialog newInstance(String key) {\n        ShadowDialog fragment = new ShadowDialog();\n        final Bundle b = new Bundle(1);\n        b.putString(ARG_KEY, key);\n        fragment.setArguments(b);\n\n        return fragment;\n    }\n\n    @Override\n    public void onDialogClosed(boolean positiveResult) {\n        if (!positiveResult)\n            return;\n        DialogPreference dialogPreference = getPreference();\n        if (!(dialogPreference instanceof CustomDialogPreference))\n            return;\n        CustomDialogPreference preference = (CustomDialogPreference) dialogPreference;\n\n        // save data when user clicked OK\n        final var offsetXValue = offsetX.getValue();\n        if (offsetXValue != null)\n            preference.persistValueIfAllowed(offsetXValue);\n\n        SharedPreferences sharedPreferences = preference.getSharedPreferences();\n        var editor = sharedPreferences != null ? sharedPreferences.edit() : null;\n        if (editor != null) {\n            final var offsetYValue = offsetY.getValue();\n            if (offsetYValue != null) {\n                final String key = preference.getKey().replace(\"-dx\", \"-dy\");\n                editor.putFloat(key, offsetYValue);\n            }\n\n            final var radiusValue = radius.getValue();\n            if (radiusValue != null) {\n                final String key = preference.getKey().replace(\"-dx\", \"-radius\");\n                editor.putFloat(key, radiusValue);\n            }\n\n            editor.apply();\n        }\n    }\n\n    @Override\n    protected void onBindDialogView(View root) {\n        super.onBindDialogView(root);\n\n        CustomDialogPreference preference = (CustomDialogPreference) getPreference();\n        final String keyX = preference.getKey();\n        if (!keyX.endsWith(\"-dx\"))\n            throw new IllegalStateException(\"pref key `\" + keyX + \"` must end with `-dx`\");\n        final String keyY = keyX.replace(\"-dx\", \"-dy\");\n        final String keyR = keyX.replace(\"-dx\", \"-radius\");\n        final String keyC = keyX.replace(\"-dx\", \"-color\");\n\n        SharedPreferences sharedPreferences = preference.getSharedPreferences();\n        if (sharedPreferences == null) {\n            Log.e(TAG, \"getSharedPreferences == null for preference `\" + keyX + \"`\");\n            return;\n        }\n        var prefMap = sharedPreferences.getAll();\n\n        CustomizeShadowView viewXY = root.findViewById(R.id.viewXY);\n        TextView textValueXY = root.findViewById(R.id.textValueXY);\n        SeekBar seekBar = root.findViewById(R.id.seekBar);\n        TextView textValueSlider = root.findViewById(R.id.textValueSlider);\n\n        // initialize LiveData\n        {\n            var value = prefMap.get(keyX);\n            offsetX.setValue(value instanceof Float ? (float) value : 0f);\n        }\n        {\n            var value = prefMap.get(keyY);\n            offsetY.setValue(value instanceof Float ? (float) value : 0f);\n        }\n        {\n            var value = prefMap.get(keyR);\n            radius.setValue(value instanceof Float ? (float) value : 0f);\n        }\n\n        // get additional data\n        final int shadowColor;\n        {\n            var value = prefMap.get(keyC);\n            shadowColor = value instanceof Integer ? (int) value : 0;\n        }\n\n        // set view parameters\n        //setShadowParameters(viewXY, shadowColor);\n\n        final Context ctx = requireContext();\n\n        // initialize shadow preview\n        int color1;\n        int color2;\n        int textColor;\n        int textSize;\n        switch (keyX) {\n            case \"result-shadow-dx\":\n                color1 = UIColors.getResultListBackground(ctx);\n                color2 = UIColors.getResultListRipple(ctx);\n                textColor = UIColors.getResultTextColor(ctx);\n                textSize = UISizes.getResultTextSize(ctx);\n                break;\n            case \"popup-shadow-dx\":\n                color1 = UIColors.getPopupBackgroundColor(ctx);\n                color2 = UIColors.getPopupRipple(ctx);\n                textColor = UIColors.getPopupTextColor(ctx);\n                textSize = getResources().getDimensionPixelSize(R.dimen.result_small_size);\n                break;\n            case \"search-bar-shadow-dx\":\n                color1 = UIColors.getColor(sharedPreferences, \"search-bar-argb\");\n                color2 = UIColors.getColor(sharedPreferences, \"search-bar-ripple-color\");\n                textColor = UIColors.getSearchTextColor(ctx);\n                textSize = UISizes.sp2px(ctx, sharedPreferences.getInt(\"search-bar-text-size\", getResources().getInteger(R.integer.default_size_text)));\n                break;\n            default:\n                color1 = 0;\n                color2 = 0;\n                textColor = 0;\n                textSize = UISizes.sp2px(ctx, getResources().getInteger(R.integer.default_size_text));\n                break;\n        }\n        if (color1 != 0) {\n            color1 = UIColors.setAlpha(color1, 0xFF);\n            color2 = UIColors.setAlpha(color2, 0xFF);\n            viewXY.setBackgroundParameters(color1, color2);\n        }\n        if (textColor != 0)\n            viewXY.setTextParameters(null, textColor, textSize);\n\n        viewXY.setOnOffsetChanged((dx, dy) -> {\n            offsetX.postValue(dx);\n            offsetY.postValue(dy);\n        });\n\n        int minValue = 0;\n        float incrementByFloat = .1f;\n\n        MediatorLiveData<LiveShadowParameters> dataMerge = new MediatorLiveData<>();\n        dataMerge.addSource(offsetX, aFloat -> dataMerge.setValue(new LiveShadowParameters(radius.getValue(), aFloat, offsetY.getValue())));\n        dataMerge.addSource(offsetY, aFloat -> dataMerge.setValue(new LiveShadowParameters(radius.getValue(), offsetX.getValue(), aFloat)));\n        dataMerge.addSource(radius, aFloat -> dataMerge.setValue(new LiveShadowParameters(aFloat, offsetX.getValue(), offsetY.getValue())));\n\n        dataMerge.observe(getDialogLifecycleOwner(), liveShadowParameters -> {\n            final float r = liveShadowParameters.radius;\n            final float dx = liveShadowParameters.dx;\n            final float dy = liveShadowParameters.dy;\n            viewXY.setShadowParameters(r, dx, dy, shadowColor);\n            textValueXY.setText(getResources().getString(R.string.value_float_xy, dx, dy));\n            textValueSlider.setText(getResources().getString(R.string.value_float, r));\n        });\n\n        // set slider value\n        SliderDialog.setProgressFromPreference(seekBar, radius.getValue(), minValue, incrementByFloat);\n\n        // set seek bar change listener\n        seekBar.setOnSeekBarChangeListener(new SeekBarChangeListener(radius, minValue, incrementByFloat));\n    }\n\n    private static class SeekBarChangeListener implements SeekBar.OnSeekBarChangeListener {\n        final MutableLiveData<Float> variable;\n        final int minValue;\n        final float incrementByFloat;\n\n        public SeekBarChangeListener(MutableLiveData<Float> var, int min, float inc) {\n            variable = var;\n            minValue = min;\n            incrementByFloat = inc;\n        }\n\n        @Override\n        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {\n            final float newValue = (progress + minValue) * incrementByFloat;\n            variable.postValue(newValue);\n        }\n\n        @Override\n        public void onStartTrackingTouch(SeekBar seekBar) {\n            // do nothing\n        }\n\n        @Override\n        public void onStopTrackingTouch(SeekBar seekBar) {\n            // do nothing\n        }\n    }\n\n    private static class LiveShadowParameters {\n        Float dx;\n        Float dy;\n        Float radius;\n\n        public LiveShadowParameters(Float radius, Float dx, Float dy) {\n            this.dx = dx;\n            this.dy = dy;\n            this.radius = radius;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/preference/SliderDialog.java",
    "content": "package rocks.tbog.tblauncher.preference;\n\nimport android.content.SharedPreferences;\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.view.View;\nimport android.widget.SeekBar;\nimport android.widget.TextView;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.preference.DialogPreference;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.utils.PrefCache;\n\npublic class SliderDialog extends BasePreferenceDialog {\n\n    private static final String TAG = SliderDialog.class.getSimpleName();\n\n    public static SliderDialog newInstance(String key) {\n        SliderDialog fragment = new SliderDialog();\n        final Bundle b = new Bundle(1);\n        b.putString(ARG_KEY, key);\n        fragment.setArguments(b);\n\n        return fragment;\n    }\n\n    @Override\n    public void onDialogClosed(boolean positiveResult) {\n        if (!positiveResult)\n            return;\n        DialogPreference dialogPreference = getPreference();\n        if (!(dialogPreference instanceof CustomDialogPreference))\n            return;\n        CustomDialogPreference preference = (CustomDialogPreference) dialogPreference;\n        // save data when user clicked OK\n        preference.persistValueIfAllowed();\n    }\n\n    @Override\n    protected void onBindDialogView(View root) {\n        super.onBindDialogView(root);\n\n        CustomDialogPreference preference = (CustomDialogPreference) getPreference();\n        final String key = preference.getKey();\n\n        // initialize value\n        SharedPreferences sharedPreferences = preference.getSharedPreferences();\n        if (sharedPreferences == null) {\n            Log.e(TAG, \"getSharedPreferences == null for preference `\" + key + \"`\");\n            return;\n        }\n        {\n            Object value = sharedPreferences.getAll().get(key);\n            if (value != null)\n                preference.setValue(value);\n        }\n        SeekBar seekBar = root.findViewById(R.id.seekBar); // seekBar default minimum is set to 0\n        if (key.endsWith(\"-alpha\")) {\n            seekBar.setMax(255);\n            ((TextView) root.findViewById(android.R.id.text1)).setText(R.string.title_select_alpha);\n        }\n\n        switch (key) {\n            case \"search-bar-text-size\":\n                ((TextView) root.findViewById(android.R.id.text1)).setText(R.string.search_bar_text_size);\n                break;\n            case \"search-bar-height\":\n                ((TextView) root.findViewById(android.R.id.text1)).setText(R.string.search_bar_height);\n                break;\n            case \"quick-list-height\":\n                ((TextView) root.findViewById(android.R.id.text1)).setText(R.string.quick_list_height);\n                break;\n            case \"popup-corner-radius\":\n            case \"quick-list-radius\":\n                ((TextView) root.findViewById(android.R.id.text1)).setText(R.string.corner_radius);\n                break;\n            case \"result-shadow-dx\":\n            case \"result-shadow-dy\":\n            case \"popup-shadow-dx\":\n            case \"popup-shadow-dy\":\n            case \"search-bar-shadow-dx\":\n            case \"search-bar-shadow-dy\":\n                ((TextView) root.findViewById(android.R.id.text1)).setText(R.string.shadow_offset);\n                break;\n            case \"result-shadow-radius\":\n            case \"popup-shadow-radius\":\n            case \"search-bar-shadow-radius\":\n                ((TextView) root.findViewById(android.R.id.text1)).setText(R.string.shadow_radius);\n                break;\n            default:\n                break;\n        }\n\n        // because we can't set minimum below API 26 we make our own\n        int minValue = 0;\n        Float incrementByFloat = null;\n        switch (key) {\n            case \"result-icon-size\":\n            case \"quick-list-icon-size\":\n            case \"tags-menu-icon-size\":\n                minValue = getResources().getInteger(R.integer.min_size_icon);\n                seekBar.setMax(getResources().getInteger(R.integer.max_size_icon) - minValue);\n                break;\n            case \"result-text-size\":\n            case \"result-text2-size\":\n            case \"search-bar-text-size\":\n            case \"search-bar-height\":\n                minValue = 2;\n                seekBar.setMax(seekBar.getMax() - minValue);\n                break;\n            case \"quick-list-height\":\n                minValue = 2 * PrefCache.getDockRowCount(sharedPreferences);\n                seekBar.setMax(100 * PrefCache.getDockRowCount(sharedPreferences) - minValue);\n                break;\n            case \"result-history-size\":\n            case \"result-history-adaptive\":\n            case \"result-search-cap\":\n                minValue = 1;\n                seekBar.setMax(1000 - minValue);\n                break;\n            case \"icon-hue\":\n                minValue = -180;\n                seekBar.setMax(180 - minValue);\n                break;\n            case \"icon-scale-red\":\n            case \"icon-scale-green\":\n            case \"icon-scale-blue\":\n            case \"icon-scale-alpha\":\n                minValue = -200;\n                seekBar.setMax(200 - minValue);\n                break;\n            case \"icon-contrast\":\n            case \"icon-brightness\":\n            case \"icon-saturation\":\n                minValue = -100;\n                seekBar.setMax(100 - minValue);\n                break;\n            case \"quick-list-columns\":\n                minValue = 1;\n                seekBar.setMax(32 - minValue);\n                break;\n            case \"quick-list-rows\":\n                minValue = 1;\n                seekBar.setMax(8 - minValue);\n                break;\n            case \"result-shadow-dx\":\n            case \"result-shadow-dy\":\n            case \"popup-shadow-dx\":\n            case \"popup-shadow-dy\":\n            case \"search-bar-shadow-dx\":\n            case \"search-bar-shadow-dy\":\n                incrementByFloat = .1f;\n                minValue = (int) (-5 / incrementByFloat);\n                seekBar.setMax((int) (5 / incrementByFloat) - minValue);\n                break;\n            case \"result-shadow-radius\":\n            case \"popup-shadow-radius\":\n            case \"search-bar-shadow-radius\":\n                incrementByFloat = .1f;\n                seekBar.setMax((int) (10 / incrementByFloat) - minValue);\n                break;\n            default:\n                break;\n        }\n\n        // set slider value\n        setProgressFromPreference(seekBar, preference.getValue(), minValue, incrementByFloat);\n\n        final TextView text2 = root.findViewById(android.R.id.text2);\n        final SeekBarChangeListener<?> listener;\n\n        // default change listener uses integers\n        if (incrementByFloat == null) {\n            listener = new SeekBarChangeListener.ProgressChangedInt(minValue, text2, (integer) -> {\n                CustomDialogPreference pref = ((CustomDialogPreference) SliderDialog.this.getPreference());\n                pref.setValue(integer);\n            });\n        } else {\n            listener = new SeekBarChangeListener.ProgressChangedFloat(minValue, incrementByFloat, text2, (aFloat) -> {\n                CustomDialogPreference pref = ((CustomDialogPreference) SliderDialog.this.getPreference());\n                pref.setValue(aFloat);\n            });\n        }\n\n        // update display value\n        listener.onProgressChanged(seekBar, seekBar.getProgress(), false);\n\n        // set change listener\n        seekBar.setOnSeekBarChangeListener(listener);\n    }\n\n    public static void setProgressFromPreference(@NonNull SeekBar seekBar, @Nullable Object prefValue, int minValue, @Nullable Float incrementByFloat) {\n        final int seekBarProgress;\n        if (prefValue instanceof Integer) {\n            seekBarProgress = (Integer) prefValue - minValue;\n        } else if (prefValue instanceof Float) {\n            float incrementBy = incrementByFloat != null ? incrementByFloat : 1f;\n            seekBarProgress = Math.round(((Float) prefValue) / incrementBy) - minValue;\n        } else {\n            seekBarProgress = 0;\n        }\n        seekBar.setProgress(seekBarProgress);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/preference/TagOrderListPreferenceDialog.java",
    "content": "package rocks.tbog.tblauncher.preference;\n\nimport android.content.Context;\nimport android.graphics.drawable.Drawable;\nimport android.os.Bundle;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.preference.DialogPreference;\nimport androidx.preference.EditTextPreference;\nimport androidx.preference.Preference;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.dataprovider.TagsProvider;\nimport rocks.tbog.tblauncher.entry.ActionEntry;\nimport rocks.tbog.tblauncher.entry.EntryItem;\nimport rocks.tbog.tblauncher.entry.TagEntry;\nimport rocks.tbog.tblauncher.utils.PrefCache;\nimport rocks.tbog.tblauncher.utils.PrefOrderedListHelper;\nimport rocks.tbog.tblauncher.utils.Utilities;\n\npublic class TagOrderListPreferenceDialog extends OrderListPreferenceDialog {\n\n    private static final String SAVE_STATE_UNTAGGED_IDX = \"TagOrderListPreferenceDialogFragment.untaggedIdx\";\n\n    private int mUntaggedIndex = 0;\n\n    public static TagOrderListPreferenceDialog newInstance(String key) {\n        final TagOrderListPreferenceDialog fragment = new TagOrderListPreferenceDialog();\n        final Bundle b = new Bundle(2);\n        b.putString(ARG_KEY, key);\n        fragment.setArguments(b);\n        return fragment;\n    }\n\n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        if (savedInstanceState == null) {\n            // find the index of the action entry (show/untagged)\n            for (int i = 0; i < mEntryValues.length; i++) {\n                CharSequence entryValue = mEntryValues[i];\n                if (entryValue.toString().startsWith(ActionEntry.SCHEME)) {\n                    mUntaggedIndex = i;\n                    break;\n                }\n            }\n        } else {\n            mUntaggedIndex = savedInstanceState.getInt(SAVE_STATE_UNTAGGED_IDX);\n        }\n    }\n\n    @Override\n    public void onSaveInstanceState(@NonNull Bundle outState) {\n        super.onSaveInstanceState(outState);\n        outState.putInt(SAVE_STATE_UNTAGGED_IDX, mUntaggedIndex);\n    }\n\n    @Nullable\n    private EditTextPreference getUntaggedIndexPreference() {\n        final DialogPreference.TargetFragment fragment = (DialogPreference.TargetFragment) getTargetFragment();\n        if (fragment == null)\n            return null;\n        Preference pref = fragment.findPreference(\"tags-menu-untagged-index\");\n        return pref instanceof EditTextPreference ? (EditTextPreference) pref : null;\n    }\n\n    @Override\n    protected ArrayList<ListEntry> generateEntryList() {\n        final int entryCount = mEntryValues.length;\n\n        Context context = getContext();\n        boolean bAddUntagged = context != null && PrefCache.showTagsMenuUntagged(context);\n        EntryItem untaggedEntry = TBApplication.dataHandler(context).getPojo(ActionEntry.SCHEME + \"show/untagged\");\n        if (!(untaggedEntry instanceof ActionEntry))\n            bAddUntagged = false;\n\n        ArrayList<ListEntry> entryArrayList = new ArrayList<>(entryCount + (bAddUntagged ? 1 : 0));\n\n        for (int i = 0; i < entryCount; i += 1) {\n            String value = mEntryValues[i].toString();\n            String tagId = TagsProvider.getTagId(value);\n            ListEntry listEntry = new ListEntry(mEntries[i], tagId);\n            entryArrayList.add(listEntry);\n        }\n\n        if (bAddUntagged) {\n            EditTextPreference pref = getUntaggedIndexPreference();\n            if (pref != null) {\n                int idx;\n                try {\n                    idx = Integer.parseInt(pref.getText());\n                } catch (Exception ignored) {\n                    idx = 0;\n                }\n                if (idx > entryArrayList.size())\n                    idx = entryArrayList.size();\n                int size = context.getResources().getDimensionPixelSize(R.dimen.icon_preview_size);\n                Drawable icon = ((ActionEntry) untaggedEntry).getIconDrawable(context);\n                icon.setBounds(0, 0, size, size);\n\n                int layoutDirection = context.getResources().getConfiguration().getLayoutDirection();\n                CharSequence label = Utilities.addDrawableBeforeString(untaggedEntry.getName(), icon, layoutDirection);\n                entryArrayList.add(idx, new ListEntry(label, untaggedEntry.id));\n            }\n        }\n\n        return entryArrayList;\n    }\n\n    @Override\n    protected void generateNewValues(List<ListEntry> list) {\n        mPreferenceChanged = true;\n        mNewValues.clear();\n        int ord = 0;\n        for (int idx = 0, listSize = list.size(); idx < listSize; idx++) {\n            String id = list.get(idx).value;\n            if (id.startsWith(TagEntry.SCHEME)) {\n                String tagName = id.substring(TagEntry.SCHEME.length());\n                mNewValues.add(PrefOrderedListHelper.makeOrderedValue(tagName, ord++));\n            } else if (id.startsWith(ActionEntry.SCHEME)) {\n                // assume it's \"show/untagged\"\n                mUntaggedIndex = idx;\n            }\n        }\n    }\n\n    @Override\n    public void onDialogClosed(boolean positiveResult) {\n        if (positiveResult && mPreferenceChanged) {\n            EditTextPreference pref = getUntaggedIndexPreference();\n            if (pref != null)\n                pref.setText(Integer.toString(mUntaggedIndex));\n        }\n        super.onDialogClosed(positiveResult);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/quicklist/DockRecycleLayoutManager.java",
    "content": "package rocks.tbog.tblauncher.quicklist;\n\nimport android.graphics.PointF;\nimport android.util.Log;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.TextView;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.recyclerview.widget.LinearSmoothScroller;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.utils.SparseArrayWrapper;\n\npublic class DockRecycleLayoutManager extends RecyclerView.LayoutManager implements RecyclerView.SmoothScroller.ScrollVectorProvider {\n    private static final String TAG = \"DRLM\";\n    private static final Boolean LOG_DEBUG = true;\n    // Reusable array. This should only be used used transiently and should not be used to retain any state over time.\n    private final SparseArrayWrapper<View> mViewCache = new SparseArrayWrapper<>();\n    private RecyclerView mRecyclerView = null;\n    private boolean mRefreshViews = false;\n    /* Consistent size applied to all child views */\n    private int mDecoratedChildWidth = 0;\n    private int mDecoratedChildHeight = 0;\n\n    private int mColumnCount = 6;\n    private int mRowCount = 1;\n    private int mScrollToAdapterPosition = -1;\n    private int mScrollOffsetHorizontal = 0;\n    private boolean mRightToLeft = false;\n\n    public DockRecycleLayoutManager(int columnCount, int rowCount) {\n        super();\n        setColumnCount(columnCount);\n        setRowCount(rowCount);\n    }\n\n    public void setColumnCount(int count) {\n        mColumnCount = count;\n    }\n\n    public void setRowCount(int count) {\n        mRowCount = count;\n    }\n\n    public void setRightToLeft(boolean rightToLeft) {\n        mRightToLeft = rightToLeft;\n    }\n\n    private static int adapterPosition(@NonNull View child) {\n        return ((RecyclerView.LayoutParams) child.getLayoutParams()).getViewLayoutPosition();\n    }\n\n    private static boolean viewNeedsUpdate(View v) {\n        RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) v.getLayoutParams();\n        return p.viewNeedsUpdate();\n    }\n\n    private static String getDebugName(View v) {\n        View name;\n        name = v.findViewById(R.id.item_app_name);\n        if (name instanceof TextView) {\n            CharSequence text = ((TextView) name).getText();\n            if (text != null && text.length() > 0)\n                return text.toString();\n        }\n        name = v.findViewById(R.id.item_contact_name);\n        if (name instanceof TextView) {\n            CharSequence text = ((TextView) name).getText();\n            if (text != null && text.length() > 0)\n                return text.toString();\n        }\n        name = v.findViewById(android.R.id.text1);\n        if (name instanceof TextView) {\n            CharSequence text = ((TextView) name).getText();\n            if (text != null && text.length() > 0)\n                return text.toString();\n        }\n        return \"\";\n    }\n\n    private static String getDebugInfo(View v) {\n        String info = \"\";\n        if (v.getLayoutParams() instanceof RecyclerView.LayoutParams) {\n            RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) v.getLayoutParams();\n            info += \"lp\" + p.getViewLayoutPosition();\n            info += \"ap\" + p.getViewAdapterPosition();\n            if (p.viewNeedsUpdate())\n                info += \"u\";\n            if (p.isItemChanged())\n                info += \"c\";\n            if (p.isItemRemoved())\n                info += \"r\";\n            if (p.isViewInvalid())\n                info += \"i\";\n        }\n        return info;\n    }\n\n    private static void logDebug(String message) {\n        if (LOG_DEBUG)\n            Log.d(TAG, message);\n    }\n\n    @Override\n    public void onAttachedToWindow(RecyclerView view) {\n        super.onAttachedToWindow(view);\n        mRecyclerView = view;\n    }\n\n    @Override\n    public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) {\n        mRecyclerView = null;\n        super.onDetachedFromWindow(view, recycler);\n    }\n\n    /*\n     * You must return true from this method if you want your\n     * LayoutManager to support anything beyond \"simple\" item\n     * animations. Enabling this causes onLayoutChildren() to\n     * be called twice on each animated change; once for a\n     * pre-layout, and again for the real layout.\n     */\n    @Override\n    public boolean supportsPredictiveItemAnimations() {\n        return false;\n    }\n\n    /*\n     * Called by RecyclerView when a view removal is triggered. This is called\n     * before onLayoutChildren() in pre-layout if the views removed are not visible. We\n     * use it in this case to inform pre-layout that a removal took place.\n     *\n     * This method is still called if the views removed were visible, but it will\n     * happen AFTER pre-layout.\n     */\n    @Override\n    public void onItemsRemoved(@NonNull RecyclerView recyclerView, int positionStart, int itemCount) {\n        logDebug(\"onItemsRemoved start=\" + positionStart + \" count=\" + itemCount);\n        mRefreshViews = true;\n    }\n\n    @Override\n    public void onItemsMoved(@NonNull RecyclerView recyclerView, int from, int to, int itemCount) {\n        logDebug(\"onItemsMoved from=\" + from + \" to=\" + to + \" count=\" + itemCount);\n        mRefreshViews = true;\n    }\n\n    @Override\n    public RecyclerView.LayoutParams generateDefaultLayoutParams() {\n        return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);\n    }\n\n    private int getVerticalSpace() {\n        return getHeight() - getPaddingBottom() - getPaddingTop();\n    }\n\n    private int getHorizontalSpace() {\n        return getWidth() - getPaddingRight() - getPaddingLeft();\n    }\n\n    /*\n     * This method is your initial call from the framework. You will receive it when you\n     * need to start laying out the initial set of views. This method will not be called\n     * repeatedly, so don't rely on it to continually process changes during user\n     * interaction.\n     *\n     * This method will be called when the data set in the adapter changes, so it can be\n     * used to update a layout based on a new item count.\n     *\n     * If predictive animations are enabled, you will see this called twice. First, with\n     * state.isPreLayout() returning true to lay out children in their initial conditions.\n     * Then again to lay out children in their final locations.\n     *\n     * When scrolling, if a view has been added, this will be called after scroll*By\n     */\n    @Override\n    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {\n        //We have nothing to show for an empty data set but clear any existing views\n        if (getItemCount() == 0) {\n            detachAndScrapAttachedViews(recycler);\n            return;\n        }\n        if (getChildCount() == 0 && state.isPreLayout()) {\n            //Nothing to do during prelayout when empty\n            return;\n        }\n\n        // Always update the visible row/column counts\n        updateSizing();\n\n        logDebug(\"onLayoutChildren\" +\n            \" childCount=\" + getChildCount() +\n            \" itemCount=\" + getItemCount() +\n            (state.isPreLayout() ? \" preLayout\" : \"\") +\n            (state.didStructureChange() ? \" structureChanged\" : \"\") +\n            \" stateItemCount=\" + state.getItemCount());\n\n        layoutChildren(recycler);\n    }\n\n    @Override\n    public void onLayoutCompleted(RecyclerView.State state) {\n        super.onLayoutCompleted(state);\n        //TODO: auto-scroll after resize\n    }\n\n    @Override\n    public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {\n        LinearSmoothScroller scroller = new LinearSmoothScroller(recyclerView.getContext());\n        scroller.setTargetPosition(position);\n        startSmoothScroll(scroller);\n    }\n\n    @Override\n    public void scrollToPosition(int position) {\n        mScrollToAdapterPosition = position;\n    }\n\n    private void updateSizing() {\n        final int width = getColumnWidth();\n        final int height = getRowHeight();\n        if (mDecoratedChildWidth != width || mDecoratedChildHeight != height) {\n            if (mDecoratedChildWidth != width)\n                logDebug(\"mDecoratedChildWidth changed from \" + mDecoratedChildWidth + \" to \" + width);\n            if (mDecoratedChildHeight != width)\n                logDebug(\"mDecoratedChildHeight changed from \" + mDecoratedChildHeight + \" to \" + height);\n            mRefreshViews = true;\n            mDecoratedChildWidth = width;\n            mDecoratedChildHeight = height;\n        }\n    }\n\n    /* Example for 15 items placed on 3 columns and 2 rows (3 pages)\n     *\n     * mRightToLeft == false\n     *       page 0      page 1     page 2\n     *       + - - - - - - - - - - - - - - - - +\n     * row 0 |  0  1  2 |  6  7  8 | 12 13 14  |\n     * row 1 |  3  4  5 |  9 10 11 | 15        |\n     *       + - - - - - - - - - - - - - - - - +\n     * column   0  1  2    3  4  5    6  7  8\n     *\n     *\n     * mRightToLeft == true\n     *       page 2      page 1     page 0\n     *       + - - - - - - - - - - - - - - - - +\n     * row 0 | 14 13 12 |  8  7  6 |  2  1  0  |\n     * row 1 |       15 | 11 10  9 |  5  4  3  |\n     *       + - - - - - - - - - - - - - - - - +\n     * column   8  7  6    5  4  3    2  1  0\n     */\n\n    private int getAdapterIdx(int colIdx, int rowIdx) {\n        int columnInPage = colIdx % mColumnCount;\n        int page = colIdx / mColumnCount;\n        return page * mColumnCount * mRowCount + rowIdx * mColumnCount + columnInPage;\n    }\n\n    private int getColumnIdx(int adapterPos) {\n        int columnInPage = adapterPos % (mColumnCount * mRowCount) % mColumnCount;\n        int page = getPageIdx(adapterPos);\n        return page * mColumnCount + columnInPage;\n    }\n\n    private int getRowIdx(int adapterPos) {\n        int group = adapterPos / mColumnCount;\n        return group % mRowCount;\n    }\n\n    private int getPageIdx(int adapterPos) {\n        return adapterPos / (mColumnCount * mRowCount);\n    }\n\n    private int getColumnWidth() {\n        return getHorizontalSpace() / mColumnCount;\n    }\n\n    private int getRowHeight() {\n        return getVerticalSpace() / mRowCount;\n    }\n\n    private int getColumnPosition(int columnIdx) {\n        int lastPageIdx = getPageIdx(getItemCount() - 1);\n        int maxColumns = (lastPageIdx + 1) * mColumnCount;\n        if (columnIdx < 0 || columnIdx >= maxColumns) {\n            Log.e(TAG, \"getColumnPosition(\" + columnIdx + \")\" +\n                \" mColumnCount=\" + mColumnCount +\n                \" mRowCount=\" + mRowCount +\n                \" itemCount=\" + getItemCount() +\n                \" lastPageIdx=\" + lastPageIdx +\n                \" maxColumns=\" + maxColumns);\n        }\n        final int columnOffset = columnIdx * mDecoratedChildWidth;\n        if (mRightToLeft)\n            return getWidth() - getPaddingRight() - columnOffset - mDecoratedChildWidth;\n        return getPaddingLeft() + columnOffset;\n    }\n\n    private int getRowPosition(int rowIdx) {\n        if (rowIdx < 0 || rowIdx >= mRowCount) {\n            Log.e(TAG, \"getRowPosition(\" + rowIdx + \"); mRowCount=\" + mRowCount);\n        }\n        return getPaddingTop() + rowIdx * mDecoratedChildHeight;\n    }\n\n    private int getPagePosition(int pageIdx) {\n        final int pageWidth = mColumnCount * mDecoratedChildWidth;\n        final int pageOffset = pageIdx * pageWidth;\n        if (mRightToLeft)\n            return getWidth() - getPaddingRight() - pageOffset - pageWidth;\n        return getPaddingLeft() + pageOffset;\n    }\n\n    private void layoutChildren(RecyclerView.Recycler recycler) {\n        /*\n         * Detach all existing views from the layout.\n         * detachView() is a lightweight operation that we can use to\n         * quickly reorder views without a full add/remove.\n         */\n        cacheChildren();\n\n        // if scrollToPosition is valid, force-scroll to that page\n        if (mScrollToAdapterPosition >= 0 && mScrollToAdapterPosition < getItemCount()) {\n            int pageIdx = getPageIdx(mScrollToAdapterPosition);\n            int pagePosition = getPagePosition(pageIdx);\n            // force-scroll the child views\n            int dx = -pagePosition - mScrollOffsetHorizontal;\n            logDebug(\"scrollToPosition \" + mScrollToAdapterPosition +\n                \" pageIdx=\" + pageIdx +\n                \" pagePos=\" + pagePosition +\n                \" dx=\" + dx);\n            offsetChildrenHorizontal(dx);\n            mScrollOffsetHorizontal += dx;\n        }\n        // turn off scrollToPosition\n        mScrollToAdapterPosition = -1;\n\n        // compute scroll position after we populate `mViewCache`\n        final int scrollOffset = mScrollOffsetHorizontal;\n        final int firstVisiblePos = findFirstVisibleAdapterPosition();\n\n        logDebug(\"layoutChildren\" +\n            \" scrollOffset=\" + scrollOffset +\n            \" firstVisiblePos=\" + firstVisiblePos +\n            \" padding=\" + getPaddingLeft() + \" \" + getPaddingTop() + \" \" + getPaddingRight() + \" \" + getPaddingBottom() +\n            \" verticalSpace=\" + getVerticalSpace() +\n            \" horizontalSpace=\" + getHorizontalSpace());\n\n        if (mRefreshViews) {\n            logDebug(\"detachAndScrapAttachedViews\" +\n                \" viewCache.size=\" + mViewCache.size());\n            // If we want to refresh all views, just scrap them and let the recycler rebind them\n            mRefreshViews = false;\n            mViewCache.clear();\n            detachAndScrapAttachedViews(recycler);\n        } else {\n            logDebug(\"detachViews\" +\n                \" viewCache.size=\" + mViewCache.size());\n            // Temporarily detach all views. We do this to easily move them.\n            detachCachedChildren(recycler);\n        }\n\n        final int nextLeftPosDelta = mRightToLeft ? -mDecoratedChildWidth : mDecoratedChildWidth;\n\n        int colIdx = getColumnIdx(firstVisiblePos);\n        int rowIdx = getRowIdx(firstVisiblePos);\n\n        int topPos = getRowPosition(rowIdx);\n        int scrolledPosition = scrollOffset + getColumnPosition(colIdx);\n        while (scrolledPosition < getWidth() && (scrolledPosition + mDecoratedChildWidth) > 0) {\n            int adapterIdx = getAdapterIdx(colIdx, rowIdx);\n            logDebug(\"col=\" + colIdx + \" row=\" + rowIdx + \" adapterIdx=\" + adapterIdx + \" scrolledPosition=\" + scrolledPosition);\n            View child = layoutAdapterPos(recycler, adapterIdx, scrolledPosition, topPos);\n            if (child == null) {\n                logDebug(\"null view in\" +\n                    \" col=\" + colIdx +\n                    \" row=\" + rowIdx);\n                if (rowIdx == 0)\n                    break;\n            }\n            rowIdx += 1;\n            if (rowIdx >= mRowCount) {\n                rowIdx = 0;\n                colIdx += 1;\n                scrolledPosition += nextLeftPosDelta;\n            }\n            topPos = getRowPosition(rowIdx);\n        }\n        clearViewCache(recycler);\n    }\n\n    /**\n     * Find adapter index of the first visible item on row 0.\n     * It will be the left-most if (mRightToLeft == false) and the right-most otherwise.\n     *\n     * @return adapter index of the first visible view\n     */\n    private int findFirstVisibleAdapterPosition() {\n        final int colIdx;\n        if (mRightToLeft)\n            colIdx = mScrollOffsetHorizontal / mDecoratedChildWidth;\n        else\n            colIdx = -mScrollOffsetHorizontal / mDecoratedChildWidth;\n        return getAdapterIdx(colIdx, 0);\n    }\n\n    /**\n     * Layout view from cache or recycler\n     *\n     * @param recycler   the recycler\n     * @param adapterPos adapter index\n     * @param leftPos    layout position X\n     * @param topPos     layout position Y\n     * @return child view\n     */\n    private View layoutAdapterPos(RecyclerView.Recycler recycler, int adapterPos, int leftPos, int topPos) {\n        if (adapterPos < 0 || adapterPos >= getItemCount())\n            return null;\n        View child = mViewCache.get(adapterPos);\n        if (child == null) {\n            /*\n             * The Recycler will give us either a newly constructed view or a recycled view it has on-hand.\n             * In either case, the view will already be fully bound to the data by the adapter for us.\n             */\n            child = recycler.getViewForPosition(adapterPos);\n            addView(child);\n            /*\n             * It is prudent to measure/layout each new view we receive from the Recycler.\n             * We don't have to do this for views we are just re-arranging.\n             */\n            measureChildWithMargins(child, getHorizontalSpace() - mDecoratedChildWidth, getVerticalSpace() - mDecoratedChildHeight);\n            final RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();\n            final int left = leftPos + lp.leftMargin;\n            final int top = topPos + lp.topMargin;\n            final int measuredWidth = child.getMeasuredWidth();\n            final int measuredHeight = child.getMeasuredHeight();\n            //TODO: if measured size is 0 (probably MeasureSpec.UNSPECIFIED) then fix it and measure again.\n\n            layoutDecorated(child, left, top,\n                left + measuredWidth,\n                top + measuredHeight);\n\n            logDebug(\"child #\" + indexOfChild(child) + \" pos=\" + adapterPos +\n                \" (\" + child.getLeft() + \" \" + child.getTop() + \" \" + child.getRight() + \" \" + child.getBottom() + \")\" +\n                \" left=\" + leftPos +\n                \" top=\" + topPos +\n                \" measured=\" + measuredWidth + \"x\" + measuredHeight +\n                \" \" + getDebugInfo(child) + \" \" + getDebugName(child));\n        } else {\n            attachView(child);\n            logDebug(\"cache #\" + indexOfChild(child) + \" pos=\" + adapterPos +\n                \" (\" + child.getLeft() + \" \" + child.getTop() + \" \" + child.getRight() + \" \" + child.getBottom() + \")\" +\n                \" left=\" + leftPos +\n                \" top=\" + topPos +\n                \" \" + getDebugInfo(child) + \" \" + getDebugName(child));\n            mViewCache.remove(adapterPos);\n        }\n        return child;\n    }\n\n    /**\n     * Cache all views by their existing position\n     */\n    private void cacheChildren() {\n        mViewCache.clear();\n\n        int childCount = getChildCount();\n        mViewCache.ensureCapacity(childCount);\n\n        for (int i = 0; i < childCount; i++) {\n            View child = getChildAt(i);\n            if (child == null)\n                throw new IllegalStateException(\"null child when count=\" + getChildCount() + \" and idx=\" + i);\n            int position = adapterPosition(child);\n            mViewCache.put(position, child);\n            logDebug(\"info #\" + i + \" pos=\" + position + \" \" + getDebugInfo(child) + \" \" + getDebugName(child));\n        }\n    }\n\n    private void detachCachedChildren(RecyclerView.Recycler recycler) {\n        for (int i = 0; i < mViewCache.size(); i++) {\n            View child = mViewCache.valueAt(i);\n            // When an update is in order, scrap the view and let the recycler rebind it\n            if (viewNeedsUpdate(child)) {\n                detachAndScrapView(child, recycler);\n                mViewCache.removeAt(i--);\n            } else {\n                detachView(child);\n            }\n        }\n    }\n\n    /**\n     * We ask the Recycler to scrap and store any views that we did not re-attach.\n     * These are views that are not currently necessary because they are no longer visible.\n     */\n    private void clearViewCache(RecyclerView.Recycler recycler) {\n        for (int i = 0; i < mViewCache.size(); i++) {\n            final View removingView = mViewCache.valueAt(i);\n            logDebug(\"recycleView pos=\" + mViewCache.keyAt(i) + \" \" + getDebugName(removingView));\n            recycler.recycleView(removingView);\n        }\n        mViewCache.clear();\n    }\n\n    private int indexOfChild(View child) {\n        for (int idx = getChildCount() - 1; idx >= 0; idx -= 1) {\n            if (getChildAt(idx) == child)\n                return idx;\n        }\n        return -1;\n    }\n\n    @Override\n    public boolean canScrollHorizontally() {\n        if (getItemCount() > (mColumnCount * mRowCount))\n            return true;\n        if (mScrollOffsetHorizontal != 0)\n            return true;\n        if (getChildCount() > 0) {\n            // Allow scrolling if child views are outside visible range\n            if (getDecoratedLeft(getLeftChildView()) < getPaddingLeft())\n                return true;\n            return getDecoratedRight(getRightChildView()) > (getPaddingLeft() + getHorizontalSpace());\n        }\n        return false;\n    }\n\n    @Override\n    public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {\n        if (getChildCount() == 0) {\n            return 0;\n        }\n\n        final int amount;\n        // compute amount of scroll without going beyond the bound\n        if (dx < 0) { // finger is moving from left to right\n            if (mRightToLeft) {\n                int lastPageIdx = getPageIdx(getItemCount() - 1);\n                int pageWidth = getHorizontalSpace();\n                int maxScroll = lastPageIdx * pageWidth;\n                logDebug(\"dx=\" + dx +\n                    \" lastPageIdx=\" + lastPageIdx +\n                    \" pageWidth=\" + pageWidth +\n                    \" maxScroll=\" + maxScroll +\n                    \" mScroll=\" + mScrollOffsetHorizontal);\n                amount = Math.max(dx, mScrollOffsetHorizontal - maxScroll);\n            } else {\n                logDebug(\"dx=\" + dx +\n                    \" mScroll=\" + mScrollOffsetHorizontal);\n                amount = Math.max(dx, mScrollOffsetHorizontal);\n            }\n        } else if (dx > 0) { // finger is moving from right to left\n            if (mRightToLeft) {\n                logDebug(\"dx=\" + dx +\n                    \" mScroll=\" + mScrollOffsetHorizontal);\n                amount = Math.min(dx, mScrollOffsetHorizontal);\n            } else {\n                int lastPageIdx = getPageIdx(getItemCount() - 1);\n                int pageWidth = getHorizontalSpace();\n                int maxScroll = lastPageIdx * -pageWidth;\n                logDebug(\"dx=\" + dx +\n                    \" lastPageIdx=\" + lastPageIdx +\n                    \" pageWidth=\" + pageWidth +\n                    \" maxScroll=\" + maxScroll +\n                    \" mScroll=\" + mScrollOffsetHorizontal);\n                amount = Math.min(dx, mScrollOffsetHorizontal - maxScroll);\n            }\n        } else {\n            amount = dx;\n        }\n\n        if (dx != amount)\n            logDebug(\"dx=\" + dx + \" amount=\" + amount);\n\n        if (amount == 0 || (dx < 0 && amount > 0) || (dx > 0 && amount < 0))\n            return 0;\n\n        // scroll children\n        offsetChildrenHorizontal(-amount);\n        mScrollOffsetHorizontal -= amount;\n\n        // check if we need to layout after the scroll\n        if (checkVisibilityAfterScroll())\n            layoutChildren(recycler);\n\n        /*\n         * Return value determines if a boundary has been reached\n         * (for edge effects and flings). If returned value does not\n         * match original delta (passed in), RecyclerView will draw\n         * an edge effect.\n         */\n        return amount;\n    }\n\n    /**\n     * Check if child views became invisible after scroll or if we need to layout more views\n     *\n     * @return true if we need la re-layout\n     */\n    private boolean checkVisibilityAfterScroll() {\n        View leftChild = getLeftChildView();\n        View rightChild = getRightChildView();\n        int left = getDecoratedLeft(leftChild);\n        int right = getDecoratedRight(rightChild);\n        // check if we should remove views\n        if ((left + mDecoratedChildWidth) < 0 || (right - mDecoratedChildWidth) > getWidth())\n            return true;\n        // check if we should add views\n        return left > 0 || right < getWidth();\n    }\n\n    /**\n     * Return left-most child view from row 0 (may not be visible on screen)\n     *\n     * @return child view\n     */\n    @NonNull\n    private View getLeftChildView() {\n        int leftChildIdx = mRightToLeft ? (getChildCount() - 1) : 0;\n        View child = getChildAt(leftChildIdx);\n        if (child == null)\n            throw new IllegalStateException(\"null child when count=\" + getChildCount() + \" and leftChildIdx=\" + leftChildIdx);\n        while (true) {\n            int idx = adapterPosition(child);\n            int row = getRowIdx(idx);\n            if (row == 0)\n                break;\n            leftChildIdx += mRightToLeft ? -1 : 1;\n            if (leftChildIdx < 0 || leftChildIdx >= getChildCount())\n                break;\n            child = getChildAt(leftChildIdx);\n            if (child == null)\n                throw new IllegalStateException(\"null child when count=\" + getChildCount() + \" and leftChildIdx=\" + leftChildIdx);\n        }\n        return child;\n    }\n\n    /**\n     * Return right-most child view from row 0 (may not be visible on screen)\n     *\n     * @return child view\n     */\n    @NonNull\n    private View getRightChildView() {\n        int rightChildIdx = mRightToLeft ? 0 : (getChildCount() - 1);\n        View child = getChildAt(rightChildIdx);\n        if (child == null)\n            throw new IllegalStateException(\"null child when count=\" + getChildCount() + \" and rightChildIdx=\" + rightChildIdx);\n        while (true) {\n            int idx = adapterPosition(child);\n            int row = getRowIdx(idx);\n            if (row == 0)\n                break;\n            rightChildIdx -= mRightToLeft ? -1 : 1;\n            if (rightChildIdx < 0 || rightChildIdx >= getChildCount())\n                break;\n            child = getChildAt(rightChildIdx);\n            if (child == null)\n                throw new IllegalStateException(\"null child when count=\" + getChildCount() + \" and rightChildIdx=\" + rightChildIdx);\n        }\n        return child;\n    }\n\n    /**\n     * 0 for leftmost scroll and page count for rightmost\n     *\n     * @return page and scroll\n     */\n    public float getPageScroll() {\n        if (getChildCount() == 0)\n            return 0f;\n        View view = getChildAt(0);\n        if (view == null)\n            return 0f;\n        int col = getColumnIdx(adapterPosition(view));\n        int pageWidth = getHorizontalSpace();\n        float scroll = (getDecoratedLeft(view) - getColumnPosition(col)) / (float) pageWidth;\n        return mRightToLeft ? scroll : -scroll;\n    }\n\n    public int getPageAdapterPosition(int page) {\n        int col = page * mColumnCount;\n        return getAdapterIdx(col, 0);\n    }\n\n    @Nullable\n    @Override\n    public PointF computeScrollVectorForPosition(int targetPosition) {\n        if (getChildCount() == 0)\n            return null;\n        View view = getChildAt(0);\n        if (view == null)\n            return null;\n        int pos = adapterPosition(view);\n        return new PointF(targetPosition - pos, 0f);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/quicklist/DragAndDropInfo.java",
    "content": "package rocks.tbog.tblauncher.quicklist;\n\nimport android.view.View;\n\nclass DragAndDropInfo {\n    // the initial view that started the drag\n    public View draggedView;\n    // child index from ViewGroup\n    public int overChildIdx;\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/quicklist/EditQuickList.java",
    "content": "package rocks.tbog.tblauncher.quicklist;\n\nimport android.content.ClipData;\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.os.Build;\nimport android.util.Log;\nimport android.view.DragEvent;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.AdapterView;\nimport android.widget.GridView;\nimport android.widget.Toast;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.preference.PreferenceManager;\nimport androidx.recyclerview.widget.RecyclerView;\nimport androidx.viewpager.widget.ViewPager;\n\nimport com.google.android.material.tabs.TabLayout;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\nimport rocks.tbog.tblauncher.CustomizeUI;\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.dataprovider.ActionProvider;\nimport rocks.tbog.tblauncher.dataprovider.FilterProvider;\nimport rocks.tbog.tblauncher.dataprovider.TagsProvider;\nimport rocks.tbog.tblauncher.db.ModRecord;\nimport rocks.tbog.tblauncher.entry.EntryItem;\nimport rocks.tbog.tblauncher.entry.TagEntry;\nimport rocks.tbog.tblauncher.handler.DataHandler;\nimport rocks.tbog.tblauncher.result.EntryAdapter;\nimport rocks.tbog.tblauncher.result.LoadDataForAdapter;\nimport rocks.tbog.tblauncher.ui.RecyclerList;\nimport rocks.tbog.tblauncher.utils.DebugInfo;\n\npublic class EditQuickList {\n\n    private static final String TAG = \"EQL\";\n    private RecycleAdapter mAdapter;\n    ViewPager mViewPager;\n    private final AdapterView.OnItemClickListener mAddToQuickList = (parent, view, pos, id) -> {\n        Object item = parent.getAdapter().getItem(pos);\n        if (item instanceof EntryItem) {\n            mAdapter.addItem((EntryItem) item);\n        }\n    };\n\n    public void applyChanges(@NonNull Context context) {\n        final int itemCount = mAdapter.getItemCount();\n        ArrayList<String> idList = new ArrayList<>(itemCount);\n        for (int i = 0; i < itemCount; i++) {\n            EntryItem entry = mAdapter.getItem(i);\n            if (entry == null) {\n                Log.e(TAG, \"Adapter item #\" + i + \" of \" + itemCount + \" is null\");\n                continue;\n            }\n            idList.add(entry.id);\n        }\n        TBApplication.dataHandler(context).setQuickList(idList);\n    }\n\n    private void addFilters(@NonNull LayoutInflater inflater, @NonNull ArrayList<ViewPagerAdapter.PageInfo> pages) {\n        GridView gridView = (GridView) inflater.inflate(R.layout.quick_list_editor_page, mViewPager, false);\n        pages.add(new ViewPagerAdapter.PageInfo(inflater.getContext().getString(R.string.edit_quick_list_tab_filters), gridView));\n\n        ArrayList<EntryItem> list = new ArrayList<>();\n        EntryAdapter adapter = new EntryAdapter(list);\n        gridView.setAdapter(adapter);\n        new LoadDataForAdapter(adapter, () -> {\n            Context ctx = gridView.getContext();\n            ArrayList<EntryItem> data = new ArrayList<>();\n            {\n                FilterProvider provider = TBApplication.dataHandler(ctx).getFilterProvider();\n                if (provider != null) {\n                    List<? extends EntryItem> entryItems = provider.getPojos();\n                    if (entryItems != null)\n                        data.addAll(entryItems);\n                }\n            }\n            return data;\n        }).execute();\n        gridView.setOnItemClickListener(mAddToQuickList);\n    }\n\n    private void addActions(@NonNull LayoutInflater inflater, @NonNull ArrayList<ViewPagerAdapter.PageInfo> pages) {\n        GridView gridView = (GridView) inflater.inflate(R.layout.quick_list_editor_page, mViewPager, false);\n        pages.add(new ViewPagerAdapter.PageInfo(inflater.getContext().getString(R.string.edit_quick_list_tab_actions), gridView));\n\n        ArrayList<EntryItem> list = new ArrayList<>();\n        EntryAdapter adapter = new EntryAdapter(list);\n        gridView.setAdapter(adapter);\n        new LoadDataForAdapter(adapter, () -> {\n            Context ctx = gridView.getContext();\n            ArrayList<EntryItem> data = new ArrayList<>();\n            {\n                ActionProvider provider = TBApplication.dataHandler(ctx).getActionProvider();\n                if (provider != null) {\n                    List<? extends EntryItem> entryItems = provider.getPojos();\n                    if (entryItems != null)\n                        data.addAll(entryItems);\n                }\n            }\n            return data;\n        }).execute();\n        gridView.setOnItemClickListener(mAddToQuickList);\n    }\n\n    private void addTags(@NonNull LayoutInflater inflater, @NonNull ArrayList<ViewPagerAdapter.PageInfo> pages) {\n        GridView gridView = (GridView) inflater.inflate(R.layout.quick_list_editor_page, mViewPager, false);\n        pages.add(new ViewPagerAdapter.PageInfo(inflater.getContext().getString(R.string.edit_quick_list_tab_tags), gridView));\n\n        ArrayList<EntryItem> list = new ArrayList<>();\n        EntryAdapter adapter = new EntryAdapter(list);\n        gridView.setAdapter(adapter);\n        new LoadDataForAdapter(adapter, () -> {\n            Context ctx = gridView.getContext();\n            ArrayList<EntryItem> data = new ArrayList<>();\n            {\n                TagsProvider tagsProvider = TBApplication.dataHandler(ctx).getTagsProvider();\n                if (tagsProvider != null) {\n                    List<String> tagNameList = new ArrayList<>(TBApplication.tagsHandler(ctx).getValidTags());\n                    Collections.sort(tagNameList);\n                    for (String tagName : tagNameList) {\n                        TagEntry tagEntry = tagsProvider.getTagEntry(tagName);\n                        data.add(tagEntry);\n                    }\n                }\n            }\n            return data;\n        }).execute();\n        gridView.setOnItemClickListener(mAddToQuickList);\n    }\n\n    private void addFavorites(@NonNull LayoutInflater inflater, @NonNull ArrayList<ViewPagerAdapter.PageInfo> pages) {\n        GridView gridView = (GridView) inflater.inflate(R.layout.quick_list_editor_page, mViewPager, false);\n        pages.add(new ViewPagerAdapter.PageInfo(inflater.getContext().getString(R.string.edit_quick_list_tab_favorites), gridView));\n\n        ArrayList<EntryItem> list = new ArrayList<>();\n        EntryAdapter adapter = new EntryAdapter(list);\n        gridView.setAdapter(adapter);\n        new LoadDataForAdapter(adapter, () -> {\n            Context ctx = gridView.getContext();\n            DataHandler dataHandler = TBApplication.dataHandler(ctx);\n            List<ModRecord> modRecords = dataHandler.getMods();\n            ArrayList<EntryItem> data = new ArrayList<>(modRecords.size());\n            for (ModRecord fav : modRecords) {\n                EntryItem entry = dataHandler.getPojo(fav.record);\n                if (entry != null)\n                    data.add(entry);\n            }\n            return data;\n        }).execute();\n        gridView.setOnItemClickListener(mAddToQuickList);\n    }\n\n    public void bindView(@NonNull View view) {\n        final Context context = view.getContext();\n\n        mAdapter = new RecycleAdapter(context, new ArrayList<>());\n        // the correct grid size will be set later\n        DockRecycleLayoutManager layoutManager = new DockRecycleLayoutManager(4, 1);\n\n        // keep the preview the same as the actual thing\n        RecyclerList quickListPreview = view.findViewById(R.id.dockPreview);\n        quickListPreview.setAdapter(mAdapter);\n        quickListPreview.setHasFixedSize(true);\n        // the default item animator will mess up when drag and dropping\n        quickListPreview.setItemAnimator(null);\n        quickListPreview.setLayoutManager(layoutManager);\n        // don't snap to pages or else we can't move items between them\n        //quickListPreview.addOnScrollListener(new PagedScrollListener());\n        quickListPreview.setOnDragListener(EditQuickList::previewDragListener);\n        quickListPreview.requestLayout();\n\n        // when user clicks, remove the view and the list item\n        mAdapter.setOnClickListener((entry, v) -> mAdapter.removeItem(entry));\n        mAdapter.setOnLongClickListener(EditQuickList::previewStartDrag);\n\n        SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n        QuickList.applyUiPref(pref, quickListPreview);\n        if (!QuickList.populateList(context, mAdapter)) {\n            TBApplication.behaviour(context).closeFragmentDialog();\n            Toast.makeText(context, \"Failed!\", Toast.LENGTH_SHORT).show();\n        }\n        //TODO: implement drag and drop for multiple rows\n        layoutManager.setRowCount(1);\n        //TODO: implement drag and drop for right to left layout\n        layoutManager.setRightToLeft(false);\n\n        mViewPager = view.findViewById(R.id.viewPager);\n        {\n            TabLayout tabLayout = mViewPager.findViewById(R.id.tabLayout);\n            tabLayout.setupWithViewPager(mViewPager);\n        }\n        {\n            ArrayList<ViewPagerAdapter.PageInfo> pages = new ArrayList<>(3);\n            LayoutInflater inflater = LayoutInflater.from(context);\n\n            // actions\n            addActions(inflater, pages);\n\n            // filters\n            addFilters(inflater, pages);\n\n            // tags\n            addTags(inflater, pages);\n\n            if (DebugInfo.enableFavorites(context)) {\n                // favorites\n                addFavorites(inflater, pages);\n            }\n\n            pages.trimToSize();\n            mViewPager.setAdapter(new ViewPagerAdapter(pages));\n\n            for (ViewPagerAdapter.PageInfo page : pages) {\n                CustomizeUI.setResultListPref(page.getView());\n            }\n        }\n    }\n\n    /**\n     * Start drag and drop action.\n     *\n     * @param entry the EntryItem we are moving\n     * @param v     The view we are dragging\n     * @return if the startDrag method completes successfully\n     */\n    private static boolean previewStartDrag(@NonNull EntryItem entry, @NonNull View v) {\n        final DragAndDropInfo dragDropInfo = new DragAndDropInfo();\n        int idx = ((ViewGroup) v.getParent()).indexOfChild(v);\n        dragDropInfo.overChildIdx = idx;\n        dragDropInfo.draggedView = v;\n        ClipData clipData = ClipData.newPlainText(Integer.toString(idx), entry.id);\n        View.DragShadowBuilder shadow = new View.DragShadowBuilder(v);\n        v.setVisibility(View.INVISIBLE);\n        return startDragAndDrop(v, clipData, shadow, dragDropInfo);\n    }\n\n    @SuppressWarnings(\"deprecation\")\n    private static boolean startDragAndDrop(@NonNull View v, ClipData clipData, View.DragShadowBuilder shadow, DragAndDropInfo dragDropInfo) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n            return v.startDragAndDrop(clipData, shadow, dragDropInfo, 0);\n        } else {\n            return v.startDrag(clipData, shadow, dragDropInfo, 0);\n        }\n    }\n\n    protected static void repositionViews(@NonNull ViewGroup quickList, int resetUntilIdx, int moveRightUntilIdx, int moveLeftUntilIdx) {\n        int idx = 0;\n        for (; idx < resetUntilIdx; idx += 1) {\n            View child = quickList.getChildAt(idx);\n            child.animate().translationX(0f);\n            //Log.d(TAG, \"child #\" + idx + \" reset pos\");\n        }\n        for (; idx < moveRightUntilIdx; idx += 1) {\n            View child = quickList.getChildAt(idx);\n            child.animate().translationX(child.getWidth());\n            //Log.d(TAG, \"child #\" + idx + \" move right\");\n        }\n        for (; idx < moveLeftUntilIdx; idx += 1) {\n            View child = quickList.getChildAt(idx);\n            child.animate().translationX(-child.getWidth());\n            //Log.d(TAG, \"child #\" + idx + \" move left\");\n        }\n        final int childCount = quickList.getChildCount();\n        for (; idx < childCount; idx += 1) {\n            View child = quickList.getChildAt(idx);\n            child.animate().translationX(0f);\n            //Log.d(TAG, \"child #\" + idx + \" reset pos\");\n        }\n    }\n\n    private static boolean previewDragListener(@Nullable View v, @NonNull DragEvent event) {\n        final DragAndDropInfo dragDropInfo;\n        final ViewGroup quickList;\n\n        Object local = event.getLocalState();\n        if (!(local instanceof DragAndDropInfo)) {\n            Log.d(TAG, \"drag outside activity?\");\n            return true;\n        }\n        if (!(v instanceof ViewGroup)) {\n            Log.d(TAG, \"only QuickList should listen\");\n            return true;\n        }\n\n        dragDropInfo = (DragAndDropInfo) local;\n        quickList = (ViewGroup) v;\n\n        switch (event.getAction()) {\n            case DragEvent.ACTION_DRAG_STARTED:\n            case DragEvent.ACTION_DRAG_ENTERED:\n            case DragEvent.ACTION_DROP:\n                return true;\n            case DragEvent.ACTION_DRAG_LOCATION: {\n                final float x = event.getX();\n                // find new location index\n                int location = 0;\n                final int childCount = quickList.getChildCount();\n                for (int idx = 0; idx < childCount; idx += 1) {\n                    View child = quickList.getChildAt(idx);\n                    int left = child.getLeft();\n                    //Log.d(TAG, \"child #\" + idx + \" left = \" + left);\n                    if (left > x)\n                        break;\n                    location = idx;\n                }\n\n                // check if we already processed this location\n                if (dragDropInfo.overChildIdx == location)\n                    return true;\n\n                final int emptyLocation = quickList.indexOfChild(dragDropInfo.draggedView);\n                if (location < emptyLocation) {\n                    repositionViews(quickList, location, emptyLocation, 0);\n                } else {\n                    repositionViews(quickList, emptyLocation + 1, 0, location + 1);\n                }\n\n                dragDropInfo.overChildIdx = location;\n//                Log.d(TAG, \"location = \" + location);\n                return true;\n            }\n            case DragEvent.ACTION_DRAG_EXITED:\n                // if dragging outside, reset locations\n                dragDropInfo.overChildIdx = quickList.indexOfChild(dragDropInfo.draggedView);\n                repositionViews(quickList, dragDropInfo.overChildIdx, 0, 0);\n                return true;\n            case DragEvent.ACTION_DRAG_ENDED:\n            default: {\n                //Log.d(TAG, \"drag ended\");\n                final int childCount = quickList.getChildCount();\n                for (int idx = 0; idx < childCount; idx += 1) {\n                    View child = quickList.getChildAt(idx);\n                    child.animate().cancel();\n                    child.setTranslationX(0f);\n                }\n                RecyclerView.Adapter<?> adapter = quickList instanceof RecyclerList ? ((RecyclerList) quickList).getAdapter() : null;\n                if (adapter instanceof RecycleAdapter) {\n                    int initialPosition = ((RecyclerView.LayoutParams) dragDropInfo.draggedView.getLayoutParams()).getViewAdapterPosition();\n                    View overChild = quickList.getChildAt(dragDropInfo.overChildIdx);\n                    int newPosition = ((RecyclerView.LayoutParams) overChild.getLayoutParams()).getViewAdapterPosition();\n                    if (initialPosition != newPosition) {\n                        ((RecycleAdapter) adapter).moveResult(initialPosition, newPosition);\n                        ((RecyclerList) quickList).scrollToPosition(newPosition);\n                    }\n                }\n                // check event.getResult() if dropping outside should matter\n                dragDropInfo.draggedView.setVisibility(View.VISIBLE);\n                return false;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/quicklist/EditQuickListDialog.java",
    "content": "package rocks.tbog.tblauncher.quicklist;\n\nimport android.content.Context;\nimport android.os.Bundle;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.ui.DialogFragment;\n\npublic class EditQuickListDialog extends DialogFragment<Void> {\n\n    private final EditQuickList mEditor = new EditQuickList();\n\n    @Override\n    protected int layoutRes() {\n        return R.layout.quick_list_editor;\n    }\n\n    @Nullable\n    @Override\n    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {\n        Context context = requireDialog().getContext();\n\n        setupDefaultButtonOkCancel(context);\n\n        // make sure we use the dialog context\n        LayoutInflater contextInflater = inflater.cloneInContext(context);\n        return super.onCreateView(contextInflater, container, savedInstanceState);\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        mEditor.bindView(view);\n\n        setOnPositiveClickListener((dialog, button) -> {\n            mEditor.applyChanges(requireContext());\n            onConfirm(null);\n        });\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/quicklist/PagedScrollListener.java",
    "content": "package rocks.tbog.tblauncher.quicklist;\n\nimport android.util.Log;\n\nimport androidx.annotation.NonNull;\nimport androidx.recyclerview.widget.RecyclerView;\n\npublic class PagedScrollListener extends RecyclerView.OnScrollListener {\n\n    private static final String TAG = \"PagedSL\";\n\n    public PagedScrollListener() {\n    }\n\n    @Override\n    public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {\n        if (newState == RecyclerView.SCROLL_STATE_IDLE) {\n            snapToPage(recyclerView);\n        }\n    }\n\n    public static void snapToPage(@NonNull RecyclerView recyclerView) {\n        if (recyclerView.getLayoutManager() instanceof DockRecycleLayoutManager) {\n            DockRecycleLayoutManager dockRecycleLayoutManager = (DockRecycleLayoutManager) recyclerView.getLayoutManager();\n            final float scroll = dockRecycleLayoutManager.getPageScroll();\n            final int page = (int) (scroll + .5f);\n            final float delta = scroll - page;\n            Log.d(TAG, \"snapToPage: pageScroll=\" + scroll + \" delta=\" + delta);\n            final int pos;\n            if (delta > .01f) {\n                pos = dockRecycleLayoutManager.getPageAdapterPosition(page);\n            } else if (delta < -0.01f) {\n                pos = dockRecycleLayoutManager.getPageAdapterPosition(page + 1) - 1;\n            } else {\n                return;\n            }\n            Log.d(TAG, \"smoothScrollToPosition \" + pos + \" page=\" + page);\n            recyclerView.smoothScrollToPosition(pos);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/quicklist/QuickList.java",
    "content": "package rocks.tbog.tblauncher.quicklist;\n\nimport android.animation.Animator;\nimport android.animation.AnimatorListenerAdapter;\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.content.res.Resources;\nimport android.graphics.drawable.Drawable;\nimport android.graphics.drawable.PaintDrawable;\nimport android.os.Build;\nimport android.util.Log;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.animation.AccelerateDecelerateInterpolator;\nimport android.view.animation.DecelerateInterpolator;\n\nimport androidx.annotation.IdRes;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.preference.PreferenceManager;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\nimport rocks.tbog.tblauncher.Behaviour;\nimport rocks.tbog.tblauncher.LauncherState;\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.TBLauncherActivity;\nimport rocks.tbog.tblauncher.dataprovider.IProvider;\nimport rocks.tbog.tblauncher.dataprovider.Provider;\nimport rocks.tbog.tblauncher.dataprovider.QuickListProvider;\nimport rocks.tbog.tblauncher.entry.ActionEntry;\nimport rocks.tbog.tblauncher.entry.EntryItem;\nimport rocks.tbog.tblauncher.entry.PlaceholderEntry;\nimport rocks.tbog.tblauncher.entry.StaticEntry;\nimport rocks.tbog.tblauncher.entry.TagEntry;\nimport rocks.tbog.tblauncher.handler.DataHandler;\nimport rocks.tbog.tblauncher.result.ResultHelper;\nimport rocks.tbog.tblauncher.searcher.Searcher;\nimport rocks.tbog.tblauncher.ui.ListPopup;\nimport rocks.tbog.tblauncher.ui.RecyclerList;\nimport rocks.tbog.tblauncher.ui.ViewStubPreview;\nimport rocks.tbog.tblauncher.utils.PrefCache;\nimport rocks.tbog.tblauncher.utils.UIColors;\nimport rocks.tbog.tblauncher.utils.UISizes;\n\n/**\n * Dock\n */\npublic class QuickList {\n    private static final String TAG = \"Dock\";\n    private static final int RETRY_COUNT = 3;\n\n    private TBLauncherActivity mTBLauncherActivity;\n    private boolean mQuickListEnabled = false;\n    private boolean mOnlyForResults = false;\n    private boolean mListDirty = true;\n    private int mRetryCountdown;\n    private RecyclerList mQuickList;\n    private RecycleAdapter mAdapter;\n    //    private final ArrayList<EntryItem> mQuickListItems = new ArrayList<>(0);\n    private SharedPreferences mSharedPreferences = null;\n    private final Runnable runCleanList = new Runnable() {\n        @Override\n        public void run() {\n            if (mListDirty && TBApplication.state().isQuickListVisible()) {\n                QuickList.this.populateList();\n                DataHandler dataHandler = TBApplication.dataHandler(mQuickList.getContext());\n                if (mListDirty && dataHandler.fullLoadOverSent()) {\n                    if (--mRetryCountdown <= 0) {\n                        Log.w(TAG, \"Can't load all entries\");\n                        return;\n                    }\n                }\n                dataHandler.runAfterLoadOver(() -> {\n                    if (mListDirty)\n                        mQuickList.postDelayed(this, 500);\n                });\n            }\n        }\n    };\n    // bAdapterEmpty is true when no search results are displayed\n    private boolean bAdapterEmpty = true;\n    // is any filter activated?\n    private boolean bFilterOn = false;\n    // is any action activated?\n    private boolean bActionOn = false;\n    // last filter scheme, used for better toggle behaviour\n    private String mLastSelection = null;\n    private String mLastAction = null;\n\n    private static int cornerRadius(@NonNull Context ctx, @NonNull SharedPreferences pref) {\n        Resources resources = ctx.getResources();\n        final int defaultCorner = resources.getInteger(R.integer.default_corner_radius);\n        final int cornerRadius = pref.getInt(\"quick-list-radius\", defaultCorner);\n        return UISizes.dp2px(ctx, cornerRadius);\n    }\n\n    public static void applyUiPref(@NonNull SharedPreferences pref, View quickList) {\n        Context ctx = quickList.getContext();\n        Resources resources = quickList.getResources();\n        // size\n        int barHeight = pref.getInt(\"quick-list-height\", 0);\n        if (barHeight <= 1)\n            barHeight = resources.getInteger(R.integer.default_dock_height);\n        barHeight = UISizes.dp2px(ctx, barHeight);\n\n        setGridSize(quickList);\n        setLayoutDirection(quickList, pref.getBoolean(\"quick-list-rtl\", false));\n\n        if (quickList.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {\n            ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) quickList.getLayoutParams();\n            // set layout height\n            params.height = barHeight;\n            int hMargin = UISizes.getQuickListMarginHorizontal(ctx);\n            int vMargin = UISizes.getQuickListMarginVertical(ctx);\n            params.setMargins(hMargin, vMargin, hMargin, vMargin);\n            quickList.setLayoutParams(params);\n        } else {\n            throw new IllegalStateException(\"quickList has the wrong layout params\");\n        }\n\n        final int cornerRadius = cornerRadius(ctx, pref);\n        final int color = getBackgroundColor(pref);\n\n        // rounded drawable\n        if (cornerRadius > 0) {\n            final PaintDrawable drawable;\n            {\n                Drawable background = quickList.getBackground();\n                if (background instanceof PaintDrawable)\n                    drawable = (PaintDrawable) background;\n                else\n                    drawable = new PaintDrawable();\n            }\n            drawable.getPaint().setColor(color);\n            drawable.setCornerRadius(cornerRadius);\n            quickList.setBackground(drawable);\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n                // clip list content to rounded corners\n                quickList.setClipToOutline(true);\n            }\n        } else {\n            quickList.setBackgroundColor(color);\n        }\n    }\n\n    private static void setGridSize(View quickList) {\n        DockRecycleLayoutManager layoutManager = null;\n        if (quickList instanceof RecyclerList) {\n            RecyclerView.LayoutManager mgr = ((RecyclerList) quickList).getLayoutManager();\n            if (mgr instanceof DockRecycleLayoutManager)\n                layoutManager = (DockRecycleLayoutManager) mgr;\n        }\n        if (layoutManager == null)\n            return;\n        Context context = quickList.getContext();\n        SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n        int colCount = PrefCache.getDockColumnCount(pref);\n        int rowCount = PrefCache.getDockRowCount(pref);\n        layoutManager.setColumnCount(colCount);\n        layoutManager.setRowCount(rowCount);\n        quickList.requestLayout();\n    }\n\n    private static void setLayoutDirection(View quickList, boolean rightToLeft) {\n        DockRecycleLayoutManager layoutManager = null;\n        if (quickList instanceof RecyclerList) {\n            RecyclerView.LayoutManager mgr = ((RecyclerList) quickList).getLayoutManager();\n            if (mgr instanceof DockRecycleLayoutManager)\n                layoutManager = (DockRecycleLayoutManager) mgr;\n        }\n        if (layoutManager == null)\n            return;\n        layoutManager.setRightToLeft(rightToLeft);\n    }\n\n    public static int getBackgroundColor(SharedPreferences pref) {\n        return UIColors.getColor(pref, \"quick-list-argb\");\n    }\n\n    public static void onClick(final EntryItem entry, View v) {\n        if (entry instanceof StaticEntry) {\n            entry.doLaunch(v, EntryItem.LAUNCHED_FROM_QUICK_LIST);\n        } else {\n            ResultHelper.launch(v, entry);\n        }\n    }\n\n    public static boolean onLongClick(final EntryItem entry, View v) {\n        ListPopup menu = entry.getPopupMenu(v, EntryItem.LAUNCHED_FROM_QUICK_LIST);\n\n        // show menu only if it contains elements\n        if (!menu.getAdapter().isEmpty()) {\n            TBApplication.getApplication(v.getContext()).registerPopup(menu);\n            menu.show(v);\n            return true;\n        }\n\n        return false;\n    }\n\n    public Context getContext() {\n        return mTBLauncherActivity;\n    }\n\n    public void onCreateActivity(TBLauncherActivity tbLauncherActivity) {\n        mTBLauncherActivity = tbLauncherActivity;\n        mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(mTBLauncherActivity);\n\n        inflateQuickListView();\n        mAdapter = new RecycleAdapter(getContext(), new ArrayList<>());\n        mAdapter.setOnClickListener(QuickList::onClick);\n        mAdapter.setOnLongClickListener(QuickList::onLongClick);\n\n        mQuickList.setHasFixedSize(true);\n        mQuickList.setAdapter(mAdapter);\n\n        mQuickList.setLayoutManager(new DockRecycleLayoutManager(8, 1));\n        mQuickList.addOnScrollListener(new PagedScrollListener());\n\n        reload();\n    }\n\n    public void reload() {\n        mRetryCountdown = RETRY_COUNT;\n        mListDirty = true;\n        if (mQuickList == null)\n            return;\n        mQuickList.removeCallbacks(runCleanList);\n        mQuickList.postDelayed(runCleanList, 100);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    private <T extends View> T inflateViewStub(@IdRes int id) {\n        View stub = mTBLauncherActivity.findViewById(id);\n        return (T) ViewStubPreview.inflateStub(stub);\n    }\n\n    private void inflateQuickListView() {\n        QuickListPosition position = PrefCache.getDockPosition(mSharedPreferences);\n        if (position == QuickListPosition.POSITION_UNDER_SEARCH_BAR && !PrefCache.searchBarAtBottom(mSharedPreferences))\n            position = QuickListPosition.POSITION_ABOVE_RESULTS;\n\n        if (position == QuickListPosition.POSITION_ABOVE_RESULTS)\n            mQuickList = inflateViewStub(R.id.dockAboveResults);\n        else if (position == QuickListPosition.POSITION_UNDER_RESULTS)\n            mQuickList = inflateViewStub(R.id.dockUnderResults);\n        else if (position == QuickListPosition.POSITION_UNDER_SEARCH_BAR)\n            mQuickList = inflateViewStub(R.id.dockAtBottom);\n        else\n            mQuickList = inflateViewStub(R.id.quickList);\n    }\n\n    private void populateList() {\n        if (isQuickListEnabled()) {\n            mListDirty = !populateList(getContext(), mAdapter);\n        } else {\n            mListDirty = false;\n            mAdapter.updateItems(Collections.emptyList());\n            mQuickList.setVisibility(View.GONE);\n        }\n    }\n\n    /**\n     * Set dock adapter content. May return false if the QuickListProvider is not loaded or it\n     * contains at least one PlaceholderEntry\n     *\n     * @param context for the DataHandler\n     * @param adapter to store the dock items\n     * @return true if the adapter was populated with valid entries\n     */\n    public static boolean populateList(Context context, RecycleAdapter adapter) {\n        if (adapter == null)\n            return false;\n\n        boolean validEntries = true;\n        final List<EntryItem> list;\n        QuickListProvider provider = TBApplication.dataHandler(context).getQuickListProvider();\n        if (provider != null && provider.isLoaded()) {\n            List<EntryItem> pojos = provider.getPojos();\n            list = pojos != null ? pojos : Collections.emptyList();\n        } else {\n            list = Collections.emptyList();\n            validEntries = false;\n        }\n\n        for (EntryItem entry : list) {\n            if (entry instanceof PlaceholderEntry) {\n                validEntries = false;\n                break;\n            }\n        }\n\n        adapter.updateItems(list);\n        return validEntries;\n    }\n\n    public void toggleSearch(@NonNull View v, @NonNull String query, @NonNull Class<? extends Searcher> searcherClass) {\n        Context ctx = v.getContext();\n        TBApplication app = TBApplication.getApplication(ctx);\n        final String actionId;\n        {\n            Object tag_actionId = v.getTag(R.id.tag_actionId);\n            actionId = tag_actionId instanceof String ? (String) tag_actionId : \"\";\n        }\n\n        // toggle off any filter\n        if (bFilterOn) {\n            animToggleOff();\n            bFilterOn = false;\n            app.behaviour().filterResults(null);\n        }\n\n        // show search content\n        {\n            // if the last action is not the current action, toggle on this action\n            if (!bActionOn || !isLastSelection(actionId)) {\n                app.behaviour().runSearcher(query, searcherClass);\n\n                // update toggle information\n                mLastSelection = actionId;\n                bActionOn = true;\n            } else {\n                // to toggle off the action, set bActionOn to false\n                app.behaviour().clearSearch();\n            }\n        }\n    }\n\n    public void toggleProvider(View v, IProvider<?> provider, @Nullable java.util.Comparator<? super EntryItem> comparator) {\n        Context ctx = v.getContext();\n        Behaviour behaviour = TBApplication.behaviour(ctx);\n        final String actionId;\n        {\n            Object tag_actionId = v.getTag(R.id.tag_actionId);\n            actionId = tag_actionId instanceof String ? (String) tag_actionId : \"\";\n        }\n\n        // toggle off any filter\n        if (bFilterOn) {\n            animToggleOff();\n            bFilterOn = false;\n            behaviour.filterResults(null);\n        }\n\n        // if the last action is not the current action, toggle on this action\n        if (!bActionOn || !isLastSelection(actionId)) {\n            behaviour.clearSearchText();\n            // show provider content or toggle off if nothing to show\n            if (behaviour.showProviderEntries(provider, comparator)) {\n                // update toggle information\n                mLastSelection = actionId;\n                bActionOn = true;\n            } else {\n                // to toggle off the action, set bActionOn to false\n                behaviour.clearSearch();\n            }\n        } else {\n            // to toggle off the action, set bActionOn to false\n            behaviour.clearSearch();\n        }\n    }\n\n    public void toggleFilter(View v, IProvider<?> provider, @NonNull String filterName) {\n        Context ctx = v.getContext();\n        TBApplication app = TBApplication.getApplication(ctx);\n        final String actionId;\n        {\n            Object tag_actionId = v.getTag(R.id.tag_actionId);\n            actionId = tag_actionId instanceof String ? (String) tag_actionId : \"\";\n        }\n\n        // if there is no search we need to filter, just show all matching entries\n        if (bAdapterEmpty) {\n            if (bFilterOn && provider != null && isLastSelection(actionId)) {\n                app.behaviour().clearAdapter();\n                bFilterOn = false;\n            } else {\n                if (app.behaviour().showProviderEntries(provider)) {\n                    mLastSelection = actionId;\n                    bFilterOn = true;\n                } else {\n                    bFilterOn = false;\n                }\n                List<? extends EntryItem> list;\n                list = provider != null ? provider.getPojos() : null;\n                if (list != null) {\n                    app.behaviour().updateAdapter(list, false);\n                    mLastSelection = actionId;\n                    bFilterOn = true;\n                } else {\n                    bFilterOn = false;\n                }\n            }\n            // updateAdapter will change `bAdapterEmpty` and we change it back because we want\n            // bAdapterEmpty to represent a search we need to filter\n            bAdapterEmpty = true;\n        } else if (bFilterOn && (provider == null || isLastSelection(actionId))) {\n            animToggleOff();\n            if (mLastAction != null) {\n                bActionOn = true;\n                mLastSelection = mLastAction;\n                mLastAction = null;\n            }\n            bFilterOn = false;\n            app.behaviour().filterResults(null);\n        } else if (provider != null) {\n            animToggleOff();\n            if (bActionOn)\n                mLastAction = mLastSelection;\n            bFilterOn = true;\n            mLastSelection = actionId;\n            app.behaviour().filterResults(filterName);\n        }\n\n        // show what is currently toggled\n        if (bFilterOn) {\n            animToggleOn(v);\n        }\n    }\n\n    public void toggleFilter(View v, @Nullable Provider<? extends EntryItem> provider) {\n        Object tag_filterText = v.getTag(R.id.tag_filterText);\n        String filterText = (tag_filterText instanceof String) ? (String) tag_filterText : \"\";\n        toggleFilter(v, provider, filterText);\n    }\n\n    private void animToggleOn(View v) {\n        v.setSelected(true);\n        v.setHovered(true);\n    }\n\n    private void animToggleOff() {\n        if (!bFilterOn)\n            return;\n        int n = mQuickList.getChildCount();\n        for (int i = 0; i < n; i += 1) {\n            View view = mQuickList.getChildAt(i);\n            if (mLastSelection == null || mLastSelection == view.getTag(R.id.tag_actionId)) {\n                view.setSelected(false);\n                view.setHovered(false);\n            }\n        }\n    }\n\n    private boolean isQuickListEnabled() {\n        if (mQuickListEnabled) {\n            if (TBApplication.state().getDesktop() == LauncherState.Desktop.SEARCH)\n                return PrefCache.modeSearchQuickListVisible(mSharedPreferences);\n            if (TBApplication.state().getDesktop() == LauncherState.Desktop.EMPTY)\n                return PrefCache.modeEmptyQuickListVisible(mSharedPreferences);\n            if (TBApplication.state().getDesktop() == LauncherState.Desktop.WIDGET)\n                return PrefCache.modeWidgetQuickListVisible(mSharedPreferences);\n        }\n        return mQuickListEnabled;\n    }\n\n    private boolean isOnlyForResults() {\n        if (TBApplication.state().getDesktop() == LauncherState.Desktop.SEARCH)\n            return mOnlyForResults;\n        return false;\n    }\n\n    public void updateVisibility() {\n        if (isQuickListEnabled()) {\n            if (isOnlyForResults()) {\n                if (TBApplication.state().isResultListVisible()) {\n                    show();\n                    return;\n                }\n            } else {\n                show();\n                return;\n            }\n        }\n        hideQuickList(false);\n    }\n\n    private void show() {\n        mQuickList.removeCallbacks(runCleanList);\n\n        if (isQuickListEnabled()) {\n            final SharedPreferences pref = mSharedPreferences;\n            if (pref.getBoolean(\"quick-list-animation\", true)) {\n                mQuickList.animate()\n                    .scaleY(1f)\n                    .setInterpolator(new AccelerateDecelerateInterpolator())\n                    .setListener(new AnimatorListenerAdapter() {\n                        @Override\n                        public void onAnimationStart(Animator animation) {\n                            TBApplication.state().setQuickList(LauncherState.AnimatedVisibility.ANIM_TO_VISIBLE);\n                        }\n\n                        @Override\n                        public void onAnimationEnd(Animator animation) {\n                            TBApplication.state().setQuickList(LauncherState.AnimatedVisibility.VISIBLE);\n                        }\n                    })\n                    .start();\n            } else {\n                mQuickList.animate().cancel();\n                mQuickList.setScaleY(1f);\n                TBApplication.state().setQuickList(LauncherState.AnimatedVisibility.VISIBLE);\n            }\n            mQuickList.setVisibility(View.VISIBLE);\n        }\n        // after state set, make sure the list is not dirty\n        runCleanList.run();\n    }\n\n    public void hideQuickList(boolean animate) {\n        if (isQuickListEnabled()) {\n            animToggleOff();\n            if (animate) {\n                mQuickList.animate()\n                    .scaleY(0f)\n                    .setInterpolator(new DecelerateInterpolator())\n                    .setListener(new AnimatorListenerAdapter() {\n                        @Override\n                        public void onAnimationStart(Animator animation) {\n                            TBApplication.state().setQuickList(LauncherState.AnimatedVisibility.ANIM_TO_HIDDEN);\n                        }\n\n                        @Override\n                        public void onAnimationEnd(Animator animation) {\n                            TBApplication.state().setQuickList(LauncherState.AnimatedVisibility.HIDDEN);\n                            mQuickList.setVisibility(View.GONE);\n                        }\n                    })\n                    .start();\n                mQuickList.setVisibility(View.VISIBLE);\n            } else {\n                TBApplication.state().setQuickList(LauncherState.AnimatedVisibility.HIDDEN);\n                mQuickList.setScaleY(0f);\n                mQuickList.setVisibility(View.GONE);\n            }\n        } else {\n            TBApplication.state().setQuickList(LauncherState.AnimatedVisibility.HIDDEN);\n            mQuickList.setVisibility(View.GONE);\n        }\n    }\n\n    public void onStart() {\n        final SharedPreferences pref = mSharedPreferences;\n        mQuickListEnabled = pref.getBoolean(\"quick-list-enabled\", true);\n        mOnlyForResults = pref.getBoolean(\"quick-list-only-for-results\", false);\n\n        applyUiPref(pref, mQuickList);\n    }\n\n    public void adapterCleared() {\n        animToggleOff();\n        bFilterOn = false;\n        bActionOn = false;\n        bAdapterEmpty = true;\n        if (isOnlyForResults())\n            hideQuickList(true);\n        mLastSelection = null;\n    }\n\n    public void adapterUpdated() {\n        if (isOnlyForResults())\n            show();\n        animToggleOff();\n        bFilterOn = false;\n        bActionOn = mLastSelection != null\n            && (mLastSelection.startsWith(ActionEntry.SCHEME)\n            || mLastSelection.startsWith(TagEntry.SCHEME));\n        bAdapterEmpty = false;\n    }\n\n    // ugly: check from where the entry was launched\n    public boolean isViewInList(View view) {\n        return mQuickList.indexOfChild(view) != -1;\n    }\n\n    public boolean isLastSelection(@NonNull String entryId) {\n        return entryId.equals(mLastSelection);\n    }\n\n    public enum QuickListPosition {\n        POSITION_ABOVE_RESULTS,\n        POSITION_UNDER_RESULTS,\n        POSITION_UNDER_SEARCH_BAR,\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/quicklist/RecycleAdapter.java",
    "content": "package rocks.tbog.tblauncher.quicklist;\n\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.graphics.drawable.Drawable;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport androidx.annotation.NonNull;\nimport androidx.preference.PreferenceManager;\n\nimport java.util.ArrayList;\n\nimport rocks.tbog.tblauncher.CustomizeUI;\nimport rocks.tbog.tblauncher.entry.EntryItem;\nimport rocks.tbog.tblauncher.entry.FilterEntry;\nimport rocks.tbog.tblauncher.result.RecycleAdapterBase;\nimport rocks.tbog.tblauncher.result.ResultHelper;\nimport rocks.tbog.tblauncher.utils.UIColors;\n\npublic class RecycleAdapter extends RecycleAdapterBase<RecycleAdapter.Holder> {\n\n    public RecycleAdapter(@NonNull Context context, @NonNull ArrayList<EntryItem> results) {\n        super(results);\n        setHasStableIds(true);\n        setGridLayout(context, false);\n    }\n\n    @NonNull\n    @Override\n    public Holder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {\n        final Context context = parent.getContext();\n        final int layoutRes = ResultHelper.getItemViewLayout(viewType);\n\n        LayoutInflater inflater = LayoutInflater.from(context);\n        View itemView = inflater.inflate(layoutRes, parent, false);\n\n        return new Holder(itemView);\n    }\n\n    public void setGridLayout(@NonNull Context context, boolean bGridLayout) {\n        final int oldFlags = mDrawFlags;\n        // get new flags\n        mDrawFlags = getDrawFlags(context);\n        mDrawFlags |= bGridLayout ? EntryItem.FLAG_DRAW_GRID : EntryItem.FLAG_DRAW_QUICK_LIST;\n        // refresh items if flags changed\n        if (oldFlags != mDrawFlags)\n            refresh();\n    }\n\n    public static int getDrawFlags(@NonNull Context context) {\n        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);\n        int drawFlags = EntryItem.FLAG_DRAW_NO_CACHE | EntryItem.FLAG_RELOAD;\n        if (prefs.getBoolean(\"quick-list-text-visible\", true))\n            drawFlags |= EntryItem.FLAG_DRAW_NAME;\n        if (prefs.getBoolean(\"quick-list-icons-visible\", true))\n            drawFlags |= EntryItem.FLAG_DRAW_ICON;\n        if (prefs.getBoolean(\"quick-list-show-badge\", true))\n            drawFlags |= EntryItem.FLAG_DRAW_ICON_BADGE;\n        if (UIColors.isColorLight(UIColors.getColor(prefs, \"quick-list-argb\"))) {\n            drawFlags |= EntryItem.FLAG_DRAW_WHITE_BG;\n        }\n        return drawFlags;\n    }\n\n    @Override\n    public void onBindViewHolder(@NonNull Holder holder, int position) {\n        super.onBindViewHolder(holder, position);\n\n        Context context = holder.itemView.getContext();\n        final int color;\n        final EntryItem entry = getItem(position);\n        if (entry instanceof FilterEntry)\n            color = UIColors.getQuickListToggleColor(context);\n        else\n            color = UIColors.getQuickListRipple(context);\n        Drawable selector = CustomizeUI.getSelectorDrawable(holder.itemView, color, true);\n        holder.itemView.setBackground(selector);\n    }\n\n    public void moveResult(int sourceIdx, int destIdx) {\n        notifyItemMoved(sourceIdx, destIdx);\n        EntryItem entryItem = entryList.remove(sourceIdx);\n        entryList.add(destIdx, entryItem);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/quicklist/ViewPagerAdapter.java",
    "content": "package rocks.tbog.tblauncher.quicklist;\n\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.viewpager.widget.PagerAdapter;\n\nimport java.util.ArrayList;\n\nclass ViewPagerAdapter extends PagerAdapter {\n    private final ArrayList<PageInfo> mPages;\n\n    ViewPagerAdapter(@NonNull ArrayList<PageInfo> pages) {\n        mPages = pages;\n    }\n\n    @Override\n    public int getCount() {\n        return mPages.size();\n    }\n\n    @Override\n    public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {\n        if (object instanceof PageInfo)\n            return ((PageInfo) object).view == view;\n        return false;\n    }\n\n    @Nullable\n    @Override\n    public CharSequence getPageTitle(int position) {\n        return mPages.get(position).title;\n    }\n\n    @NonNull\n    @Override\n    public Object instantiateItem(@NonNull ViewGroup container, int position) {\n        final PageInfo pageInfo = mPages.get(position);\n        container.addView(pageInfo.view);\n        return pageInfo;\n    }\n\n    @Override\n    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {\n        if (object instanceof PageInfo)\n            container.removeView(((PageInfo) object).view);\n        else\n            container.removeView(mPages.get(position).view);\n    }\n\n    public static class PageInfo {\n        private final String title;\n        private final View view;\n\n        public PageInfo(String title, View view) {\n            this.title = title;\n            this.view = view;\n        }\n\n        public View getView() {\n            return view;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/result/AsyncSetEntryDrawable.java",
    "content": "package rocks.tbog.tblauncher.result;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.graphics.drawable.Drawable;\nimport android.util.Log;\nimport android.widget.ImageView;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.UiThread;\nimport androidx.annotation.WorkerThread;\n\nimport java.lang.ref.WeakReference;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.WorkAsync.AsyncTask;\nimport rocks.tbog.tblauncher.WorkAsync.TaskRunner;\nimport rocks.tbog.tblauncher.entry.EntryItem;\nimport rocks.tbog.tblauncher.utils.Utilities;\n\npublic abstract class AsyncSetEntryDrawable<Entry extends EntryItem> extends AsyncTask<Void, Drawable> {\n    private static final String TAG = \"AyncSED\";\n    protected final String cacheId;\n    private final WeakReference<ImageView> weakImage;\n    protected int drawFlags;\n    protected Entry entryItem;\n\n    public AsyncSetEntryDrawable(@NonNull ImageView image, int drawFlags, @NonNull Entry entryItem) {\n        super();\n        cacheId = entryItem.getIconCacheId();\n\n        Object tag_cacheId = image.getTag(R.id.tag_cacheId);\n        Object tag_iconTask = image.getTag(R.id.tag_iconTask);\n\n        image.setTag(R.id.tag_cacheId, cacheId);\n        image.setTag(R.id.tag_iconTask, this);\n\n        boolean keepIcon = false;\n        if (tag_iconTask instanceof AsyncSetEntryDrawable) {\n            AsyncSetEntryDrawable<?> task = (AsyncSetEntryDrawable<?>) tag_iconTask;\n            task.cancel(false);\n            // if the old task was loading the same entry we can keep the icon while we refresh it\n            keepIcon = entryItem.equals(task.entryItem);\n        } else if (tag_cacheId instanceof String) {\n            // if the tag equals cacheId then we can keep the icon while we refresh it\n            keepIcon = tag_cacheId.equals(cacheId);\n        }\n        Log.i(TAG, \"start task=\" + Integer.toHexString(hashCode()) +\n            \" view=\" + Integer.toHexString(image.hashCode()) +\n            \" tag_iconTask=\" + (tag_iconTask != null ? Integer.toHexString(tag_iconTask.hashCode()) : \"null\") +\n            \" entry=`\" + entryItem.getName() + \"`\" +\n            \" keepIcon=\" + keepIcon +\n            \" tag_cacheId=\" + tag_cacheId +\n            \" cacheId=\" + cacheId);\n        if (!keepIcon) {\n            ResultViewHelper.setLoadingIcon(image);\n        }\n        this.weakImage = new WeakReference<>(image);\n        this.drawFlags = drawFlags;\n        this.entryItem = entryItem;\n    }\n\n    @Nullable\n    public ImageView getImageView() {\n        ImageView imageView = weakImage.get();\n        // make sure we have a valid activity\n        Activity act = Utilities.getActivity(imageView);\n        if (act == null)\n            return null;\n        return imageView;\n    }\n\n    @Override\n    protected Drawable doInBackground(Void param) {\n        ImageView image = getImageView();\n        if (isCancelled() || image == null) {\n            weakImage.clear();\n            return null;\n        }\n        Context ctx = image.getContext();\n        return getDrawable(ctx);\n    }\n\n    @WorkerThread\n    protected abstract Drawable getDrawable(Context context);\n\n    @UiThread\n    protected void setDrawable(ImageView image, Drawable drawable) {\n        image.setImageDrawable(drawable);\n        // async task finished, set icon task to null\n        image.setTag(R.id.tag_iconTask, null);\n        // start animation if it's possible\n        Utilities.startAnimatable(image);\n    }\n\n    @Override\n    protected void onPostExecute(Drawable drawable) {\n        ImageView image = getImageView();\n        if (image == null || drawable == null) {\n            Log.i(TAG, \"end task=\" + Integer.toHexString(hashCode()) +\n                \" view=\" + (image == null ? \"null\" : Integer.toHexString(image.hashCode())) +\n                \" drawable=\" + drawable +\n                \" cacheId=`\" + cacheId + \"`\");\n            weakImage.clear();\n            return;\n        }\n        Object tag_cacheId = image.getTag(R.id.tag_cacheId);\n        Object tag_iconTask = image.getTag(R.id.tag_iconTask);\n\n        if (cacheId != null && !Utilities.checkFlag(drawFlags, EntryItem.FLAG_DRAW_NO_CACHE))\n            TBApplication.drawableCache(image.getContext()).cacheDrawable(cacheId, drawable);\n\n        Log.i(TAG, \"end task=\" + Integer.toHexString(hashCode()) +\n            \" view=\" + Integer.toHexString(image.hashCode()) +\n            \" tag_iconTask=\" + (tag_iconTask != null ? Integer.toHexString(tag_iconTask.hashCode()) : \"null\") +\n            \" cacheId=`\" + cacheId + \"`\");\n        if (tag_iconTask instanceof AsyncSetEntryDrawable) {\n            AsyncSetEntryDrawable<?> task = (AsyncSetEntryDrawable<?>) tag_iconTask;\n            if (!entryItem.equals(task.entryItem)) {\n                Log.d(TAG, \"[task] skip reason: `\" + entryItem.getName() + \"` \\u2260 `\" + task.entryItem.getName() + \"`\");\n                weakImage.clear();\n                return;\n            }\n        } else {\n            Log.d(TAG, \"[task] skip reason: tag_iconTask=null entry=`\" + entryItem.getName() + \"`\");\n            weakImage.clear();\n            return;\n        }\n        // if the cacheId changed, skip\n        if (!tag_cacheId.equals(cacheId)) {\n            Log.d(TAG, \"[cacheId] skip reason: `\" + tag_cacheId + \"` \\u2260 `\" + cacheId + \"`\");\n            weakImage.clear();\n            return;\n        }\n        setDrawable(image, drawable);\n    }\n\n    @Override\n    protected void onCancelled() {\n        ImageView image = getImageView();\n        Log.i(TAG, \"cancelled task=\" + Integer.toHexString(hashCode()) + \" view=\" + (image != null ? Integer.toHexString(image.hashCode()) : \"null\"));\n    }\n\n    public void execute() {\n        TaskRunner.executeOnExecutor(ResultViewHelper.EXECUTOR_LOAD_ICON, this);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/result/CustomRecycleLayoutManager.java",
    "content": "package rocks.tbog.tblauncher.result;\n\nimport android.util.Log;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.TextView;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.recyclerview.widget.LinearSmoothScroller;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.utils.SparseArrayWrapper;\n\npublic class CustomRecycleLayoutManager extends RecyclerView.LayoutManager implements ReversibleAdapterRecyclerLayoutManager {\n\n    private static final String TAG = \"CRLM\";\n    private static final Boolean LOG_DEBUG = false;\n\n    private RecyclerView mRecyclerView = null;\n\n    /* First position visible at any point (adapter index) */\n    private int mFirstVisiblePosition;\n    /* Number of items to show on a row */\n    private int mColCount = 1;\n    /* Number of visible rows. It is computed when the size changes but may also increase when layout occurs */\n    private int mVisibleRows;\n    /* Consistent size applied to all child views */\n    private int mDecoratedChildWidth = 0;\n\n    private final ArrayList<RowInfo> mRowInfo = new ArrayList<>(0);\n\n    /* Used for starting the layout from the bottom. If true the first item is places at the bottom */\n    private boolean mBottomToTop;\n    /* Used for starting the layout from the right. If true the first item of each row is places at the right */\n    private boolean mRightToLeft;\n    /* Used for reversing adapter order */\n    private boolean mReverseAdapter;\n    /* Compute column count based on available space */\n    private boolean mAutoColumn = false;\n    /* When list height grows, keep bottom view at the bottom */\n    private boolean mAutoScrollBottom = true;\n    /* Keep track of the amount of over-scroll */\n    private int mOverScrollVertical = 0;\n    private OverScrollListener mOverScrollListener = null;\n\n    private boolean mRefreshViews = false;\n\n    // Reusable array. This should only be used used transiently and should not be used to retain any state over time.\n    private final SparseArrayWrapper<View> mViewCache = new SparseArrayWrapper<>();\n\n    public interface OverScrollListener {\n        void onOverScroll(RecyclerView recyclerView, int amount);\n    }\n\n    private static class RowInfo {\n        /* row alignment offset. When layout is bottom to top the first row has pos==-height */\n        int pos = 0;\n        /* height of this row */\n        int height = 0;\n    }\n\n    public CustomRecycleLayoutManager() {\n        this(false, false, false);\n    }\n\n    public CustomRecycleLayoutManager(boolean bottomToTop, boolean rightToLeft, boolean reverseAdapter) {\n        super();\n        mBottomToTop = bottomToTop;\n        mRightToLeft = rightToLeft;\n        mReverseAdapter = reverseAdapter;\n    }\n\n    public void setBottomToTop(boolean bottomToTop) {\n        assertNotInLayoutOrScroll(null);\n        if (mBottomToTop == bottomToTop) {\n            return;\n        }\n        mBottomToTop = bottomToTop;\n        requestLayout();\n    }\n\n    public boolean isBottomToTop() {\n        return mBottomToTop;\n    }\n\n    public void setRightToLeft(boolean rightToLeft) {\n        assertNotInLayoutOrScroll(null);\n        if (mRightToLeft == rightToLeft) {\n            return;\n        }\n        mRightToLeft = rightToLeft;\n        requestLayout();\n    }\n\n    public boolean isRightToLeft() {\n        return mRightToLeft;\n    }\n\n    @Override\n    public void setReverseAdapter(boolean reverseAdapter) {\n        assertNotInLayoutOrScroll(null);\n        if (mReverseAdapter == reverseAdapter) {\n            return;\n        }\n        mReverseAdapter = reverseAdapter;\n        requestLayout();\n    }\n\n    @Override\n    public boolean isReverseAdapter() {\n        return mReverseAdapter;\n    }\n\n    /**\n     * Set column parameters\n     *\n     * @param columnCount number of columns\n     * @param autoFill    if true the column count will be recomputed to fit the width\n     */\n    public void setColumns(int columnCount, boolean autoFill) {\n        assertNotInLayoutOrScroll(null);\n        mColCount = columnCount <= 0 ? 1 : columnCount;\n        mAutoColumn = autoFill;\n\n        // make sure we are using recycled views\n        mRefreshViews = true;\n\n        // reset row info\n        mRowInfo.clear();\n\n        requestLayout();\n    }\n\n    public int getColumnCount() {\n        return mColCount;\n    }\n\n    public void setAutoScrollBottom(boolean autoScrollBottom) {\n        mAutoScrollBottom = autoScrollBottom;\n    }\n\n    public void setOverScrollListener(@Nullable OverScrollListener listener) {\n        mOverScrollListener = listener;\n    }\n\n\n    @Override\n    public int computeVerticalScrollExtent(@NonNull RecyclerView.State state) {\n        return getVerticalSpace();\n    }\n\n    @Override\n    public int computeVerticalScrollOffset(@NonNull RecyclerView.State state) {\n        if (getChildCount() <= 1 || mRowInfo.isEmpty())\n            return 0;\n\n        int offset;\n        View child = getChildAt(0);\n        if (child != null) {\n            int rowPosition = getRowPosition(getRowIdx(adapterPosition(child)));\n            rowPosition -= getPaddingTop();\n\n            int childTop = getDecoratedTop(child);\n            childTop -= getPaddingTop();\n\n            offset = rowPosition - childTop;\n\n            if (mBottomToTop) {\n                // align to bottom\n                offset = -mRowInfo.get(mRowInfo.size() - 1).pos + offset - getVerticalSpace();\n            }\n        } else {\n            final View view = findBottomVisibleItemView();\n            final int topRow = getRowIdx(topAdapterItemIdx());\n            final int bottomRow = getRowIdx(adapterPosition(view));\n\n            final int topRowPos = mRowInfo.get(topRow).pos;\n            final int bottomRowPos = mRowInfo.get(bottomRow).pos;\n\n            int topRowsHeight = bottomRowPos - topRowPos;\n            topRowsHeight += getRowHeight(bottomRow);\n\n            offset = getPaddingTop() - getDecoratedBottom(view) + topRowsHeight;\n        }\n\n        return offset;\n    }\n\n    @Override\n    public int computeVerticalScrollRange(@NonNull RecyclerView.State state) {\n        if (!mRowInfo.isEmpty()) {\n            RowInfo rowInfo = mRowInfo.get(mRowInfo.size() - 1);\n            return mBottomToTop ? -rowInfo.pos : (rowInfo.pos + rowInfo.height);\n        }\n        return 0;\n    }\n\n    @Override\n    public void onAttachedToWindow(RecyclerView view) {\n        super.onAttachedToWindow(view);\n        mRecyclerView = view;\n    }\n\n    @Override\n    public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) {\n        mRecyclerView = null;\n        super.onDetachedFromWindow(view, recycler);\n    }\n\n    /*\n     * You must return true from this method if you want your\n     * LayoutManager to support anything beyond \"simple\" item\n     * animations. Enabling this causes onLayoutChildren() to\n     * be called twice on each animated change; once for a\n     * pre-layout, and again for the real layout.\n     */\n    @Override\n    public boolean supportsPredictiveItemAnimations() {\n        return false;\n    }\n\n    /*\n     * Called by RecyclerView when a view removal is triggered. This is called\n     * before onLayoutChildren() in pre-layout if the views removed are not visible. We\n     * use it in this case to inform pre-layout that a removal took place.\n     *\n     * This method is still called if the views removed were visible, but it will\n     * happen AFTER pre-layout.\n     */\n    @Override\n    public void onItemsRemoved(@NonNull RecyclerView recyclerView, int positionStart, int itemCount) {\n        logDebug(\"onItemsRemoved start=\" + positionStart + \" count=\" + itemCount);\n        mRefreshViews = true;\n        mRowInfo.clear();\n    }\n\n    /**\n     * Called in response to a call to {@link RecyclerView.Adapter#notifyDataSetChanged()} or\n     * {@link RecyclerView#swapAdapter(RecyclerView.Adapter, boolean)} ()} and signals that the the entire\n     * data set has changed.\n     *\n     * @param recyclerView not used\n     */\n    @Override\n    public void onItemsChanged(@NonNull RecyclerView recyclerView) {\n        mRefreshViews = true;\n        if (mFirstVisiblePosition > getItemCount()) {\n            int oldValue = mFirstVisiblePosition;\n            int newValue = mBottomToTop ? bottomAdapterItemIdx() : topAdapterItemIdx();\n            logDebug(\"onItemsChanged mFirstVisiblePosition changed from \" + oldValue + \" to \" + newValue);\n            mFirstVisiblePosition = newValue;\n        } else {\n            logDebug(\"onItemsChanged\");\n        }\n        if (mRowInfo.size() != computeRowCount(getItemCount()))\n            mRowInfo.clear();\n    }\n\n    @Override\n    public void onItemsUpdated(@NonNull RecyclerView recyclerView, int positionStart, int itemCount) {\n        logDebug(\"onItemsUpdated start=\" + positionStart + \" count=\" + itemCount);\n        mRefreshViews = true;\n    }\n\n    @Override\n    public void onAdapterChanged(@Nullable RecyclerView.Adapter oldAdapter, @Nullable RecyclerView.Adapter newAdapter) {\n        logDebug(\"onAdapterChanged\");\n        mRefreshViews = true;\n        mRowInfo.clear();\n    }\n\n    @Override\n    public void scrollToPosition(int adapterPos) {\n        if (adapterPos < 0 || adapterPos >= getItemCount()) {\n            Log.e(TAG, \"Cannot scroll to \" + adapterPos + \", item count \" + getItemCount());\n            return;\n        }\n        int oldValue = mFirstVisiblePosition;\n        int newValue = adapterPos / mColCount * mColCount;\n        if (oldValue != newValue) {\n            logDebug(\"scrollToPosition mFirstVisiblePosition changed from \" + oldValue + \" to \" + newValue);\n            mFirstVisiblePosition = newValue;\n        }\n    }\n\n    @Override\n    public RecyclerView.LayoutParams generateDefaultLayoutParams() {\n        return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);\n    }\n\n    private int getVerticalSpace() {\n        return getHeight() - getPaddingBottom() - getPaddingTop();\n    }\n\n    private int getHorizontalSpace() {\n        return getWidth() - getPaddingRight() - getPaddingLeft();\n    }\n\n    /*\n     * This method is your initial call from the framework. You will receive it when you\n     * need to start laying out the initial set of views. This method will not be called\n     * repeatedly, so don't rely on it to continually process changes during user\n     * interaction.\n     *\n     * This method will be called when the data set in the adapter changes, so it can be\n     * used to update a layout based on a new item count.\n     *\n     * If predictive animations are enabled, you will see this called twice. First, with\n     * state.isPreLayout() returning true to lay out children in their initial conditions.\n     * Then again to lay out children in their final locations.\n     *\n     * When scrolling, if a view has been added, this will be called after scroll*By\n     */\n    @Override\n    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {\n        //We have nothing to show for an empty data set but clear any existing views\n        if (getItemCount() == 0) {\n            detachAndScrapAttachedViews(recycler);\n            return;\n        }\n        if (getChildCount() == 0 && state.isPreLayout()) {\n            //Nothing to do during prelayout when empty\n            return;\n        }\n\n        if (getChildCount() == 0) {\n            // First or empty layout\n            mFirstVisiblePosition = mBottomToTop ? bottomAdapterItemIdx() : topAdapterItemIdx();\n        }\n\n        if (getChildCount() == 0 || mRowInfo.isEmpty()) {\n            final int decoratedChildHeight;\n            if (getChildCount() == 0) {\n                // Scrap measure one child\n                View scrap = recycler.getViewForPosition(mFirstVisiblePosition);\n                addView(scrap);\n                measureChildWithMargins(scrap, 0, 0);\n                mDecoratedChildWidth = getDecoratedMeasuredWidth(scrap);\n                decoratedChildHeight = getDecoratedMeasuredHeight(scrap);\n\n                detachAndScrapView(scrap, recycler);\n            } else {\n                View child = getChildAt(0);\n                if (child == null)\n                    throw new IllegalStateException(\"null child at idx 0 when childCount=\" + getChildCount());\n\n                mDecoratedChildWidth = getDecoratedMeasuredWidth(child);\n                decoratedChildHeight = getDecoratedMeasuredHeight(child);\n            }\n\n            updateRowInfo(decoratedChildHeight);\n        }\n\n        // Always update the visible row/column counts\n        updateSizing();\n\n        logDebug(\"onLayoutChildren\" +\n                 \" childCount=\" + getChildCount() +\n                 \" itemCount=\" + getItemCount() +\n                 \" columns=\" + mColCount +\n                 \" mDecoratedChildWidth=\" + mDecoratedChildWidth +\n                 (state.isPreLayout() ? \" preLayout\" : \"\") +\n                 (state.didStructureChange() ? \" structureChanged\" : \"\") +\n                 \" stateItemCount=\" + state.getItemCount());\n\n        layoutChildren(recycler);\n    }\n\n    @Override\n    public void onLayoutCompleted(RecyclerView.State state) {\n        super.onLayoutCompleted(state);\n\n        if (getChildCount() > 0) {\n            // check if this was a resize that requires a forced scroll\n            if (mBottomToTop && mAutoScrollBottom) {\n                View bottomView = getBottomView();\n                int childHeight = getDecoratedMeasuredHeight(bottomView);\n                int bottomDelta = getPaddingTop() + getVerticalSpace() - getDecoratedBottom(bottomView);\n                if (bottomDelta > 0) {\n                    // the last view is too high\n                    offsetChildrenVertical(bottomDelta);\n                    logDebug(\"(1) auto-scroll (\" + getDebugName(bottomView) + \") bottom amount=\" + bottomDelta);\n                } else if (bottomDelta > (-childHeight * 3 / 5) && bottomAdapterItemIdx() == mFirstVisiblePosition) {\n                    // the first visible item (at the bottom) is hidden by a small amount, scroll it into view smoothly\n                    //TODO: detect if this occurred because of a user scroll so we can skip this if so\n                    logDebug(\"(2) smooth auto-scroll (\" + getDebugName(bottomView) + \") bottom amount=\" + bottomDelta);\n\n                    LinearSmoothScroller smoothScroller = new LinearSmoothScroller(bottomView.getContext());\n                    smoothScroller.setTargetPosition(mFirstVisiblePosition);\n                    startSmoothScroll(smoothScroller);\n                } else if (bottomAdapterItemIdx() == mFirstVisiblePosition) {\n                    // list got resized and the first visible item (at the bottom) is now hidden\n                    offsetChildrenVertical(bottomDelta);\n                    logDebug(\"(3) auto-scroll (\" + getDebugName(bottomView) + \") bottom amount=\" + bottomDelta);\n                } else {\n                    logDebug(\"(4) no auto-scroll (\" + getDebugName(bottomView) + \") bottom amount=\" + bottomDelta);\n                }\n            }\n        }\n    }\n\n    /**\n     * Compute scroll offset based on mViewCache\n     *\n     * @return difference between child top position and initial layout position\n     */\n    private int computeScrollOffset() {\n        if (mViewCache.size() > 0) {\n            int adapterPos = mViewCache.keyAt(0);\n            int rowIdx = getRowIdx(adapterPos);\n            int rowPosition = getRowPosition(rowIdx);\n            View child = mViewCache.valueAt(0);\n            return getDecoratedTop(child) - rowPosition;\n        }\n        return 0;\n    }\n\n    private int computeRowCount(int itemCount) {\n        return itemCount / mColCount + (itemCount % mColCount == 0 ? 0 : 1);\n    }\n\n    private class LayoutRowHelper {\n        public final RecyclerView.Recycler recycler;\n        @NonNull\n        public final View[] viewRow;\n        public int mDecoratedHeight;\n        public int mDecoratedTop;\n        public int mDecoratedBottom;\n        public int nextLeftPosDelta;\n\n        private LayoutRowHelper(RecyclerView.Recycler recycler, int columnCount) {\n            this.recycler = recycler;\n            viewRow = new View[columnCount];\n        }\n\n        public void setPositionIncrement(int nextLeft) {\n            nextLeftPosDelta = nextLeft;\n        }\n\n        public void layoutRow(int rowIdx, int posX, int posY) {\n            int columnCount = viewRow.length;\n            mDecoratedHeight = 0;\n            mDecoratedTop = Integer.MAX_VALUE;\n            mDecoratedBottom = Integer.MIN_VALUE;\n\n            for (int colIdx = 0; colIdx < columnCount; colIdx += 1) {\n                final int leftPos = posX + nextLeftPosDelta * colIdx;\n                final int adapterPos = adapterPosition(colIdx, rowIdx);\n\n                View child = layoutView(adapterPos, leftPos, posY);\n\n                // child may be null if row is incomplete\n                viewRow[colIdx] = child;\n                if (child == null)\n                    continue;\n\n                mDecoratedTop = Math.min(mDecoratedTop, CustomRecycleLayoutManager.this.getDecoratedTop(child));\n                mDecoratedBottom = Math.max(mDecoratedBottom, CustomRecycleLayoutManager.this.getDecoratedBottom(child));\n\n                // update row height\n                int measuredHeight = getDecoratedMeasuredHeight(child);\n                mDecoratedHeight = Math.max(mDecoratedHeight, measuredHeight);\n            }\n\n            int heightDelta = posY - getDecoratedTop();\n            if (heightDelta != 0) {\n                offsetVertical(heightDelta);\n            }\n\n            // make all views in this row the same height\n            for (View child : viewRow) {\n                if (child != null) {\n                    // set bottom; we expect the top to not change\n                    child.setBottom(mDecoratedBottom - getBottomDecorationHeight(child));\n                }\n            }\n        }\n\n        @Nullable\n        private View layoutView(int adapterPos, int leftPos, int topPos) {\n            if (adapterPos < 0 || adapterPos >= getItemCount())\n                return null;\n            return layoutAdapterPos(recycler, adapterPos, leftPos, topPos);\n        }\n\n        public void offsetVertical(int offsetY) {\n            for (View child : viewRow) {\n                if (child == null)\n                    continue;\n                child.offsetTopAndBottom(offsetY);\n\n                int childIdx = indexOfChild(child);\n                int position = adapterPosition(child);\n                logDebug(\"move #\" + childIdx + \" pos=\" + position +\n                         \" (\" + child.getLeft() + \" \" + child.getTop() + \" \" + child.getRight() + \" \" + child.getBottom() + \")\" +\n                         \" \" + getDebugInfo(child) + \" \" + getDebugName(child));\n            }\n            mDecoratedTop += offsetY;\n            mDecoratedBottom += offsetY;\n        }\n\n        public void initRow() {\n            Arrays.fill(viewRow, null);\n        }\n\n        public int getDecoratedHeight() {\n            return mDecoratedHeight;\n        }\n\n        public int getDecoratedTop() {\n            return mDecoratedTop;\n        }\n\n        public int getDecoratedBottom() {\n            return mDecoratedBottom;\n        }\n    }\n\n    private int getRowPosition(int rowIdx) {\n        if (rowIdx < 0 || rowIdx >= mRowInfo.size()) {\n            Log.e(TAG, \"getRowPosition(\" + rowIdx + \"); rowInfo.size=\" + mRowInfo.size());\n            return 0;\n        }\n\n        int pos = getPaddingTop();\n        if (mBottomToTop)\n            pos += getVerticalSpace();\n        pos += mRowInfo.get(rowIdx).pos;\n        return pos;\n    }\n\n    private int getRowHeight(int rowIdx) {\n        if (rowIdx < 0 || rowIdx >= mRowInfo.size()) {\n            Log.e(TAG, \"getRowHeight(\" + rowIdx + \"); rowInfo.size=\" + mRowInfo.size());\n            return 0;\n        }\n        return mRowInfo.get(rowIdx).height;\n    }\n\n    private void layoutChildren(RecyclerView.Recycler recycler) {\n        /*\n         * Detach all existing views from the layout.\n         * detachView() is a lightweight operation that we can use to\n         * quickly reorder views without a full add/remove.\n         */\n        cacheChildren();\n\n        // compute scroll position after we populate `mViewCache`\n        final int scrollOffset = computeScrollOffset();\n\n        logDebug(\"layoutChildren\" +\n                 \" mFirstVisiblePosition=\" + mFirstVisiblePosition +\n                 \" scrollOffset=\" + scrollOffset +\n                 \" paddingTop=\" + getPaddingTop() +\n                 \" paddingBottom=\" + getPaddingBottom() +\n                 \" verticalSpace=\" + getVerticalSpace());\n\n        if (mRefreshViews) {\n            logDebug(\"detachAndScrapAttachedViews\" +\n                     \" viewCache.size=\" + mViewCache.size());\n            // If we want to refresh all views, just scrap them and let the recycler rebind them\n            mRefreshViews = false;\n            mViewCache.clear();\n            detachAndScrapAttachedViews(recycler);\n        } else {\n            logDebug(\"detachViews\" +\n                     \" viewCache.size=\" + mViewCache.size());\n            // Temporarily detach all views. We do this to easily reorder them.\n            detachCachedChildren(recycler);\n        }\n\n        final int posX = getPaddingLeft() + (mRightToLeft ? (getHorizontalSpace() - mDecoratedChildWidth) : 0);\n        final int nextLeftPosDelta = mRightToLeft ? -mDecoratedChildWidth : mDecoratedChildWidth;\n        LayoutRowHelper rowHelper = new LayoutRowHelper(recycler, mColCount);\n        rowHelper.setPositionIncrement(nextLeftPosDelta);\n\n        //TODO: start laying out children from the ones we have in cache;\n        // this way there is less chance of moving already cached children\n\n        //TODO: if (mBottomToTop==true) and we scroll up (thumb goes up) the rows should\n        // update in reverse (starting from current row to 0)\n\n        // layout rows\n        int visibleRowIdx = 0;\n        while (visibleRowIdx < mVisibleRows) {\n            final int adapterPos = adapterPosition(visibleRowIdx * mColCount);\n            if (adapterPos < 0 || adapterPos >= getItemCount()) {\n                Log.w(TAG, \"! visibleRowIdx=\" + visibleRowIdx + \" missing adapter pos=\" + adapterPos + \" itemCount=\" + getItemCount() + \" mVisibleRows=\" + mVisibleRows);\n                visibleRowIdx += 1;\n                continue;\n            }\n            rowHelper.initRow();\n            final int rowIdx = getRowIdx(adapterPos);\n            final int rowHeight = getRowHeight(rowIdx);\n            final int rowPosition = getRowPosition(rowIdx);\n            final int scrolledRowPosition = scrollOffset + rowPosition;\n\n            logDebug(\"row #\" + rowIdx +\n                     \" pos=\" + adapterPos +\n                     \" before layout\" +\n                     \" rowHeight=\" + rowHeight +\n                     \" rowPosition=\" + rowPosition +\n                     \" scrolledPos=\" + scrolledRowPosition);\n\n            rowHelper.layoutRow(rowIdx, posX, scrolledRowPosition);\n\n            if (rowHeight != rowHelper.getDecoratedHeight()) {\n                updateRowHeight(rowIdx, rowHelper.getDecoratedHeight());\n                if (mBottomToTop) // same as (rowPosition != getRowPosition(rowIdx))\n                    rowHelper.offsetVertical(rowHeight - rowHelper.getDecoratedHeight());\n            }\n\n            visibleRowIdx += 1;\n\n            // if this is the last visible row, check if we could show more\n            if (visibleRowIdx == mVisibleRows) {\n                boolean needsMoreRows = mBottomToTop ? (rowHelper.getDecoratedTop() > 0) : (rowHelper.getDecoratedBottom() < getHeight());\n                needsMoreRows = needsMoreRows && (visibleRowIdx * mColCount < getItemCount());\n                if (needsMoreRows) {\n                    // force-show more views\n                    mVisibleRows += 1;\n                }\n            }\n        }\n\n        clearViewCache(recycler);\n    }\n\n    /**\n     * Cache all views by their existing position\n     */\n    private void cacheChildren() {\n        mViewCache.clear();\n\n        int childCount = getChildCount();\n        mViewCache.ensureCapacity(childCount);\n\n        for (int i = 0; i < childCount; i++) {\n            View child = getChildAt(i);\n            if (child == null)\n                throw new IllegalStateException(\"null child when count=\" + getChildCount() + \" and idx=\" + i);\n            int position = adapterPosition(child);\n            mViewCache.put(position, child);\n            logDebug(\"info #\" + i + \" pos=\" + position + \" \" + getDebugInfo(child) + \" \" + getDebugName(child));\n        }\n    }\n\n    private void detachCachedChildren(RecyclerView.Recycler recycler) {\n        for (int i = 0; i < mViewCache.size(); i++) {\n            View child = mViewCache.valueAt(i);\n            // When an update is in order, scrap the view and let the recycler rebind it\n            if (viewNeedsUpdate(child)) {\n                detachAndScrapView(child, recycler);\n                mViewCache.removeAt(i--);\n            } else {\n                detachView(child);\n            }\n        }\n    }\n\n    /**\n     * Layout view from cache or recycler\n     *\n     * @param recycler   the recycler\n     * @param adapterPos adapter index\n     * @param leftPos    layout position X\n     * @param topPos     layout position Y\n     * @return child view\n     */\n    private View layoutAdapterPos(RecyclerView.Recycler recycler, int adapterPos, int leftPos, int topPos) {\n        View child = mViewCache.get(adapterPos);\n        if (child == null) {\n            /*\n             * The Recycler will give us either a newly constructed view or a recycled view it has on-hand.\n             * In either case, the view will already be fully bound to the data by the adapter for us.\n             */\n            child = recycler.getViewForPosition(adapterPos);\n            addView(child);\n            /*\n             * It is prudent to measure/layout each new view we receive from the Recycler.\n             * We don't have to do this for views we are just re-arranging.\n             */\n            measureChildWithMargins(child, (mColCount - 1) * mDecoratedChildWidth, 0);\n\n            layoutChildView(child, leftPos, topPos);\n\n            logDebug(\"child #\" + indexOfChild(child) + \" pos=\" + adapterPos +\n                     \" (\" + child.getLeft() + \" \" + child.getTop() + \" \" + child.getRight() + \" \" + child.getBottom() + \")\" +\n                     \" \" + getDebugInfo(child) + \" \" + getDebugName(child));\n        } else {\n            attachView(child);\n            logDebug(\"cache #\" + indexOfChild(child) + \" pos=\" + adapterPos +\n                     \" (\" + child.getLeft() + \" \" + child.getTop() + \" \" + child.getRight() + \" \" + child.getBottom() + \")\" +\n                     \" top=\" + topPos +\n                     \" \" + getDebugInfo(child) + \" \" + getDebugName(child));\n            mViewCache.remove(adapterPos);\n        }\n        return child;\n    }\n\n    private void layoutChildView(View view, int left, int top) {\n        layoutChildView(view, left, top, mDecoratedChildWidth, getDecoratedMeasuredHeight(view));\n    }\n\n    private void layoutChildView(View view, int left, int top, int width, int height) {\n        layoutDecorated(view, left, top,\n        left + width,\n        top + height);\n    }\n\n    /**\n     * We ask the Recycler to scrap and store any views that we did not re-attach.\n     * These are views that are not currently necessary because they are no longer visible.\n     */\n    private void clearViewCache(RecyclerView.Recycler recycler) {\n        for (int i = 0; i < mViewCache.size(); i++) {\n            final View removingView = mViewCache.valueAt(i);\n            logDebug(\"recycleView pos=\" + mViewCache.keyAt(i) + \" \" + getDebugName(removingView));\n            recycler.recycleView(removingView);\n        }\n        mViewCache.clear();\n    }\n\n    /*\n     * Rather than continuously checking how many views we can fit\n     * based on scroll offsets, we simplify the math by computing the\n     * visible grid as what will initially fit on screen, plus one.\n     */\n    private void updateSizing() {\n        int visibleWidth = getHorizontalSpace();\n        int visibleCols;\n        if (mAutoColumn)\n            visibleCols = visibleWidth / mDecoratedChildWidth;\n        else\n            visibleCols = mColCount;\n        if (visibleCols <= 0)\n            visibleCols = 1;\n\n        int averageChildHeight;\n        if (mBottomToTop) {\n            averageChildHeight = -mRowInfo.get(mRowInfo.size() - 1).pos;\n        } else {\n            RowInfo rowInfo = mRowInfo.get(mRowInfo.size() - 1);\n            averageChildHeight = rowInfo.pos + rowInfo.height;\n        }\n        averageChildHeight /= mRowInfo.size();\n\n        // we use getHeight instead of `getVerticalSpace` because we can scroll vertically\n        int visibleHeight = getHeight();\n        int visibleRows = (visibleHeight / averageChildHeight) + 1;\n        if (visibleHeight % averageChildHeight > 0) {\n            visibleRows++;\n        }\n\n        if (mColCount != visibleCols) {\n            mColCount = visibleCols;\n            int rowHeight = getRowHeight(0);\n            updateRowInfo(rowHeight);\n        }\n        mVisibleRows = computeRowCount(Math.min(visibleRows * visibleCols, getItemCount()));\n        mDecoratedChildWidth = visibleWidth / visibleCols;\n    }\n\n    /**\n     * Initialize all rows with valid information. This should be called every time the number of\n     * rows or columns changes.\n     *\n     * @param expectedRowHeight initial height. Row information will be updated during the layout\n     *                          phase. See `updateRowHeight`\n     */\n    private void updateRowInfo(int expectedRowHeight) {\n        final int rowCount = computeRowCount(getItemCount());\n        mRowInfo.clear();\n        mRowInfo.ensureCapacity(rowCount);\n        logDebug(\"updateRowInfo\" +\n                 \" height=\" + expectedRowHeight +\n                 \" rowCount=\" + rowCount);\n\n        for (int rowIdx = 0; rowIdx < rowCount; rowIdx++) {\n            RowInfo rowInfo = new RowInfo();\n            rowInfo.height = expectedRowHeight;\n            if (mBottomToTop)\n                rowInfo.pos = -expectedRowHeight * (rowIdx + 1);\n            else\n                rowInfo.pos = expectedRowHeight * rowIdx;\n            mRowInfo.add(rowInfo);\n        }\n    }\n\n    /**\n     * This is called during layout when a row height needs adjusting. All rows after the current\n     * one will have their position updated. In bottom to top layout the current row position will\n     * also be updated.\n     *\n     * @param rowIdx    row that needs to be adjusted\n     * @param newHeight new height of the row\n     */\n    private void updateRowHeight(int rowIdx, int newHeight) {\n        if (rowIdx < 0 || rowIdx >= mRowInfo.size()) {\n            Log.e(TAG, \"updateRowHeight(\" + rowIdx + \")\" +\n                       \" rowInfo.size=\" + mRowInfo.size());\n            return;\n        }\n        RowInfo rowInfo = mRowInfo.get(rowIdx);\n\n        final int oldPos = rowInfo.pos;\n        final int oldHeight = rowInfo.height;\n\n        if (mBottomToTop)\n            rowInfo.pos -= newHeight - rowInfo.height;\n        rowInfo.height = newHeight;\n\n        logDebug(\"updateRowHeight\" +\n                 \" row #\" + rowIdx +\n                 \" posY changed from \" + oldPos + \" to \" + rowInfo.pos +\n                 \" height changed from \" + oldHeight + \" to \" + rowInfo.height);\n\n        for (int row = rowIdx + 1, rowCount = mRowInfo.size(); row < rowCount; row += 1) {\n            RowInfo rowIt = mRowInfo.get(row);\n            if (mBottomToTop)\n                rowIt.pos = rowInfo.pos - rowIt.height;\n            else\n                rowIt.pos = rowInfo.pos + rowInfo.height;\n\n            rowInfo = rowIt;\n        }\n    }\n\n    /*\n     * Use this method to tell the RecyclerView if scrolling is even possible\n     * in the vertical direction.\n     */\n    @Override\n    public boolean canScrollVertically() {\n        if (getItemCount() != getChildCount())\n            return true;\n        if (getChildCount() > 0) {\n            //We do allow scrolling\n            if (getDecoratedTop(getTopView()) < getPaddingTop())\n                return true;\n            return getDecoratedBottom(getBottomView()) > (getPaddingTop() + getVerticalSpace());\n        }\n        return false;\n    }\n\n    private int indexOfChild(View child) {\n        for (int idx = getChildCount() - 1; idx >= 0; idx -= 1) {\n            if (getChildAt(idx) == child)\n                return idx;\n        }\n        return -1;\n    }\n\n    private int adapterPosition(@NonNull View child) {\n        return ((RecyclerView.LayoutParams) child.getLayoutParams()).getViewLayoutPosition();\n    }\n\n    private int adapterPosition(int childIdx) {\n        int idx = childIdx;\n        if (mReverseAdapter)\n            idx = -idx;\n        return mFirstVisiblePosition + idx;\n    }\n\n    private int adapterPosition(int colIdx, int rowIdx) {\n        int idx = rowIdx * mColCount + colIdx;\n        if (mReverseAdapter)\n            idx = getItemCount() - 1 - idx;\n        return idx;\n    }\n\n    private int getRowIdx(int adapterPos) {\n        if (mReverseAdapter)\n            return (getItemCount() - 1 - adapterPos) / mColCount;\n        return adapterPos / mColCount;\n    }\n\n    /**\n     * Return top child view on screen (may not be visible)\n     *\n     * @return child view\n     */\n    @NonNull\n    private View getTopView() {\n        final int topChildIdx = mBottomToTop ? (getChildCount() - 1) : 0;\n        View child = getChildAt(topChildIdx);\n        if (child == null)\n            throw new IllegalStateException(\"null child when count=\" + getChildCount() + \" and topChildIdx=\" + topChildIdx);\n        return child;\n    }\n\n    /**\n     * Return bottom child view on screen (may not be visible)\n     *\n     * @return child view\n     */\n    @NonNull\n    private View getBottomView() {\n        final int bottomChildIdx = mBottomToTop ? 0 : (getChildCount() - 1);\n        View child = getChildAt(bottomChildIdx);\n        if (child == null)\n            throw new IllegalStateException(\"null child when count=\" + getChildCount() + \" and bottomChildIdx=\" + bottomChildIdx);\n        return child;\n    }\n\n    @NonNull\n    private View findBottomVisibleItemView() {\n        final int childCount = getChildCount();\n        int botChildIdx = mBottomToTop ? 0 : (childCount - 1);\n        View child = getChildAt(botChildIdx);\n        if (child == null)\n            throw new IllegalStateException(\"null child when count=\" + childCount + \" and bottomChildIdx=\" + botChildIdx);\n        while (getDecoratedTop(child) > getHeight()) {\n            botChildIdx += mBottomToTop ? 1 : -1;\n            if (botChildIdx < 0 || botChildIdx >= childCount)\n                return child;\n            child = getChildAt(botChildIdx);\n            if (child == null)\n                throw new IllegalStateException(\"null child when count=\" + childCount + \" and bottomChildIdx=\" + botChildIdx);\n        }\n        return child;\n    }\n\n    public int findLastVisibleItemPosition() {\n        final int childCount = getChildCount();\n        int botChildIdx = mBottomToTop ? 0 : (childCount - 1);\n        if (botChildIdx < 0 || botChildIdx >= childCount)\n            return -1;\n        View child = getChildAt(botChildIdx);\n        if (child == null)\n            throw new IllegalStateException(\"null child when count=\" + childCount + \" and bottomChildIdx=\" + botChildIdx);\n        while (getDecoratedTop(child) > getHeight()) {\n            botChildIdx += mBottomToTop ? 1 : -1;\n            if (botChildIdx < 0 || botChildIdx >= childCount)\n                return adapterPosition(child);\n            child = getChildAt(botChildIdx);\n            if (child == null)\n                throw new IllegalStateException(\"null child when count=\" + childCount + \" and bottomChildIdx=\" + botChildIdx);\n        }\n        return adapterPosition(child);\n    }\n\n    /**\n     * First (or last) item from the adapter that can be displayed at the top of the list\n     *\n     * @return index from adapter\n     */\n    private int topAdapterItemIdx() {\n        return (mBottomToTop ^ mReverseAdapter) ? (getItemCount() - 1) : 0;\n    }\n\n    /**\n     * First (or last) item from the adapter that can be displayed at the bottom of the list\n     *\n     * @return index from adapter\n     */\n    private int bottomAdapterItemIdx() {\n        return (mBottomToTop ^ mReverseAdapter) ? 0 : (getItemCount() - 1);\n    }\n\n    private int aboveAdapterItemIdx(int idx) {\n        return idx - belowAdapterItemIdx(0);\n    }\n\n    private int belowAdapterItemIdx(int idx) {\n        return idx + ((mBottomToTop ^ mReverseAdapter) ? -mColCount : mColCount);\n    }\n\n    /*\n     * This method describes how far RecyclerView thinks the contents should scroll vertically.\n     * You are responsible for verifying edge boundaries, and determining if this scroll\n     * event somehow requires that new views be added or old views get recycled.\n     */\n    @Override\n    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {\n        if (getChildCount() == 0) {\n            return 0;\n        }\n\n        final int amount;\n        // compute amount of scroll without going beyond the bound\n        if (dy < 0) { // finger is moving downward\n            View topView = getTopView();\n            boolean topBoundReached = adapterPosition(topView) == topAdapterItemIdx();\n            final int topBound = getPaddingTop();\n            final int childTop = getDecoratedTop(topView);\n            if (topBoundReached && (childTop - dy) > topBound) {\n                //If top bound reached, enforce limit\n                int topOffset = topBound - childTop;\n\n                amount = -Math.min(-dy, topOffset);\n            } else {\n                amount = dy;\n            }\n        } else if (dy > 0) { // finger is moving upward\n            View bottomView = getBottomView();\n            boolean bottomBoundReached = adapterPosition(bottomView) == bottomAdapterItemIdx();\n            final int bottomBound = getVerticalSpace() + getPaddingTop();\n            final int childBottom = getDecoratedBottom(bottomView);\n            if (bottomBoundReached && (childBottom - dy) < bottomBound) {\n                //If we've reached the last row, enforce limits\n                int bottomOffset = childBottom - bottomBound;\n\n                amount = Math.min(dy, bottomOffset);\n            } else {\n                amount = dy;\n            }\n        } else {\n            amount = dy;\n        }\n\n        if (dy != amount) {\n            logDebug(\"dy=\" + dy + \" amount=\" + amount + \" overScroll=\" + mOverScrollVertical);\n            overScrollVertical(dy - amount);\n        } else {\n            resetOverScrollVertical();\n        }\n\n        if (amount == 0 || (dy < 0 && amount > 0) || (dy > 0 && amount < 0))\n            return 0;\n\n        // scroll children\n        offsetChildrenVertical(-amount);\n\n        // check if we need to layout after the scroll\n        checkVisibilityAfterScroll(recycler, true);\n        checkVisibilityAfterScroll(recycler, false);\n\n        /*\n         * Return value determines if a boundary has been reached\n         * (for edge effects and flings). If returned value does not\n         * match original delta (passed in), RecyclerView will draw\n         * an edge effect.\n         */\n        return amount;\n    }\n\n    private void overScrollVertical(int amount) {\n        // sum up amount until we can trigger the over scroll event\n        mOverScrollVertical += amount;\n        if (mOverScrollListener != null && mRecyclerView != null) {\n            if (mOverScrollVertical > getDecoratedMeasuredHeight(getBottomView())) {\n                mOverScrollListener.onOverScroll(mRecyclerView, mOverScrollVertical);\n            }\n        }\n    }\n\n    private void resetOverScrollVertical() {\n        // reset amount\n        mOverScrollVertical = 0;\n    }\n\n    private void checkVisibilityAfterScroll(RecyclerView.Recycler recycler, boolean checkTop) {\n        View child = checkTop ? getTopView() : getBottomView();\n        int adapterPosition = adapterPosition(child);\n        int top = getDecoratedTop(child);\n        int newFirstVisible = mFirstVisiblePosition;\n        while (needsVisibilityChange(adapterPosition, top, checkTop)) {\n            if (checkTop) {\n                adapterPosition = aboveAdapterItemIdx(adapterPosition);\n                newFirstVisible = aboveAdapterItemIdx(newFirstVisible);\n                top -= getRowHeight(getRowIdx(adapterPosition));\n            } else {\n                top += getRowHeight(getRowIdx(adapterPosition));\n                adapterPosition = belowAdapterItemIdx(adapterPosition);\n                newFirstVisible = belowAdapterItemIdx(newFirstVisible);\n            }\n        }\n        changeFirstVisible(recycler, newFirstVisible);\n    }\n\n    /**\n     * @param adapterPosition needed to stop checking if adapter start or end reached\n     * @param top             child decorated top\n     * @param checkTop        `bound` is the top position or the bottom\n     * @return if we need to change `mFirstVisiblePosition`\n     */\n    private boolean needsVisibilityChange(int adapterPosition, int top, boolean checkTop) {\n        if (adapterPosition <= 0 || adapterPosition >= (getItemCount() - 1))\n            return false;\n        if (checkTop)\n            return top > 0;\n        int rowHeight = getRowHeight(getRowIdx(adapterPosition));\n        return (top + rowHeight) < getHeight();\n    }\n\n    private void changeFirstVisible(RecyclerView.Recycler recycler, int value) {\n        int oldValue = mFirstVisiblePosition;\n        int newValue = Math.max(Math.min(value, getItemCount() - 1), 0);\n        if (oldValue != newValue) {\n            logDebug(\"mFirstVisiblePosition changed from \" + oldValue + \" to \" + newValue);\n            mFirstVisiblePosition = newValue;\n            layoutChildren(recycler);\n        }\n    }\n\n    private static boolean viewNeedsUpdate(View v) {\n        RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) v.getLayoutParams();\n        return p.viewNeedsUpdate();\n    }\n\n    private static String getDebugName(View v) {\n        View name;\n        name = v.findViewById(R.id.item_app_name);\n        if (name instanceof TextView) {\n            CharSequence text = ((TextView) name).getText();\n            if (text != null && text.length() > 0)\n                return text.toString();\n        }\n        name = v.findViewById(R.id.item_contact_name);\n        if (name instanceof TextView) {\n            CharSequence text = ((TextView) name).getText();\n            if (text != null && text.length() > 0)\n                return text.toString();\n        }\n        name = v.findViewById(android.R.id.text1);\n        if (name instanceof TextView) {\n            CharSequence text = ((TextView) name).getText();\n            if (text != null && text.length() > 0)\n                return text.toString();\n        }\n        return \"\";\n    }\n\n    private static String getDebugInfo(View v) {\n        String info = \"\";\n        if (v.getLayoutParams() instanceof RecyclerView.LayoutParams) {\n            RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) v.getLayoutParams();\n            info += \"lp\" + p.getViewLayoutPosition();\n            info += \"ap\" + p.getViewAdapterPosition();\n            if (p.viewNeedsUpdate())\n                info += \"u\";\n            if (p.isItemChanged())\n                info += \"c\";\n            if (p.isItemRemoved())\n                info += \"r\";\n            if (p.isViewInvalid())\n                info += \"i\";\n        }\n        return info;\n    }\n\n    private static void logDebug(String message) {\n        if (LOG_DEBUG)\n            Log.d(TAG, message);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/result/EntryAdapter.java",
    "content": "package rocks.tbog.tblauncher.result;\n\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.BaseAdapter;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport java.util.Collection;\nimport java.util.List;\n\nimport rocks.tbog.tblauncher.entry.EntryItem;\n\npublic class EntryAdapter extends BaseAdapter {\n    private final List<EntryItem> mItems;\n    private final int mDrawFlags;\n\n    public EntryAdapter(@NonNull List<EntryItem> objects) {\n        mItems = objects;\n        mDrawFlags = EntryItem.FLAG_DRAW_GRID | EntryItem.FLAG_DRAW_NAME | EntryItem.FLAG_DRAW_ICON | EntryItem.FLAG_DRAW_ICON_BADGE;\n    }\n\n    public EntryAdapter(@NonNull List<EntryItem> objects, int drawFlags) {\n        mItems = objects;\n        mDrawFlags = drawFlags;\n    }\n\n    public void addAll(Collection<EntryItem> newElements) {\n        mItems.addAll(newElements);\n        notifyDataSetChanged();\n    }\n\n    @Override\n    public int getViewTypeCount() {\n        return ResultHelper.getItemViewTypeCount();\n    }\n\n    @Override\n    public int getItemViewType(int position) {\n        return ResultHelper.getItemViewType(mItems.get(position), mDrawFlags);\n    }\n\n    @Override\n    public EntryItem getItem(int position) {\n        return mItems.get(position);\n    }\n\n    @Override\n    public int getCount() {\n        return mItems.size();\n    }\n\n    @Override\n    public long getItemId(int position) {\n        return getItem(position).hashCode();\n    }\n\n    @NonNull\n    @Override\n    public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {\n        final View view;\n        EntryItem content = getItem(position);\n        if (convertView == null) {\n            //final int viewType = ResultHelper.getItemViewLayout(getItemViewType(position));\n            final int viewType = content.getResultLayout(mDrawFlags);\n            view = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);\n        } else {\n            view = convertView;\n        }\n\n        content.displayResult(view, mDrawFlags);\n\n        return view;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/result/LoadDataForAdapter.java",
    "content": "package rocks.tbog.tblauncher.result;\n\nimport java.util.ArrayList;\n\nimport rocks.tbog.tblauncher.WorkAsync.AsyncTask;\nimport rocks.tbog.tblauncher.entry.EntryItem;\nimport rocks.tbog.tblauncher.utils.Utilities;\n\npublic class LoadDataForAdapter extends AsyncTask<Void, ArrayList<EntryItem>> {\n    private final EntryAdapter adapter;\n    private final LoadInBackground task;\n\n    public interface LoadInBackground {\n        ArrayList<EntryItem> loadInBackground();\n    }\n\n    public LoadDataForAdapter(EntryAdapter adapter, LoadInBackground loadInBackground) {\n        super();\n        this.adapter = adapter;\n        task = loadInBackground;\n    }\n\n    @Override\n    protected ArrayList<EntryItem> doInBackground(Void param) {\n        ArrayList<EntryItem> data = task.loadInBackground();\n        data.trimToSize();\n        return data;\n    }\n\n    @Override\n    protected void onPostExecute(ArrayList<EntryItem> data) {\n        if (data == null)\n            return;\n        adapter.addAll(data);\n    }\n\n    public void execute() {\n        Utilities.executeAsync(this);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/result/RecycleAdapter.java",
    "content": "package rocks.tbog.tblauncher.result;\n\nimport android.annotation.SuppressLint;\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.Filter;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.preference.PreferenceManager;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\n\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.entry.EntryItem;\nimport rocks.tbog.tblauncher.ui.ListPopup;\n\npublic class RecycleAdapter extends RecycleAdapterBase<RecycleAdapterBase.Holder> {\n\n    @Nullable\n    private ArrayList<EntryItem> resultsOriginal = null;\n\n    private final Filter mFilter = new RecycleAdapter.FilterById();\n\n    public RecycleAdapter(@NonNull Context context, @NonNull ArrayList<EntryItem> results) {\n        super(results);\n        setHasStableIds(true);\n        setGridLayout(context, false);\n        setOnClickListener(RecycleAdapter::onClick);\n        setOnLongClickListener(RecycleAdapter::onLongClick);\n    }\n\n    @NonNull\n    @Override\n    public RecycleAdapterBase.Holder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {\n        final Context context = parent.getContext();\n        final int layoutRes = ResultHelper.getItemViewLayout(viewType);\n\n        LayoutInflater inflater = LayoutInflater.from(context);\n        View itemView = inflater.inflate(layoutRes, parent, false);\n\n        return new RecycleAdapterBase.Holder(itemView);\n    }\n\n    public void setGridLayout(@NonNull Context context, boolean bGridLayout) {\n        final int oldFlags = mDrawFlags;\n        // get new flags\n        mDrawFlags = getDrawFlags(context);\n        mDrawFlags |= bGridLayout ? EntryItem.FLAG_DRAW_GRID : EntryItem.FLAG_DRAW_LIST;\n        // refresh items if flags changed\n        if (oldFlags != mDrawFlags)\n            refresh();\n    }\n\n    private int getDrawFlags(@NonNull Context context) {\n        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);\n        int drawFlags = EntryItem.FLAG_DRAW_NAME;\n        if (prefs.getBoolean(\"tags-enabled\", true))\n            drawFlags |= EntryItem.FLAG_DRAW_TAGS;\n        if (prefs.getBoolean(\"icons-visible\", true))\n            drawFlags |= EntryItem.FLAG_DRAW_ICON;\n        if (prefs.getBoolean(\"shortcut-show-badge\", true))\n            drawFlags |= EntryItem.FLAG_DRAW_ICON_BADGE;\n        return drawFlags;\n    }\n\n    public void onClick(int index, View anyView) {\n        final EntryItem result = getItem(index);\n        if (result == null) {\n            return;\n        }\n\n        onClick(result, anyView);\n    }\n\n    public static void onClick(final EntryItem result, @NonNull View v) {\n        ResultHelper.launch(v, result);\n    }\n\n    public static boolean onLongClick(final EntryItem result, @NonNull View v) {\n        ListPopup menu = result.getPopupMenu(v);\n\n        // check if menu contains elements and if yes show it\n        if (!menu.getAdapter().isEmpty()) {\n            TBApplication.getApplication(v.getContext()).registerPopup(menu);\n            menu.show(v);\n            return true;\n        }\n\n        return false;\n    }\n\n    @Override\n    public void clear() {\n        resultsOriginal = null;\n        super.clear();\n    }\n\n    @SuppressLint(\"NotifyDataSetChanged\")\n    public void updateItems(Collection<? extends EntryItem> results) {\n        resultsOriginal = null;\n        super.updateItems(results);\n    }\n\n    public void removeItem(EntryItem result) {\n        if (resultsOriginal != null)\n            resultsOriginal.remove(result);\n        super.removeItem(result);\n    }\n\n    public Filter getFilter() {\n        if (resultsOriginal == null)\n            resultsOriginal = new ArrayList<>(entryList);\n        return mFilter;\n    }\n\n    private class FilterById extends Filter {\n\n        //Invoked in a worker thread to filter the data according to the constraint.\n        @Override\n        protected FilterResults performFiltering(CharSequence constraint) {\n            if (constraint == null || constraint.length() == 0 || resultsOriginal == null)\n                return null;\n            String schema = constraint.toString();\n            ArrayList<EntryItem> filterList = new ArrayList<>();\n            for (EntryItem entryItem : resultsOriginal) {\n                if (entryItem.id.startsWith(schema))\n                    filterList.add(entryItem);\n            }\n            FilterResults filterResults = new FilterResults();\n            filterResults.values = filterList;\n            filterResults.count = filterList.size();\n            return filterResults;\n        }\n\n        //Invoked in the UI thread to publish the filtering results in the user interface.\n        @SuppressWarnings(\"unchecked\")\n        @SuppressLint(\"NotifyDataSetChanged\")\n        @Override\n        protected void publishResults(CharSequence constraint, FilterResults filterResults) {\n            if (filterResults != null) {\n                entryList.clear();\n                entryList.addAll((ArrayList<EntryItem>) filterResults.values);\n                notifyDataSetChanged();\n            } else if (resultsOriginal != null) {\n                entryList.clear();\n                entryList.addAll(resultsOriginal);\n                resultsOriginal = null;\n                notifyDataSetChanged();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/result/RecycleAdapterBase.java",
    "content": "package rocks.tbog.tblauncher.result;\n\nimport android.annotation.SuppressLint;\nimport android.graphics.drawable.Drawable;\nimport android.util.Log;\nimport android.view.View;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport java.util.Collection;\nimport java.util.List;\n\nimport rocks.tbog.tblauncher.CustomizeUI;\nimport rocks.tbog.tblauncher.entry.EntryItem;\nimport rocks.tbog.tblauncher.utils.UIColors;\n\npublic abstract class RecycleAdapterBase<VH extends RecycleAdapterBase.Holder> extends RecyclerView.Adapter<VH> {\n\n    private static final String TAG = RecycleAdapterBase.class.getSimpleName();\n\n    @NonNull\n    protected final List<EntryItem> entryList;\n    protected int mDrawFlags;\n    @Nullable\n    private OnClickListener mOnClickListener = null;\n    @Nullable\n    private OnLongClickListener mOnLongClickListener = null;\n\n    public RecycleAdapterBase(@NonNull List<EntryItem> list) {\n        entryList = list;\n    }\n\n    public void setOnClickListener(@Nullable OnClickListener listener) {\n        mOnClickListener = listener;\n    }\n\n    public void setOnLongClickListener(@Nullable OnLongClickListener listener) {\n        mOnLongClickListener = listener;\n    }\n\n    @Override\n    public int getItemViewType(int position) {\n        final EntryItem entry = getItem(position);\n        if (entry == null)\n            return -1; // this is invalid and will throw later on\n        return ResultHelper.getItemViewType(entry, mDrawFlags);\n    }\n\n    @Override\n    public long getItemId(int position) {\n        final EntryItem entry = getItem(position);\n        if (entry == null)\n            return -1;\n        return entry.id.hashCode();\n    }\n\n    @Override\n    public void onBindViewHolder(@NonNull VH holder, int position) {\n        final EntryItem entry = getItem(position);\n        if (entry == null)\n            return;\n\n        if (mOnClickListener != null)\n            holder.setOnClickListener(v -> mOnClickListener.onClick(entry, v));\n        else\n            holder.setOnClickListener(null);\n\n        if (mOnLongClickListener != null)\n            holder.setOnLongClickListener(v -> mOnLongClickListener.onLongClick(entry, v));\n        else\n            holder.setOnLongClickListener(null);\n\n        entry.displayResult(holder.itemView, mDrawFlags);\n    }\n\n    @Override\n    public int getItemCount() {\n        return entryList.size();\n    }\n\n    @Nullable\n    public EntryItem getItem(int index) {\n        final EntryItem entry;\n        try {\n            entry = entryList.get(index);\n        } catch (ArrayIndexOutOfBoundsException e) {\n            Log.e(TAG, \"pos=\" + index + \" size=\" + entryList.size(), e);\n            return null;\n        }\n        return entry;\n    }\n\n    public void removeItem(EntryItem result) {\n        int position = entryList.indexOf(result);\n        entryList.remove(result);\n        notifyItemRemoved(position);\n    }\n\n    public void addItem(EntryItem item) {\n        notifyItemInserted(entryList.size());\n        entryList.add(item);\n    }\n\n    public void clear() {\n        final int itemCount = entryList.size();\n        entryList.clear();\n        notifyItemRangeRemoved(0, itemCount);\n    }\n\n    public void refresh() {\n        final int itemCount = entryList.size();\n        notifyItemRangeChanged(0, itemCount);\n    }\n\n    @SuppressLint(\"NotifyDataSetChanged\")\n    public void updateItems(Collection<? extends EntryItem> results) {\n        this.entryList.clear();\n        this.entryList.addAll(results);\n        notifyDataSetChanged();\n    }\n\n    public void notifyItemChanged(EntryItem result) {\n        int position = entryList.indexOf(result);\n        Log.d(TAG, \"notifyItemChanged #\" + position + \" id=\" + result.id);\n        if (position >= 0)\n            notifyItemChanged(position);\n    }\n\n    public static class Holder extends RecyclerView.ViewHolder {\n\n        public Holder(@NonNull View itemView) {\n            super(itemView);\n            itemView.setTag(this);\n\n            // we set background selector here to do it only once\n            int touchColor = UIColors.getResultListRipple(itemView.getContext());\n            Drawable selectorBackground = CustomizeUI.getSelectorDrawable(itemView, touchColor, false);\n            itemView.setBackground(selectorBackground);\n        }\n\n        public void setOnClickListener(@Nullable View.OnClickListener listener) {\n            itemView.setOnClickListener(listener);\n            if (listener == null)\n                itemView.setClickable(false);\n        }\n\n        public void setOnLongClickListener(@Nullable View.OnLongClickListener listener) {\n            itemView.setOnLongClickListener(listener);\n            if (listener == null)\n                itemView.setLongClickable(false);\n        }\n    }\n\n    public interface OnClickListener {\n        void onClick(EntryItem entryItem, View view);\n    }\n\n    public interface OnLongClickListener {\n        boolean onLongClick(EntryItem entryItem, View view);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/result/RecycleScrollListener.java",
    "content": "package rocks.tbog.tblauncher.result;\n\nimport android.animation.Animator;\nimport android.animation.ValueAnimator;\nimport android.util.Log;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.ViewTreeObserver;\nimport android.view.animation.DecelerateInterpolator;\n\nimport androidx.annotation.NonNull;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.ui.KeyboardHandler;\nimport rocks.tbog.tblauncher.ui.RecyclerList;\nimport rocks.tbog.tblauncher.ui.WindowInsetsHelper;\nimport rocks.tbog.tblauncher.utils.DebugInfo;\nimport rocks.tbog.tblauncher.utils.DebugString;\n\npublic class RecycleScrollListener extends RecyclerView.OnScrollListener implements CustomRecycleLayoutManager.OverScrollListener {\n    private static final String TAG = \"RScrL\";\n    private final KeyboardHandler handler;\n    private int mScrollAmountY = 0;\n    private int mHideKeyboardThreshold = -1;\n    private int mListHeight = -1;\n    private final State mState = new State();\n\n    private static class State {\n        // list resize after keyboard hidden has finished\n        private boolean resizeFinished = false;\n        // keyboard hide requested\n        private boolean resizeInProgress = false;\n        // waiting for the keyboard to set window insets\n        private boolean waitForInsets = false;\n        private boolean resizeWithScroll = false;\n\n        public boolean resizeFinished() {\n            return resizeFinished;\n        }\n\n        public boolean resizeInProgress() {\n            return resizeInProgress && !resizeFinished;\n        }\n\n        public boolean resizeWithScroll() {\n            return resizeWithScroll && !resizeFinished;\n        }\n\n        @NonNull\n        @Override\n        public String toString() {\n            return \"[resizeWithScroll=\" + resizeWithScroll + \" waitForInsets=\" + waitForInsets + \" resizeInProgress=\" + resizeInProgress + \" resizeFinished=\" + resizeFinished + \"]\";\n        }\n\n        public void reset() {\n            resizeFinished = false;\n            resizeInProgress = false;\n            waitForInsets = false;\n            resizeWithScroll = false;\n        }\n\n        public void setResizeAnimation() {\n            resizeInProgress = false;\n        }\n\n        public void setResizeWithScroll() {\n            resizeWithScroll = true;\n        }\n\n        public void setResizeFinished() {\n            resizeFinished = true;\n        }\n    }\n\n    public RecycleScrollListener(KeyboardHandler handler) {\n        this.handler = handler;\n    }\n\n    @Override\n    public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {\n        if (mHideKeyboardThreshold == -1) {\n            if (recyclerView.getLayoutManager() instanceof CustomRecycleLayoutManager) {\n                final int lastItem = ((CustomRecycleLayoutManager) recyclerView.getLayoutManager()).findLastVisibleItemPosition();\n                RecyclerView.ViewHolder vh = recyclerView.findViewHolderForAdapterPosition(lastItem);\n                int lastItemHeight = vh != null ? vh.itemView.getHeight() : 0;\n                if (lastItemHeight == 0)\n                    lastItemHeight = recyclerView.getResources().getDimensionPixelSize(R.dimen.icon_size);\n                mHideKeyboardThreshold = lastItemHeight * 3 / 5;\n            } else {\n                int itemHeight = recyclerView.getResources().getDimensionPixelSize(R.dimen.icon_size);\n                mHideKeyboardThreshold = itemHeight * 3 / 5;\n            }\n        }\n        Log.d(TAG, \"scroll state=\" + scrollStateString(newState));\n        if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {\n            mState.reset();\n            mListHeight = recyclerView.getHeight();\n            int keyboardHeight = WindowInsetsHelper.getKeyboardHeight(recyclerView);\n            boolean keyboardVisible = WindowInsetsHelper.isKeyboardVisible(recyclerView);\n            Log.i(TAG, \"start drag; keyboardHeight=\" + keyboardHeight + \" keyboardVisible=\" + keyboardVisible + \" mListHeight=\" + mListHeight);\n            if (DebugInfo.keyboardScrollHiderTouch(recyclerView.getContext()))\n                recyclerView.setBackgroundColor(0x80ffd700);\n\n            // if keyboard is already hidden, we are done\n            if (!WindowInsetsHelper.isKeyboardVisible(recyclerView))\n                handleResizeDone(recyclerView);\n        } else if (newState == RecyclerView.SCROLL_STATE_IDLE) {\n            if (recyclerView.getLayoutManager() instanceof CustomRecycleLayoutManager) {\n                int lastVisible = ((CustomRecycleLayoutManager) recyclerView.getLayoutManager()).findLastVisibleItemPosition();\n                int itemCount = recyclerView.getAdapter() != null ? recyclerView.getAdapter().getItemCount() : 0;\n                if (lastVisible < (itemCount - 1)) {\n                    final int range = recyclerView.computeVerticalScrollRange();\n                    final int extent = recyclerView.computeVerticalScrollExtent();\n                    final int offset = recyclerView.computeVerticalScrollOffset();\n                    Log.d(TAG, \"lastVisible=\" + (lastVisible + 1) + \"/\" + itemCount + \" range=\" + range + \" extent=\" + extent + \" offset=\" + offset);\n                    mScrollAmountY = range - extent - offset;\n                } else {\n                    mScrollAmountY = 0;\n                }\n            }\n            Log.i(TAG, \"scrollY=\" + mScrollAmountY);\n            if (mState.resizeInProgress()) {\n                mState.setResizeAnimation();\n                int layoutParamsHeight = recyclerView.getLayoutParams().height;\n                final int fromValue = layoutParamsHeight < 0 ? recyclerView.getHeight() : layoutParamsHeight;\n                final int toValue = ((View) recyclerView.getParent()).getHeight();\n                if (fromValue == toValue) {\n                    handleResizeDone(recyclerView);\n                } else {\n                    ValueAnimator anim = ValueAnimator.ofInt(fromValue, toValue);\n                    anim.setInterpolator(new DecelerateInterpolator());\n                    anim.setDuration(recyclerView.getResources().getInteger(android.R.integer.config_longAnimTime));\n                    anim.addUpdateListener(animation -> {\n                        int h = (int) animation.getAnimatedValue();\n                        setListLayoutHeight(recyclerView, h);\n                    });\n                    anim.addListener(new Animator.AnimatorListener() {\n                        @Override\n                        public void onAnimationStart(Animator animation) {\n                            if (DebugInfo.keyboardScrollHiderTouch(recyclerView.getContext()))\n                                recyclerView.setBackgroundColor(0x800000ff);\n                            setListAutoScroll(recyclerView, true);\n                        }\n\n                        @Override\n                        public void onAnimationEnd(Animator animation) {\n                            handleResizeDone(recyclerView);\n                        }\n\n                        @Override\n                        public void onAnimationCancel(Animator animation) {\n                            handleResizeDone(recyclerView);\n                        }\n\n                        @Override\n                        public void onAnimationRepeat(Animator animation) {\n                            // not happening\n                        }\n                    });\n                    anim.start();\n                }\n            }\n        }\n    }\n\n    @NonNull\n    private static String scrollStateString(int state) {\n        switch (state) {\n            case RecyclerView.SCROLL_STATE_IDLE:\n                return \"idle\";\n            case RecyclerView.SCROLL_STATE_DRAGGING:\n                return \"drag\";\n            case RecyclerView.SCROLL_STATE_SETTLING:\n                return \"sett\";\n            default:\n                return \"undef\";\n        }\n    }\n\n    @Override\n    public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {\n        if (!(recyclerView instanceof RecyclerList))\n            return;\n        final RecyclerList list = (RecyclerList) recyclerView;\n        int state = list.getScrollState();\n        if (mState.resizeFinished() || state == RecyclerView.SCROLL_STATE_IDLE)\n            return;\n\n        mScrollAmountY -= dy;\n        final boolean keyboardVisible = WindowInsetsHelper.isKeyboardVisible(list);\n        Log.d(TAG, \"state=\" + scrollStateString(state) + \" scrollY=\" + mScrollAmountY + \" KV=\" + keyboardVisible + \" state=\" + mState);\n\n        // if resize while scrolling is active ... resize\n        if (mState.resizeWithScroll()) {\n            int containerHeight = ((View) list.getParent()).getHeight();\n            int newHeight = Math.min(mListHeight + mScrollAmountY, containerHeight);\n            Log.d(TAG, \"onScrolled: resizeWithScroll newHeight=\" + newHeight + \" containerHeight=\" + containerHeight);\n            if (newHeight == containerHeight) {\n                handleResizeDone(list);\n            } else if (newHeight > list.getHeight()) {\n                setListLayoutHeight(list, newHeight);\n            }\n            return;\n        }\n\n        // if scrolled enough to trigger the keyboard hiding\n        if (mScrollAmountY > mHideKeyboardThreshold) {\n            int containerHeight = ((View) list.getParent()).getHeight();\n            Log.d(TAG, \"containerHeight=\" + containerHeight);\n\n            if (!mState.waitForInsets && mState.resizeInProgress()) {\n                // resize list to keep items under the dragging finger\n                int newHeight = Math.min(mListHeight + mScrollAmountY, containerHeight);\n                if (newHeight == containerHeight) {\n                    handleResizeDone(list);\n                } else {\n                    setListLayoutHeight(list, newHeight);\n                    // activate resize while scrolling\n                    mState.setResizeWithScroll();\n                    if (DebugInfo.keyboardScrollHiderTouch(list.getContext()))\n                        list.setBackgroundColor(0x80ff0000);\n                }\n            } else if ((mListHeight + mScrollAmountY) >= containerHeight) {\n                setListLayoutHeight(list, mListHeight);\n                hideKeyboardWhileDragging(list);\n                // let the Scroll Listener resize the list without any additional scrolling\n                setListAutoScroll(list, false);\n            }\n        }\n    }\n\n    @Override\n    public void onOverScroll(RecyclerView list, int amount) {\n        if (mState.resizeFinished() || !WindowInsetsHelper.isKeyboardVisible(list)) {\n            Log.i(TAG, \"overscroll show keyboard with state=\" + mState);\n            handler.showKeyboard();\n        }\n    }\n\n    private void hideKeyboardWhileDragging(RecyclerView list) {\n        if (mState.waitForInsets) {\n            int keyboardHeight = WindowInsetsHelper.getKeyboardHeight(list);\n            Log.d(TAG, \"onScrolled called while mWaitForInsets=true and keyboard height=\" + keyboardHeight);\n        } else {\n            // start the resize process (hide keyboard)\n            mState.waitForInsets = true;\n            mState.resizeInProgress = true;\n            handler.hideKeyboard();\n\n            ViewTreeObserver vto = ((View) list.getParent()).getViewTreeObserver();\n            vto.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {\n                @Override\n                public boolean onPreDraw() {\n                    int keyboardHeight = WindowInsetsHelper.getKeyboardHeight(list);\n                    boolean keyboardVisible = WindowInsetsHelper.isKeyboardVisible(list);\n                    if (keyboardVisible) {\n                        Log.d(TAG, \"onPreDraw called with keyboard visible and height=\" + keyboardHeight);\n\n                        // proceed with the current drawing pass, waiting for the keyboard to close\n                        return true;\n                    }\n                    ViewTreeObserver vto = ((View) list.getParent()).getViewTreeObserver();\n                    vto.removeOnPreDrawListener(this);\n                    if (!mState.waitForInsets || mState.resizeFinished()) {\n                        Log.d(TAG, \"onPreDraw called when mWaitForInsets=\" + mState.waitForInsets + \" mResizeFinished=\" + mState.resizeFinished);\n                        return true;\n                    }\n                    if (list.getScrollState() == RecyclerView.SCROLL_STATE_IDLE) {\n                        handleResizeDone(list);\n                    } else {\n                        mState.waitForInsets = false;\n                        int containerHeight = ((View) list.getParent()).getHeight();\n                        int newHeight = Math.min(mListHeight + mScrollAmountY, containerHeight);\n                        Log.d(TAG, \"onPreDraw height=min(\" + (mListHeight + mScrollAmountY) + \", \" + containerHeight + \")\");\n                        setListLayoutHeight(list, newHeight);\n\n                        if (DebugInfo.keyboardScrollHiderTouch(list.getContext()))\n                            list.setBackgroundColor(0x8000ff00);\n\n                        // request new pass\n                        return false;\n                    }\n                    // proceed with the current drawing pass\n                    return true;\n                }\n            });\n        }\n    }\n\n    private void setListAutoScroll(@NonNull RecyclerView list, boolean value) {\n        if (list.getLayoutManager() instanceof CustomRecycleLayoutManager)\n            ((CustomRecycleLayoutManager) list.getLayoutManager()).setAutoScrollBottom(value);\n    }\n\n    private void handleResizeDone(@NonNull RecyclerView list) {\n        if (mState.resizeFinished()) {\n            return;\n        }\n        mScrollAmountY = 0;\n        Log.i(TAG, \"resize finished.\");\n        if (DebugInfo.keyboardScrollHiderTouch(list.getContext()))\n            list.setBackgroundColor(0x00000000);\n\n        setListAutoScroll(list, true);\n\n        setListLayoutHeight(list, ViewGroup.LayoutParams.MATCH_PARENT);\n\n        mState.setResizeFinished();\n    }\n\n    public static void setListLayoutHeight(ViewGroup list, int height) {\n        final ViewGroup.LayoutParams params = list.getLayoutParams();\n        if (params.height != height) {\n            Log.d(TAG, \"change layout height from \" + DebugString.layoutParamSize(params.height) + \" to \" + DebugString.layoutParamSize(height));\n            params.height = height;\n            list.setLayoutParams(params);\n            list.forceLayout();\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/result/ResultHelper.java",
    "content": "package rocks.tbog.tblauncher.result;\n\nimport static rocks.tbog.tblauncher.entry.EntryItem.LAUNCHED_FROM_QUICK_LIST;\nimport static rocks.tbog.tblauncher.entry.EntryItem.LAUNCHED_FROM_RESULT_LIST;\n\nimport android.annotation.SuppressLint;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.net.Uri;\nimport android.provider.ContactsContract;\nimport android.util.Log;\nimport android.view.View;\nimport android.widget.Toast;\n\nimport androidx.annotation.LayoutRes;\nimport androidx.annotation.NonNull;\nimport androidx.collection.ArraySet;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\nimport rocks.tbog.tblauncher.Behaviour;\nimport rocks.tbog.tblauncher.Permission;\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.dataprovider.QuickListProvider;\nimport rocks.tbog.tblauncher.db.DBHelper;\nimport rocks.tbog.tblauncher.entry.AppEntry;\nimport rocks.tbog.tblauncher.entry.ContactEntry;\nimport rocks.tbog.tblauncher.entry.EntryItem;\nimport rocks.tbog.tblauncher.entry.SearchEntry;\nimport rocks.tbog.tblauncher.entry.ShortcutEntry;\nimport rocks.tbog.tblauncher.entry.StaticEntry;\nimport rocks.tbog.tblauncher.handler.DataHandler;\nimport rocks.tbog.tblauncher.utils.MimeTypeUtils;\nimport rocks.tbog.tblauncher.utils.Utilities;\n\npublic class ResultHelper {\n\n    /**\n     * Use index as view type\n     * Value is the layout id\n     */\n    @NonNull\n    static final private ArraySet<Integer> sEntryViewType = new ArraySet<>(8);\n\n    static {\n        addViewTypes(AppEntry.getResultLayout());\n        addViewTypes(ContactEntry.getResultLayout());\n        addViewTypes(StaticEntry.getResultLayout());\n        addViewTypes(ShortcutEntry.getResultLayout());\n        addViewTypes(SearchEntry.getResultLayout());\n        Log.i(\"log\", \"result view type count=\" + sEntryViewType.size());\n    }\n\n    private ResultHelper() {\n        // this is a static class\n    }\n\n    private static void addViewTypes(int[] viewTypes) {\n        for (int viewType : viewTypes)\n            sEntryViewType.add(viewType);\n    }\n\n    /**\n     * How to launch a result. Most probably, will fire an intent.\n     * This function will record history and then call EntryItem.doLaunch\n     *\n     * @param view {@link View} that was touched\n     * @param pojo the {@link EntryItem} that the user is launching\n     */\n    public static void launch(@NonNull View view, @NonNull EntryItem pojo) {\n        final int launchedFrom;\n        if (TBApplication.quickList(view.getContext()).isViewInList(view))\n            launchedFrom = LAUNCHED_FROM_QUICK_LIST;\n        else\n            launchedFrom = LAUNCHED_FROM_RESULT_LIST;\n\n        launch(view, pojo, launchedFrom);\n    }\n\n    /**\n     * How to launch a result. Most probably, will fire an intent.\n     * This function will record history and then call EntryItem.doLaunch\n     *\n     * @param view         {@link View} that was touched\n     * @param pojo         the {@link EntryItem} that the user is launching\n     * @param launchedFrom is the view from QuickList, ResultList, Gesture?\n     */\n    public static void launch(@NonNull View view, @NonNull EntryItem pojo, int launchedFrom) {\n        if (pojo instanceof StaticEntry) {\n            Log.i(\"log\", \"Launching StaticEntry \" + pojo.id);\n\n            recordLaunch(pojo, view.getContext());\n            pojo.doLaunch(view, launchedFrom);\n            return;\n        }\n\n        TBApplication.behaviour(view.getContext()).beforeLaunchOccurred();\n\n        Log.i(\"log\", \"Launching \" + pojo.id);\n\n        recordLaunch(pojo, view.getContext());\n\n        // Launch\n        view.postDelayed(() -> {\n            pojo.doLaunch(view, launchedFrom);\n            TBApplication.behaviour(view.getContext()).afterLaunchOccurred();\n        }, Behaviour.LAUNCH_DELAY);\n    }\n\n    /**\n     * Put this item in application history\n     *\n     * @param pojo    the {@link EntryItem} that the user is launching\n     * @param context android context\n     */\n    public static void recordLaunch(@NonNull EntryItem pojo, @NonNull Context context) {\n        if (pojo.isExcludedFromHistory())\n            return;\n        // Save in history\n        TBApplication.getApplication(context).getDataHandler().addToHistory(pojo.getHistoryId());\n    }\n\n    /**\n     * Remove the current result from the list\n     *\n     * @param context android context\n     */\n    public static void removeFromResultsAndHistory(@NonNull EntryItem pojo, @NonNull Context context) {\n        removeFromHistory(pojo, context);\n        //TODO: remove from results only if we are showing history\n        TBApplication.behaviour(context).removeResult(pojo);\n        //TODO: make an UndoBar\n        Toast.makeText(context, context.getString(R.string.removed_item, pojo.getName()), Toast.LENGTH_SHORT).show();\n    }\n\n    private static void removeFromHistory(@NonNull EntryItem pojo, @NonNull Context context) {\n        DBHelper.removeFromHistory(context, pojo.id);\n    }\n\n    public static void launchAddToQuickList(@NonNull Context context, EntryItem pojo) {\n        final DataHandler dataHandler = TBApplication.dataHandler(context);\n        QuickListProvider provider = dataHandler.getQuickListProvider();\n\n        // get current Quick List content\n        List<? extends EntryItem> list = provider != null ? provider.getPojos() : Collections.emptyList();\n        final ArrayList<String> idList = new ArrayList<>(list.size());\n        for (EntryItem entryItem : list) {\n            idList.add(entryItem.id);\n        }\n\n        // add the new entry\n        idList.add(pojo.id);\n\n        // save Quick List\n        dataHandler.setQuickList(idList);\n    }\n\n    public static void launchRemoveFromQuickList(@NonNull Context context, EntryItem pojo) {\n        final DataHandler dataHandler = TBApplication.dataHandler(context);\n        QuickListProvider provider = dataHandler.getQuickListProvider();\n\n        // get current Quick List content\n        List<? extends EntryItem> list = provider != null ? provider.getPojos() : Collections.emptyList();\n        final ArrayList<String> idList = new ArrayList<>(list.size());\n        for (EntryItem entryItem : list) {\n            idList.add(entryItem.id);\n        }\n\n        // remove the entry\n        idList.remove(pojo.id);\n\n        // save Quick List\n        dataHandler.setQuickList(idList);\n    }\n\n    public static void launchMessaging(ContactEntry contactPojo, View v) {\n        Context context = v.getContext();\n        TBApplication.behaviour(context).beforeLaunchOccurred();\n\n        String url = \"sms:\" + Uri.encode(contactPojo.getPhone());\n        Intent i = new Intent(Intent.ACTION_SENDTO, Uri.parse(url));\n        Utilities.setIntentSourceBounds(i, v);\n        i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n        context.startActivity(i);\n\n        TBApplication.behaviour(context).afterLaunchOccurred();\n    }\n\n    public static void launchIm(ContactEntry.ImData imData, View v) {\n        Context context = v.getContext();\n\n        Intent intent = MimeTypeUtils.getRegisteredIntentByMimeType(context, imData.getMimeType(), imData.getId(), imData.getIdentifier());\n        if (intent != null) {\n            TBApplication.behaviour(context).beforeLaunchOccurred();\n\n            Utilities.setIntentSourceBounds(intent, v);\n            context.startActivity(intent);\n\n            TBApplication.behaviour(context).afterLaunchOccurred();\n        }\n    }\n\n    public static void launchContactView(ContactEntry contactPojo, Context context, View v) {\n        TBApplication.behaviour(context).beforeLaunchOccurred();\n\n        Intent action = new Intent(Intent.ACTION_VIEW);\n\n        action.setData(Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_LOOKUP_URI,\n            String.valueOf(contactPojo.lookupKey)));\n        Utilities.setIntentSourceBounds(action, v);\n\n        action.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n        action.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);\n        context.startActivity(action);\n\n        TBApplication.behaviour(context).afterLaunchOccurred();\n    }\n\n    public static void launchCall(Context context, View v, String phone) {\n        TBApplication.behaviour(context).beforeLaunchOccurred();\n\n        Intent phoneIntent = new Intent(Intent.ACTION_CALL);\n        phoneIntent.setData(Uri.parse(\"tel:\" + Uri.encode(phone)));\n        Utilities.setIntentSourceBounds(phoneIntent, v);\n\n        phoneIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n\n        // Make sure we have permission to call someone as this is considered a dangerous permission\n        if (!Permission.checkPermission(context, Permission.PERMISSION_CALL_PHONE)) {\n            Permission.askPermission(Permission.PERMISSION_CALL_PHONE, new Permission.PermissionResultListener() {\n                @SuppressLint(\"MissingPermission\")\n                @Override\n                public void onGranted() {\n                    // Great! Start the intent we stored for later use.\n                    context.startActivity(phoneIntent);\n                }\n\n                @Override\n                public void onDenied() {\n                    Toast.makeText(context, R.string.permission_denied, Toast.LENGTH_SHORT).show();\n                }\n            });\n            return;\n        }\n\n        // Pre-android 23, or we already have permission\n        context.startActivity(phoneIntent);\n\n        TBApplication.behaviour(context).afterLaunchOccurred();\n    }\n\n    public static int getItemViewTypeCount() {\n        return sEntryViewType.size();\n    }\n\n    @LayoutRes\n    public static int getItemViewLayout(int viewType) {\n        if (viewType < 0 || viewType >= sEntryViewType.size())\n            throw new IllegalStateException(\"view type \" + viewType + \" out of range\");\n        Integer layout = sEntryViewType.valueAt(viewType);\n        if (layout == null || layout == 0)\n            throw new IllegalStateException(\"view type \" + viewType + \" has invalid layout\");\n        return layout;\n    }\n\n    public static int getItemViewType(@NonNull EntryItem item, int drawFlags) {\n        int layout = item.getResultLayout(drawFlags);\n        int viewType = sEntryViewType.indexOf(layout);\n        if (viewType < 0)\n            throw new IllegalStateException(\"no view type for \" + item.getClass().getName() + \" drawFlags=\" + drawFlags);\n        return viewType;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/result/ResultItemDecoration.java",
    "content": "package rocks.tbog.tblauncher.result;\n\nimport android.graphics.Rect;\nimport android.view.View;\n\nimport androidx.annotation.NonNull;\nimport androidx.recyclerview.widget.RecyclerView;\n\npublic class ResultItemDecoration extends RecyclerView.ItemDecoration {\n    private final int mHorizontal;\n    private final int mVertical;\n    private final boolean mOnlyForGrid;\n\n    public ResultItemDecoration(int horizontal, int vertical, boolean onlyForGrid) {\n        mHorizontal = horizontal;\n        mVertical = vertical;\n        mOnlyForGrid = onlyForGrid;\n    }\n\n    @Override\n    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {\n        super.getItemOffsets(outRect, view, parent, state);\n        int columns = 1;\n        boolean bottom2top = false;\n        boolean right2left = false;\n        boolean reverseAdapter = false;\n        RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();\n        if (layoutManager instanceof CustomRecycleLayoutManager) {\n            columns = ((CustomRecycleLayoutManager) layoutManager).getColumnCount();\n            bottom2top = ((CustomRecycleLayoutManager) layoutManager).isBottomToTop();\n            reverseAdapter = ((CustomRecycleLayoutManager) layoutManager).isReverseAdapter();\n            right2left = ((CustomRecycleLayoutManager) layoutManager).isRightToLeft();\n        }\n        if (mOnlyForGrid && columns <= 1)\n            return;\n\n        int adapterPos = parent.getChildAdapterPosition(view);\n        if (reverseAdapter) {\n            int itemCount = parent.getAdapter() != null ? parent.getAdapter().getItemCount() : 0;\n            adapterPos = itemCount - 1 - adapterPos;\n        }\n\n        int leftColumn = right2left ? (columns - 1) : 0;\n\n        // add left margin only to the leftmost column\n        if (adapterPos % columns == leftColumn)\n            outRect.left += mHorizontal;\n\n        // add top margin only to the topmost row\n        if (bottom2top) {\n            int itemCount = parent.getAdapter() != null ? parent.getAdapter().getItemCount() : 0;\n            int rowCount = itemCount / columns + (itemCount % columns == 0 ? 0 : 1);\n            int row = adapterPos / columns;\n            if (row == (rowCount - 1))\n                outRect.top += mVertical;\n        } else {\n            if (adapterPos < columns)\n                outRect.top += mVertical;\n        }\n        outRect.right += mHorizontal;\n        outRect.bottom += mVertical;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/result/ResultViewHelper.java",
    "content": "package rocks.tbog.tblauncher.result;\n\nimport android.content.Context;\nimport android.graphics.ColorFilter;\nimport android.graphics.drawable.Drawable;\nimport android.text.Spannable;\nimport android.text.SpannableString;\nimport android.text.SpannableStringBuilder;\nimport android.text.style.ForegroundColorSpan;\nimport android.util.Log;\nimport android.util.Pair;\nimport android.util.TypedValue;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\nimport android.widget.TextView;\n\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.constraintlayout.widget.ConstraintLayout;\n\nimport java.lang.reflect.Constructor;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.TreeSet;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.entry.EntryItem;\nimport rocks.tbog.tblauncher.entry.EntryWithTags;\nimport rocks.tbog.tblauncher.entry.ResultRelevance;\nimport rocks.tbog.tblauncher.normalizer.StringNormalizer;\nimport rocks.tbog.tblauncher.utils.FuzzyScore;\nimport rocks.tbog.tblauncher.utils.PrefCache;\nimport rocks.tbog.tblauncher.utils.UIColors;\nimport rocks.tbog.tblauncher.utils.UISizes;\nimport rocks.tbog.tblauncher.utils.Utilities;\n\npublic final class ResultViewHelper {\n\n    public final static ExecutorService EXECUTOR_LOAD_ICON = Executors.newSingleThreadExecutor();\n    private static final String TAG = \"RVH\";\n\n    private ResultViewHelper() {\n        // this is a namespace\n    }\n\n    private static CharSequence highlightText(@NonNull StringNormalizer.Result normText, @NonNull String text, @NonNull ResultRelevance relevance, int color) {\n        // merge all results that have the same text source\n        final TreeSet<Integer> matchedPositions = new TreeSet<>();\n        relevance.forEach(result -> {\n            if (result.relevance.match && result.relevanceSource.equals(normText))\n                matchedPositions.addAll(result.relevance.matchedIndices);\n            });\n        if (matchedPositions.isEmpty()) {\n            return text;\n        }\n\n        List<Pair<Integer, Integer>> matchedSequences = FuzzyScore.MatchInfo.getMatchedSequences(new ArrayList<>(matchedPositions));\n        return highlightText(normText, text, matchedSequences, color);\n    }\n\n    private static SpannableString highlightText(StringNormalizer.Result normalized, String text, Iterable<Pair<Integer, Integer>> matchedSequences, int color) {\n        SpannableString enriched = new SpannableString(text);\n\n        for (Pair<Integer, Integer> position : matchedSequences) {\n            enriched.setSpan(\n                new ForegroundColorSpan(color),\n                normalized.mapPosition(position.first),\n                normalized.mapPosition(position.second),\n                Spannable.SPAN_INCLUSIVE_EXCLUSIVE\n            );\n        }\n\n        return enriched;\n    }\n\n    /**\n     * Highlight text\n     *\n     * @param relevance result match information\n     * @param normText  we'll use this to match the result with the text we try to highlight\n     * @param text      provided visible text that may need highlighting\n     * @param view      TextView that gets the text\n     */\n    public static void displayHighlighted(@NonNull ResultRelevance relevance, @NonNull StringNormalizer.Result normText,\n                                          @NonNull String text, @NonNull TextView view) {\n        int color = UIColors.getResultHighlightColor(view.getContext());\n        view.setText(highlightText(normText, text, relevance, color));\n    }\n\n    public static boolean displayHighlighted(@NonNull ResultRelevance relevance, Iterable<EntryWithTags.TagDetails> tags,\n                                             TextView view, Context context) {\n        AtomicBoolean matchFound = new AtomicBoolean(false);\n        TreeSet<Integer> matchedPositions = new TreeSet<>();\n        int color = UIColors.getResultHighlightColor(context);\n        SpannableStringBuilder builder = new SpannableStringBuilder();\n        boolean first = true;\n        for (EntryWithTags.TagDetails tag : tags) {\n            if (!first)\n                builder.append(\" \\u2223 \");\n            first = false;\n\n            matchedPositions.clear();\n            // find all matched positions\n            relevance.forEach(result-> {\n                if (result.relevance.match && result.relevanceSource.equals(tag.normalized)) {\n                    matchedPositions.addAll(result.relevance.matchedIndices);\n                    matchFound.set(true);\n                }\n            });\n\n            if (matchedPositions.isEmpty()) {\n                // no matches found\n                builder.append(tag.name);\n            } else {\n                // highlight found matches\n                List<Pair<Integer, Integer>> matchedSequences = FuzzyScore.MatchInfo.getMatchedSequences(new ArrayList<>(matchedPositions));\n                builder.append(highlightText(tag.normalized, tag.name, matchedSequences, color));\n            }\n        }\n\n        view.setText(builder);\n\n        return matchFound.get();\n    }\n\n    public static void setButtonIconAsync(@NonNull ImageView iconView, @NonNull String buttonId, @NonNull Utilities.GetDrawable getDefaultIcon) {\n        Context context = iconView.getContext();\n\n        Drawable cache = TBApplication.drawableCache(context).getCachedDrawable(buttonId);\n        if (cache != null) {\n            Log.d(TAG, \"cache found, view=\" + Integer.toHexString(iconView.hashCode()) + \" button=\" + buttonId);\n            // found the icon in cache\n            iconView.setImageDrawable(cache);\n            return;\n        }\n\n        Utilities.setIconAsync(iconView, ctx -> {\n            Drawable buttonIcon = TBApplication.iconsHandler(ctx).getButtonIcon(buttonId);\n            if (buttonIcon == null) {\n                buttonIcon = getDefaultIcon.getDrawable(ctx);\n            }\n            TBApplication.drawableCache(ctx).cacheDrawable(buttonId, buttonIcon);\n            return buttonIcon;\n        });\n    }\n\n    public static <E extends EntryItem, T extends AsyncSetEntryDrawable<E>> void setIconAsync(int drawFlags, @NonNull E entry, @NonNull ImageView iconView, @NonNull Class<T> asyncSetEntryIconClass, @NonNull Class<E> entryItemClass) {\n        String cacheId = entry.getIconCacheId();\n        if (cacheId.equals(iconView.getTag(R.id.tag_cacheId)) && !Utilities.checkFlag(drawFlags, EntryItem.FLAG_RELOAD))\n            return;\n\n        if (!Utilities.checkFlag(drawFlags, EntryItem.FLAG_DRAW_NO_CACHE)) {\n            Drawable cache = TBApplication.drawableCache(iconView.getContext()).getCachedDrawable(cacheId);\n            if (cache != null) {\n                Log.d(TAG, \"cache found, view=\" + Integer.toHexString(iconView.hashCode()) + \" entry=\" + entry.getName() + \" cacheId=\" + cacheId);\n                // found the icon in cache\n                iconView.setImageDrawable(cache);\n                iconView.setTag(R.id.tag_cacheId, cacheId);\n                iconView.setTag(R.id.tag_iconTask, null);\n                // continue to run the async task only if FLAG_RELOAD set\n                if (!Utilities.checkFlag(drawFlags, EntryItem.FLAG_RELOAD))\n                    return;\n            }\n        }\n\n        // Below we have 2 methods for getting rid of `entryItemClass` parameter\n\n        /* METHOD 1: Get the actual type of EntryItem from template; this may be faster after the first run, but it needs further profiling\n        T task;\n        var superClass = asyncSetEntryIconClass.getGenericSuperclass();\n        Class<?> entryClass = EntryItem.class;\n        if (superClass instanceof ParameterizedType) {\n            var actualTypeArguments = ((ParameterizedType) superClass).getActualTypeArguments();\n            if (actualTypeArguments.length == 1)\n                entryClass = (Class<?>) actualTypeArguments[0];\n        }\n        // make new task instance from class asyncSetEntryIconClass\n        try {\n            var constructor = asyncSetEntryIconClass.getConstructor(ImageView.class, int.class, entryClass);\n            task = constructor.newInstance(iconView, drawFlags, entry);\n        } catch (IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) {\n            Log.e(TAG, \"new <? extends AsyncSetEntryDrawable>, ?=\" + asyncSetEntryIconClass.getName(), e);\n            return;\n        }\n        //*/\n\n        /* METHOD 2: Find a constructor testing arguments by hand because we don't know the actual type of the EntryItem from asyncSetEntryIconClass\n        T task = null;\n        @SuppressWarnings(\"unchecked\")\n        Constructor<T>[] declaredConstructors = (Constructor<T>[]) asyncSetEntryIconClass.getDeclaredConstructors();\n        // find and call constructor for template class\n        for (Constructor<T> constructor : declaredConstructors) {\n            var paramTypes = constructor.getParameterTypes();\n            if (paramTypes.length == 3\n                && paramTypes[0] == ImageView.class\n                && paramTypes[1] == int.class\n                && paramTypes[2].isAssignableFrom(entry.getClass())) {\n                try {\n                    task = constructor.newInstance(iconView, drawFlags, entry);\n                } catch (ReflectiveOperationException e) {\n                    Log.e(TAG, \"new \" + constructor, e);\n                    return;\n                }\n                break;\n            }\n        }\n        if (task == null) {\n            Log.e(TAG, \"constructor not found for \" + asyncSetEntryIconClass.getName() + \"\\n declaredConstructors=\" + Arrays.toString(declaredConstructors));\n            return;\n        }\n        //*/\n\n        // make new task instance from class `asyncSetEntryIconClass` using `entryItemClass`\n        T task;\n        Constructor<T> constructor = null;\n        try {\n            constructor = asyncSetEntryIconClass.getConstructor(ImageView.class, int.class, entryItemClass);\n            task = constructor.newInstance(iconView, drawFlags, entry);\n        } catch (ReflectiveOperationException e) {\n            if (constructor != null)\n                Log.e(TAG, \"new \" + constructor, e);\n            else\n                Log.e(TAG, \"constructor not found for `\" + asyncSetEntryIconClass.getName() + \"` and entry `\" + entry.getClass() + \"`\\n declaredConstructors=\" + Arrays.toString(asyncSetEntryIconClass.getDeclaredConstructors()));\n            return;\n        }\n\n        // run the async task\n        task.execute();\n    }\n\n    public static void applyResultItemShadow(@NonNull TextView textView) {\n        Context ctx = textView.getContext();\n        float radius = UISizes.getResultListShadowRadius(ctx);\n        float dx = UISizes.getResultListShadowOffsetHorizontal(ctx);\n        float dy = UISizes.getResultListShadowOffsetVertical(ctx);\n        int color = UIColors.getResultListShadowColor(ctx);\n\n        if (radius != textView.getShadowRadius()\n            || dx != textView.getShadowDx()\n            || dy != textView.getShadowDy()\n            || color != textView.getShadowColor()) {\n            textView.setShadowLayer(radius, dx, dy, color);\n        }\n    }\n\n    public static void applyPreferences(int drawFlags, TextView nameView, ImageView iconView) {\n        Context ctx = nameView.getContext();\n\n        nameView.setTextColor(UIColors.getResultTextColor(ctx));\n        nameView.setTextSize(TypedValue.COMPLEX_UNIT_PX, UISizes.getResultTextSize(ctx));\n        applyResultItemShadow(nameView);\n\n        if (Utilities.checkAnyFlag(drawFlags, EntryItem.FLAG_DRAW_LIST | EntryItem.FLAG_DRAW_GRID)) {\n            ViewGroup.LayoutParams params = iconView.getLayoutParams();\n            int size = UISizes.getResultIconSize(ctx);\n            if (params.width != size || params.height != size) {\n                params.width = size;\n                params.height = size;\n                iconView.setLayoutParams(params);\n            }\n        } else if (Utilities.checkFlag(drawFlags, EntryItem.FLAG_DRAW_QUICK_LIST)) {\n            ViewGroup.LayoutParams params = iconView.getLayoutParams();\n            if (params instanceof ConstraintLayout.LayoutParams) {\n                ConstraintLayout.LayoutParams cParams = (ConstraintLayout.LayoutParams) params;\n                int size = UISizes.getDockMaxIconSize(ctx);\n                if (cParams.matchConstraintMaxWidth != size || cParams.matchConstraintMaxHeight != size) {\n                    cParams.matchConstraintMaxWidth = size;\n                    cParams.matchConstraintMaxHeight = size;\n                    iconView.setLayoutParams(params);\n                }\n            }\n        }\n\n//        if (Utilities.checkFlag(drawFlags, EntryItem.FLAG_DRAW_LIST))\n//        {\n//            int[] colors = new int[] {0xFF000000, 0xFF00ff00, 0xFF000000};\n//            GradientDrawable bkg = new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM, colors);\n//            iconView.setBackground(bkg);\n//        }\n    }\n\n    public static void applyPreferences(int drawFlags, TextView nameView, TextView tagsView, ImageView iconView) {\n        applyPreferences(drawFlags, nameView, iconView);\n\n        Context ctx = tagsView.getContext();\n\n        tagsView.setTextColor(UIColors.getResultText2Color(ctx));\n        tagsView.setTextSize(TypedValue.COMPLEX_UNIT_PX, UISizes.getResultText2Size(ctx));\n    }\n\n    public static void applyListRowPreferences(ViewGroup rowView) {\n        // set result list item height\n        Context ctx = rowView.getContext();\n        int rowHeight = UISizes.getResultListRowHeight(ctx);\n        ViewGroup.LayoutParams params = rowView.getLayoutParams();\n        if (params.height != rowHeight) {\n            params.height = rowHeight;\n            rowView.setLayoutParams(params);\n        }\n    }\n\n    @Nullable\n    private static ColorFilter getColorFilter(@NonNull Context context, int drawFlags) {\n        final ColorFilter colorFilter;\n        if (Utilities.checkFlag(drawFlags, EntryItem.FLAG_DRAW_QUICK_LIST))\n            colorFilter = UIColors.colorFilterQuickIcon(context);\n        else\n            colorFilter = UIColors.colorFilter(context);\n        return colorFilter;\n    }\n\n    @Nullable\n    public static ColorFilter setIconColorFilter(@NonNull ImageView icon, int drawFlags) {\n        ColorFilter colorFilter = getColorFilter(icon.getContext(), drawFlags);\n        icon.setColorFilter(colorFilter);\n        return colorFilter;\n    }\n\n    public static void removeIconColorFilter(@NonNull ImageView icon) {\n        icon.clearColorFilter();\n    }\n\n    public static void setLoadingIcon(@NonNull ImageView image) {\n        @DrawableRes\n        int drawableId = PrefCache.getLoadingIconRes(image.getContext());\n        image.setImageResource(drawableId);\n        Utilities.startAnimatable(image);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/result/ReversibleAdapterRecyclerLayoutManager.java",
    "content": "package rocks.tbog.tblauncher.result;\n\npublic interface ReversibleAdapterRecyclerLayoutManager {\n    void setReverseAdapter(boolean reverseAdapter);\n    boolean isReverseAdapter();\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/searcher/HistorySearcher.java",
    "content": "package rocks.tbog.tblauncher.searcher;\n\nimport android.app.Activity;\nimport android.content.Context;\n\nimport androidx.annotation.NonNull;\n\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\n\nimport rocks.tbog.tblauncher.handler.DataHandler;\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.db.DBHelper;\nimport rocks.tbog.tblauncher.db.ModRecord;\nimport rocks.tbog.tblauncher.entry.EntryItem;\nimport rocks.tbog.tblauncher.utils.PrefCache;\nimport rocks.tbog.tblauncher.utils.Utilities;\n\npublic class HistorySearcher extends Searcher {\n\n    DBHelper.HistoryMode mHistoryMode;\n\n    public HistorySearcher(ISearchActivity activity, @NonNull String query) {\n        super(activity, query);\n        mHistoryMode = DataHandler.getHistoryMode(query);\n    }\n\n    @Override\n    protected Void doInBackground(Void param) {\n\n        ISearchActivity searchActivity = activityWeakReference.get();\n        Activity activity = searchActivity != null ? Utilities.getActivity(searchActivity.getContext()) : null;\n        if (activity == null)\n            return null;\n\n//        int maxResults = getMaxResultCount(activity);\n\n        processedPojos.clear();\n        List<EntryItem> history = getHistory(activity, mHistoryMode);\n        int order = history.size();\n        for (EntryItem item : history) {\n            item.setRelevance(item.normalizedName, null);\n            item.boostRelevance(order--);\n\n            //addResult(item);\n            processedPojos.add(item);\n            if (processedPojos.size() > maxResults)\n                processedPojos.poll();\n        }\n        return null;\n    }\n\n    static List<EntryItem> getHistory(@NonNull Context context, DBHelper.HistoryMode mode) {\n        DataHandler dataHandler = TBApplication.dataHandler(context);\n        Set<String> exclude;\n        // exclude favorites\n        {\n            exclude = new HashSet<>();\n            for (ModRecord rec : dataHandler.getMods()) {\n                if (rec.isInQuickList())\n                    exclude.add(rec.record);\n            }\n        }\n        int itemCount = PrefCache.getResultHistorySize(context);\n        return dataHandler.getHistory(itemCount, mode, false, exclude);\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/searcher/ISearchActivity.java",
    "content": "package rocks.tbog.tblauncher.searcher;\n\nimport android.content.Context;\n\nimport androidx.annotation.NonNull;\n\nimport java.util.List;\n\nimport rocks.tbog.tblauncher.entry.EntryItem;\n\npublic interface ISearchActivity {\n    void displayLoader(boolean b);\n\n    @NonNull\n    Context getContext();\n\n    /**\n     * Called after the search task finished\n     */\n    void resetTask();\n\n    /**\n     * Called when the searcher found no results\n     */\n    void clearAdapter();\n\n    /**\n     * Called when searcher found results\n     */\n    void updateAdapter(@NonNull List<? extends EntryItem> results, boolean isRefresh);\n\n    /**\n     * Called when user removed/hidden app\n     */\n    void removeResult(@NonNull EntryItem result);\n\n    /**\n     * Show only results matching filter text\n     * @param text to filter for\n     */\n    void filterResults(String text);\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/searcher/ISearcher.java",
    "content": "package rocks.tbog.tblauncher.searcher;\n\nimport androidx.annotation.WorkerThread;\n\nimport rocks.tbog.tblauncher.entry.EntryItem;\n\npublic interface ISearcher {\n    @WorkerThread\n    boolean addResult(EntryItem... items);\n\n    boolean tagsEnabled();\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/searcher/QuerySearcher.java",
    "content": "package rocks.tbog.tblauncher.searcher;\n\nimport android.content.Context;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.WorkerThread;\n\nimport java.util.HashMap;\nimport java.util.List;\n\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.db.DBHelper;\nimport rocks.tbog.tblauncher.db.ValuedHistoryRecord;\nimport rocks.tbog.tblauncher.entry.EntryItem;\nimport rocks.tbog.tblauncher.utils.MapCompat;\n\n/**\n * AsyncTask retrieving data from the providers and updating the view\n *\n * @author dorvaryn\n */\npublic class QuerySearcher extends Searcher {\n    private final String trimmedQuery;\n    private final HashMap<String, Integer> knownIds = new HashMap<>();\n\n    public QuerySearcher(ISearchActivity activity, @NonNull String query) {\n        super(activity, query);\n        trimmedQuery = query.trim();\n    }\n\n    @Override\n    public boolean addResult(EntryItem... pojos) {\n        // Give a boost if item was previously selected for this query\n        for (EntryItem pojo : pojos) {\n            int historyRecord = MapCompat.getOrDefault(knownIds, pojo.id, 0);\n            if (historyRecord != 0) {\n                pojo.boostRelevance(25 * historyRecord);\n            }\n        }\n\n        // call super implementation to update the adapter\n        return super.addResult(pojos);\n    }\n\n    /**\n     * Called on the background thread\n     */\n    @WorkerThread\n    @Override\n    protected Void doInBackground(Void param) {\n        ISearchActivity searchActivity = activityWeakReference.get();\n        Context context = searchActivity != null ? searchActivity.getContext() : null;\n        if (context == null)\n            return null;\n\n        // Have we ever made the same query and selected something ?\n        List<ValuedHistoryRecord> lastIdsForQuery = DBHelper.getPreviousResultsForQuery(context, trimmedQuery);\n        knownIds.clear();\n        for (ValuedHistoryRecord id : lastIdsForQuery) {\n            knownIds.put(id.record, (int) id.value);\n        }\n\n        // Request results via \"addResult\"\n        TBApplication.getApplication(context).getDataHandler().requestResults(trimmedQuery, this);\n        return null;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/searcher/ResultBuffer.java",
    "content": "package rocks.tbog.tblauncher.searcher;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\n\nimport rocks.tbog.tblauncher.entry.EntryItem;\n\npublic class ResultBuffer<T extends EntryItem> implements ISearcher {\n    private final boolean tagsEnabled;\n    private final ArrayList<T> entryItems = new ArrayList<>();\n    Class<T> typeClass;\n\n    public ResultBuffer(boolean tagsEnabled, Class<T> typeClass) {\n        this.tagsEnabled = tagsEnabled;\n        this.typeClass = typeClass;\n    }\n\n    public Collection<T> getEntryItems() {\n        return entryItems;\n    }\n\n    @Override\n    public boolean addResult(EntryItem... items) {\n        boolean result = false;\n        for (EntryItem item : items)\n            result |= entryItems.add(typeClass.cast(item));\n        return result;\n    }\n\n    @Override\n    public boolean tagsEnabled() {\n        return tagsEnabled;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/searcher/Searcher.java",
    "content": "package rocks.tbog.tblauncher.searcher;\n\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.util.Log;\n\nimport androidx.annotation.CallSuper;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.WorkerThread;\n\nimport java.lang.ref.WeakReference;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.PriorityQueue;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\n\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.WorkAsync.AsyncTask;\nimport rocks.tbog.tblauncher.WorkAsync.TaskRunner;\nimport rocks.tbog.tblauncher.entry.EntryItem;\nimport rocks.tbog.tblauncher.utils.PrefCache;\nimport rocks.tbog.tblauncher.utils.Utilities;\n\npublic abstract class Searcher extends AsyncTask<Void, Void> implements ISearcher {\n    // define a different thread than the default AsyncTask thread or else we will block everything else that uses AsyncTask while we search\n    public static final ExecutorService SEARCH_THREAD = Executors.newSingleThreadExecutor();\n    protected static final int INITIAL_CAPACITY = 50;\n    protected final WeakReference<ISearchActivity> activityWeakReference;\n    protected final PriorityQueue<EntryItem> processedPojos;\n    protected final int maxResults;\n    private final boolean tagsEnabled;\n    private long start;\n    /**\n     * Set to true when we are simply refreshing current results (scroll will not be reset)\n     * When false, we reset the scroll back to the last item in the list\n     */\n    private boolean isRefresh = false;\n    @NonNull\n    protected final String query;\n\n    public Searcher(ISearchActivity activity, @NonNull String query) {\n        super();\n        this.query = query;\n        activityWeakReference = new WeakReference<>(activity);\n        processedPojos = getPojoProcessor(activity);\n        tagsEnabled = PrefCache.getFuzzySearchTags(activity.getContext());\n        maxResults = getMaxResultCount(activity.getContext());\n    }\n\n    @Nullable\n    public Context getContext() {\n        ISearchActivity activity = activityWeakReference.get();\n        return activity != null ? activity.getContext() : null;\n    }\n\n    @NonNull\n    public String getQuery() {\n        return query;\n    }\n\n    protected PriorityQueue<EntryItem> getPojoProcessor(ISearchActivity activity) {\n        return new PriorityQueue<>(INITIAL_CAPACITY, EntryItem.RELEVANCE_COMPARATOR);\n    }\n\n    protected int getMaxResultCount(Context context) {\n        return PrefCache.getResultSearcherCap(context);\n    }\n\n    /**\n     * This is called from the background thread by the providers\n     */\n    @WorkerThread\n    @Override\n    public boolean addResult(EntryItem... pojos) {\n        if (isCancelled())\n            return false;\n\n        ISearchActivity searchActivity = activityWeakReference.get();\n        Activity activity = searchActivity != null ? Utilities.getActivity(searchActivity.getContext()) : null;\n        if (activity == null)\n            return false;\n\n        Collections.addAll(processedPojos, pojos);\n        while (processedPojos.size() > maxResults)\n            processedPojos.poll();\n\n        return true;\n    }\n\n    @CallSuper\n    @Override\n    protected void onPreExecute() {\n        super.onPreExecute();\n        start = System.currentTimeMillis();\n\n        displayActivityLoader();\n    }\n\n    private void displayActivityLoader() {\n        ISearchActivity searchActivity = activityWeakReference.get();\n        Activity activity = searchActivity != null ? Utilities.getActivity(searchActivity.getContext()) : null;\n        if (activity == null)\n            return;\n\n        searchActivity.displayLoader(true);\n    }\n\n    @Override\n    protected void onPostExecute(Void param) {\n        ISearchActivity searchActivity = activityWeakReference.get();\n        Activity activity = searchActivity != null ? Utilities.getActivity(searchActivity.getContext()) : null;\n        if (activity == null)\n            return;\n\n        // Loader should still be displayed until all the providers have finished loading\n        searchActivity.displayLoader(!TBApplication.getApplication(activity).getDataHandler().fullLoadOverSent());\n\n        if (this.processedPojos.isEmpty()) {\n            searchActivity.clearAdapter();\n        } else {\n            PriorityQueue<EntryItem> queue = this.processedPojos;\n            ArrayList<EntryItem> results = new ArrayList<>(queue.size());\n            while (queue.peek() != null) {\n                results.add(queue.poll());\n            }\n\n            searchActivity.updateAdapter(results, isRefresh);\n        }\n\n        searchActivity.resetTask();\n\n        long time = System.currentTimeMillis() - start;\n        Log.v(\"Timing\", \"Time to run query `\" + query + \"` on \" + getClass().getSimpleName() + \" to completion: \" + time + \"ms\");\n    }\n\n    public void setRefresh(boolean refresh) {\n        isRefresh = refresh;\n    }\n\n    @Override\n    public boolean tagsEnabled() {\n        return tagsEnabled;\n    }\n\n    public void execute() {\n        TaskRunner.executeOnExecutor(Searcher.SEARCH_THREAD, this);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/searcher/TagList.java",
    "content": "package rocks.tbog.tblauncher.searcher;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.SharedPreferences;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.WorkerThread;\nimport androidx.preference.PreferenceManager;\n\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.PriorityQueue;\n\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.dataprovider.TagsProvider;\nimport rocks.tbog.tblauncher.entry.ActionEntry;\nimport rocks.tbog.tblauncher.entry.EntryItem;\nimport rocks.tbog.tblauncher.entry.EntryWithTags;\nimport rocks.tbog.tblauncher.entry.TagEntry;\nimport rocks.tbog.tblauncher.handler.DataHandler;\nimport rocks.tbog.tblauncher.utils.PrefCache;\nimport rocks.tbog.tblauncher.utils.PrefOrderedListHelper;\nimport rocks.tbog.tblauncher.utils.Utilities;\n\npublic class TagList extends Searcher {\n    final HashSet<String> foundIdSet = new HashSet<>();\n\n    public TagList(ISearchActivity activity, @NonNull String query) {\n        super(activity, query);\n    }\n\n    @Override\n    protected PriorityQueue<EntryItem> getPojoProcessor(ISearchActivity activity) {\n        if (\"untagged\".equals(query))\n            return new PriorityQueue<>(INITIAL_CAPACITY, EntryItem.NAME_COMPARATOR);\n        return super.getPojoProcessor(activity);\n    }\n\n    @WorkerThread\n    @Override\n    public boolean addResult(EntryItem... pojos) {\n        if (isCancelled())\n            return false;\n\n        ISearchActivity searchActivity = activityWeakReference.get();\n        Activity activity = searchActivity != null ? Utilities.getActivity(searchActivity.getContext()) : null;\n        if (activity == null)\n            return false;\n\n        // only allow untagged entries\n        for (EntryItem entryItem : pojos) {\n            if (entryItem instanceof EntryWithTags) {\n                if (((EntryWithTags) entryItem).getTags().isEmpty()) {\n                    addProcessedPojo(entryItem);\n                }\n            }\n        }\n        return true;\n    }\n\n    private void addProcessedPojo(EntryItem entryItem) {\n        // if id already processed, skip it\n        if (!foundIdSet.add(entryItem.id))\n            return;\n\n        processedPojos.add(entryItem);\n        if (processedPojos.size() > maxResults)\n            processedPojos.poll();\n    }\n\n    @WorkerThread\n    @Override\n    protected Void doInBackground(Void param) {\n        ISearchActivity searchActivity = activityWeakReference.get();\n        Context context = searchActivity != null ? searchActivity.getContext() : null;\n        if (context == null)\n            return null;\n\n        DataHandler dh = TBApplication.dataHandler(context);\n\n        // Request results via \"addResult\"\n        if (\"untagged\".equals(query))\n            dh.requestAllRecords(this);\n        else if (\"list\".equals(query) || \"listReversed\".equals(query))\n        {\n            TagsProvider tagsProvider = dh.getTagsProvider();\n            boolean reversed = query.endsWith(\"Reversed\");\n\n            // add tags from the tags menu list\n            if (tagsProvider != null) {\n                SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n                List<String> tagOrder = PrefOrderedListHelper.getOrderedList(pref, \"tags-menu-list\", \"tags-menu-order\");\n                for (String orderValue : tagOrder) {\n                    String tagName = PrefOrderedListHelper.getOrderedValueName(orderValue);\n                    int order = 10 * PrefOrderedListHelper.getOrderedValueIndex(orderValue);\n\n                    TagEntry tagEntry = tagsProvider.getTagEntry(tagName);\n                    tagEntry.setRelevance(tagEntry.normalizedName, null);\n                    tagEntry.boostRelevance(reversed ? order : -order);\n                    addProcessedPojo(tagEntry);\n                }\n            }\n\n            // add the show untagged action to the result list\n            EntryItem untaggedEntry;\n            boolean bAddUntagged = PrefCache.showTagsMenuUntagged(context);\n            if (bAddUntagged) {\n                untaggedEntry = TBApplication.dataHandler(context).getPojo(ActionEntry.SCHEME + \"show/untagged\");\n                if (untaggedEntry instanceof ActionEntry) {\n                    int idx = -1 + 10 * PrefCache.getTagsMenuUntaggedIndex(context);\n\n                    untaggedEntry.setRelevance(untaggedEntry.normalizedName, null);\n                    untaggedEntry.boostRelevance(reversed ? idx : -idx);\n                    addProcessedPojo(untaggedEntry);\n                }\n            }\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/searcher/TagSearcher.java",
    "content": "package rocks.tbog.tblauncher.searcher;\n\nimport android.app.Activity;\nimport android.content.Context;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.WorkerThread;\n\nimport java.util.HashSet;\nimport java.util.PriorityQueue;\n\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.entry.EntryItem;\nimport rocks.tbog.tblauncher.entry.EntryWithTags;\nimport rocks.tbog.tblauncher.utils.Utilities;\n\npublic class TagSearcher extends Searcher {\n    final EntryWithTags.TagDetails tagDetails;\n    final HashSet<String> foundIdSet = new HashSet<>();\n\n    public TagSearcher(ISearchActivity activity, @NonNull String query) {\n        super(activity, query);\n        tagDetails = new EntryWithTags.TagDetails(query);\n    }\n\n    @Override\n    protected PriorityQueue<EntryItem> getPojoProcessor(ISearchActivity activity) {\n        return new PriorityQueue<>(INITIAL_CAPACITY, EntryItem.NAME_COMPARATOR);\n    }\n\n    @WorkerThread\n    @Override\n    public boolean addResult(EntryItem... pojos) {\n        if (isCancelled())\n            return false;\n\n        ISearchActivity searchActivity = activityWeakReference.get();\n        Activity activity = searchActivity != null ? Utilities.getActivity(searchActivity.getContext()) : null;\n        if (activity == null)\n            return false;\n\n        for (EntryItem entryItem : pojos) {\n            if (entryItem instanceof EntryWithTags) {\n                if (((EntryWithTags) entryItem).getTags().contains(tagDetails)) {\n                    addProcessedPojo((EntryWithTags) entryItem);\n                }\n            }\n        }\n        return true;\n    }\n\n    private void addProcessedPojo(EntryWithTags entryItem) {\n        // if id already processed, skip it\n        if (!foundIdSet.add(entryItem.id))\n            return;\n\n        processedPojos.add(entryItem);\n        if (processedPojos.size() > maxResults)\n            processedPojos.poll();\n    }\n\n    @WorkerThread\n    @Override\n    protected Void doInBackground(Void param) {\n        ISearchActivity searchActivity = activityWeakReference.get();\n        Context context = searchActivity != null ? searchActivity.getContext() : null;\n        if (context == null)\n            return null;\n\n        // Request results via \"addResult\"\n        TBApplication.getApplication(context).getDataHandler().requestAllRecords(this);\n        return null;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/shortcut/SaveSingleOreoShortcutAsync.java",
    "content": "package rocks.tbog.tblauncher.shortcut;\n\nimport android.annotation.TargetApi;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.pm.LauncherApps;\nimport android.content.pm.ShortcutInfo;\nimport android.os.Build;\nimport android.util.Log;\nimport android.widget.Toast;\n\nimport androidx.annotation.NonNull;\n\nimport java.lang.ref.WeakReference;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.WorkAsync.AsyncTask;\nimport rocks.tbog.tblauncher.dataprovider.ShortcutsProvider;\nimport rocks.tbog.tblauncher.db.ShortcutRecord;\nimport rocks.tbog.tblauncher.handler.DataHandler;\nimport rocks.tbog.tblauncher.utils.Utilities;\n\n@TargetApi(Build.VERSION_CODES.O)\npublic class SaveSingleOreoShortcutAsync extends AsyncTask<Void, Boolean> {// AsyncTask<Void, Integer, Boolean> {\n\n    private static final String TAG = \"OreoShortcutAsync\";\n    private final WeakReference<Context> context;\n    private final WeakReference<DataHandler> dataHandler;\n    private final Intent intent;\n\n    public SaveSingleOreoShortcutAsync(@NonNull Context context, @NonNull Intent intent) {\n        this.context = new WeakReference<>(context);\n        this.dataHandler = new WeakReference<>(TBApplication.getApplication(context).getDataHandler());\n        this.intent = intent;\n    }\n\n\n    @Override\n    protected Boolean doInBackground(Void v) {\n\n        final LauncherApps.PinItemRequest pinItemRequest = intent.getParcelableExtra(LauncherApps.EXTRA_PIN_ITEM_REQUEST);\n        final ShortcutInfo shortcutInfo = pinItemRequest != null ? pinItemRequest.getShortcutInfo() : null;\n\n        if (shortcutInfo == null) {\n            cancel(true);\n            return null;\n        }\n\n        Context context = this.context.get();\n        if (context == null) {\n            cancel(true);\n            return null;\n        }\n\n        // Create Pojo\n        ShortcutRecord record = ShortcutUtil.createShortcutRecord(context, shortcutInfo, false);\n        if (record == null) {\n            return false;\n        }\n\n        final DataHandler dataHandler = this.dataHandler.get();\n        if (dataHandler == null) {\n            cancel(true);\n            return null;\n        }\n\n        try {\n            if (!pinItemRequest.accept())\n                return false;\n        } catch (IllegalStateException e) {\n            return false;\n        }\n\n        // Add shortcut to the DataHandler\n        return dataHandler.addShortcut(record);\n    }\n\n    @Override\n    protected void onPostExecute(Boolean success) {\n        if (success == null || isCancelled()) {\n            Context ctx = context.get();\n            if (ctx != null)\n                Toast.makeText(ctx, R.string.cant_pin_shortcut, Toast.LENGTH_LONG).show();\n            return;\n        }\n        if (success) {\n            Log.i(TAG, \"Shortcut added to KISS\");\n\n            final DataHandler dataHandler = this.dataHandler.get();\n            if (dataHandler == null)\n                return;\n            ShortcutsProvider shortcutsProvider = dataHandler.getShortcutsProvider();\n            if (shortcutsProvider != null)\n                shortcutsProvider.reload(true);\n        }\n    }\n\n    public void execute() {\n        Utilities.executeAsync(this);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/shortcut/ShortcutUtil.java",
    "content": "package rocks.tbog.tblauncher.shortcut;\n\nimport android.annotation.TargetApi;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.SharedPreferences;\nimport android.content.pm.ApplicationInfo;\nimport android.content.pm.LauncherApps;\nimport android.content.pm.PackageManager;\nimport android.content.pm.ShortcutInfo;\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.graphics.drawable.Drawable;\nimport android.os.Build;\nimport android.os.Process;\nimport android.os.UserHandle;\nimport android.os.UserManager;\nimport android.text.TextUtils;\nimport android.util.Log;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.WorkerThread;\nimport androidx.preference.PreferenceManager;\n\nimport java.io.ByteArrayOutputStream;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\nimport rocks.tbog.tblauncher.db.DBHelper;\nimport rocks.tbog.tblauncher.db.ShortcutRecord;\nimport rocks.tbog.tblauncher.utils.Utilities;\n\nimport static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_MANIFEST;\nimport static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED;\n\npublic class ShortcutUtil {\n\n    final static private String TAG = \"ShortcutUtil\";\n\n    /**\n     * @return true if shortcuts are enabled in settings and android version is higher or equals android 8\n     */\n    public static boolean areShortcutsEnabled(Context context) {\n        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);\n        return android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&\n                prefs.getBoolean(\"enable-shortcuts\", true);\n\n    }\n\n    /**\n     * Save single shortcut to DB via pin request\n     */\n    @TargetApi(Build.VERSION_CODES.O)\n    public static void addShortcut(Context context, Intent intent) {\n        new SaveSingleOreoShortcutAsync(context, intent).execute();\n    }\n\n    /**\n     * Remove all shortcuts saved in the database\n     */\n    public static void removeAllShortcuts(Context context) {\n        DBHelper.removeAllShortcuts(context);\n    }\n\n    /**\n     * @return all shortcuts from all applications available on the device\n     */\n    @TargetApi(Build.VERSION_CODES.O)\n    public static List<ShortcutInfo> getAllShortcuts(Context context) {\n        return getShortcut(context, null);\n    }\n\n    @TargetApi(Build.VERSION_CODES.O)\n    public static List<ShortcutInfo> getShortcut(Context context, String packageName) {\n        return getShortcut(context, packageName, FLAG_MATCH_MANIFEST | FLAG_MATCH_PINNED);\n    }\n\n    /**\n     * @return all shortcuts for given package name\n     */\n    @TargetApi(Build.VERSION_CODES.O)\n    public static List<ShortcutInfo> getShortcut(Context context, String packageName, int queryFlags) {\n        List<ShortcutInfo> shortcutInfoList = new ArrayList<>();\n\n        UserManager manager = (UserManager) context.getSystemService(Context.USER_SERVICE);\n        assert manager != null;\n        LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);\n        assert launcherApps != null;\n        if (launcherApps.hasShortcutHostPermission()) {\n            LauncherApps.ShortcutQuery shortcutQuery = new LauncherApps.ShortcutQuery();\n            shortcutQuery.setQueryFlags(queryFlags);\n\n            if (!TextUtils.isEmpty(packageName)) {\n                shortcutQuery.setPackage(packageName);\n            }\n\n            for (android.os.UserHandle profile : manager.getUserProfiles()) {\n                List<ShortcutInfo> list;\n                try {\n                    list = launcherApps.getShortcuts(shortcutQuery, profile);\n                } catch (IllegalStateException e) {\n                    // profile is locked or user not running\n                    list = null;\n                }\n                if (list != null)\n                    shortcutInfoList.addAll(list);\n            }\n        }\n        return shortcutInfoList;\n    }\n\n    /**\n     * Create ShortcutPojo from ShortcutInfo\n     */\n    @TargetApi(Build.VERSION_CODES.O)\n    public static ShortcutRecord createShortcutRecord(Context context, ShortcutInfo shortcutInfo, boolean includePackageName) {\n        ShortcutRecord record = new ShortcutRecord();\n        record.packageName = shortcutInfo.getPackage();\n        record.infoData = shortcutInfo.getId();\n        record.addFlags(ShortcutRecord.FLAG_OREO);\n\n        LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);\n        assert launcherApps != null;\n        final Drawable iconDrawable = launcherApps.getShortcutIconDrawable(shortcutInfo, 0);\n        record.iconPng = iconDrawable != null ? getIconBlob(iconDrawable) : null;\n\n        String appName = includePackageName ? getAppNameFromPackageName(context, shortcutInfo.getPackage()) : null;\n\n        if (shortcutInfo.getShortLabel() != null) {\n            if (!TextUtils.isEmpty(appName)) {\n                record.displayName = appName + \": \" + shortcutInfo.getShortLabel().toString();\n            } else {\n                record.displayName = shortcutInfo.getShortLabel().toString();\n            }\n        } else if (shortcutInfo.getLongLabel() != null) {\n            if (!TextUtils.isEmpty(appName)) {\n                record.displayName = appName + \": \" + shortcutInfo.getLongLabel().toString();\n            } else {\n                record.displayName = shortcutInfo.getLongLabel().toString();\n            }\n        } else {\n            Log.d(TAG, \"Invalid shortcut for \" + record.packageName + \", ignoring\");\n            return null;\n        }\n\n        return record;\n    }\n\n    /**\n     * @return App name from package name\n     */\n    @NonNull\n    public static String getAppNameFromPackageName(Context context, String packageName) {\n        try {\n            PackageManager packageManager = context.getPackageManager();\n            ApplicationInfo info = packageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA);\n            return packageManager.getApplicationLabel(info).toString();\n        } catch (PackageManager.NameNotFoundException ignored) {\n            return \"\";\n        }\n    }\n\n    @NonNull\n    public static byte[] getIconBlob(@NonNull Drawable iconDrawable) {\n        Bitmap icon = Utilities.drawableToBitmap(iconDrawable);\n        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();\n        // Can't user WEBP compression because from API v18 to v21 there is no alpha encoding\n        // see: https://stackoverflow.com/questions/38753798/android-webp-encoding-in-api-v18-and-above-bitmap-compressbitmap-compressforma\n        icon.compress(Bitmap.CompressFormat.PNG, 100, outputStream);\n        return outputStream.toByteArray();\n    }\n\n    @Nullable\n    public static Bitmap getInitialIcon(@NonNull Context context, long dbId) {\n        byte[] iconBlob = DBHelper.getShortcutIcon(context, dbId);\n\n        if (iconBlob != null)\n            return BitmapFactory.decodeByteArray(iconBlob, 0, iconBlob.length);\n\n        return null;\n    }\n\n    /**\n     * Removes the given shortcut from the current list of pinned shortcuts.\n     * (Should run on background thread)\n     */\n    @WorkerThread\n    public static void removeShortcut(@NonNull Context context, @NonNull ShortcutInfo shortcutInfo) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {\n            LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);\n            assert launcherApps != null;\n            String packageName = shortcutInfo.getPackage();\n            String id = shortcutInfo.getId();\n            UserHandle user = shortcutInfo.getUserHandle();\n\n            // query for pinned shortcuts\n            List<String> shortcutIds;\n            {\n                LauncherApps.ShortcutQuery q = new LauncherApps.ShortcutQuery();\n                q.setQueryFlags(LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED);\n                List<ShortcutInfo> shortcutInfos = launcherApps.getShortcuts(q, Process.myUserHandle());\n                if (shortcutInfos == null)\n                    shortcutInfos = Collections.emptyList();\n                shortcutIds = new ArrayList<>(shortcutInfos.size());\n                for (ShortcutInfo info : shortcutInfos)\n                    shortcutIds.add(info.getId());\n            }\n\n            // unpin the shortcut\n            shortcutIds.remove(id);\n            try {\n                launcherApps.pinShortcuts(packageName, shortcutIds, user);\n            } catch (SecurityException | IllegalStateException e) {\n                Log.w(TAG, \"Failed to unpin shortcut\", e);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/ui/BlockableListView.java",
    "content": "package rocks.tbog.tblauncher.ui;\n\nimport android.annotation.SuppressLint;\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android.view.MotionEvent;\nimport android.widget.ListView;\n\n/**\n * ListView subclass that provides an interface for (temporarily) blocking all of it's input events\n */\npublic class BlockableListView extends ListView {\n    private boolean touchEventsBlocked = false;\n\n    public BlockableListView(Context context) {\n        super(context);\n    }\n\n    public BlockableListView(Context context, AttributeSet attrs) {\n        super(context, attrs);\n    }\n\n    public BlockableListView(Context context, AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n    }\n\n    /**\n     * Prevent this ListView from receiving any new touch events\n     * <p>\n     * Use {@link #unblockTouchEvents()} to end the blockage.\n     */\n    public void blockTouchEvents() {\n        this.touchEventsBlocked = true;\n    }\n\n    /**\n     * Stop preventing this ListView from receiving touch events\n     */\n    public void unblockTouchEvents() {\n        this.touchEventsBlocked = false;\n    }\n\n    @SuppressLint(\"ClickableViewAccessibility\")\n    @Override\n    public boolean onTouchEvent(MotionEvent ev) {\n        return this.touchEventsBlocked || super.onTouchEvent(ev);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/ui/BottomPullEffectView.java",
    "content": "package rocks.tbog.tblauncher.ui;\n\nimport android.content.Context;\nimport android.graphics.Canvas;\nimport android.os.Build;\nimport android.util.AttributeSet;\nimport android.view.View;\nimport android.widget.EdgeEffect;\n\n/**\n * View that renders that over-scroll/\"pulled too far\" effect\n * <p>\n * Parts (or even all) of the given effect parameters may be discarded with the underlying Android\n * platform does not support them.\n */\npublic class BottomPullEffectView extends View {\n    private EdgeEffect effect;\n    private float lastPullDistance;\n    private float lastPullDisplacement;\n    private boolean lastPullAnimated;\n\n    public BottomPullEffectView(Context context) {\n        super(context);\n    }\n\n    public BottomPullEffectView(Context context, AttributeSet attrs) {\n        super(context, attrs);\n    }\n\n    public BottomPullEffectView(Context context, AttributeSet attrs, int flags) {\n        super(context, attrs, flags);\n    }\n\n    /**\n     * Force the pull effect to display the given pull distance and left/right displacement\n     *\n     * @param distance     Pull distance (0.0f – 1.0f)\n     * @param displacement Left/right displacement (0.0f → right, 0.5f → center, 1.0f → left)\n     * @param animated     Should this pull eventually fade away?\n     */\n    public void setPull(float distance, float displacement, boolean animated) {\n        // Reset internal effect state by creating a new instance\n        //XXX: This may cause unnecessary GC runs!\n        this.effect = new EdgeEffect(this.getContext());\n\n        // Provide new pull effect data\n        this.effect.setSize(this.getWidth(), this.getHeight());\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            this.effect.onPull(distance, displacement);\n        } else {\n            this.effect.onPull(distance);\n        }\n\n        if (!animated) {\n            // Prevent more than one frame being drawn\n            this.effect.finish();\n        }\n\n        this.lastPullDistance = distance;\n        this.lastPullDisplacement = displacement;\n        this.lastPullAnimated = animated;\n\n        // Request scene to be redrawn\n        this.invalidate();\n    }\n\n    /**\n     * Draw a release animation for the previous pull effect\n     */\n    public void releasePull() {\n        if (this.effect == null) {\n            return;\n        }\n\n        // Recreate effect without `finish()`-ing it, so that the release effect will be\n        // properly drawn\n        if (!this.lastPullAnimated) {\n            this.setPull(this.lastPullDistance, this.lastPullDisplacement, true);\n        }\n\n        this.effect.onRelease();\n        this.invalidate();\n    }\n\n    @Override\n    protected void onDraw(Canvas canvas) {\n        super.onDraw(canvas);\n\n        if (this.effect == null) {\n            return;\n        }\n\n        final int canvas_save_count = canvas.save();\n        canvas.translate(-this.getWidth(), this.getHeight());\n        canvas.rotate(180, this.getWidth(), 0);\n\n        this.effect.setSize(this.getWidth(), this.getHeight());\n        boolean invalidate = this.effect.draw(canvas);\n\n        canvas.restoreToCount(canvas_save_count);\n\n        if (invalidate) {\n            this.invalidate();\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/ui/CenteredImageSpan.java",
    "content": "package rocks.tbog.tblauncher.ui;\n\nimport android.graphics.Canvas;\nimport android.graphics.Paint;\nimport android.graphics.Rect;\nimport android.graphics.drawable.Drawable;\nimport android.text.style.ImageSpan;\n\nimport androidx.annotation.NonNull;\n\npublic class CenteredImageSpan extends ImageSpan {\n\n    // Extra variables used to redefine the Font Metrics when an ImageSpan is added\n    private int initialDescent = 0;\n    private int extraSpace = 0;\n\n    public CenteredImageSpan(@NonNull Drawable drawable) {\n        super(drawable, ImageSpan.ALIGN_BOTTOM);\n    }\n\n    @Override\n    public int getSize(Paint paint, CharSequence text,\n                       int start, int end,\n                       Paint.FontMetricsInt fm) {\n        Drawable d = getDrawable();\n        Rect rect = d.getBounds();\n\n        if (fm != null) {\n            // Centers the text with the ImageSpan\n            if (rect.bottom - (fm.descent - fm.ascent) >= 0) {\n                // Stores the initial descent and computes the margin available\n                initialDescent = fm.descent;\n                extraSpace = rect.bottom - (fm.descent - fm.ascent);\n            }\n\n            fm.descent = extraSpace / 2 + initialDescent;\n            fm.bottom = fm.descent;\n\n            fm.ascent = -rect.bottom + fm.descent;\n            fm.top = fm.ascent;\n        }\n\n        return rect.right;\n    }\n\n    @Override\n    public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint) {\n        Drawable b = getDrawable();\n        canvas.save();\n\n        // center\n        int transY = top + (bottom - top) / 2 - b.getBounds().height() / 2;\n\n        canvas.translate(x, transY);\n        b.draw(canvas);\n        canvas.restore();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/ui/CustomizeMarginView.java",
    "content": "package rocks.tbog.tblauncher.ui;\n\nimport android.annotation.SuppressLint;\nimport android.content.Context;\nimport android.graphics.Canvas;\nimport android.graphics.Paint;\nimport android.graphics.Rect;\nimport android.util.AttributeSet;\nimport android.view.MotionEvent;\nimport android.view.View;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport rocks.tbog.tblauncher.R;\n\npublic class CustomizeMarginView extends View {\n    private final Rect targetRect = new Rect();\n    private final Rect windowRect = new Rect();\n    private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);\n    private float offsetX = 0f;\n    private float offsetY = 0f;\n    private int bgColor1;\n    private int bgColor2;\n    private float horizontalMargin;\n    private float verticalMargin;\n    private float offsetScale;\n    private final int _padding;\n    private final int _width;\n    private final int _height;\n    private final float _sampleRadius;\n    private final float _sampleFrameRadius;\n    private OnOffsetChanged onOffsetChanged = null;\n    private final int colorSampleFrame;\n\n    public interface OnOffsetChanged {\n        void onOffsetChanged(float dx, float dy);\n    }\n\n    public CustomizeMarginView(Context context, @Nullable AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    public CustomizeMarginView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n        _padding = getResources().getDimensionPixelSize(R.dimen.mm2d_cc_panel_margin);\n        _width = getResources().getDimensionPixelSize(R.dimen.mm2d_cc_hsv_size) + _padding * 2;\n        _height = getResources().getDimensionPixelSize(R.dimen.mm2d_cc_hsv_size) + _padding * 2;\n        _sampleRadius = getResources().getDimension(R.dimen.mm2d_cc_sample_radius);\n        _sampleFrameRadius = _sampleRadius + getResources().getDimension(R.dimen.mm2d_cc_sample_frame);\n        colorSampleFrame = getResources().getColor(R.color.mm2d_cc_sample_frame);\n\n        paint.setStrokeWidth(0f);\n        setLayerType(LAYER_TYPE_SOFTWARE, paint);\n\n        bgColor1 = 0xFF000000;\n        bgColor2 = 0xFFffffff;\n\n        horizontalMargin = _padding * 2;\n        verticalMargin = _padding * 2;\n        offsetScale = Math.max(horizontalMargin, verticalMargin);\n    }\n\n    public void setOnOffsetChanged(OnOffsetChanged listener) {\n        onOffsetChanged = listener;\n    }\n\n    public void setPreviewColors(int bgColor1, int bgColor2) {\n        this.bgColor1 = bgColor1;\n        this.bgColor2 = bgColor2;\n    }\n\n    public void setMarginParameters(float horizontalMargin, float verticalMargin) {\n        this.horizontalMargin = horizontalMargin;\n        this.verticalMargin = verticalMargin;\n        this.offsetScale = Math.max(horizontalMargin, verticalMargin);\n        invalidate();\n    }\n\n    public void setOffsetValues(float offsetX, float offsetY) {\n        this.offsetX = offsetX;\n        this.offsetY = offsetY;\n        invalidate();\n    }\n\n    @Override\n    protected void onDraw(@NonNull Canvas canvas) {\n        if (targetRect.isEmpty()) {\n            canvas.getClipBounds(targetRect);\n        }\n        windowRect.set(targetRect);\n        windowRect.inset(Math.round(horizontalMargin), Math.round(verticalMargin));\n        windowRect.offset(Math.round(offsetX), Math.round(offsetY));\n\n        // background\n        paint.setColor(bgColor1);\n        paint.setStyle(Paint.Style.FILL_AND_STROKE);\n        canvas.drawRect(targetRect, paint);\n\n        // window\n        paint.setColor(bgColor2);\n        paint.setStyle(Paint.Style.FILL_AND_STROKE);\n        canvas.drawRect(windowRect, paint);\n\n        // draw offset circle\n        var x = (offsetX / offsetScale * .5f + .5f) * targetRect.width() + targetRect.left;\n        var y = (offsetY / offsetScale * .5f + .5f) * targetRect.height() + targetRect.top;\n\n        paint.setColor(colorSampleFrame);\n        canvas.drawCircle(x, y, _sampleFrameRadius, paint);\n        paint.setColor(bgColor1);\n        canvas.drawCircle(x, y, _sampleRadius, paint);\n    }\n\n    private static float clampOffset(float value) {\n        return Math.min(1f, Math.max(0f, value));\n    }\n\n    @SuppressLint(\"ClickableViewAccessibility\")\n    @Override\n    public boolean onTouchEvent(MotionEvent event) {\n        if (event.getAction() == MotionEvent.ACTION_DOWN) {\n            getParent().requestDisallowInterceptTouchEvent(true);\n        }\n        offsetX = clampOffset((event.getX() - targetRect.left) / targetRect.width());\n        offsetY = clampOffset((event.getY() - targetRect.top) / targetRect.height());\n\n        // center and scale offset\n        offsetX = (offsetX * 2f - 1f) * offsetScale;\n        offsetY = (offsetY * 2f - 1f) * offsetScale;\n\n        // round values\n        offsetX = Math.round(offsetX);\n        offsetY = Math.round(offsetY);\n\n        if (onOffsetChanged != null)\n            onOffsetChanged.onOffsetChanged(offsetX, offsetY);\n        invalidate();\n        return true;\n    }\n\n    @Override\n    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {\n        targetRect.set(\n            getPaddingLeft() + _padding,\n            getPaddingTop() + _padding,\n            getWidth() - getPaddingRight() - _padding,\n            getHeight() - getPaddingBottom() - _padding\n        );\n    }\n\n    @Override\n    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n        final var paddingHorizontal = getPaddingLeft() + getPaddingRight();\n        final var paddingVertical = getPaddingTop() + getPaddingBottom();\n        final var resizeWidth = MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY;\n        final var resizeHeight = MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;\n\n        if (!resizeWidth && !resizeHeight) {\n            setMeasuredDimension(\n                resolveSizeAndState(\n                    Math.max(_width + paddingHorizontal, getSuggestedMinimumWidth()),\n                    widthMeasureSpec,\n                    MeasureSpec.UNSPECIFIED\n                ),\n                resolveSizeAndState(\n                    Math.max(_height + paddingVertical, getSuggestedMinimumHeight()),\n                    heightMeasureSpec,\n                    MeasureSpec.UNSPECIFIED\n                )\n            );\n            return;\n        }\n\n        var widthSize = resolveAdjustedSize(_width + paddingHorizontal, widthMeasureSpec);\n        var heightSize = resolveAdjustedSize(_height + paddingVertical, heightMeasureSpec);\n        var actualAspect = (widthSize - paddingHorizontal) / (float) (heightSize - paddingVertical);\n\n        if (Math.abs(actualAspect - 1f) < 0.0000001f) {\n            setMeasuredDimension(widthSize, heightSize);\n            return;\n        }\n        if (resizeWidth) {\n            final var newWidth = heightSize - paddingVertical + paddingHorizontal;\n            if (!resizeHeight) {\n                widthSize = resolveAdjustedSize(newWidth, widthMeasureSpec);\n            }\n            if (newWidth <= widthSize) {\n                widthSize = newWidth;\n                setMeasuredDimension(widthSize, heightSize);\n                return;\n            }\n        }\n        if (resizeHeight) {\n            final var newHeight = widthSize - paddingHorizontal + paddingVertical;\n            if (!resizeWidth) {\n                heightSize = resolveAdjustedSize(newHeight, heightMeasureSpec);\n            }\n            if (newHeight <= heightSize) {\n                heightSize = newHeight;\n            }\n        }\n        setMeasuredDimension(widthSize, heightSize);\n    }\n\n    private int resolveAdjustedSize(int desiredSize, int measureSpec) {\n        int specMode = MeasureSpec.getMode(measureSpec);\n        int specSize = MeasureSpec.getSize(measureSpec);\n        switch (specMode) {\n            case MeasureSpec.AT_MOST:\n                return Math.min(desiredSize, specSize);\n            case MeasureSpec.EXACTLY:\n                return specSize;\n            case MeasureSpec.UNSPECIFIED:\n            default:\n                return desiredSize;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/ui/CustomizeShadowView.java",
    "content": "package rocks.tbog.tblauncher.ui;\n\nimport android.annotation.SuppressLint;\nimport android.content.Context;\nimport android.graphics.Canvas;\nimport android.graphics.Paint;\nimport android.graphics.Rect;\nimport android.util.AttributeSet;\nimport android.view.MotionEvent;\nimport android.view.View;\n\nimport androidx.annotation.Nullable;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.utils.UISizes;\n\npublic class CustomizeShadowView extends View {\n    private final Rect targetRect = new Rect();\n    private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);\n    private float offsetX = 0f;\n    private float offsetY = 0f;\n    private float shadowRadius;\n    private int shadowColor;\n    private int textColor;\n    private float textSize;\n    private int bgColor1;\n    private int bgColor2;\n    private final int _gridSize;\n    private final float _offsetScale = 5f;\n    private final int _padding;\n    private final int _width;\n    private final int _height;\n    private final float _sampleRadius;\n    private final float _sampleFrameRadius;\n    private final float _sampleShadowRadius;\n    private String text;\n    private String[] lines;\n    private OnOffsetChanged onOffsetChanged = null;\n    private final int colorSampleFrame;\n    private final int colorSampleShadow;\n\n    public interface OnOffsetChanged {\n        void onOffsetChanged(float dx, float dy);\n    }\n\n    public CustomizeShadowView(Context context, @Nullable AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    public CustomizeShadowView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n        _gridSize = getResources().getInteger(R.integer.shadow_preview_grid);\n        _padding = getResources().getDimensionPixelSize(R.dimen.mm2d_cc_panel_margin);\n        _width = getResources().getDimensionPixelSize(R.dimen.mm2d_cc_hsv_size) + _padding * 2;\n        _height = getResources().getDimensionPixelSize(R.dimen.mm2d_cc_hsv_size) + _padding * 2;\n        _sampleRadius = getResources().getDimension(R.dimen.mm2d_cc_sample_radius);\n        _sampleFrameRadius = _sampleRadius + getResources().getDimension(R.dimen.mm2d_cc_sample_frame);\n        _sampleShadowRadius = _sampleFrameRadius + getResources().getDimension(R.dimen.mm2d_cc_sample_shadow);\n        colorSampleFrame = getResources().getColor(R.color.mm2d_cc_sample_frame);\n        colorSampleShadow = getResources().getColor(R.color.mm2d_cc_sample_shadow);\n\n        shadowRadius = 5f;\n        shadowColor = 0xFF000000;\n        setTextParameters(getResources().getText(R.string.shadow_offset_preview), 0xFFffffff, UISizes.sp2px(context, getResources().getInteger(R.integer.default_size_text)));\n\n        paint.setTextAlign(Paint.Align.CENTER);\n        setLayerType(LAYER_TYPE_SOFTWARE, paint);\n\n        bgColor1 = shadowColor;\n        bgColor2 = textColor;\n    }\n\n    public void setShadowParameters(float radius, float dx, float dy, int color) {\n        shadowRadius = radius;\n        offsetX = dx;\n        offsetY = dy;\n        shadowColor = color | 0xFF000000;\n        invalidate();\n    }\n\n    public void setTextParameters(@Nullable CharSequence text, int color, int size) {\n        if (text != null) {\n            this.text = text.toString();\n            lines = this.text.split(\"\\\\s\");\n        }\n        textColor = color;\n        textSize = size;\n        invalidate();\n    }\n\n    public void setOnOffsetChanged(OnOffsetChanged listener) {\n        onOffsetChanged = listener;\n    }\n\n    public void setBackgroundParameters(int bgColor1, int bgColor2) {\n        this.bgColor1 = bgColor1;\n        this.bgColor2 = bgColor2;\n    }\n\n    @Override\n    protected void onDraw(Canvas canvas) {\n        if (targetRect.isEmpty())\n            canvas.getClipBounds(targetRect);\n\n        paint.setColor(bgColor1);\n        canvas.drawRect(targetRect, paint);\n\n        //draw checker board\n        {\n            float size = targetRect.width() / (float) _gridSize;\n            paint.setColor(bgColor2);\n            for (int y = 0; y < _gridSize; y += 1)\n                for (int x = 0; x < _gridSize; x += 1)\n                    if ((x + y) % 2 == 0) {\n                        float cX = targetRect.left + x * size;\n                        float cY = targetRect.top + y * size;\n                        canvas.drawRect(cX, cY, cX + size, cY + size, paint);\n                    }\n        }\n\n        paint.setShadowLayer(shadowRadius, offsetX, offsetY, shadowColor);\n\n        final float centerX = targetRect.centerX();\n        // draw small text\n        paint.setColor(textColor);\n        paint.setTextSize(textSize);\n        canvas.drawText(text, centerX, targetRect.top - paint.ascent() + _offsetScale, paint);\n        canvas.drawText(text, centerX, targetRect.bottom - paint.descent() - _offsetScale, paint);\n\n        // prepare big text\n        paint.setTextSize(targetRect.height() / (lines.length + 2f));\n        final float lineHeight = paint.descent() - paint.ascent();\n        float lineY = targetRect.centerY() - (lineHeight * lines.length) / 2f - paint.descent();\n        // write big text split by whitespace\n        for (String line : lines) {\n            lineY += lineHeight;\n            canvas.drawText(line, centerX, lineY, paint);\n        }\n\n        paint.clearShadowLayer();\n\n        // draw offset circle\n        var x = (offsetX / _offsetScale * .5f + .5f) * targetRect.width() + targetRect.left;\n        var y = (offsetY / _offsetScale * .5f + .5f) * targetRect.height() + targetRect.top;\n\n        paint.setColor(colorSampleShadow);\n        canvas.drawCircle(x, y, _sampleShadowRadius, paint);\n        paint.setColor(colorSampleFrame);\n        canvas.drawCircle(x, y, _sampleFrameRadius, paint);\n        paint.setColor(shadowColor);\n        canvas.drawCircle(x, y, _sampleRadius, paint);\n    }\n\n    private static float clampOffset(float value) {\n        return Math.min(1f, Math.max(0f, value));\n    }\n\n    @SuppressLint(\"ClickableViewAccessibility\")\n    @Override\n    public boolean onTouchEvent(MotionEvent event) {\n        if (event.getAction() == MotionEvent.ACTION_DOWN) {\n            getParent().requestDisallowInterceptTouchEvent(true);\n        }\n        offsetX = clampOffset((event.getX() - targetRect.left) / targetRect.width());\n        offsetY = clampOffset((event.getY() - targetRect.top) / targetRect.height());\n\n        // center and scale offset\n        offsetX = (offsetX * 2f - 1f) * _offsetScale;\n        offsetY = (offsetY * 2f - 1f) * _offsetScale;\n\n        // round values to 1/10\n        offsetX = Math.round(offsetX * 10f) * .1f;\n        offsetY = Math.round(offsetY * 10f) * .1f;\n\n        if (onOffsetChanged != null)\n            onOffsetChanged.onOffsetChanged(offsetX, offsetY);\n        invalidate();\n        return true;\n    }\n\n    @Override\n    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {\n        targetRect.set(\n            getPaddingLeft() + _padding,\n            getPaddingTop() + _padding,\n            getWidth() - getPaddingRight() - _padding,\n            getHeight() - getPaddingBottom() - _padding\n        );\n    }\n\n    @Override\n    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n        final var paddingHorizontal = getPaddingLeft() + getPaddingRight();\n        final var paddingVertical = getPaddingTop() + getPaddingBottom();\n        final var resizeWidth = MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY;\n        final var resizeHeight = MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;\n\n        if (!resizeWidth && !resizeHeight) {\n            setMeasuredDimension(\n                resolveSizeAndState(\n                    Math.max(_width + paddingHorizontal, getSuggestedMinimumWidth()),\n                    widthMeasureSpec,\n                    MeasureSpec.UNSPECIFIED\n                ),\n                resolveSizeAndState(\n                    Math.max(_height + paddingVertical, getSuggestedMinimumHeight()),\n                    heightMeasureSpec,\n                    MeasureSpec.UNSPECIFIED\n                )\n            );\n            return;\n        }\n\n        var widthSize = resolveAdjustedSize(_width + paddingHorizontal, widthMeasureSpec);\n        var heightSize = resolveAdjustedSize(_height + paddingVertical, heightMeasureSpec);\n        var actualAspect = (widthSize - paddingHorizontal) / (float) (heightSize - paddingVertical);\n\n        if (Math.abs(actualAspect - 1f) < 0.0000001f) {\n            setMeasuredDimension(widthSize, heightSize);\n            return;\n        }\n        if (resizeWidth) {\n            final var newWidth = heightSize - paddingVertical + paddingHorizontal;\n            if (!resizeHeight) {\n                widthSize = resolveAdjustedSize(newWidth, widthMeasureSpec);\n            }\n            if (newWidth <= widthSize) {\n                widthSize = newWidth;\n                setMeasuredDimension(widthSize, heightSize);\n                return;\n            }\n        }\n        if (resizeHeight) {\n            final var newHeight = widthSize - paddingHorizontal + paddingVertical;\n            if (!resizeWidth) {\n                heightSize = resolveAdjustedSize(newHeight, heightMeasureSpec);\n            }\n            if (newHeight <= heightSize) {\n                heightSize = newHeight;\n            }\n        }\n        setMeasuredDimension(widthSize, heightSize);\n    }\n\n    private int resolveAdjustedSize(int desiredSize, int measureSpec) {\n        int specMode = MeasureSpec.getMode(measureSpec);\n        int specSize = MeasureSpec.getSize(measureSpec);\n        switch (specMode) {\n            case MeasureSpec.AT_MOST:\n                return Math.min(desiredSize, specSize);\n            case MeasureSpec.EXACTLY:\n                return specSize;\n            case MeasureSpec.UNSPECIFIED:\n            default:\n                return desiredSize;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/ui/CutoutFactory.java",
    "content": "package rocks.tbog.tblauncher.ui;\n\nimport android.annotation.SuppressLint;\nimport android.annotation.TargetApi;\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.graphics.Rect;\nimport android.os.Build;\nimport android.view.DisplayCutout;\nimport android.view.View;\nimport android.view.WindowInsets;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport java.lang.reflect.Method;\n\nimport rocks.tbog.tblauncher.utils.DeviceUtils;\n\npublic class CutoutFactory {\n\n    @Nullable\n    public static ICutout getByManufacturer(Context context, String manufacturer) {\n        if (\"Huawei\".equalsIgnoreCase(manufacturer))\n            return new HuaweiCutout(context);\n        if (\"Oppo\".equalsIgnoreCase(manufacturer))\n            return new OppoCutout(context);\n        if (\"Vivo\".equalsIgnoreCase(manufacturer))\n            return new VivoCutout(context);\n        if (\"Xiaomi\".equalsIgnoreCase(manufacturer))\n            return new XiaomiCutout(context);\n\n        return null;\n    }\n\n    @TargetApi(Build.VERSION_CODES.P)\n    @Nullable\n    public static ICutout getForAndroidPie(Activity activity) {\n        WindowInsets windowInsets =  activity.getWindow().getDecorView().getRootWindowInsets();\n        if ( windowInsets == null ) {\n            // getRootWindowInsets() must be called after onAttachedToWindow()\n            return null;\n        }\n        final DisplayCutout displayCutout = windowInsets.getDisplayCutout();\n        if (displayCutout == null)\n            return null;\n        return new AndroidPCutout(displayCutout);\n    }\n\n    @NonNull\n    public static ICutout getStatusBar(Context context) {\n        return new StatusBarCutout(context);\n    }\n\n    @NonNull\n    public static ICutout getNoCutout()\n    {\n        return new NoCutout();\n    }\n\n    private static abstract class ComputeSafeZoneFromCutout implements ICutout {\n        @NonNull\n        protected final Context context;\n\n        ComputeSafeZoneFromCutout(@NonNull Context context) {\n            this.context = context;\n        }\n\n        @Override\n        public Rect getSafeZone() {\n            Rect safe = new Rect(0, 0, 0, 0);\n            Rect[] cutout = getCutout();\n\n            if (cutout.length == 1) {\n                // assume one notch in screen top and phone in portrait\n                safe.top = cutout[0].top + cutout[0].bottom;\n            }\n//            else if (cutout.length > 1) {\n//                throw new RuntimeException();  // not implemented yet.\n//            }\n            return safe;\n        }\n    }\n\n    // Huawei https://developer.huawei.com/consumer/cn/devservice/doc/50114\n    private static class HuaweiCutout extends ComputeSafeZoneFromCutout {\n\n        HuaweiCutout(@NonNull Context context) {\n            super(context);\n        }\n\n        @Override\n        public boolean hasCutout() {\n            try {\n                ClassLoader classLoader = context.getClassLoader();\n                Class class_HwNotchSizeUtil = classLoader.loadClass(\"com.huawei.android.util.HwNotchSizeUtil\");\n                @SuppressWarnings(\"unchecked\")\n                Method method_hasNotchInScreen = class_HwNotchSizeUtil.getMethod(\"hasNotchInScreen\");\n                return (boolean) method_hasNotchInScreen.invoke(class_HwNotchSizeUtil);\n            } catch (Exception e) {\n                //ignored\n            }\n            return false;\n        }\n\n        @Override\n        public Rect[] getCutout() {\n            try {\n                ClassLoader classLoader = context.getClassLoader();\n                Class class_HwNotchSizeUtil = classLoader.loadClass(\"com.huawei.android.util.HwNotchSizeUtil\");\n                @SuppressWarnings(\"unchecked\")\n                Method method_getNotchSize = class_HwNotchSizeUtil.getMethod(\"getNotchSize\");\n\n                int[] size = (int[]) method_getNotchSize.invoke(class_HwNotchSizeUtil);\n                @SuppressWarnings(\"ConstantConditions\")\n                int notchWidth = size[0];\n                int notchHeight = size[1];\n                int screenWidth = DeviceUtils.getScreenWidth(context);\n\n                int x = (screenWidth - notchWidth) >> 1;\n                int y = 0;\n                Rect rect = new Rect(x, y, x + notchWidth, y + notchHeight);\n                return new Rect[]{rect};\n            } catch (Exception e) {\n                //ignored\n            }\n            return new Rect[0];\n        }\n    }\n\n    // Oppo https://open.oppomobile.com/wiki/doc#id=10159\n    private static class OppoCutout extends ComputeSafeZoneFromCutout {\n\n        OppoCutout(@NonNull Context context) {\n            super(context);\n        }\n\n        @Override\n        public boolean hasCutout() {\n            String CutoutFeature = \"com.oppo.feature.screen.heteromorphism\";\n            return context.getPackageManager().hasSystemFeature(CutoutFeature);\n        }\n\n        @Override\n        public Rect[] getCutout() {\n            String value = System.getProperty(\"ro.oppo.screen.heteromorphism\");\n            @SuppressWarnings(\"ConstantConditions\")\n            String[] texts = value.split(\"[,:]\");\n            int[] values = new int[texts.length];\n\n            try {\n                for (int i = 0; i < texts.length; ++i)\n                    values[i] = Integer.parseInt(texts[i]);\n            } catch (NumberFormatException e) {\n                values = null;\n            }\n\n            if (values != null && values.length == 4) {\n                Rect rect = new Rect();\n                rect.left = values[0];\n                rect.top = values[1];\n                rect.right = values[2];\n                rect.bottom = values[3];\n\n                return new Rect[]{rect};\n            }\n\n            return new Rect[0];\n        }\n    }\n\n    // Vivo https://dev.vivo.com.cn/documentCenter/doc/145\n    private static class VivoCutout extends ComputeSafeZoneFromCutout {\n\n        VivoCutout(@NonNull Context context) {\n            super(context);\n        }\n\n        @Override\n        public boolean hasCutout() {\n            try {\n                ClassLoader clazz = context.getClassLoader();\n                @SuppressLint(\"PrivateApi\")\n                Class ftFeature = clazz.loadClass(\"android.util.FtFeature\");\n                Method[] methods = ftFeature.getDeclaredMethods();\n                for (Method method : methods) {\n                    if (method.getName().equalsIgnoreCase(\"isFeatureSupport\")) {\n                        int NOTCH_IN_SCREEN = 0x00000020;  // 表示是否有凹槽\n                        int ROUNDED_IN_SCREEN = 0x00000008;  // 表示是否有圆角\n                        return (boolean) method.invoke(ftFeature, NOTCH_IN_SCREEN);\n                    }\n                }\n            } catch (Exception e) {\n                //ignored\n            }\n            return false;\n        }\n\n        @Override\n        public Rect[] getCutout() {\n            // throw new RuntimeException();  // not implemented yet.\n            return new Rect[0];\n        }\n    }\n\n    // Xiaomi\n    // Oreo: https://dev.mi.com/console/doc/detail?pId=1293\n    // Pie: https://dev.mi.com/console/doc/detail?pId=1341\n    private static class XiaomiCutout extends ComputeSafeZoneFromCutout {\n\n        XiaomiCutout(@NonNull Context context) {\n            super(context);\n        }\n\n        @Override\n        public boolean hasCutout() {\n            // `getprop ro.miui.notch` output 1 if it's a notch screen.\n            String text = System.getProperty(\"ro.miui.notch\");\n            return \"1\".equals(text);\n        }\n\n        @SuppressWarnings(\"UnnecessaryLocalVariable\")\n        @Override\n        public Rect[] getCutout() {\n            Resources res = context.getResources();\n            int widthResId = res.getIdentifier(\"notch_width\", \"dimen\", \"android\");\n            int heightResId = res.getIdentifier(\"notch_height\", \"dimen\", \"android\");\n            if (widthResId > 0 && heightResId > 0) {\n                int notchWidth = res.getDimensionPixelSize(widthResId);\n                int notchHeight = res.getDimensionPixelSize(heightResId);\n\n                // one notch in screen top\n                int screenWidth = DeviceUtils.getScreenWidth(context);\n                int left = (screenWidth - notchWidth) >> 1;\n                int right = left + notchWidth;\n                int top = 0;\n                int bottom = notchHeight;\n                Rect rect = new Rect(left, top, right, bottom);\n                return new Rect[]{rect};\n            }\n\n            return new Rect[0];\n        }\n    }\n\n    // In case some manufactures' not coming up with a getNotchHeight() method, you can just use the status bar's height. Android has guarantee that notch height is at most the status bar height.\n    public static class StatusBarCutout extends ComputeSafeZoneFromCutout {\n\n        StatusBarCutout(@NonNull Context context) {\n            super(context);\n        }\n\n        public static int getStatusBarHeight(Context context) {\n            int statusBarHeight = 0;\n            Resources res = context.getResources();\n            int resourceId = res.getIdentifier(\"status_bar_height\", \"dimen\", \"android\");\n            if (resourceId > 0) {\n                statusBarHeight = res.getDimensionPixelSize(resourceId);\n            }\n            return statusBarHeight;\n        }\n\n        @Override\n        public boolean hasCutout() {\n            return true;\n        }\n\n        @Override\n        public Rect[] getCutout() {\n            return new Rect[]{new Rect(0, 0, 0, getStatusBarHeight(context))};\n        }\n    }\n\n    // Android P cutout\n    @TargetApi(Build.VERSION_CODES.P)\n    private static class AndroidPCutout implements ICutout {\n\n        @NonNull\n        final Rect safeInset;\n\n        AndroidPCutout(@NonNull DisplayCutout displayCutout) {\n            int left = displayCutout.getSafeInsetLeft();\n            int top = displayCutout.getSafeInsetTop();\n            int right = displayCutout.getSafeInsetRight();\n            int bottom = displayCutout.getSafeInsetBottom();\n            safeInset = new Rect(left, top, right, bottom);\n        }\n\n        @Override\n        public boolean hasCutout() {\n            return safeInset.top != 0 || safeInset.bottom != 0 || safeInset.left != 0 || safeInset.right != 0;\n        }\n\n        @Override\n        public Rect[] getCutout() {\n            // throw new RuntimeException();  // not implemented yet. Should return displayCutout.getBoundingRectsAll()\n            return new Rect[0];\n        }\n\n        @Override\n        public Rect getSafeZone() {\n            return safeInset;\n        }\n    }\n\n    private static class NoCutout implements ICutout {\n        @Override\n        public boolean hasCutout() {\n            return false;\n        }\n\n        @Override\n        public Rect[] getCutout() {\n            return new Rect[0];\n        }\n\n        @Override\n        public Rect getSafeZone() {\n            return new Rect(0, 0, 0, 0);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/ui/DialogFragment.java",
    "content": "package rocks.tbog.tblauncher.ui;\n\nimport android.app.Dialog;\nimport android.content.Context;\nimport android.content.DialogInterface;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.util.TypedValue;\nimport android.view.ContextThemeWrapper;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.Window;\nimport android.view.WindowManager;\nimport android.widget.TextView;\n\nimport androidx.annotation.IdRes;\nimport androidx.annotation.LayoutRes;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.utils.UITheme;\n\npublic abstract class DialogFragment<Output> extends androidx.fragment.app.DialogFragment {\n    private static final String TAG = \"DialogFrag\";\n    private OnDismissListener<Output> mOnDismissListener = null;\n    private OnConfirmListener<Output> mOnConfirmListener = null;\n    private OnButtonClickListener<Output> mOnPositiveClickListener = null;\n    private OnButtonClickListener<Output> mOnNeutralClickListener = null;\n    private OnButtonClickListener<Output> mOnNegativeClickListener = null;\n\n    public enum Button {\n        POSITIVE, NEGATIVE, NEUTRAL\n    }\n\n    @LayoutRes\n    protected abstract int layoutRes();\n\n    public interface OnDismissListener<T> {\n        void onDismiss(@NonNull DialogFragment<T> dialog);\n    }\n\n    public interface OnConfirmListener<T> {\n        void onConfirm(@Nullable T output);\n    }\n\n    public interface OnButtonClickListener<T> {\n        void onButtonClick(@NonNull DialogFragment<T> dialog, @NonNull Button button);\n    }\n\n    public void setOnDismissListener(OnDismissListener<Output> listener) {\n        mOnDismissListener = listener;\n    }\n\n    public void setOnConfirmListener(OnConfirmListener<Output> listener) {\n        mOnConfirmListener = listener;\n    }\n\n    public void setOnPositiveClickListener(OnButtonClickListener<Output> listener) {\n        mOnPositiveClickListener = listener;\n    }\n\n    public void setOnNegativeClickListener(OnButtonClickListener<Output> listener) {\n        mOnNegativeClickListener = listener;\n    }\n\n    public void setOnNeutralClickListener(OnButtonClickListener<Output> listener) {\n        mOnNeutralClickListener = listener;\n    }\n\n    public DialogFragment<Output> putArgString(@Nullable String key, @Nullable String value) {\n        Bundle args = getArguments();\n        if (args == null)\n            args = new Bundle();\n        args.putString(key, value);\n        setArguments(args);\n        return this;\n    }\n\n    public DialogFragment<Output> putArgLong(@Nullable String key, long value) {\n        Bundle args = getArguments();\n        if (args == null)\n            args = new Bundle();\n        args.putLong(key, value);\n        setArguments(args);\n        return this;\n    }\n\n    public DialogFragment<Output> putArgInt(@Nullable String key, int value) {\n        Bundle args = getArguments();\n        if (args == null)\n            args = new Bundle();\n        args.putInt(key, value);\n        setArguments(args);\n        return this;\n    }\n\n    @Override\n    public void onDismiss(@NonNull DialogInterface dialog) {\n        if (mOnDismissListener != null)\n            mOnDismissListener.onDismiss(this);\n        super.onDismiss(dialog);\n    }\n\n    public void onConfirm(@Nullable Output output) {\n        if (mOnConfirmListener != null)\n            mOnConfirmListener.onConfirm(output);\n    }\n\n    public void onButtonClick(@NonNull Button button) {\n        switch (button) {\n            case POSITIVE:\n                if (mOnPositiveClickListener != null)\n                    mOnPositiveClickListener.onButtonClick(this, button);\n                break;\n            case NEGATIVE:\n                if (mOnNegativeClickListener != null)\n                    mOnNegativeClickListener.onButtonClick(this, button);\n                break;\n            case NEUTRAL:\n            default:\n                if (mOnNeutralClickListener != null)\n                    mOnNeutralClickListener.onButtonClick(this, button);\n                break;\n        }\n        dismiss();\n    }\n\n    @Override\n    public void onCreate(@Nullable Bundle savedInstanceState) {\n        //Log.i(TAG, \"---> onCreate <---\");\n        super.onCreate(savedInstanceState);\n        int theme = UITheme.getDialogTheme(requireContext());\n        if (theme == UITheme.ID_NULL)\n            theme = R.style.NoTitleDialogTheme;\n        setStyle(DialogFragment.STYLE_NO_FRAME, theme);\n        //Log.i(TAG, \"theme=\" + getTheme());\n        //Log.i(TAG, \"context=\" + getContext());\n    }\n\n    @NonNull\n    @Override\n    public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {\n        //Log.i(TAG, \"---> onCreateDialog <---\");\n        Context themeWrapper = new ContextThemeWrapper(requireContext(), getTheme());\n        TypedValue outValue = new TypedValue();\n        themeWrapper.getTheme().resolveAttribute(com.google.android.material.R.attr.alertDialogTheme, outValue, true);\n        int dialogStyle = outValue.resourceId;\n        DialogWrapper dialog = new DialogWrapper(themeWrapper, dialogStyle);\n        //Log.i(TAG, \"dialog=\" + dialog);\n        Log.i(TAG, \"dialog.context=\" + dialog.getContext());\n        return dialog;\n    }\n\n    @NonNull\n    public View inflateLayoutRes(@NonNull LayoutInflater inflater, @Nullable ViewGroup container) {\n        //Log.i(TAG, \"context=\" + getContext());\n        //Log.i(TAG, \"dialog.context=\" + dialog.getContext());\n        Log.i(TAG, \"inflater.context=\" + inflater.getContext());\n        return inflater.inflate(layoutRes(), container, false);\n    }\n\n    @Nullable\n    @Override\n    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {\n        //Log.i(TAG, \"---> onCreateView <---\");\n        Dialog dialog = requireDialog();\n        //Log.i(TAG, \"dialog=\" + dialog);\n\n        Window window = dialog.getWindow();\n        if (window != null) {\n            window.requestFeature(Window.FEATURE_NO_TITLE);\n            window.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);\n            window.setDimAmount(0.7f);\n        }\n        dialog.setCanceledOnTouchOutside(true);\n\n        View view = inflateLayoutRes(inflater, container);\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            view.setClipToOutline(true);\n        }\n\n        createButtonBar(view, inflater);\n\n        return view;\n    }\n\n    protected void setupDefaultButtonOkCancel(Context context) {\n        Bundle args = getArguments() != null ? getArguments() : new Bundle();\n        if (!isStateSaved()) {\n            args.putCharSequence(\"btnPositiveText\", context.getText(android.R.string.ok));\n            args.putCharSequence(\"btnNegativeText\", context.getText(android.R.string.cancel));\n            setArguments(args);\n        }\n    }\n\n    protected void setupDefaultButtonOk(Context context) {\n        Bundle args = getArguments() != null ? getArguments() : new Bundle();\n        if (!isStateSaved()) {\n            args.putCharSequence(\"btnPositiveText\", context.getText(android.R.string.ok));\n            setArguments(args);\n        }\n    }\n\n    private void createButtonBar(View view, LayoutInflater inflater) {\n        Bundle args = getArguments();\n        if (args == null)\n            return;\n\n        CharSequence btnPositiveText = args.getCharSequence(\"btnPositiveText\", \"\");\n        CharSequence btnNegativeText = args.getCharSequence(\"btnNegativeText\", \"\");\n        CharSequence btnNeutralText = args.getCharSequence(\"btnNeutralText\", \"\");\n        if (btnPositiveText.length() == 0 && btnNegativeText.length() == 0 && btnNeutralText.length() == 0)\n            return;\n\n        View buttonPanel = resolvePanel(view, inflater);\n        if (buttonPanel == null) {\n            Log.e(TAG, \"failed to inflate button bar\");\n            return;\n        }\n\n        TextView button1 = buttonPanel.findViewById(android.R.id.button1);\n        TextView button2 = buttonPanel.findViewById(android.R.id.button2);\n        TextView button3 = buttonPanel.findViewById(android.R.id.button3);\n\n        if (btnPositiveText.length() == 0) {\n            button1.setVisibility(View.GONE);\n        } else {\n            button1.setVisibility(View.VISIBLE);\n            button1.setText(btnPositiveText);\n            button1.setOnClickListener(v -> onButtonClick(Button.POSITIVE));\n        }\n\n        if (btnNegativeText.length() == 0) {\n            button2.setVisibility(View.GONE);\n        } else {\n            button2.setVisibility(View.VISIBLE);\n            button2.setText(btnNegativeText);\n            button2.setOnClickListener(v -> onButtonClick(Button.NEGATIVE));\n        }\n\n        if (btnNeutralText.length() == 0) {\n            button3.setVisibility(View.GONE);\n            View spacer = buttonPanel.findViewById(R.id.spacer);\n            if (spacer != null)\n                spacer.setVisibility(View.GONE);\n        } else {\n            button3.setVisibility(View.VISIBLE);\n            button3.setText(btnNeutralText);\n            button3.setOnClickListener(v -> onButtonClick(Button.NEUTRAL));\n        }\n    }\n\n    @Nullable\n    private ViewGroup resolvePanel(@NonNull View view, @NonNull LayoutInflater inflater) {\n        View buttonPanel = view.findViewById(R.id.buttonPanel);\n        if (buttonPanel instanceof ViewGroup)\n            return (ViewGroup) buttonPanel;\n        if (view instanceof ViewGroup) {\n            buttonPanel = inflater.inflate(R.layout.ok_cancel_button_bar, (ViewGroup) view, false);\n            ((ViewGroup) view).addView(buttonPanel);\n            return (ViewGroup) buttonPanel;\n        }\n        return null;\n    }\n\n    @Nullable\n    public <T extends View> T findViewById(@IdRes int id) {\n        return requireDialog().findViewById(id);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/ui/DialogWrapper.java",
    "content": "package rocks.tbog.tblauncher.ui;\n\nimport android.content.Context;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.appcompat.app.AppCompatDialog;\n\npublic class DialogWrapper extends AppCompatDialog {\n\n    private OnWindowFocusChanged mOnWindowFocusChanged = null;\n\n    public interface OnWindowFocusChanged {\n        void onWindowFocusChanged(@NonNull DialogWrapper dialog, boolean hasFocus);\n    }\n\n    public DialogWrapper(Context context) {\n        super(context);\n    }\n\n    public DialogWrapper(Context context, int theme) {\n        super(context, theme);\n    }\n\n    protected DialogWrapper(Context context, boolean cancelable, OnCancelListener cancelListener) {\n        super(context, cancelable, cancelListener);\n    }\n\n    public void setOnWindowFocusChanged(@Nullable OnWindowFocusChanged callback) {\n        mOnWindowFocusChanged = callback;\n    }\n\n    @Override\n    public void onWindowFocusChanged(boolean hasFocus) {\n        super.onWindowFocusChanged(hasFocus);\n        if (mOnWindowFocusChanged != null)\n            mOnWindowFocusChanged.onWindowFocusChanged(this, hasFocus);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/ui/ICutout.java",
    "content": "package rocks.tbog.tblauncher.ui;\n\nimport android.graphics.Rect;\n\npublic interface ICutout {\n    boolean hasCutout();\n\n    Rect[] getCutout();\n    Rect getSafeZone();\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/ui/KeyboardHandler.java",
    "content": "package rocks.tbog.tblauncher.ui;\n\npublic interface KeyboardHandler {\n    void showKeyboard();\n    void hideKeyboard();\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/ui/LinearAdapter.java",
    "content": "package rocks.tbog.tblauncher.ui;\n\nimport android.annotation.SuppressLint;\nimport android.content.Context;\nimport android.graphics.PorterDuff;\nimport android.graphics.PorterDuffColorFilter;\nimport android.graphics.drawable.Drawable;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.BaseAdapter;\nimport android.widget.TextView;\n\nimport androidx.annotation.LayoutRes;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.StringRes;\nimport androidx.appcompat.content.res.AppCompatResources;\n\nimport java.util.ArrayList;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.utils.UIColors;\n\n/**\n * Adapter used to inflate views in a LinearLayout\n * WARNING: don't use this adapter for long lists, it does not recycle views\n */\npublic class LinearAdapter extends BaseAdapter {\n    final ArrayList<MenuItem> list = new ArrayList<>(0);\n\n    public interface MenuItem {\n        @LayoutRes\n        int getLayoutResource();\n\n        boolean isEnabled();\n    }\n\n    public static class ItemDivider implements MenuItem {\n        @Override\n        public int getLayoutResource() {\n            return R.layout.popup_divider;\n        }\n\n        @Override\n        public boolean isEnabled() {\n            return false;\n        }\n    }\n\n    public static class ItemTitle implements MenuItem {\n        @NonNull\n        final String name;\n\n        public ItemTitle(Context context, @StringRes int nameRes) {\n            this.name = context.getString(nameRes);\n        }\n\n        public ItemTitle(@NonNull String string) {\n            this.name = string;\n        }\n\n        @NonNull\n        @Override\n        public String toString() {\n            return name;\n        }\n\n        @Override\n        public int getLayoutResource() {\n            return R.layout.popup_title;\n        }\n\n        @Override\n        public boolean isEnabled() {\n            return false;\n        }\n    }\n\n    public static class ItemString implements MenuItem {\n        @NonNull\n        final String string;\n\n        public ItemString(@NonNull String string) {\n            this.string = string;\n        }\n\n        @NonNull\n        @Override\n        public String toString() {\n            return string;\n        }\n\n        @Override\n        public int getLayoutResource() {\n            return R.layout.popup_list_item;\n        }\n\n        @Override\n        public boolean isEnabled() {\n            return true;\n        }\n    }\n\n    public static class ItemText extends ItemString {\n        public ItemText(@NonNull String string) {\n            super(string);\n        }\n\n        @Override\n        public int getLayoutResource() {\n            return R.layout.popup_list_text;\n        }\n\n        @Override\n        public boolean isEnabled() {\n            return false;\n        }\n    }\n\n    public static class Item extends ItemString {\n        @StringRes\n        public final int stringId;\n\n        public Item(Context context, @StringRes int stringId) {\n            super(context.getResources().getString(stringId));\n            this.stringId = stringId;\n        }\n\n//        public Item(@NonNull String string) {\n//            super(string);\n//            this.stringId = 0;\n//        }\n    }\n\n    @Override\n    public int getCount() {\n        return list.size();\n    }\n\n    @Override\n    public MenuItem getItem(int position) {\n        return list.get(position);\n    }\n\n    @Override\n    public long getItemId(int position) {\n        return position;\n    }\n\n    @SuppressLint(\"ViewHolder\")\n    @Override\n    public View getView(int position, View convertView, ViewGroup parent) {\n        MenuItem item = getItem(position);\n        convertView = LayoutInflater.from(parent.getContext()).inflate(item.getLayoutResource(), parent, false);\n\n        // set color of the divider\n        View divider = convertView.findViewById(R.id.title_divider);\n        if (divider != null) {\n            Context ctx = divider.getContext();\n            int color = UIColors.getPopupBorderColor(ctx);\n            int background = UIColors.getPopupBackgroundColor(ctx);\n            int separator = UIColors.isColorLight(background) ? R.drawable.list_separator_dark : R.drawable.list_separator_light;\n            Drawable drawable = AppCompatResources.getDrawable(ctx, separator);\n            if (drawable == null)\n                drawable = divider.getBackground();\n            if (drawable != null) {\n                drawable.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY));\n                divider.setBackground(drawable);\n            }\n        }\n        if (item instanceof ItemDivider) {\n            return convertView;\n        }\n\n        bindView(convertView, item);\n\n        return convertView;\n    }\n\n    protected void bindView(View convertView, MenuItem item) {\n        String text = item.toString();\n        TextView textView = convertView.findViewById(android.R.id.text1);\n        textView.setText(text);\n    }\n\n    public void add(MenuItem item) {\n        list.add(item);\n        notifyDataSetChanged();\n    }\n\n    public void add(int index, MenuItem item) {\n        list.add(index, item);\n        notifyDataSetChanged();\n    }\n\n    @Override\n    public boolean areAllItemsEnabled() {\n        return false;\n    }\n\n    @Override\n    public boolean isEnabled(int position) {\n        MenuItem item = list.get(position);\n        return item.isEnabled();\n    }\n\n    public void remove(MenuItem item) {\n        list.remove(item);\n        notifyDataSetChanged();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/ui/LinearAdapterPlus.java",
    "content": "package rocks.tbog.tblauncher.ui;\n\nimport android.graphics.drawable.Drawable;\nimport android.view.View;\nimport android.widget.ImageView;\n\nimport androidx.annotation.NonNull;\n\n/**\n * Add callbacks to the LinearAdapter\n */\npublic class LinearAdapterPlus extends LinearAdapter {\n    public interface BindCallback {\n        /**\n         * @param view the view to bind with\n         * @return true if super.bindView should not be called\n         */\n        boolean bindView(View view);\n    }\n\n    public static class ItemStringIcon extends ItemString implements BindCallback {\n        @NonNull\n        final Drawable icon;\n\n        public ItemStringIcon(@NonNull String string, @NonNull Drawable icon) {\n            super(string);\n            this.icon = icon;\n        }\n\n        @Override\n        public int getLayoutResource() {\n            return android.R.layout.activity_list_item;\n        }\n\n        @Override\n        public boolean bindView(View view) {\n            ImageView image = view.findViewById(android.R.id.icon);\n            image.setImageDrawable(icon);\n            return false;\n        }\n    }\n\n\n    @Override\n    protected void bindView(View view, MenuItem item) {\n        if (item instanceof BindCallback && ((BindCallback) item).bindView(view))\n            return;\n        super.bindView(view, item);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/ui/ListPopup.java",
    "content": "package rocks.tbog.tblauncher.ui;\n\nimport android.content.Context;\nimport android.database.DataSetObserver;\nimport android.graphics.Rect;\nimport android.graphics.drawable.Drawable;\nimport android.os.Build;\nimport android.util.Log;\nimport android.view.ContextThemeWrapper;\nimport android.view.Gravity;\nimport android.view.MotionEvent;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.WindowManager;\nimport android.widget.LinearLayout;\nimport android.widget.ListAdapter;\nimport android.widget.PopupWindow;\nimport android.widget.TextView;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.StringRes;\n\nimport rocks.tbog.tblauncher.CustomizeUI;\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.utils.UIColors;\nimport rocks.tbog.tblauncher.utils.UISizes;\nimport rocks.tbog.tblauncher.utils.UITheme;\n\npublic class ListPopup extends PopupWindow {\n    private static final String TAG = \"Popup\";\n    private final Rect mTempRect = new Rect();\n    private final int[] mTempLocation = new int[2];\n    private OnItemLongClickListener mItemLongClickListener = null;\n    private OnItemClickListener mItemClickListener = null;\n    private DataSetObserver mObserver = null;\n    private ListAdapter mAdapter = null;\n    private boolean dismissOnClick = true;\n    private float dimAmount = .7f;\n    private boolean mIsModal = false; // send all touch events to this window\n\n    public static ListPopup create(@NonNull Context context, ListAdapter adapter) {\n        Log.d(TAG, \"initial context=\" + context);\n        ContextThemeWrapper ctx = new ContextThemeWrapper(context, R.style.ListPopupTheme);\n        ListPopup popup = new ListPopup(ctx);\n        View root = popup.getContentView().getRootView();\n\n        Drawable background = CustomizeUI.getPopupBackgroundDrawable(context);\n        root.setBackground(background);\n        int padding = UISizes.dp2px(context, 1);\n        root.setPadding(padding, padding, padding, padding);\n        ((ViewGroup) root).setClipToPadding(true);\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            root.setClipToOutline(true);\n        }\n\n        CustomizeUI.setListViewScrollbarPref(popup.getContentView(), UIColors.getPopupRipple(ctx));\n\n        popup.setAdapter(adapter);\n        return popup;\n    }\n\n    private ListPopup(@NonNull Context context) {\n        super(context, null, android.R.attr.popupMenuStyle);\n        ScrollView scrollView = new ScrollView(context);\n        LinearLayout layout = new LinearLayout(context);\n        layout.setId(R.id.root_layout);\n        layout.setOrientation(LinearLayout.VERTICAL);\n        scrollView.addView(layout);\n\n        setContentView(scrollView);\n        setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);\n        setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);\n        // set transparent window background\n        setBackgroundDrawable(null);\n    }\n\n    public ListPopup setOnItemClickListener(OnItemClickListener onItemClickListener) {\n        mItemClickListener = onItemClickListener;\n        return this;\n    }\n\n    public ListPopup setOnItemLongClickListener(OnItemLongClickListener onItemLongClickListener) {\n        mItemLongClickListener = onItemLongClickListener;\n        return this;\n    }\n\n    public ListAdapter getAdapter() {\n        return mAdapter;\n    }\n\n    public ListPopup setDismissOnItemClick(boolean dismissOnClick) {\n        this.dismissOnClick = dismissOnClick;\n        return this;\n    }\n\n    public boolean isInsideViewBounds(int x, int y) {\n        final int[] pos = mTempLocation;\n        final Rect rect = mTempRect;\n\n        View rootView = getContentView().getRootView();\n        rootView.getDrawingRect(rect);\n        rootView.getLocationOnScreen(pos);\n        rect.offset(pos[0], pos[1]);\n        return rect.contains(x, y);\n    }\n\n    /**\n     * Set background dim amount (0=no dim, 1=full dim)\n     *\n     * @param dimAmount a value of 0 or less to disable dimming\n     */\n    public ListPopup setDimAmount(float dimAmount) {\n        this.dimAmount = dimAmount;\n        return this;\n    }\n\n    /**\n     * Sets the adapter that provides the data and the views to represent the data\n     * in this popup window.\n     *\n     * @param adapter The adapter to use to create this window's content.\n     */\n    public void setAdapter(ListAdapter adapter) {\n        if (mObserver == null) {\n            mObserver = new PopupDataSetObserver();\n        } else if (mAdapter != null) {\n            mAdapter.unregisterDataSetObserver(mObserver);\n        }\n        mAdapter = adapter;\n        if (mAdapter != null) {\n            adapter.registerDataSetObserver(mObserver);\n        }\n    }\n\n    private LinearLayout getLinearLayout() {\n        return getContentView().findViewById(R.id.root_layout);\n        //return (LinearLayout) ((ScrollView) getContentView()).getChildAt(0);\n    }\n\n    private void updateItems() {\n        LinearLayout layout = getLinearLayout();\n        Context ctx = layout.getContext();\n        int selectorColor = UIColors.getPopupRipple(ctx);\n        int textColor = UIColors.getPopupTextColor(ctx);\n        int titleColor = UIColors.getPopupTitleColor(ctx);\n        layout.removeAllViews();\n        int adapterCount = mAdapter.getCount();\n        for (int i = 0; i < adapterCount; i += 1) {\n            View view = mAdapter.getView(i, null, layout);\n\n            // apply selector background\n            view.setBackground(CustomizeUI.getSelectorDrawable(view, selectorColor, false));\n            setTextColorRecursive(view, mAdapter.isEnabled(i) ? textColor : titleColor);\n\n            layout.addView(view);\n            if (mAdapter.isEnabled(i)) {\n                view.setOnClickListener(this::onItemClicked);\n                if (mItemLongClickListener == null) {\n                    view.setLongClickable(false);\n                } else {\n                    view.setOnLongClickListener(this::onItemLongClicked);\n                }\n            }\n        }\n        layout.forceLayout();\n    }\n\n    /**\n     * Set the text color of the first TextView\n     *\n     * @param view  TextView to set color of or GroupView to search\n     * @param color text color\n     * @return if color applied\n     */\n    private static boolean setTextColorRecursive(View view, int color) {\n        if (view instanceof TextView) {\n            TextView textView = (TextView) view;\n            textView.setTextColor(color);\n            UITheme.applyPopupTextShadow(textView);\n            return true;\n        } else if (view instanceof ViewGroup) {\n            int childCount = ((ViewGroup) view).getChildCount();\n            for (int childIdx = 0; childIdx < childCount; childIdx += 1) {\n                View child = ((ViewGroup) view).getChildAt(childIdx);\n                if (setTextColorRecursive(child, color))\n                    return true;\n            }\n        }\n        return false;\n    }\n\n    private void onItemClicked(View view) {\n        if (mItemClickListener != null) {\n            LinearLayout layout1 = getLinearLayout();\n            int position = layout1.indexOfChild(view);\n            mItemClickListener.onItemClick(mAdapter, view, position);\n        }\n        if (dismissOnClick)\n            dismiss();\n    }\n\n    private boolean onItemLongClicked(View view) {\n        if (mItemLongClickListener == null)\n            return false;\n        LinearLayout layout12 = getLinearLayout();\n        int position = layout12.indexOfChild(view);\n        return mItemLongClickListener.onItemLongClick(mAdapter, view, position);\n    }\n\n    private void beforeShow() {\n        updateItems();\n\n        if (mIsModal) {\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n                setTouchModal(true);\n            }\n            setOutsideTouchable(false);\n            setFocusable(true);\n        } else {\n            // don't steal the focus, this will prevent the keyboard from changing\n            setFocusable(false);\n        }\n        // draw over stuff if needed\n        setClippingEnabled(false);\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n            // we already take this into account\n            setOverlapAnchor(false);\n        }\n    }\n\n    public void showCenter(@NonNull View viewForWindowToken) {\n        showAtLocation(viewForWindowToken, Gravity.CENTER, 0, 0);\n    }\n\n    public void show(@NonNull View anchor) {\n        show(anchor, .5f);\n    }\n\n    public void show(@NonNull View anchor, float anchorOverlap) {\n        beforeShow();\n\n        final Rect displayFrame = mTempRect;\n        anchor.getWindowVisibleDisplayFrame(displayFrame);\n\n        final int[] anchorPos = mTempLocation;\n        anchor.getLocationInWindow(anchorPos);\n        //anchor.getLocationOnScreen(anchorPos);\n\n        final int distanceToBottom = displayFrame.bottom - (anchorPos[1] + anchor.getHeight());\n        final int distanceToTop = anchorPos[1] - displayFrame.top;\n\n        View rootView = getContentView().getRootView();\n        rootView.forceLayout();\n        rootView.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),\n            View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));\n\n        int xOffset = anchorPos[0] + anchor.getPaddingLeft();\n        if (xOffset + rootView.getMeasuredWidth() > displayFrame.right)\n            xOffset = displayFrame.right - rootView.getMeasuredWidth();\n\n        int overlapAmount = (int) (anchor.getHeight() * anchorOverlap);\n        int yOffset;\n        if (distanceToBottom > rootView.getMeasuredHeight()) {\n            // show below anchor\n            yOffset = anchorPos[1] + overlapAmount;\n            setAnimationStyle(R.style.PopupAnimationTop);\n        } else if (distanceToTop > distanceToBottom) {\n            // show above anchor\n            yOffset = anchorPos[1] + overlapAmount - rootView.getMeasuredHeight();\n            setAnimationStyle(R.style.PopupAnimationBottom);\n            if (distanceToTop < rootView.getMeasuredHeight()) {\n                yOffset += rootView.getMeasuredHeight() - distanceToTop - overlapAmount;\n            }\n        } else {\n            // show below anchor with scroll\n            yOffset = anchorPos[1] + overlapAmount;\n            setAnimationStyle(R.style.PopupAnimationTop);\n        }\n\n        final int width = rootView.getMeasuredWidth();\n        final int height = rootView.getMeasuredHeight();\n        int[] offset = setSizeAndPosition(displayFrame, xOffset, yOffset, width, height);\n        super.showAtLocation(anchor, Gravity.START | Gravity.TOP, offset[0], offset[1]);\n        applyDim();\n    }\n\n    @Override\n    public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {\n        //TODO: this is just a placeholder, implement if used\n        show(anchor);\n    }\n\n    @Override\n    public void showAtLocation(View parent, int gravity, int x, int y) {\n        beforeShow();\n        final Rect displayFrame = mTempRect;\n        parent.getWindowVisibleDisplayFrame(displayFrame);\n\n        int[] offset = setSizeAndPosition(displayFrame, x, y);\n        if (y - offset[1] > getHeight() / 2)\n            setAnimationStyle(R.style.PopupAnimationBottom);\n        else\n            setAnimationStyle(R.style.PopupAnimationTop);\n        super.showAtLocation(parent, gravity, offset[0], offset[1]);\n        applyDim();\n    }\n\n    private int[] setSizeAndPosition(Rect displayFrame, int xOffset, int yOffset) {\n        View rootView = getContentView().getRootView();\n        rootView.forceLayout();\n        rootView.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),\n            View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));\n\n        int width = rootView.getMeasuredWidth();\n        int height = rootView.getMeasuredHeight();\n        return setSizeAndPosition(displayFrame, xOffset, yOffset, width, height);\n    }\n\n    /**\n     * set size and recompute offset\n     *\n     * @param displayFrame display bounds\n     * @param xOffset      x offset\n     * @param yOffset      y offset\n     * @param width        measured width of root view\n     * @param height       measured height of root view\n     * @return new offset returned using mTempLocation\n     */\n    private int[] setSizeAndPosition(Rect displayFrame, int xOffset, int yOffset, int width, int height) {\n        if (xOffset + width > displayFrame.right)\n            xOffset = displayFrame.right - width;\n\n        if (yOffset + height > displayFrame.bottom)\n            yOffset = displayFrame.bottom - height;\n\n        if (xOffset < displayFrame.left) {\n            xOffset = displayFrame.left;\n            // may enable scroll\n            width = Math.min(width, displayFrame.width());\n        }\n\n        if (yOffset < displayFrame.top) {\n            yOffset = displayFrame.top;\n            // may enable scroll\n            height = Math.min(height, displayFrame.height());\n        }\n\n        setWidth(width);\n        setHeight(height);\n\n        mTempLocation[0] = xOffset;\n        mTempLocation[1] = yOffset;\n        return mTempLocation;\n    }\n\n    /**\n     * Must be called after calling show*\n     */\n    private void applyDim() {\n        if (dimAmount > 0.f) {\n            View container = getContentView().getRootView();\n            WindowManager.LayoutParams p = (WindowManager.LayoutParams) container.getLayoutParams();\n            // add flag\n            p.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;\n            p.dimAmount = 0.7f;\n            WindowManager wm = (WindowManager) container.getContext().getSystemService(Context.WINDOW_SERVICE);\n            assert wm != null;\n            wm.updateViewLayout(container, p);\n        }\n\n    }\n\n    public ListPopup setModal(boolean modal) {\n        mIsModal = modal;\n        return this;\n    }\n\n    public interface OnItemClickListener {\n        void onItemClick(ListAdapter adapter, View view, int position);\n    }\n\n    public interface OnItemLongClickListener {\n        boolean onItemLongClick(ListAdapter adapter, View view, int position);\n    }\n\n    /**\n     * Use `Item` for fast prototyping in an `ArrayAdapter<ListPopup.Item>`\n     */\n    public static class Item {\n        @StringRes\n        public final int stringId;\n        final String string;\n\n        public Item(Context context, @StringRes int stringId) {\n            super();\n            this.stringId = stringId;\n            this.string = context.getResources()\n                .getString(stringId);\n        }\n\n        public Item(String string) {\n            super();\n            this.stringId = 0;\n            this.string = string;\n        }\n\n        @NonNull\n        @Override\n        public String toString() {\n            return this.string;\n        }\n    }\n\n    protected class ScrollView extends android.widget.ScrollView {\n        public ScrollView(Context context) {\n            super(context);\n        }\n\n        @Override\n        public boolean dispatchTouchEvent(MotionEvent event) {\n            // act as a modal, if we click outside dismiss the popup\n            final int x = (int) event.getX();\n            final int y = (int) event.getY();\n            if ((event.getAction() == MotionEvent.ACTION_DOWN)\n                && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) {\n                dismiss();\n                return true;\n            } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {\n                dismiss();\n                return true;\n            }\n            return super.dispatchTouchEvent(event);\n        }\n    }\n\n    private class PopupDataSetObserver extends DataSetObserver {\n        @Override\n        public void onChanged() {\n            if (isShowing()) {\n                // Resize the popup to fit new content\n                updateItems();\n                update();\n            }\n        }\n\n        @Override\n        public void onInvalidated() {\n            dismiss();\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/ui/RecyclerList.java",
    "content": "package rocks.tbog.tblauncher.ui;\n\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android.util.Log;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport rocks.tbog.tblauncher.result.ReversibleAdapterRecyclerLayoutManager;\n\npublic class RecyclerList extends RecyclerView {\n    private static final String TAG = \"list\";\n\n    public RecyclerList(@NonNull Context context) {\n        this(context, null);\n    }\n\n    public RecyclerList(@NonNull Context context, @Nullable AttributeSet attrs) {\n        super(context, attrs);\n    }\n\n    public RecyclerList(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n    }\n\n    public int getAdapterFirstItemIdx() {\n        if (getLayoutManager() instanceof ReversibleAdapterRecyclerLayoutManager) {\n            ReversibleAdapterRecyclerLayoutManager lm = (ReversibleAdapterRecyclerLayoutManager) getLayoutManager();\n            return lm.isReverseAdapter() ? (getLayoutManager().getItemCount() - 1) : 0;\n        }\n        return 0;\n    }\n\n    public void scrollToFirstItem() {\n        final int adapterPos = getAdapterFirstItemIdx();\n        if (adapterPos >= 0) {\n            Log.d(TAG, \"scrollToPosition( \" + adapterPos + \" )\");\n            scrollToPosition(adapterPos);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/ui/SearchEditText.java",
    "content": "package rocks.tbog.tblauncher.ui;\r\n\r\nimport android.content.Context;\r\nimport android.util.AttributeSet;\r\nimport android.view.DragEvent;\r\nimport android.view.KeyEvent;\r\n\r\nimport androidx.appcompat.widget.AppCompatEditText;\r\n\r\npublic class SearchEditText extends AppCompatEditText {\r\n    private OnEditorActionListener mEditorListener;\r\n\r\n    public SearchEditText(Context context) {\r\n        super(context);\r\n    }\r\n\r\n    public SearchEditText(Context context, AttributeSet attrs) {\r\n        super(context, attrs);\r\n    }\r\n\r\n    public SearchEditText(Context context, AttributeSet attrs, int defStyleAttr) {\r\n        super(context, attrs, defStyleAttr);\r\n    }\r\n\r\n    @Override\r\n    public void setOnEditorActionListener(OnEditorActionListener listener) {\r\n        mEditorListener = listener;\r\n        super.setOnEditorActionListener(listener);\r\n    }\r\n\r\n    @Override\r\n    public boolean onKeyPreIme(int keyCode, KeyEvent event) {\r\n        if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP)\r\n            if (mEditorListener != null && mEditorListener.onEditorAction(this, android.R.id.closeButton, event))\r\n                return true;\r\n        return super.onKeyPreIme(keyCode, event);\r\n    }\r\n\r\n    @Override\r\n    public boolean onDragEvent(DragEvent event) {\r\n        // Fixes bug when dropping onto a textEdit widget which can cause a NPE\r\n        // This fix should be on ALL TextEdit Widgets !!!\r\n        // See : https://stackoverflow.com/a/23483957\r\n        return true;\r\n    }\r\n}\r\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/ui/SquareImageView.java",
    "content": "package rocks.tbog.tblauncher.ui;\n\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android.util.Log;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\npublic class SquareImageView extends androidx.appcompat.widget.AppCompatImageView {\n    private static final String TAG = \"SqImgView\";\n\n    protected int mComputedSize = -1;\n    //protected boolean mRequestLayout = false;\n\n    public SquareImageView(@NonNull Context context) {\n        super(context);\n    }\n\n    public SquareImageView(@NonNull Context context, @Nullable AttributeSet attrs) {\n        super(context, attrs);\n    }\n\n    public SquareImageView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n    }\n\n    interface ToString<T> {\n        @NonNull\n        String fromMode(T input);\n    }\n\n    @Override\n    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n        int size;\n        int widthMode = MeasureSpec.getMode(widthMeasureSpec);\n        int heightMode = MeasureSpec.getMode(heightMeasureSpec);\n        int widthSize = MeasureSpec.getSize(widthMeasureSpec);\n        int heightSize = MeasureSpec.getSize(heightMeasureSpec);\n\n\n        ToString<Integer> toString = input -> {\n            switch (MeasureSpec.getMode(input)) {\n                case MeasureSpec.UNSPECIFIED:\n                    return \"UNSPECIFIED\";\n                case MeasureSpec.EXACTLY:\n                    return \"EXACTLY\";\n                case MeasureSpec.AT_MOST:\n                    return \"AT_MOST\";\n                default:\n                    return Integer.toString(input);\n            }\n        };\n        // Log.v(TAG, Integer.toHexString(System.identityHashCode(this))\n        //     + \" measured=\" + getMeasuredWidth() + \"x\" + getMeasuredHeight()\n        //     + \"\\n\\t\"\n        //     + \" widthMode=\" + toString.fromMode(widthMode)\n        //     + \" widthSize=\" + widthSize\n        //     + \"\\n\\t\"\n        //     + \" heightMode=\" + toString.fromMode(heightMode)\n        //     + \" heightSize=\" + heightSize\n        // );\n        if (widthMode == MeasureSpec.EXACTLY && widthSize > 0) {\n            size = widthSize;\n            mComputedSize = size;\n        } else if (heightMode == MeasureSpec.EXACTLY && heightSize > 0) {\n            size = heightSize;\n            mComputedSize = size;\n        } else if (heightMode == MeasureSpec.AT_MOST && widthMode == MeasureSpec.UNSPECIFIED) {\n            if (mComputedSize > 0) {\n                mComputedSize = Math.min(mComputedSize, heightSize);\n                size = mComputedSize;\n            } else {\n                size = heightSize;\n            }\n        } else if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.UNSPECIFIED) {\n            if (mComputedSize > 0) {\n                mComputedSize = Math.min(widthSize, mComputedSize);\n                size = mComputedSize;\n            } else {\n                size = widthSize;\n            }\n        } else {\n            int minSize = Math.min(widthSize, heightSize);\n            mComputedSize = Math.min(mComputedSize, minSize);\n            size = mComputedSize > 0 ? mComputedSize : minSize;\n        }\n\n        final int widthSizeAndState = resolveSizeAndState(size, widthMeasureSpec, 0);\n        final int heightSizeAndState = resolveSizeAndState(size, heightMeasureSpec, 0);\n        widthSize = widthSizeAndState & MEASURED_SIZE_MASK;\n        heightSize = heightSizeAndState & MEASURED_SIZE_MASK;\n\n        if ((widthSizeAndState & MEASURED_STATE_TOO_SMALL) != 0 || (heightSizeAndState & MEASURED_STATE_TOO_SMALL) != 0) {\n            Log.d(TAG, Integer.toHexString(System.identityHashCode(this))\n                + \" mark for re-layout\"\n                + \" | too small \" + widthSize + \"×\" + heightSize\n                + \" | size=\" + size);\n            setMeasuredDimension(widthSizeAndState, heightSizeAndState);\n            post(this::requestLayout);\n            return;\n        }\n\n        int finalWidthSpec;\n        int finalHeightSpec;\n\n        if (mComputedSize > 0) {\n            finalWidthSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);\n            finalHeightSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);\n        } else {\n            Log.d(TAG, Integer.toHexString(System.identityHashCode(this))\n                + \" AT_MOST \" + widthSize + \"×\" + heightSize\n                + \" | size=\" + size);\n            finalWidthSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.AT_MOST);\n            finalHeightSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.AT_MOST);\n        }\n\n        // let super method call `setMeasuredDimension`\n        super.onMeasure(finalWidthSpec, finalHeightSpec);\n\n        if (mComputedSize > 0 && (getMeasuredWidth() != size || getMeasuredHeight() != size)) {\n            Log.d(TAG, Integer.toHexString(System.identityHashCode(this))\n                + \" mark for re-layout\"\n                + \" | measured at \" + getMeasuredWidth() + \"×\" + getMeasuredHeight()\n                + \" | \" + toString.fromMode(finalWidthSpec) + \" \" + widthSize + \"×\" + heightSize\n                + \" | size=\" + size);\n            post(this::requestLayout);\n        }\n    }\n\n    @Override\n    protected void onSizeChanged(int w, int h, int oldWidth, int oldHeight) {\n        if (oldWidth == oldHeight && oldWidth == mComputedSize) {\n            Log.d(TAG, Integer.toHexString(System.identityHashCode(this)) + \" onSizeChanged to \"\n                + w + \"×\" + h + \" from \" + oldWidth + \"×\" + oldHeight + \" | reset computed size\");\n            // reset computed size\n            mComputedSize = -1;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/ui/TagsMenuUtils.java",
    "content": "package rocks.tbog.tblauncher.ui;\n\nimport static rocks.tbog.tblauncher.entry.EntryItem.LAUNCHED_FROM_GESTURE;\n\nimport android.annotation.SuppressLint;\nimport android.content.Context;\nimport android.graphics.drawable.Drawable;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.BaseAdapter;\nimport android.widget.TextView;\n\nimport androidx.annotation.NonNull;\n\nimport java.util.ArrayList;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.dataprovider.TagsProvider;\nimport rocks.tbog.tblauncher.entry.ActionEntry;\nimport rocks.tbog.tblauncher.entry.EntryItem;\nimport rocks.tbog.tblauncher.entry.StaticEntry;\nimport rocks.tbog.tblauncher.entry.TagEntry;\nimport rocks.tbog.tblauncher.searcher.TagSearcher;\nimport rocks.tbog.tblauncher.utils.PrefCache;\nimport rocks.tbog.tblauncher.utils.UISizes;\nimport rocks.tbog.tblauncher.utils.Utilities;\n\npublic class TagsMenuUtils {\n    public static ListPopup createTagsMenu(Context ctx, Iterable<String> tagNames) {\n        TagsProvider tagsProvider = TBApplication.dataHandler(ctx).getTagsProvider();\n        MenuTagAdapter adapter = new MenuTagAdapter();\n        for (String tagName : tagNames) {\n            TagEntry tagEntry = tagsProvider != null ? tagsProvider.getTagEntry(tagName) : null;\n            MenuTagAdapter.MenuItem menuItem = tagEntry != null ? new MenuTagAdapter.MenuItem(tagEntry) : new MenuTagAdapter.MenuItem(tagName);\n            adapter.addItem(menuItem);\n        }\n        EntryItem untaggedEntry;\n        boolean bAddUntagged = PrefCache.showTagsMenuUntagged(ctx);\n        if (bAddUntagged) {\n            untaggedEntry = TBApplication.dataHandler(ctx).getPojo(ActionEntry.SCHEME + \"show/untagged\");\n            if (untaggedEntry instanceof ActionEntry) {\n                int idx = PrefCache.getTagsMenuUntaggedIndex(ctx);\n                if (idx > adapter.getCount())\n                    idx = adapter.getCount();\n                adapter.addItem(idx, new MenuTagAdapter.MenuItem((ActionEntry) untaggedEntry));\n            }\n        }\n        return ListPopup.create(ctx, adapter)\n            .setOnItemClickListener((a, v, pos) -> {\n                MenuTagAdapter.MenuItem item = (MenuTagAdapter.MenuItem) a.getItem(pos);\n                if (item == null)\n                    return;\n                if (item.staticEntry != null) {\n                    item.staticEntry.doLaunch(v, LAUNCHED_FROM_GESTURE);\n                    return;\n                }\n                TBApplication.quickList(ctx).toggleSearch(v, item.toString(), TagSearcher.class);\n            });\n    }\n\n    private static class MenuTagAdapter extends BaseAdapter {\n\n        private final ArrayList<MenuItem> mList = new ArrayList<>();\n\n        private static class MenuItem {\n            final String text;\n            final private StaticEntry staticEntry;\n\n            public MenuItem(@NonNull String tagName) {\n                text = tagName;\n                staticEntry = null;\n            }\n\n            public MenuItem(@NonNull StaticEntry entry) {\n                text = entry.getName();\n                staticEntry = entry;\n            }\n\n            @NonNull\n            @Override\n            public String toString() {\n                return text;\n            }\n\n            public void setIcon(TextView textView) {\n                if (staticEntry == null) {\n                    // this is not likely to happen\n                    return;\n                }\n                Context ctx = textView.getContext();\n                if (!PrefCache.showTagsMenuIcons(ctx))\n                    return;\n                // make sure we have enough space to inline the drawable\n                final int size = UISizes.getTagsMenuIconSize(ctx);\n                Drawable loadingIcon = PrefCache.getLoadingIconDrawable(ctx);\n                loadingIcon.setBounds(0, 0, size, size);\n                textView.setCompoundDrawables(loadingIcon, null, null, null);\n                textView.setCompoundDrawablePadding(UISizes.sp2px(ctx, 2));\n                Utilities.startAnimatable(textView);\n\n                // async load and show the icon\n                Utilities.setViewAsync(textView,\n                    staticEntry::getIconDrawable,\n                    (view, drawable) -> {\n                        if (view instanceof TextView) {\n                            drawable.setBounds(0, 0, size, size);\n                            TextView v = (TextView) view;\n                            v.setCompoundDrawables(drawable, null, null, null);\n                        }\n                    });\n            }\n        }\n\n        public MenuTagAdapter() {\n            super();\n        }\n\n        public void addItem(MenuItem item) {\n            mList.add(item);\n            notifyDataSetChanged();\n        }\n\n        public void addItem(int index, MenuItem item) {\n            mList.add(index, item);\n            notifyDataSetChanged();\n        }\n\n        @Override\n        public int getCount() {\n            return mList.size();\n        }\n\n        @Override\n        public MenuItem getItem(int position) {\n            return mList.get(position);\n        }\n\n        @Override\n        public long getItemId(int position) {\n            return getItem(position).hashCode();\n        }\n\n        @Override\n        public boolean hasStableIds() {\n            return true;\n        }\n\n        @SuppressLint(\"ViewHolder\")\n        @Override\n        public View getView(int position, View convertView, ViewGroup parent) {\n            final View view;\n            view = LayoutInflater.from(parent.getContext()).inflate(getItemViewType(position), parent, false);\n\n            final MenuItem item = getItem(position);\n            if (view instanceof TextView) {\n                TextView textView = (TextView) view;\n\n                textView.setText(item.toString());\n                item.setIcon(textView);\n            }\n\n\n            return view;\n        }\n\n        @Override\n        public int getItemViewType(int position) {\n            return R.layout.popup_list_item;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/ui/ViewStubPreview.java",
    "content": "package rocks.tbog.tblauncher.ui;\n\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport android.graphics.Canvas;\nimport android.util.AttributeSet;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.ViewParent;\nimport android.view.ViewStub;\n\nimport androidx.annotation.LayoutRes;\nimport androidx.annotation.Nullable;\nimport androidx.constraintlayout.widget.ConstraintHelper;\nimport androidx.constraintlayout.widget.ConstraintLayout;\n\nimport java.lang.ref.WeakReference;\n\nimport rocks.tbog.tblauncher.R;\n\n/**\n * Copy of {@link android.view.ViewStub} so that we can see something in the preview\n */\npublic final class ViewStubPreview extends View {\n    private int mLayoutResource;\n    private int mInflatedId;\n    private WeakReference<View> mInflatedViewRef = null;\n    private LayoutInflater mInflater = null;\n    private OnInflateListener mInflateListener = null;\n\n    public ViewStubPreview(Context context, AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    public ViewStubPreview(Context context, AttributeSet attrs, int defStyle) {\n        super(context, attrs, defStyle);\n        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ViewStubPreview, defStyle, 0);\n        mInflatedId = a.getResourceId(R.styleable.ViewStubPreview_inflatedId, NO_ID);\n        mLayoutResource = a.getResourceId(R.styleable.ViewStubPreview_layout, 0);\n        a.recycle();\n        if (!isInEditMode()) {\n            setVisibility(GONE);\n            setWillNotDraw(true);\n        }\n    }\n\n    /**\n     * Returns the id taken by the inflated view. If the inflated id is\n     * {@link View#NO_ID}, the inflated view keeps its original id.\n     *\n     * @return A positive integer used to identify the inflated view or\n     * {@link #NO_ID} if the inflated view should keep its id.\n     * @attr name android:inflatedId\n     * @see #setInflatedId(int)\n     */\n    public int getInflatedId() {\n        return mInflatedId;\n    }\n\n    /**\n     * Defines the id taken by the inflated view. If the inflated id is\n     * {@link View#NO_ID}, the inflated view keeps its original id.\n     *\n     * @param inflatedId A positive integer used to identify the inflated view or\n     *                   {@link #NO_ID} if the inflated view should keep its id.\n     * @attr name android:inflatedId\n     * @see #getInflatedId()\n     */\n    public void setInflatedId(int inflatedId) {\n        mInflatedId = inflatedId;\n    }\n\n    /**\n     * Returns the layout resource that will be used by {@link #setVisibility(int)} or\n     * {@link #inflate()} to replace this StubbedView\n     * in its parent by another view.\n     *\n     * @return The layout resource identifier used to inflate the new View.\n     * @attr name android:layout\n     * @see #setLayoutResource(int)\n     * @see #setVisibility(int)\n     * @see #inflate()\n     */\n    public int getLayoutResource() {\n        return mLayoutResource;\n    }\n\n    /**\n     * Specifies the layout resource to inflate when this StubbedView becomes visible or invisible\n     * or when {@link #inflate()} is invoked. The View created by inflating the layout resource is\n     * used to replace this StubbedView in its parent.\n     *\n     * @param layoutResource A valid layout resource identifier (different from 0.)\n     * @attr name android:layout\n     * @see #getLayoutResource()\n     * @see #setVisibility(int)\n     * @see #inflate()\n     */\n    public void setLayoutResource(int layoutResource) {\n        mLayoutResource = layoutResource;\n    }\n\n    /**\n     * Set {@link LayoutInflater} to use in {@link #inflate()}, or {@code null}\n     * to use the default.\n     */\n    public void setLayoutInflater(LayoutInflater inflater) {\n        mInflater = inflater;\n    }\n\n    /**\n     * Get current {@link LayoutInflater} used in {@link #inflate()}.\n     */\n    public LayoutInflater getLayoutInflater() {\n        return mInflater;\n    }\n\n    @Override\n    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n        if (isInEditMode()) {\n            super.onMeasure(widthMeasureSpec, heightMeasureSpec);\n            return;\n        }\n\n        setMeasuredDimension(0, 0);\n    }\n\n    @Override\n    public void draw(Canvas canvas) {\n        if (isInEditMode())\n            super.draw(canvas);\n    }\n\n    @Override\n    protected void dispatchDraw(Canvas canvas) {\n        // don't draw the stub\n    }\n\n    /**\n     * When visibility is set to {@link #VISIBLE} or {@link #INVISIBLE},\n     * {@link #inflate()} is invoked and this StubbedView is replaced in its parent\n     * by the inflated layout resource. After that calls to this function are passed\n     * through to the inflated view.\n     *\n     * @param visibility One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}.\n     * @see #inflate()\n     */\n    @Override\n    public void setVisibility(int visibility) {\n        if (mInflatedViewRef != null) {\n            View view = mInflatedViewRef.get();\n            if (view != null) {\n                view.setVisibility(visibility);\n            } else {\n                throw new IllegalStateException(\"setVisibility called on un-referenced view\");\n            }\n        } else {\n            super.setVisibility(visibility);\n            if (visibility == VISIBLE || visibility == INVISIBLE) {\n                inflate();\n            }\n        }\n    }\n\n    /**\n     * Inflates the layout resource identified by {@link #getLayoutResource()}\n     * and replaces this StubbedView in its parent by the inflated layout resource.\n     *\n     * @return The inflated layout resource.\n     */\n    public View inflate() {\n        final ViewParent viewParent = getParent();\n        if (viewParent instanceof ViewGroup) {\n            if (mLayoutResource != 0) {\n                final ViewGroup parent = (ViewGroup) viewParent;\n                final LayoutInflater factory;\n                if (mInflater != null) {\n                    factory = mInflater;\n                } else {\n                    factory = LayoutInflater.from(getContext());\n                }\n                final View view = factory.inflate(mLayoutResource, parent, false);\n                if (mInflatedId != NO_ID) {\n                    view.setId(mInflatedId);\n                }\n                final int index = parent.indexOfChild(this);\n                parent.removeViewInLayout(this);\n                final ViewGroup.LayoutParams layoutParams = getLayoutParams();\n                if (layoutParams != null) {\n                    parent.addView(view, index, layoutParams);\n                } else {\n                    parent.addView(view, index);\n                }\n\n                // update parent ConstraintLayout constraints\n                if (parent instanceof ConstraintLayout)\n                    updateConstraintsAfterStubInflate((ConstraintLayout) parent, getId(), view.getId());\n\n                mInflatedViewRef = new WeakReference<>(view);\n                if (mInflateListener != null) {\n                    mInflateListener.onInflate(this, view);\n                }\n                return view;\n            } else {\n                throw new IllegalArgumentException(\"ViewStub must have a valid layoutResource\");\n            }\n        } else {\n            throw new IllegalStateException(\"ViewStub must have a non-null ViewGroup viewParent\");\n        }\n    }\n\n    /**\n     * Specifies the inflate listener to be notified after this ViewStub successfully\n     * inflated its layout resource.\n     *\n     * @param inflateListener The OnInflateListener to notify of successful inflation.\n     * @see android.view.ViewStub.OnInflateListener\n     */\n    public void setOnInflateListener(OnInflateListener inflateListener) {\n        mInflateListener = inflateListener;\n    }\n\n    @Nullable\n    public static View inflateStub(@Nullable View view) {\n        return inflateStub(view, 0);\n    }\n\n    @Nullable\n    public static View inflateStub(@Nullable View view, @LayoutRes int layoutRes) {\n        if (view instanceof ViewStubPreview) {\n            if (layoutRes != 0)\n                ((ViewStubPreview) view).setLayoutResource(layoutRes);\n            // ViewStubPreview already calls updateConstraintsAfterStubInflate\n            return ((ViewStubPreview) view).inflate();\n        }\n\n        if (!(view instanceof ViewStub))\n            return view;\n\n        ViewStub stub = (ViewStub) view;\n        int stubId = stub.getId();\n\n        // get parent before the call to inflate\n        ConstraintLayout constraintLayout = stub.getParent() instanceof ConstraintLayout ? (ConstraintLayout) stub.getParent() : null;\n\n        if (layoutRes != 0)\n            stub.setLayoutResource(layoutRes);\n        View inflatedView = stub.inflate();\n        int inflatedId = inflatedView.getId();\n\n        updateConstraintsAfterStubInflate(constraintLayout, stubId, inflatedId);\n\n        return inflatedView;\n    }\n\n    private static void updateConstraintsAfterStubInflate(@Nullable ConstraintLayout constraintLayout, int stubId, int inflatedId) {\n        if (inflatedId == View.NO_ID)\n            return;\n        // change parent ConstraintLayout constraints\n        if (constraintLayout != null && stubId != inflatedId) {\n            int childCount = constraintLayout.getChildCount();\n            for (int childIdx = 0; childIdx < childCount; childIdx += 1) {\n                View child = constraintLayout.getChildAt(childIdx);\n                if (child instanceof ConstraintHelper) {\n                    // get a copy of the id list\n                    int[] refIds = ((ConstraintHelper) child).getReferencedIds();\n                    boolean changed = false;\n                    // change constraint reference IDs\n                    for (int idx = 0; idx < refIds.length; idx += 1) {\n                        if (refIds[idx] == stubId) {\n                            refIds[idx] = inflatedId;\n                            changed = true;\n                        }\n                    }\n                    if (changed)\n                        ((ConstraintHelper) child).setReferencedIds(refIds);\n                }\n                ConstraintLayout.LayoutParams params = (ConstraintLayout.LayoutParams) child.getLayoutParams();\n                if (changeConstraintLayoutParamsTarget(params, stubId, inflatedId))\n                    child.setLayoutParams(params);\n            }\n        }\n    }\n\n    private static boolean changeConstraintLayoutParamsTarget(ConstraintLayout.LayoutParams params, int fromId, int toId) {\n        boolean changed = false;\n        if (params.leftToLeft == fromId) {\n            params.leftToLeft = toId;\n            changed = true;\n        }\n        if (params.leftToRight == fromId) {\n            params.leftToRight = toId;\n            changed = true;\n        }\n        if (params.rightToLeft == fromId) {\n            params.rightToLeft = toId;\n            changed = true;\n        }\n        if (params.rightToRight == fromId) {\n            params.rightToRight = toId;\n            changed = true;\n        }\n        if (params.topToTop == fromId) {\n            params.topToTop = toId;\n            changed = true;\n        }\n        if (params.topToBottom == fromId) {\n            params.topToBottom = toId;\n            changed = true;\n        }\n        if (params.bottomToTop == fromId) {\n            params.bottomToTop = toId;\n            changed = true;\n        }\n        if (params.bottomToBottom == fromId) {\n            params.bottomToBottom = toId;\n            changed = true;\n        }\n        if (params.baselineToBaseline == fromId) {\n            params.baselineToBaseline = toId;\n            changed = true;\n        }\n        if (params.baselineToTop == fromId) {\n            params.baselineToTop = toId;\n            changed = true;\n        }\n        if (params.circleConstraint == fromId) {\n            params.circleConstraint = toId;\n            changed = true;\n        }\n        if (params.startToEnd == fromId) {\n            params.startToEnd = toId;\n            changed = true;\n        }\n        if (params.startToStart == fromId) {\n            params.startToStart = toId;\n            changed = true;\n        }\n        if (params.endToStart == fromId) {\n            params.endToStart = toId;\n            changed = true;\n        }\n        if (params.endToEnd == fromId) {\n            params.endToEnd = toId;\n            changed = true;\n        }\n        return changed;\n    }\n\n    /**\n     * Listener used to receive a notification after a ViewStub has successfully\n     * inflated its layout resource.\n     *\n     * @see android.view.ViewStub#setOnInflateListener(android.view.ViewStub.OnInflateListener)\n     */\n    public interface OnInflateListener {\n        /**\n         * Invoked after a ViewStub successfully inflated its layout resource.\n         * This method is invoked after the inflated view was added to the\n         * hierarchy but before the layout pass.\n         *\n         * @param stub     The ViewStub that initiated the inflation.\n         * @param inflated The inflated View.\n         */\n        void onInflate(ViewStubPreview stub, View inflated);\n    }\n}"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/ui/WindowInsetsHelper.java",
    "content": "package rocks.tbog.tblauncher.ui;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.ContextWrapper;\nimport android.os.Build;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.Window;\nimport android.view.inputmethod.InputMethodManager;\nimport android.widget.EditText;\n\nimport androidx.annotation.NonNull;\nimport androidx.core.view.ViewCompat;\nimport androidx.core.view.WindowCompat;\nimport androidx.core.view.WindowInsetsCompat;\nimport androidx.core.view.WindowInsetsControllerCompat;\n\nimport rocks.tbog.tblauncher.R;\n\npublic class WindowInsetsHelper implements KeyboardHandler {\n    private final WindowInsetsControllerCompat controller;\n    private final View mRoot;\n\n    /**\n     * Initialize WindowInsetsControllerCompat. It's best to have the root as an EditText to\n     * simplify the `showKeyboard` code\n     *\n     * @param root any view in the window. Used to get the context and window token.\n     */\n    public WindowInsetsHelper(View root) {\n        if (root == null)\n            throw new IllegalStateException(\"WindowInsetsHelper root == null\");\n        mRoot = root;\n        Window window = findWindow(root.getContext());\n        if (window == null)\n            throw new IllegalStateException(\"WindowInsetsHelper window == null for \" + root);\n        controller = WindowCompat.getInsetsController(window, root);\n        controller.setSystemBarsBehavior(WindowInsetsControllerCompat.BEHAVIOR_DEFAULT);\n    }\n\n    @Override\n    public void showKeyboard() {\n        // on KitKat `controller.show` is no-op\n        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {\n            Context ctx = mRoot.getContext();\n            InputMethodManager imm = (InputMethodManager) ctx.getSystemService(Context.INPUT_METHOD_SERVICE);\n            View view = mRoot;\n            if (view.isInEditMode() || view.onCheckIsTextEditor()) {\n                view.requestFocus();\n            } else {\n                Window window = findWindow(ctx);\n                if (window != null) {\n                    // we should display the keyboard for the currently focused view\n                    view = window.getCurrentFocus();\n                } else {\n                    view = null;\n                }\n            }\n\n            // Fallback on finding the first EditText\n            if (view == null) {\n                view = findTextEditor(mRoot);\n            }\n            imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT);\n        } else {\n            controller.show(WindowInsetsCompat.Type.ime());\n        }\n    }\n\n    private static Window findWindow(Context ctx) {\n        Context context = ctx;\n        while (context instanceof ContextWrapper) {\n            if (context instanceof Activity) {\n                Window window = ((Activity) context).getWindow();\n                if (window != null)\n                    return window;\n            }\n            context = ((ContextWrapper) context).getBaseContext();\n        }\n        return null;\n    }\n\n    private static View findTextEditor(View root) {\n        if (root instanceof ViewGroup) {\n            int childCount = ((ViewGroup) root).getChildCount();\n            // search this level\n            for (int childIdx = 0; childIdx < childCount; childIdx += 1) {\n                View child = ((ViewGroup) root).getChildAt(childIdx);\n                if (child.onCheckIsTextEditor() || child instanceof EditText)\n                    return child;\n            }\n            // look deeper\n            for (int childIdx = 0; childIdx < childCount; childIdx += 1) {\n                View child = ((ViewGroup) root).getChildAt(childIdx);\n                child = findTextEditor(child);\n                if (child.onCheckIsTextEditor() || child instanceof EditText)\n                    return child;\n            }\n        }\n        return root;\n    }\n\n    @Override\n    public void hideKeyboard() {\n        // on KitKat `controller.hide` is no-op\n        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {\n            Context ctx = mRoot.getContext();\n            InputMethodManager imm = (InputMethodManager) ctx.getSystemService(Context.INPUT_METHOD_SERVICE);\n            imm.hideSoftInputFromWindow(mRoot.getWindowToken(), 0);\n        } else {\n            controller.hide(WindowInsetsCompat.Type.ime());\n        }\n\n        // we need to keep focus on some window view or else the keyboard may eat the next back press\n        // example: \"Multiling O Keyboard + emoji\" by Honso (kl.ime.oh) will not send the key event KEYCODE_BACK\n        // after we hide the keyboard by scrolling\n        Window window = findWindow(mRoot.getContext());\n        if (window != null)\n            window.getDecorView().requestFocus();\n    }\n\n    public void showSystemBars() {\n        controller.show(WindowInsetsCompat.Type.systemBars());\n    }\n\n    public void hideSystemBars() {\n        controller.hide(WindowInsetsCompat.Type.systemBars());\n    }\n\n    @NonNull\n    private static View getRootView(View view) {\n        // we need a root view that has `android:fitsSystemWindows=\"true\"` to find the height of the keyboard\n        ViewGroup rootView = (ViewGroup) view.getRootView();\n        ViewGroup rootLayout = rootView.findViewById(R.id.root_layout);\n        // child 0 is `R.id.notificationBackground`\n        // child 1 is a full-screen ViewGroup that has `android:fitsSystemWindows=\"true\"`\n        return rootLayout.getChildAt(1);\n    }\n\n    public static int getKeyboardHeight(View view) {\n        // we need a root view that has `android:fitsSystemWindows=\"true\"` to find the height of the keyboard\n        return getRootView(view).getPaddingBottom();\n    }\n\n    public static boolean isKeyboardVisible(View view) {\n        // On devices running API 20 and below, getRootWindowInsets always returns null.\n        WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(view);\n        if (insets != null)\n            return insets.isVisible(WindowInsetsCompat.Type.ime());\n        // in the unlikely case we can't get the insets, assume we have a keyboard if the bottom padding is greater than 150px\n        return getKeyboardHeight(view) > 150;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/ui/dialog/ConfirmDialog.java",
    "content": "package rocks.tbog.tblauncher.ui.dialog;\n\nimport android.content.Context;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.TextView;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.ui.DialogFragment;\n\npublic class ConfirmDialog extends DialogFragment<Void> {\n    @Override\n    protected int layoutRes() {\n        return R.layout.pref_confirm;\n    }\n\n    @Nullable\n    @Override\n    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {\n        Context context = requireDialog().getContext();\n\n        setupDefaultButtonOkCancel(context);\n\n        // make sure we use the dialog context\n        LayoutInflater contextInflater = inflater.cloneInContext(context);\n        return super.onCreateView(contextInflater, container, savedInstanceState);\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            view.setClipToOutline(true);\n        }\n\n        Bundle args = getArguments() != null ? getArguments() : new Bundle();\n        CharSequence descriptionText = args.getCharSequence(\"descriptionText\", \"\");\n        CharSequence titleText = args.getCharSequence(\"titleText\", \"\");\n\n        ((TextView) view.findViewById(android.R.id.text1)).setText(titleText);\n        ((TextView) view.findViewById(android.R.id.text2)).setText(descriptionText);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/ui/dialog/EditTextDialog.java",
    "content": "package rocks.tbog.tblauncher.ui.dialog;\n\nimport android.app.Dialog;\nimport android.content.Context;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.view.KeyEvent;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.inputmethod.EditorInfo;\nimport android.widget.EditText;\nimport android.widget.TextView;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.StringRes;\nimport androidx.core.view.ViewCompat;\nimport androidx.core.view.WindowInsetsCompat;\nimport androidx.core.view.WindowInsetsControllerCompat;\n\nimport com.google.android.material.textfield.TextInputLayout;\n\nimport rocks.tbog.tblauncher.Behaviour;\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.ui.DialogFragment;\nimport rocks.tbog.tblauncher.ui.DialogWrapper;\n\npublic class EditTextDialog extends DialogFragment<CharSequence> {\n    private static final String TAG = EditTextDialog.class.getSimpleName();\n\n    private EditTextDialog() {\n        super();\n    }\n\n    @Override\n    protected int layoutRes() {\n        return R.layout.dialog_rename;\n    }\n\n    @Nullable\n    @Override\n    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {\n        // make sure we use the dialog context\n        LayoutInflater dialogInflater = inflater.cloneInContext(requireDialog().getContext());\n        return super.onCreateView(dialogInflater, container, savedInstanceState);\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            view.setClipToOutline(true);\n        }\n\n        // read and apply the arguments\n        Bundle args = getArguments() != null ? getArguments() : new Bundle();\n        CharSequence initialText = args.getCharSequence(\"initialText\", \"\");\n        CharSequence titleText = args.getCharSequence(\"titleText\", \"\");\n        CharSequence hintText = args.getCharSequence(\"hintText\", \"\");\n        //hint\n        if (hintText.length() != 0) {\n            TextInputLayout textInputLayout = view.findViewById(android.R.id.hint);\n            textInputLayout.setHintEnabled(true);\n            textInputLayout.setHint(hintText);\n        }\n        // initial text\n        {\n            EditText textView = view.findViewById(R.id.rename);\n            textView.setText(initialText);\n            textView.setOnEditorActionListener((v, actionId, event) -> {\n                if (event == null) {\n                    if (actionId != EditorInfo.IME_ACTION_NONE) {\n                        final CharSequence name = v.getText();\n                        if (name.length() == 0) {\n                            dismiss();\n                            return true;\n                        }\n                        onConfirm(name);\n                        return true;\n                    }\n                } else if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) {\n                    if (event.getAction() == KeyEvent.ACTION_UP) {\n                        final CharSequence name = v.getText();\n                        onConfirm(name);\n                    }\n                    return true;\n                }\n                return false;\n            });\n            textView.requestFocus();\n        }\n        // title\n        {\n            TextView textView = view.findViewById(android.R.id.title);\n            textView.setText(titleText);\n        }\n    }\n\n    @Override\n    public void onStart() {\n        super.onStart();\n        Dialog dialog = getDialog();\n        if (dialog instanceof DialogWrapper) {\n            ((DialogWrapper) dialog).setOnWindowFocusChanged((dlg, hasFocus) -> {\n                if (hasFocus) {\n                    dlg.setOnWindowFocusChanged(null);\n                    View view = dlg.getCurrentFocus();\n                    if (view == null)\n                        view = dlg.findViewById(android.R.id.content);\n                    WindowInsetsControllerCompat ctrl = view != null ? ViewCompat.getWindowInsetsController(view) : null;\n                    if (ctrl != null)\n                        ctrl.show(WindowInsetsCompat.Type.ime());\n                    else\n                        Log.e(TAG, \"failed to show keyboard\");\n                }\n            });\n        }\n    }\n\n    public static class Builder {\n        private final Context mContext;\n        private final Bundle mArgs = new Bundle();\n        private EditTextDialog mDialog = null;\n        private OnButtonClickListener<CharSequence> mClickPositive = null;\n        private OnButtonClickListener<CharSequence> mClickNegative = null;\n        private OnButtonClickListener<CharSequence> mClickNeutral = null;\n        private OnConfirmListener<CharSequence> mOnConfirm = null;\n\n        public Builder(@NonNull Context context) {\n            super();\n            mContext = context;\n        }\n\n        public Builder setTitle(@Nullable CharSequence title) {\n            mArgs.putCharSequence(\"titleText\", title);\n            return this;\n        }\n\n        public Builder setTitle(@StringRes int titleId) {\n            mArgs.putCharSequence(\"titleText\", mContext.getText(titleId));\n            return this;\n        }\n\n        public Builder setHint(@Nullable CharSequence hint) {\n            mArgs.putCharSequence(\"hintText\", hint);\n            return this;\n        }\n\n        public Builder setHint(@StringRes int hintId) {\n            return setHint(mContext.getText(hintId));\n        }\n\n        public Builder setInitialText(@Nullable CharSequence text) {\n            mArgs.putCharSequence(\"initialText\", text);\n            return this;\n        }\n\n        public Builder setPositiveButton(@StringRes int btnTextId, OnButtonClickListener<CharSequence> onClickListener) {\n            mArgs.putCharSequence(\"btnPositiveText\", mContext.getText(btnTextId));\n            mClickPositive = onClickListener;\n            if (mDialog != null)\n                mDialog.setOnPositiveClickListener(mClickPositive);\n            return this;\n        }\n\n        public Builder setNegativeButton(@StringRes int btnTextId, OnButtonClickListener<CharSequence> onClickListener) {\n            mArgs.putCharSequence(\"btnNegativeText\", mContext.getText(btnTextId));\n            mClickNegative = onClickListener;\n            if (mDialog != null)\n                mDialog.setOnNegativeClickListener(mClickNegative);\n            return this;\n        }\n\n        public Builder setNeutralButton(@StringRes int btnTextId, OnButtonClickListener<CharSequence> onClickListener) {\n            mArgs.putCharSequence(\"btnNeutralText\", mContext.getText(btnTextId));\n            mClickNeutral = onClickListener;\n            if (mDialog != null)\n                mDialog.setOnNeutralClickListener(mClickNeutral);\n            return this;\n        }\n\n        public Builder setConfirmListener(@StringRes int positiveTextId, OnConfirmListener<CharSequence> onConfirm) {\n            mArgs.putCharSequence(\"btnPositiveText\", mContext.getText(positiveTextId));\n            mClickPositive = (dialog, button) -> {\n                EditText input = dialog.findViewById(R.id.rename);\n                dialog.onConfirm(input != null ? input.getText() : null);\n            };\n            mOnConfirm = onConfirm;\n            if (mDialog != null) {\n                mDialog.setOnPositiveClickListener(mClickPositive);\n                mDialog.setOnConfirmListener(mOnConfirm);\n            }\n            return this;\n        }\n\n        @NonNull\n        public EditTextDialog getDialog() {\n            if (mDialog == null) {\n                mDialog = new EditTextDialog();\n                mDialog.setArguments(mArgs);\n                mDialog.setOnPositiveClickListener(mClickPositive);\n                mDialog.setOnNegativeClickListener(mClickNegative);\n                mDialog.setOnNeutralClickListener(mClickNeutral);\n                mDialog.setOnConfirmListener(mOnConfirm);\n            }\n            return mDialog;\n        }\n\n        public void show() {\n            EditTextDialog dialog = getDialog();\n            Behaviour.showDialog(mContext, dialog, \"dialog_rename\");\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/ui/dialog/PleaseWaitDialog.java",
    "content": "package rocks.tbog.tblauncher.ui.dialog;\n\nimport android.app.Dialog;\nimport android.content.Context;\nimport android.graphics.drawable.Animatable;\nimport android.os.Bundle;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\nimport android.widget.TextView;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.ui.DialogFragment;\nimport rocks.tbog.tblauncher.utils.UISizes;\n\npublic class PleaseWaitDialog extends DialogFragment<Void> {\n    public static final String ARG_TITLE = \"title\";\n    public static final String ARG_DESCRIPTION = \"desc\";\n\n    View mView = null;\n    Runnable mWork = null;\n\n    public void setWork(@Nullable Runnable work) {\n        mWork = work;\n    }\n\n    public void onWorkFinished() {\n        if (mView == null) {\n            onConfirm(null);\n            dismiss();\n            return;\n        }\n        // OK button\n        {\n            View button = mView.findViewById(android.R.id.button1);\n            button.setEnabled(true);\n        }\n        // progress indicator\n        {\n            View view = mView.findViewById(android.R.id.progress);\n            if (view != null)\n                view.setVisibility(View.INVISIBLE);\n        }\n    }\n\n    @Override\n    protected int layoutRes() {\n        return R.layout.pref_confirm;\n    }\n\n    @Nullable\n    @Override\n    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {\n        @NonNull\n        Dialog dialog = requireDialog();\n        Context context = dialog.getContext();\n\n        setupDefaultButtonOk(context);\n\n        // make sure we use the dialog context\n        inflater = inflater.cloneInContext(context);\n        mView = super.onCreateView(inflater, container, savedInstanceState);\n//        Window window = dialog.getWindow();\n//        if (window != null)\n//            window.setBackgroundDrawableResource(R.drawable.dialog_background_dark);\n\n        // add progress indicator\n        if (mView instanceof ViewGroup) {\n            ImageView loading = new ImageView(context);\n            loading.setImageResource(R.drawable.ic_loading_arrows);\n            if (loading.getDrawable() instanceof Animatable)\n                ((Animatable) loading.getDrawable()).start();\n            //loading.setImageDrawable(DrawableUtils.getProgressBarIndeterminate(context));\n            loading.setId(android.R.id.progress);\n\n            // add progress bar before the button panel\n            {\n                View buttonPanel = mView.findViewById(R.id.buttonPanel);\n                int index = ((ViewGroup) mView).indexOfChild(buttonPanel);\n                ((ViewGroup) mView).addView(loading, index);\n            }\n\n            ViewGroup.LayoutParams params = loading.getLayoutParams();\n            params.width = ViewGroup.LayoutParams.MATCH_PARENT;\n            params.height = UISizes.getResultIconSize(context) * 2;\n            loading.setLayoutParams(params);\n        }\n\n        // while we wait, we wait, not cancel\n        dialog.setCanceledOnTouchOutside(false);\n        dialog.setCancelable(false);\n        return mView;\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n\n        Bundle args = getArguments() != null ? getArguments() : new Bundle();\n\n        // title\n        {\n            TextView text = view.findViewById(android.R.id.text1);\n            String title = args.getString(ARG_TITLE);\n            if (title != null)\n                text.setText(title);\n            else\n                text.setText(R.string.please_wait);\n        }\n\n        // description\n        {\n            TextView text = view.findViewById(android.R.id.text2);\n            String description = args.getString(ARG_DESCRIPTION);\n            if (description != null)\n                text.setText(description);\n            else\n                text.setText(\"\");\n        }\n\n        // OK button\n        {\n            setOnPositiveClickListener((dialog, button) -> onConfirm(null));\n            View button = view.findViewById(android.R.id.button1);\n            button.setEnabled(false);\n        }\n    }\n\n    @Override\n    public void onStart() {\n        super.onStart();\n        if (mWork != null) {\n            // start the loading after the dialog is visible\n            mView.postDelayed(mWork, 500);\n        } else {\n            onWorkFinished();\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/ui/dialog/TagsManagerDialog.java",
    "content": "package rocks.tbog.tblauncher.ui.dialog;\n\nimport android.content.Context;\nimport android.os.Bundle;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport rocks.tbog.tblauncher.Behaviour;\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.TagsManager;\nimport rocks.tbog.tblauncher.entry.EntryItem;\nimport rocks.tbog.tblauncher.searcher.TagSearcher;\nimport rocks.tbog.tblauncher.ui.DialogFragment;\nimport rocks.tbog.tblauncher.utils.DialogHelper;\n\npublic class TagsManagerDialog extends DialogFragment<Void> {\n\n    private final TagsManager mManager = new TagsManager();\n\n    @Override\n    protected int layoutRes() {\n        return R.layout.tags_manager;\n    }\n\n    @Nullable\n    @Override\n    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {\n        Context context = requireDialog().getContext();\n\n        setupDefaultButtonOkCancel(context);\n\n        // make sure we use the dialog context\n        inflater = inflater.cloneInContext(context);\n        return super.onCreateView(inflater, container, savedInstanceState);\n    }\n\n    @Override\n    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        mManager.bindView(view, (v, info) -> {\n            if (info.staticEntry != null) {\n                info.staticEntry.doLaunch(v, EntryItem.LAUNCHED_FROM_GESTURE);\n            } else {\n                Context ctx = v.getContext();\n                TBApplication.quickList(ctx).toggleSearch(v, info.tagName, TagSearcher.class);\n            }\n            if (mManager.hasChangesMade())\n                askBeforeDismiss();\n        });\n    }\n\n    @Override\n    public void onButtonClick(@NonNull Button button) {\n        if (button == Button.POSITIVE) {\n            mManager.applyChanges(requireContext());\n            onConfirm(null);\n        } else if (button == Button.NEGATIVE && mManager.hasChangesMade()) {\n            askBeforeDismiss();\n            return;\n        }\n        super.onButtonClick(button);\n    }\n\n    @Override\n    public void onStart() {\n        super.onStart();\n        mManager.onStart();\n    }\n\n    private void askBeforeDismiss() {\n        Context ctx = requireContext();\n        DialogFragment<?> dlg = DialogHelper.makeConfirmDialog(ctx,\n            R.string.exit_tags_manager_confirm,\n            R.string.exit_tags_manager_description,\n            (dialog, btn) -> TagsManagerDialog.this.dismiss());\n        Behaviour.showDialog(ctx, dlg, \"dialog_confirm\");\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/utils/ArrayHelper.java",
    "content": "package rocks.tbog.tblauncher.utils;\n\npublic class ArrayHelper {\n    public static boolean contains(int[] arr, int find) {\n        for (int value : arr)\n            if (value == find)\n                return true;\n        return false;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/utils/ClipboardUtils.java",
    "content": "package rocks.tbog.tblauncher.utils;\n\nimport android.content.ClipData;\nimport android.content.ClipboardManager;\nimport android.content.Context;\n\npublic class ClipboardUtils {\n    public static void setClipboard(Context context, String text) {\n        ClipboardManager clipboard;\n        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {\n            clipboard = context.getSystemService(ClipboardManager.class);\n        } else {\n            clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);\n        }\n\n        ClipData clip = ClipData.newPlainText(\"Copied Text\", text);\n        clipboard.setPrimaryClip(clip);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/utils/ColorFilterHelper.java",
    "content": "package rocks.tbog.tblauncher.utils;\n\nimport android.graphics.ColorFilter;\nimport android.graphics.ColorMatrix;\nimport android.graphics.ColorMatrixColorFilter;\n\n/**\n * http://groups.google.com/group/android-developers/browse_thread/thread/9e215c83c3819953\n * http://gskinner.com/blog/archives/2007/12/colormatrix_cla.html\n * https://medium.com/mobile-app-development-publication/android-image-color-change-with-colormatrix-e927d7fb6eb4\n */\npublic class ColorFilterHelper {\n\n    private static final float[] DELTA_INDEX = {\n            0f, 0.01f, 0.02f, 0.04f, 0.05f, 0.06f, 0.07f, 0.08f, 0.1f, 0.11f,\n            0.12f, 0.14f, 0.15f, 0.16f, 0.17f, 0.18f, 0.20f, 0.21f, 0.22f, 0.24f,\n            0.25f, 0.27f, 0.28f, 0.30f, 0.32f, 0.34f, 0.36f, 0.38f, 0.40f, 0.42f,\n            0.44f, 0.46f, 0.48f, 0.5f, 0.53f, 0.56f, 0.59f, 0.62f, 0.65f, 0.68f,\n            0.71f, 0.74f, 0.77f, 0.80f, 0.83f, 0.86f, 0.89f, 0.92f, 0.95f, 0.98f,\n            1.0f, 1.06f, 1.12f, 1.18f, 1.24f, 1.30f, 1.36f, 1.42f, 1.48f, 1.54f,\n            1.60f, 1.66f, 1.72f, 1.78f, 1.84f, 1.90f, 1.96f, 2.0f, 2.12f, 2.25f,\n            2.37f, 2.50f, 2.62f, 2.75f, 2.87f, 3.0f, 3.2f, 3.4f, 3.6f, 3.8f,\n            4.0f, 4.3f, 4.7f, 4.9f, 5.0f, 5.5f, 6.0f, 6.5f, 6.8f, 7.0f,\n            7.3f, 7.5f, 7.8f, 8.0f, 8.4f, 8.7f, 9.0f, 9.4f, 9.6f, 9.8f,\n            10.f\n    };\n\n    public static boolean adjustHue(ColorMatrix cm, int amount) {\n        if (amount == 0)\n            return false;\n        int value = clampValue(amount, 180);\n        double rad = Math.toRadians(value);\n        float cosVal = (float) Math.cos(rad);\n        float sinVal = (float) Math.sin(rad);\n        float R = 0.2125f;\n        float G = 0.7154f;\n        float B = 0.0721f;\n        float[] mat = new float[]{\n                R + cosVal * (1 - R) + sinVal * (-R), G + cosVal * (-G) + sinVal * (-G), B + cosVal * (-B) + sinVal * (1 - B), 0, 0,\n                R + cosVal * (-R) + sinVal * (0.143f), G + cosVal * (1 - G) + sinVal * (0.140f), B + cosVal * (-B) + sinVal * (-0.283f), 0, 0,\n                R + cosVal * (-R) + sinVal * (-(1 - R)), G + cosVal * (-G) + sinVal * (G), B + cosVal * (1 - B) + sinVal * (B), 0, 0,\n                0f, 0f, 0f, 1f, 0f,\n                0f, 0f, 0f, 0f, 1f};\n        cm.postConcat(new ColorMatrix(mat));\n        return true;\n    }\n\n    public static boolean adjustBrightness(ColorMatrix cm, int amount) {\n        if (amount == 0)\n            return false;\n        int value = clampValue(amount, 100);\n\n        // convert from -100..100 to -255..255\n        value = value * 255 / 100;\n        float[] mat = new float[]{\n                1, 0, 0, 0, value,\n                0, 1, 0, 0, value,\n                0, 0, 1, 0, value,\n                0, 0, 0, 1, 0,\n                0, 0, 0, 0, 1\n        };\n        cm.postConcat(new ColorMatrix(mat));\n        return true;\n    }\n\n    public static boolean adjustContrast(ColorMatrix cm, int amount) {\n        if (amount == 0)\n            return false;\n        int value = clampValue(amount, 100);\n        float x;\n        if (value < 0) {\n            x = 127.5f + value / 100f * 127.5f;\n        } else {\n            x = 127.5f + DELTA_INDEX[value] * 127.5f;\n        }\n\n        float c = x / 127.5f;\n        float b = .5f * (127.5f - x);\n        float[] mat = new float[]{\n                c, 0, 0, 0, b,\n                0, c, 0, 0, b,\n                0, 0, c, 0, b,\n                0, 0, 0, 1, 0,\n                0, 0, 0, 0, 1\n        };\n        cm.postConcat(new ColorMatrix(mat));\n        return true;\n    }\n\n    public static boolean adjustSaturation(ColorMatrix cm, int amount) {\n        if (amount == 0)\n            return false;\n        int value = clampValue(amount, 100);\n        final float x = 1f + ((value > 0) ? 3f * value / 100f : value / 100f);\n        final float inv = 1f - x;\n        final float R = 0.3086f * inv;\n        final float G = 0.6094f * inv;\n        final float B = 0.0820f * inv;\n        float[] mat = new float[]{\n                R + x, G, B, 0, 0,\n                R, G + x, B, 0, 0,\n                R, G, B + x, 0, 0,\n                0, 0, 0, 1, 0,\n                0, 0, 0, 0, 1\n        };\n        cm.postConcat(new ColorMatrix(mat));\n        return true;\n    }\n\n    public static boolean adjustScale(ColorMatrix cm, int r, int g, int b, int a) {\n        if (r == 0 && g == 0 && b == 0 && a == 0)\n            return false;\n        final float R = getChannelScale(r);\n        final float G = getChannelScale(g);\n        final float B = getChannelScale(b);\n        final float A = getChannelScale(a);\n        float[] mat = new float[]{\n                R, 0, 0, 0, -255f * (R - 1f) * .5f,\n                0, G, 0, 0, -255f * (G - 1f) * .5f,\n                0, 0, B, 0, -255f * (B - 1f) * .5f,\n                0, 0, 0, A, -255f * (A - 1f) * .5f,\n                0, 0, 0, 0, 1\n        };\n        cm.postConcat(new ColorMatrix(mat));\n        return true;\n    }\n\n    private static float getChannelScale(int scale) {\n        float s = clampValue(scale, 200);\n        return s / 100f + 1f;\n    }\n\n    // make sure values are within the specified range, hue has a limit of 180, others are 100:\n    private static int clampValue(int val, int limit) {\n        return Math.min(limit, Math.max(-limit, val));\n    }\n\n    public static ColorFilter adjustColor(int brightness, int contrast, int saturation, int hue) {\n        ColorMatrix cm = new ColorMatrix();\n        adjustHue(cm, hue);\n        adjustContrast(cm, contrast);\n        adjustBrightness(cm, brightness);\n        adjustSaturation(cm, saturation);\n\n        return new ColorMatrixColorFilter(cm);\n    }\n}"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/utils/DebugInfo.java",
    "content": "package rocks.tbog.tblauncher.utils;\n\nimport android.content.Context;\nimport android.content.SharedPreferences;\n\nimport androidx.preference.PreferenceManager;\n\npublic class DebugInfo {\n\n    public static boolean widgetAdd(Context context) {\n        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);\n        return prefs.getBoolean(\"debug-widget-add-info\", false);\n    }\n\n    public static boolean widgetInfo(Context context) {\n        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);\n        return prefs.getBoolean(\"debug-widget-info\", false);\n    }\n\n    public static boolean itemRelevance(Context context) {\n        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);\n        return prefs.getBoolean(\"debug-item-relevance\", false);\n    }\n\n    public static boolean keyboardScrollHiderTouch(Context context) {\n        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);\n        return prefs.getBoolean(\"debug-ksh-touch\", false);\n    }\n\n    public static boolean enableFavorites(Context context) {\n        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);\n        return prefs.getBoolean(\"debug-favorites\", false);\n    }\n\n    public static boolean providerStatus(Context context) {\n        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);\n        return prefs.getBoolean(\"debug-provider-status\", false);\n    }\n\n    public static boolean itemIconInfo(Context context) {\n        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);\n        return prefs.getBoolean(\"debug-item-icon-info\", false);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/utils/DebugString.java",
    "content": "package rocks.tbog.tblauncher.utils;\n\nimport android.view.ViewGroup;\n\npublic class DebugString {\n\n    public static String layoutParamSize(int size) {\n        switch (size) {\n            case ViewGroup.LayoutParams.MATCH_PARENT:\n                return \"MATCH_PARENT\";\n            case ViewGroup.LayoutParams.WRAP_CONTENT:\n                return \"WRAP_CONTENT\";\n            default:\n                return String.valueOf(size);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/utils/DeviceUtils.java",
    "content": "package rocks.tbog.tblauncher.utils;\n\nimport android.annotation.SuppressLint;\nimport android.bluetooth.BluetoothAdapter;\nimport android.content.Context;\nimport android.content.res.Configuration;\nimport android.net.wifi.WifiManager;\nimport android.os.Build;\nimport android.provider.Settings;\nimport android.telephony.TelephonyManager;\nimport android.text.TextUtils;\nimport android.util.DisplayMetrics;\nimport android.util.Log;\nimport android.view.WindowManager;\n//import android.webkit.WebView;\n\nimport androidx.annotation.Nullable;\n\nimport java.io.UnsupportedEncodingException;\nimport java.lang.reflect.Method;\nimport java.net.URLEncoder;\nimport java.security.MessageDigest;\nimport java.util.Locale;\nimport java.util.UUID;\n\n/**\n * Created by KhanhDuong on 8/14/2015.\n * Author Khanh Duong\n * Email khanhduong@innoria.com\n * Company www.innoria.com\n */\npublic class DeviceUtils {\n    /**\n     * get device id base on current system config\n     * @param context\n     * @return\n     */\n    public static String getDeviceId(Context context) {\n        //get IMEI first\n        String imei = getIMEI(context);\n        if (!TextUtils.isEmpty(imei)) {\n            return encodeMD5(imei);\n        }\n        Log.e(\"DeviceUtils\", \"getDeviceId, IMEI is NULL. start get device serial\");\n        //get Android device serial for device without telephony\n        String serialNum = getDeviceSerial(context);\n        if (!TextUtils.isEmpty(serialNum)) {\n            return encodeMD5(serialNum);\n        }\n        Log.e(\"DeviceUtils\", \"getDeviceId, SERIAL is NULL. start get device Android_ID\");\n        //get Android_ID. not trust, it's reseted after factory reset\n        String androidId = getAndroidId(context);\n        if (!TextUtils.isEmpty(androidId)) {\n            return encodeMD5(androidId);\n        }\n        Log.e(\"DeviceUtils\", \"getDeviceId, Can not generate device ID\");\n        return null;\n    }\n\n    /**\n     * get device serial id.\n     * @param context\n     * @return\n     */\n    @SuppressLint(\"HardwareIds\")\n    public static String getDeviceSerial(Context context) {\n        String serial = null;\n\n        try {\n            @SuppressLint(\"PrivateApi\")\n            Class<?> c = Class.forName(\"android.os.SystemProperties\");\n            Method get = c.getMethod(\"get\", String.class);\n            serial = (String)get.invoke(c, \"ro.serialno\");\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        return serial;\n    }\n\n    @Nullable\n    private static String getIMEI(Context context) {\n//        try {\n//            TelephonyManager telephonyManager = (TelephonyManager) context\n//                    .getSystemService(Context.TELEPHONY_SERVICE);\n//            @SuppressLint(\"HardwareIds\")\n//            String imei = telephonyManager.getDeviceId();\n//\n//            return imei;\n//        } catch (SecurityException e) {\n//            Log.e(\"\", \"TELEPHONY_SERVICE\", e);\n//        } catch (Exception e) {\n//            e.printStackTrace();\n//        }\n        return null;\n    }\n\n//    /**\n//     * this method will generate the special id with WIFI MAC Address\n//     *\n//     * @param context\n//     * @return\n//     */\n//    public static String getDeviceIDWithWifiMACAddress(Context context, String saltCode) {\n//        if (saltCode == null) {\n//            saltCode = \"_JonnyKenAndRuby_\";\n//        }\n//        String deviceId = getIMEI(context);\n//        try {\n//            // DEVICE_ID // Requires READ_PHONE_STATE\n//            if (deviceId == null) {\n//                deviceId = \"\";\n//            }\n//\n//            // DEVICE SERIAL\n//            String deviceSerial = getDeviceSerial(context);\n//            if (deviceSerial == null) {\n//                deviceSerial = \"\";\n//            }\n//\n//            String androidId = getAndroidId(context);\n//            if (androidId == null) {\n//                androidId = \"\";\n//            }\n//\n//            // WIFI MAC ADDRESS: android.permission.ACCESS_WIFI_STATE\n//            WifiManager wm = (WifiManager)context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);\n//            String wifiMAC = wm.getConnectionInfo().getMacAddress();\n//\n//            // sum\n//            deviceId = deviceId + saltCode + androidId + saltCode + wifiMAC + saltCode + deviceSerial;\n//            deviceId = encodeMD5(deviceId);\n//        } catch (Exception e) {\n//            e.printStackTrace();\n//        }\n//        Log.d(\"KST\", \"DeviceID With WifiMAC: \" + deviceId);\n//        return deviceId;\n//    }\n\n    /**\n     * get Android ID of device\n     * @param context\n     * @return\n     */\n    public static String getAndroidId(Context context) {\n        @SuppressLint(\"HardwareIds\")\n        String androidId = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);\n        return androidId;\n    }\n\n//    /**\n//     * this method will generate the special id with\n//     * Bluetooth MAC Address.\n//     *\n//     * @param context\n//     * @return\n//     */\n//    public static String getDeviceIDWithBluetoothAddress(Context context, String saltCode) {\n//        if (saltCode == null) {\n//            saltCode = \"_JonnyKenAndRuby_\";\n//        }\n//        String deviceId = getIMEI(context);\n//        try {\n//            // DEVICE_ID // Requires READ_PHONE_STATE\n//            if (deviceId == null) {\n//                deviceId = \"\";\n//            }\n//\n//            // DEVICE SERIAL\n//            String serial = getDeviceSerial(context);\n//            if (serial == null) {\n//                serial = \"\";\n//            }\n//\n//            // Bluetooth MAC address android.permission.BLUETOOTH required\n//            BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();\n//            String bluetoothMAC = bluetoothAdapter.getAddress();\n//\n//            // sum\n//            deviceId = deviceId + saltCode + bluetoothMAC + saltCode + serial;\n//            deviceId = encodeMD5(deviceId);\n//        } catch (Exception e) {\n//            e.printStackTrace();\n//        }\n//        return deviceId;\n//    }\n\n    /**\n     * help us convert to MD5 hash value.\n     * @param text\n     * @return\n     */\n    public static String encodeMD5(String text) {\n        MessageDigest m = null;\n        try {\n            m = MessageDigest.getInstance(\"MD5\");\n            m.update(text.getBytes(), 0, text.length());\n            byte p_md5Data[] = m.digest();\n\n            String m_szUniqueID = new String();\n            for (int i=0;i<p_md5Data.length;i++) {\n                int b =  (0xFF & p_md5Data[i]);\n                // if it is a single digit, make sure it have 0 in front (proper padding)\n                if (b <= 0xF) m_szUniqueID+=\"0\";\n                // add number to string\n                m_szUniqueID += Integer.toHexString(b);\n            }\n            m_szUniqueID = m_szUniqueID.toUpperCase(Locale.getDefault());\n            return m_szUniqueID;\n        } catch (Exception e) {\n            e.printStackTrace();\n            try {\n                return UUID.nameUUIDFromBytes(text.getBytes(\"utf8\")).toString();\n            } catch (UnsupportedEncodingException e1) {\n                e1.printStackTrace();\n            }\n        }\n        return text;\n    }\n\n    /**\n     * get screen width\n     *\n     * @return\n     */\n    public static int getScreenWidth(Context context) {\n        DisplayMetrics outMetrics = new DisplayMetrics();\n        WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);\n        wm.getDefaultDisplay().getMetrics(outMetrics);\n        int screenW = outMetrics.widthPixels;\n\n        return screenW;\n    }\n\n    /**\n     * get screen height\n     *\n     * @return\n     */\n    public static int getScreenHeight(Context context) {\n        DisplayMetrics outMetrics = new DisplayMetrics();\n        WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);\n        wm.getDefaultDisplay().getMetrics(outMetrics);\n        int screenH = outMetrics.heightPixels;\n\n        return screenH;\n    }\n\n    /**\n     * convert dp values to px\n     *\n     * @param context\n     * @param dp\n     * @return\n     */\n    public static int convertDpToPx(Context context, int dp) {\n        DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();\n        return (int)((dp * displayMetrics.density) + 0.5);\n    }\n\n    /**\n     * convert px values to dp\n     *\n     * @param context\n     * @param px\n     * @return\n     */\n    public static int convertPxToDp(Context context, int px) {\n        DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();\n        return (int)((px / displayMetrics.density) + 0.5);\n    }\n\n    /**\n     * convert dp values to px with float type\n     *\n     * @param context\n     * @param dp\n     * @return\n     */\n    public static int convertDpToPx(Context context, float dp) {\n        DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();\n        return (int)((dp * displayMetrics.density) + 0.5);\n    }\n\n    /**\n     * get device name\n     * @param context\n     * @return\n     */\n    public static String getDeviceName(Context context) {\n        return Build.MODEL;\n    }\n\n    /**\n     * get URL encoded device name\n     * @return\n     */\n    public static String getURLEncodedDeviceName() {\n        String terminalName = Build.MODEL;\n        try {\n            terminalName = URLEncoder.encode(terminalName, \"UTF-8\");\n        } catch (UnsupportedEncodingException e) {\n            e.printStackTrace();\n        }\n        return terminalName;\n    }\n\n    /**\n     * show device information.\n     *\n     * @param tagOfLogCat this is for export to logcat.\n     * @param context\n     */\n    public static void showDeviceInfo(String tagOfLogCat, Context context) {\n        if (tagOfLogCat == null || tagOfLogCat.equals(\"\")) {\n            tagOfLogCat = \"Innoria\";\n        }\n        Log.i(tagOfLogCat, \"===Start show device information===\");\n        // resolution.\n        int width = DeviceUtils.getScreenWidth(context);\n        int height = DeviceUtils.getScreenHeight(context);\n        Log.i(tagOfLogCat, \"screenW=\" + width);\n        Log.i(tagOfLogCat, \"screenH=\" + height);\n        // check density.\n        Log.i(tagOfLogCat, \"density=\" + context.getResources().getDisplayMetrics().density);\n        Log.i(tagOfLogCat, \"scaledDensity=\"\n                + context.getResources().getDisplayMetrics().scaledDensity);\n        Log.i(tagOfLogCat, \"screenLayoutType=\" + getScreenLayoutTypeText(context));\n\n        Log.i(tagOfLogCat, \"densityDpi=\" + getDensityDpi(context));\n        Log.i(tagOfLogCat, \"densityType=\" + getDensityDpiText(context));\n        // check device.\n        Log.i(tagOfLogCat, \"MANUFACTURER=\" + Build.MANUFACTURER);\n        Log.i(tagOfLogCat, \"DEVICE=\" + Build.DEVICE);\n        Log.i(tagOfLogCat, \"MODEL=\" + Build.MODEL);\n        Log.i(tagOfLogCat, \"PRODUCT=\" + Build.PRODUCT);\n        Log.i(tagOfLogCat, \"DISPLAY=\" + Build.DISPLAY);\n        Log.i(tagOfLogCat, \"BRAND=\" + Build.BRAND);\n        Log.i(tagOfLogCat, \"CPU_ABI=\" + Build.CPU_ABI);\n        Log.i(tagOfLogCat, \"BOARD=\" + Build.BOARD);\n        Log.i(tagOfLogCat, \"SDK INT=\" + Build.VERSION.SDK_INT);\n        Log.i(tagOfLogCat, \"SDK RELEASE=\" + Build.VERSION.RELEASE);\n        Log.i(tagOfLogCat, \"SDK CODENAME=\" + Build.VERSION.CODENAME);\n        Log.i(tagOfLogCat, \"SDK INCREMENTAL=\" + Build.VERSION.INCREMENTAL);\n        //Log.i(tagOfLogCat, \"User Agent=\" + new WebView(context).getSettings().getUserAgentString());\n        Log.i(tagOfLogCat, \"===End show device information===\");\n    }\n\n    public static int getDensityDpi(Context context) {\n        return context.getResources().getDisplayMetrics().densityDpi;\n    }\n\n    public static String getDensityDpiText(Context context) {\n        int density = getDensityDpi(context);\n        String text = \"DENSITY_MEDIUM\";\n        switch (density) {\n            case DisplayMetrics.DENSITY_HIGH:\n                text = \"DisplayMetrics.DENSITY_HIGH\";\n                break;\n            case DisplayMetrics.DENSITY_LOW:\n                text = \"DisplayMetrics.DENSITY_LOW\";\n                break;\n            case DisplayMetrics.DENSITY_MEDIUM:\n                text = \"DisplayMetrics.DENSITY_MEDIUM\";\n                break;\n            case DisplayMetrics.DENSITY_TV:\n                text = \"DisplayMetrics.DENSITY_TV\";\n                break;\n            case DisplayMetrics.DENSITY_XHIGH:\n                text = \"DisplayMetrics.DENSITY_XHIGH\";\n                break;\n            case DisplayMetrics.DENSITY_XXHIGH:\n                text = \"DisplayMetrics.DENSITY_XXHIGH\";\n                break;\n            default:\n                break;\n        }\n        return text;\n    }\n    /**\n     * Returns the language code for this Locale or the empty string if no\n     * language was set\n     *\n     * @return\n     */\n    public static String getLanguageCode() {\n        return Locale.getDefault().getLanguage().toLowerCase(Locale.getDefault());\n    }\n\n    /**\n     * get screen size type\n     * @param context\n     * @return\n     */\n    public static int getScreenLayoutType(Context context) {\n        return (context.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK);\n    }\n\n\n    /**\n     * get screen size as text.\n     * @param context\n     * @return\n     */\n    public static String getScreenLayoutTypeText(Context context) {\n        int screenType = getScreenLayoutType(context);\n        String screenTypeS = \"SCREENLAYOUT_SIZE_UNDEFINED\";\n        switch (screenType) {\n            case Configuration.SCREENLAYOUT_SIZE_LARGE:\n                screenTypeS = \"SCREENLAYOUT_SIZE_LARGE\";\n                break;\n            case Configuration.SCREENLAYOUT_SIZE_NORMAL:\n                screenTypeS = \"SCREENLAYOUT_SIZE_NORMAL\";\n                break;\n            case Configuration.SCREENLAYOUT_SIZE_SMALL:\n                screenTypeS = \"SCREENLAYOUT_SIZE_SMALL\";\n                break;\n            case 4:\n                screenTypeS = \"SCREENLAYOUT_SIZE_XLARGE\";\n                break;\n            case Configuration.SCREENLAYOUT_SIZE_UNDEFINED:\n                screenTypeS = \"SCREENLAYOUT_SIZE_UNDEFINED\";\n                break;\n            default:\n                break;\n        }\n        return screenTypeS;\n    }\n\n    /**\n     * check this device is tablet or not.\n     * @param context\n     * @return\n     */\n    public static boolean isTablet(Context context) {\n        return isXLarge(context) | isLarge(context);\n    }\n\n    /**\n     * check device is large screen\n     * @param context\n     * @return\n     */\n    public static boolean isLarge(Context context) {\n        return (context.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_LARGE;\n    }\n\n    /**\n     * check device is Xlarge screen.\n     * @param context\n     * @return\n     */\n    public static boolean isXLarge(Context context) {\n        return (context.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == 4; // 4\n        // is\n        // xlarge\n    }\n\n//    /**\n//     * check support honey com (sdk_int >= 11) tablet\n//     * @return\n//     */\n//    public static boolean isSupportedHoneycombTablet(Context context) {\n//        return hasHoneycomb() && isTablet(context);\n//    }\n//\n//\n//    /**\n//     * check support froyo\n//     * @return\n//     */\n//    public static boolean hasFroyo() {\n//        // Can use static final constants like FROYO, declared in later versions\n//        // of the OS since they are inlined at compile time. This is guaranteed\n//        // behavior.\n//        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO;\n//    }\n//\n//    public static boolean hasGingerbread() {\n//        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD;\n//    }\n//\n//    /**\n//     * check support honey com (sdk_int >= 11)\n//     * @return\n//     */\n//    public static boolean hasHoneycomb() {\n//        // return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;\n//        return Build.VERSION.SDK_INT >= 11;\n//    }\n//\n//    /**\n//     * check support honey com (sdk_int >= 12)\n//     * @return\n//     */\n//    public static boolean hasHoneycombMR1() {\n//        // return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1;\n//        return Build.VERSION.SDK_INT >= 12;\n//    }\n//\n//    /**\n//     * check support Jelly bean (sdk_int >= 16)\n//     * @return\n//     */\n//    public static boolean hasJellyBean() {\n//        // return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;\n//        return Build.VERSION.SDK_INT >= 16;\n//    }\n}"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/utils/DialogHelper.java",
    "content": "package rocks.tbog.tblauncher.utils;\n\nimport android.annotation.SuppressLint;\nimport android.app.Dialog;\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.graphics.drawable.Drawable;\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewParent;\nimport android.widget.EditText;\nimport android.widget.TextView;\n\nimport androidx.annotation.IdRes;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.StringRes;\nimport androidx.appcompat.app.AlertDialog;\n\nimport rocks.tbog.tblauncher.CustomizeUI;\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.ui.DialogFragment;\nimport rocks.tbog.tblauncher.ui.dialog.ConfirmDialog;\nimport rocks.tbog.tblauncher.ui.dialog.EditTextDialog;\n\npublic class DialogHelper {\n\n    private static final String TAG = DialogHelper.class.getSimpleName();\n\n    public static void setCustomTitle(AlertDialog.Builder builder, CharSequence title) {\n        // Dialog doesn't provide a view we can send as root\n        @SuppressLint(\"InflateParams\")\n        View customTitle = LayoutInflater.from(builder.getContext()).inflate(R.layout.dialog_title, null, false);\n        TextView titleView = customTitle.findViewById(android.R.id.title);\n        titleView.setText(title);\n        builder.setCustomTitle(customTitle);\n    }\n\n    @SuppressLint(\"RestrictedApi\")\n    public static void setButtonBarBackground(Dialog dialog) {\n        Context ctx = dialog.getContext();\n        View buttonLayout;\n        @IdRes int buttonPanel;\n\n        // try to find the `buttonPanel` defined in the android layout\n        buttonPanel = ctx.getResources().getIdentifier(\"buttonPanel\", \"id\", \"android\");\n        buttonLayout = dialog.findViewById(buttonPanel);\n\n        // try to find the `buttonPanel` defined in the androidx layout\n        if (buttonLayout == null) {\n            buttonPanel = ctx.getResources().getIdentifier(\"buttonPanel\", \"id\", ctx.getPackageName());\n            buttonLayout = dialog.findViewById(buttonPanel);\n        }\n\n        // hack: can't find the button container by id, get the parent of one button\n        if (buttonLayout == null) {\n            View button = dialog.findViewById(android.R.id.button1);\n            ViewParent parent = button == null ? null : button.getParent();\n            if (parent instanceof View) {\n                buttonLayout = (View) parent;\n\n                // assuming the buttonPanel is inflated from `abc_alert_dialog_button_bar_material.xml`\n                if (buttonLayout instanceof androidx.appcompat.widget.ButtonBarLayout) {\n                    parent = buttonLayout.getParent();\n                    if (parent instanceof android.widget.ScrollView)\n                        buttonLayout = (View) parent;\n                }\n            }\n        }\n\n        // apply the background\n        if (buttonLayout != null) {\n            Drawable background = CustomizeUI.getDialogButtonBarBackgroundDrawable(ctx.getTheme());\n            if (background != null)\n                buttonLayout.setBackground(background);\n        }\n    }\n\n    public static ConfirmDialog makeConfirmDialog(@NonNull Context context, @StringRes int titleId, @StringRes int descId, DialogFragment.OnButtonClickListener<Void> onOk) {\n        Resources r = context.getResources();\n        Bundle args = new Bundle();\n        args.putCharSequence(\"titleText\", r.getText(titleId));\n        args.putCharSequence(\"descriptionText\", r.getText(descId));\n\n        ConfirmDialog confirmDialog = new ConfirmDialog();\n        confirmDialog.setArguments(args);\n        confirmDialog.setOnPositiveClickListener(onOk);\n\n        return confirmDialog;\n    }\n\n    public interface OnRename {\n        void rename(Dialog dialog, String name);\n    }\n\n    public static EditTextDialog.Builder makeRenameDialog(@NonNull Context ctx, CharSequence currentName, @NonNull OnRename callback) {\n//        // get activity theme for this dialog\n//        Context themeWrapper = UITheme.getDialogThemedContext(ctx);\n        EditTextDialog.Builder builder = new EditTextDialog.Builder(ctx)\n            .setInitialText(currentName)\n            .setPositiveButton(R.string.menu_action_rename, (dialog, button) -> {\n                EditText input = dialog.findViewById(R.id.rename);\n                dialog.onConfirm(input != null ? input.getText() : null);\n            })\n            .setNegativeButton(android.R.string.cancel, null);\n\n        EditTextDialog dialog = builder.getDialog();\n        dialog.setOnConfirmListener(newName -> {\n            Log.d(TAG, \"rename confirm: `\" + newName + \"`\");\n            if (newName == null)\n                return;\n            callback.rename(dialog.getDialog(), newName.toString().trim());\n        });\n\n        return builder;\n//                .afterInflate(dialog -> {\n//                    @SuppressLint(\"CutPasteId\")\n//                    EditText nameView = ((Dialog) dialog).findViewById(R.id.rename);\n//                    nameView.setText(currentName);\n//\n////                    showKeyboard((Dialog) dialog, nameView);\n////                    nameView.postDelayed(() -> showKeyboard((Dialog) dialog, nameView), 500);\n//                    int color = 0xFFffd700;//UIColors.getThemeColor(((Dialog) dialog).getContext(), android.R.attr.textColor);\n//                    Utilities.setTextSelectHandleColor(nameView, color);\n//                });\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/utils/EdgeGlowHelper.java",
    "content": "package rocks.tbog.tblauncher.utils;\n\nimport android.graphics.PorterDuff;\nimport android.graphics.PorterDuffColorFilter;\nimport android.graphics.drawable.Drawable;\nimport android.os.Build;\nimport android.util.Log;\nimport android.widget.AbsListView;\nimport android.widget.EdgeEffect;\n\nimport androidx.annotation.ColorInt;\nimport androidx.annotation.NonNull;\nimport androidx.core.widget.EdgeEffectCompat;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport java.lang.reflect.Field;\nimport java.util.Arrays;\n\n/**\n * Found this in a comment from https://stackoverflow.com/questions/27342957/how-to-change-the-color-of-overscroll-edge-and-overscroll-glow\n * Taken from https://pastebin.com/TAujMUu9\n */\npublic final class EdgeGlowHelper {\n    private static final String TAG = EdgeGlowHelper.class.getSimpleName();\n\n    private static final Class<RecyclerView> CLASS_RECYCLER_VIEW = RecyclerView.class;\n    private static final Field RECYCLER_VIEW_FIELD_EDGE_GLOW_TOP;\n    private static final Field RECYCLER_VIEW_FIELD_EDGE_GLOW_LEFT;\n    private static final Field RECYCLER_VIEW_FIELD_EDGE_GLOW_RIGHT;\n    private static final Field RECYCLER_VIEW_FIELD_EDGE_GLOW_BOTTOM;\n\n    private static final Class<AbsListView> CLASS_LIST_VIEW = AbsListView.class;\n    private static final Field LIST_VIEW_FIELD_EDGE_GLOW_TOP;\n    private static final Field LIST_VIEW_FIELD_EDGE_GLOW_BOTTOM;\n\n    private static final Field EDGE_GLOW_FIELD_EDGE;\n    private static final Field EDGE_GLOW_FIELD_GLOW;\n    private static final Field EDGE_EFFECT_COMPAT_FIELD_EDGE_EFFECT;\n\n    static {\n        Field edgeGlowTop = null;\n        Field edgeGlowBottom = null;\n        Field edgeGlowLeft = null;\n        Field edgeGlowRight = null;\n        for (Field f : CLASS_RECYCLER_VIEW.getDeclaredFields()) {\n            switch (f.getName()) {\n                case \"mTopGlow\":\n                    f.setAccessible(true);\n                    edgeGlowTop = f;\n                    break;\n                case \"mBottomGlow\":\n                    f.setAccessible(true);\n                    edgeGlowBottom = f;\n                    break;\n                case \"mLeftGlow\":\n                    f.setAccessible(true);\n                    edgeGlowLeft = f;\n                    break;\n                case \"mRightGlow\":\n                    f.setAccessible(true);\n                    edgeGlowRight = f;\n                    break;\n                default:\n                    // do nothing\n                    break;\n            }\n        }\n\n        RECYCLER_VIEW_FIELD_EDGE_GLOW_TOP = edgeGlowTop;\n        RECYCLER_VIEW_FIELD_EDGE_GLOW_BOTTOM = edgeGlowBottom;\n        RECYCLER_VIEW_FIELD_EDGE_GLOW_LEFT = edgeGlowLeft;\n        RECYCLER_VIEW_FIELD_EDGE_GLOW_RIGHT = edgeGlowRight;\n    }\n\n    static {\n        Field edgeGlowTop = null;\n        Field edgeGlowBottom = null;\n        for (Field f : CLASS_LIST_VIEW.getDeclaredFields()) {\n            switch (f.getName()) {\n                case \"mEdgeGlowTop\":\n                    f.setAccessible(true);\n                    edgeGlowTop = f;\n                    break;\n                case \"mEdgeGlowBottom\":\n                    f.setAccessible(true);\n                    edgeGlowBottom = f;\n                    break;\n                default:\n                    // do nothing\n                    break;\n            }\n        }\n\n        LIST_VIEW_FIELD_EDGE_GLOW_TOP = edgeGlowTop;\n        LIST_VIEW_FIELD_EDGE_GLOW_BOTTOM = edgeGlowBottom;\n    }\n\n    static {\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {\n            Field edge = null;\n            Field glow = null;\n\n            for (Field f : EdgeEffect.class.getDeclaredFields()) {\n                switch (f.getName()) {\n                    case \"mEdge\":\n                        f.setAccessible(true);\n                        edge = f;\n                        break;\n                    case \"mGlow\":\n                        f.setAccessible(true);\n                        glow = f;\n                        break;\n                    default:\n                        // do nothing\n                        break;\n                }\n            }\n\n            EDGE_GLOW_FIELD_EDGE = edge;\n            EDGE_GLOW_FIELD_GLOW = glow;\n        } else {\n            EDGE_GLOW_FIELD_EDGE = null;\n            EDGE_GLOW_FIELD_GLOW = null;\n        }\n    }\n\n    static {\n        Field efc = null;\n        try {\n            efc = EdgeEffectCompat.class.getDeclaredField(\"mEdgeEffect\");\n        } catch (NoSuchFieldException e) {\n            Log.e(TAG, \"field `mEdgeEffect` not found in `EdgeEffectCompat`\", e);\n        }\n        EDGE_EFFECT_COMPAT_FIELD_EDGE_EFFECT = efc;\n    }\n\n    public static void setEdgeGlowColor(RecyclerView recyclerView, @ColorInt int color) {\n        recyclerView.setEdgeEffectFactory(new EdgeGlowFactory(color));\n        try {\n            Object ee;\n            ee = RECYCLER_VIEW_FIELD_EDGE_GLOW_TOP.get(recyclerView);\n            setEffectGlowColor(ee, color);\n            ee = RECYCLER_VIEW_FIELD_EDGE_GLOW_BOTTOM.get(recyclerView);\n            setEffectGlowColor(ee, color);\n            ee = RECYCLER_VIEW_FIELD_EDGE_GLOW_LEFT.get(recyclerView);\n            setEffectGlowColor(ee, color);\n            ee = RECYCLER_VIEW_FIELD_EDGE_GLOW_RIGHT.get(recyclerView);\n            setEffectGlowColor(ee, color);\n        } catch (Exception e) {\n            Log.e(TAG, \"set RecyclerView(\" + recyclerView.getClass().getSimpleName() + \") edge color to 0x\" + Integer.toHexString(color).toUpperCase(), e);\n        }\n    }\n\n    public static void setEdgeGlowColor(AbsListView listView, @ColorInt int color) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n            listView.setEdgeEffectColor(color);\n        } else {\n            try {\n                Object ee;\n                ee = LIST_VIEW_FIELD_EDGE_GLOW_TOP.get(listView);\n                setEffectGlowColor(ee, color);\n                ee = LIST_VIEW_FIELD_EDGE_GLOW_BOTTOM.get(listView);\n                setEffectGlowColor(ee, color);\n            } catch (Exception e) {\n                Log.e(TAG, \"set AbsListView(\" + listView.getClass().getSimpleName() + \") edge color to 0x\" + Integer.toHexString(color).toUpperCase(), e);\n            }\n        }\n    }\n\n    private static void setEffectGlowColor(Object effect, @ColorInt int color) {\n        Object edgeEffect = effect;\n        if (edgeEffect instanceof EdgeEffectCompat) {\n            // EdgeEffectCompat\n            try {\n                edgeEffect = EDGE_EFFECT_COMPAT_FIELD_EDGE_EFFECT.get(edgeEffect);\n            } catch (IllegalAccessException e) {\n                Log.e(TAG, \"can't set glow color for overscroll\", e);\n                return;\n            }\n        }\n\n        if (edgeEffect == null)\n            return;\n\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {\n            // EdgeGlow\n            try {\n                final Drawable mEdge = (Drawable) EDGE_GLOW_FIELD_EDGE.get(edgeEffect);\n                final Drawable mGlow = (Drawable) EDGE_GLOW_FIELD_GLOW.get(edgeEffect);\n                for (Drawable drawable : Arrays.asList(mEdge, mGlow)) {\n                    drawable.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN));\n                    drawable.setCallback(null); // free up any references\n                }\n            } catch (Exception e) {\n                Log.e(TAG, \"can't set glow color for overscroll\", e);\n            }\n        } else {\n            // EdgeEffect\n            ((EdgeEffect) edgeEffect).setColor(color);\n        }\n    }\n\n    public static class EdgeGlowFactory extends RecyclerView.EdgeEffectFactory {\n        @ColorInt\n        private final int mColor;\n\n        public EdgeGlowFactory(@ColorInt int color) {\n            mColor = color;\n        }\n\n        @NonNull\n        @Override\n        protected EdgeEffect createEdgeEffect(@NonNull RecyclerView view, int direction) {\n            EdgeEffect effect = super.createEdgeEffect(view, direction);\n            setEffectGlowColor(effect, mColor);\n            return effect;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/utils/FileUtils.java",
    "content": "package rocks.tbog.tblauncher.utils;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.pm.PackageManager;\nimport android.content.pm.ResolveInfo;\nimport android.net.Uri;\nimport android.os.ParcelFileDescriptor;\nimport android.util.Log;\nimport android.util.Xml;\nimport android.widget.Toast;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.core.app.ShareCompat;\nimport androidx.core.content.FileProvider;\n\nimport org.xmlpull.v1.XmlPullParser;\nimport org.xmlpull.v1.XmlPullParserException;\n\nimport java.io.BufferedWriter;\nimport java.io.Closeable;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileNotFoundException;\nimport java.io.FileOutputStream;\nimport java.io.FileWriter;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.io.OutputStreamWriter;\nimport java.io.Writer;\nimport java.nio.channels.FileChannel;\nimport java.nio.charset.StandardCharsets;\nimport java.util.List;\n\nimport rocks.tbog.tblauncher.R;\n\npublic class FileUtils {\n    private static final String TAG = \"FileUtils\";\n    private static final String SETTINGS_FOLDER = \"settings\";\n    private static final String SETTINGS_EXT = \".xml\";\n\n    @Nullable\n    public static FileInputStream getFileInputStream(@NonNull Context context, @Nullable Uri uri) {\n        if (uri == null)\n            return null;\n        ParcelFileDescriptor descriptor;\n        try {\n            descriptor = context.getContentResolver().openFileDescriptor(uri, \"r\");\n        } catch (FileNotFoundException e) {\n            Log.e(TAG, \"openFileDescriptor \" + uri, e);\n            return null;\n        }\n        if (descriptor != null)\n            return new ParcelFileDescriptor.AutoCloseInputStream(descriptor);\n        return null;\n    }\n\n    private static void sendFile(@NonNull Activity activity, @NonNull String directory, @NonNull String filename, @NonNull String extension, @Nullable ContentGenerator content) {\n        Context context = activity.getApplicationContext();\n        File cacheDir = new File(context.getCacheDir(), directory);\n        File cacheFile = new File(cacheDir, filename + extension);\n\n        if (content != null) {\n            try {\n                cacheDir.mkdirs();\n            } catch (Exception ignored) {\n            }\n            try {\n                FileWriter fw = new FileWriter(cacheFile);\n                BufferedWriter bw = new BufferedWriter(fw);\n                //bw.write(content);\n                content.generate(bw);\n                bw.close();\n            } catch (IOException e) {\n                Log.e(TAG, \"Failed to write \" + filename, e);\n            }\n        }\n\n        Uri uri = FileProvider.getUriForFile(context, context.getPackageName() + \".provider\", cacheFile);\n\n        try {\n            String title;\n            String type;\n            if (extension.endsWith(SETTINGS_EXT)) {\n                title = activity.getString(R.string.export_chooser_xml, filename);\n                type = (\"text/xml\");\n            } else {\n                title = activity.getString(R.string.export_chooser, filename);\n                type = \"text/plain\";\n            }\n\n            Intent intent = new ShareCompat.IntentBuilder(activity)\n                    .setType(type)\n                    .setSubject(title)\n                    .setStream(uri)\n                    .setChooserTitle(title)\n                    .createChooserIntent()\n                    .addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)\n                    .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);\n\n            // grant permission for all apps that can handle given intent\n            List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);\n            for (ResolveInfo resolveInfo : resInfoList) {\n                String packageName = resolveInfo.activityInfo.packageName;\n                context.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);\n            }\n\n            activity.startActivity(intent);\n        } catch (Exception e) {\n            Log.d(TAG, \"startChooserIntent\", e);\n            Toast.makeText(activity, context.getString(R.string.error, e.getLocalizedMessage()), Toast.LENGTH_LONG).show();\n        }\n    }\n\n    public static void sendSettingsFile(@NonNull Activity activity, @NonNull String filename) {\n        sendFile(activity, SETTINGS_FOLDER, filename, SETTINGS_EXT, null);\n    }\n\n    public static void writeSettingsFile(@NonNull Context context, @NonNull String filename, @NonNull ContentGenerator generator) {\n        writeFile(context.getCacheDir(), SETTINGS_FOLDER, filename, SETTINGS_EXT, generator);\n    }\n\n    private static void writeFile(@NonNull File path, @NonNull String directory, @NonNull String filename, @Nullable String extension, @NonNull ContentGenerator content) {\n        File cacheDir = new File(path, directory);\n        String cacheName = extension != null ? (filename + extension) : filename;\n        File cacheFile = new File(cacheDir, cacheName);\n\n        try {\n            cacheDir.mkdirs();\n        } catch (Exception ignored) {\n        }\n        try {\n            OutputStream os = new FileOutputStream(cacheFile);\n            OutputStreamWriter osw = new OutputStreamWriter(os, StandardCharsets.UTF_8);\n            BufferedWriter bw = new BufferedWriter(osw);\n            content.generate(bw);\n            bw.close();\n        } catch (IOException e) {\n            Log.e(TAG, \"Failed to write \" + filename, e);\n        }\n    }\n\n//    @Nullable\n//    private static InputStream getInputStream(Context context, String directory, String filename, String extension) {\n//        File cacheDir = new File(context.getCacheDir(), directory);\n//        File cacheFile = new File(cacheDir, filename + extension);\n//        try {\n//            return new FileInputStream(cacheFile);\n//        } catch (FileNotFoundException e) {\n//            Log.e(TAG, \"new FileInputStream \" + filename, e);\n//        }\n//        return null;\n//    }\n\n//    @Nullable\n//    public static XmlPullParser getSettingsFromFile(@NonNull Context context, @NonNull String filename) {\n//        XmlPullParser parser;\n//        try {\n//            XmlPullParserFactory xppf = XmlPullParserFactory.newInstance();\n//            //xppf.setNamespaceAware(true);\n//            parser = xppf.newPullParser();\n//        } catch (XmlPullParserException e) {\n//            //TODO: implement custom parser if this ever happens\n//            Log.e(TAG, \"XmlPullParserFactory::newPullParser\", e);\n//            return null;\n//        }\n//        InputStream inputStream = getInputStream(context, SETTINGS_FOLDER, filename, SETTINGS_EXT);\n//        if (inputStream == null)\n//            return null;\n//        try {\n//            parser.setInput(inputStream, StandardCharsets.UTF_8.name());\n//        } catch (XmlPullParserException e) {\n//            Log.e(TAG, \"XmlPullParser.setInput\", e);\n//            parser = null;\n//        }\n//        return parser;\n//    }\n\n//    @Nullable\n//    public static XmlPullParser getXmlParser(@NonNull Context context, @Nullable Uri uri) {\n//        return getXmlParser(context, getInputStream(context, uri));\n//    }\n\n    @Nullable\n    public static XmlPullParser getXmlParser(@NonNull Context context, @Nullable InputStream inputStream) {\n        if (inputStream == null)\n            return null;\n        XmlPullParser parser = Xml.newPullParser();\n        try {\n            parser.setInput(inputStream, StandardCharsets.UTF_8.name());\n        } catch (XmlPullParserException e) {\n            Log.e(TAG, \"XmlPullParser.setInput\", e);\n            parser = null;\n        }\n        return parser;\n    }\n\n    public static void chooseSettingsFile(@NonNull Activity activity, int requestCode) {\n        chooseFile(activity, \"text/xml\", requestCode);\n    }\n\n    private static void chooseFile(@NonNull Activity activity, String type, int requestCode) {\n        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);\n        intent.setTypeAndNormalize(type);\n        intent.addCategory(Intent.CATEGORY_OPENABLE);\n\n        try {\n            activity.startActivityForResult(\n                    Intent.createChooser(intent, activity.getString(R.string.import_chooser)),\n                    requestCode);\n        } catch (android.content.ActivityNotFoundException ex) {\n            // Potentially direct the user to the Market with a Dialog\n            Toast.makeText(activity, R.string.choose_file_activity_not_found, Toast.LENGTH_SHORT).show();\n        }\n    }\n\n    public static void closeQuietly(@Nullable Closeable c) {\n        if (c == null)\n            return;\n        try {\n            c.close();\n        } catch (Exception ignored) {\n        }\n    }\n\n    /**\n     * Copy file from Uri to Cache and return the cache file\n     *\n     * @param context  application context\n     * @param uri      source file\n     * @param filename cache file name\n     * @return new cache file or null if we failed to copy\n     */\n    @Nullable\n    public static File copyFile(@NonNull Context context, @Nullable Uri uri, @NonNull String filename) {\n        FileInputStream inputStream = getFileInputStream(context, uri);\n        if (inputStream == null)\n            return null;\n        FileOutputStream outputStream = null;\n        FileChannel inChannel = null;\n        FileChannel outChannel = null;\n\n        File cacheFile = new File(context.getCacheDir(), filename);\n        try {\n            outputStream = new FileOutputStream(cacheFile);\n\n            // prepare channels\n            inChannel = inputStream.getChannel();\n            outChannel = outputStream.getChannel();\n\n            // copy from `inChannel` to `outChannel` 8 KB at a time\n            long amount = 8 * 1024;\n            final long size = inChannel.size();\n            long position = 0;\n            while (position < size)\n                position += inChannel.transferTo(position, amount, outChannel);\n        } catch (IOException e) {\n            Log.e(TAG, \"Failed to copy \" + uri, e);\n            // we must return null if we failed to copy\n            cacheFile = null;\n        }\n        closeQuietly(inChannel);\n        closeQuietly(outChannel);\n        closeQuietly(inputStream);\n        closeQuietly(outputStream);\n        return cacheFile;\n    }\n\n//    private static BufferedReader loadFile(Context context, String directory, String filename, String extension) {\n//        File cacheDir = new File(context.getCacheDir(), directory);\n//        File cacheFile = new File(cacheDir, filename + extension);\n//\n//        try {\n//            InputStream is = new FileInputStream(cacheFile);\n//            InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8);\n//            BufferedReader br = new BufferedReader(isr);\n//            return br;\n////            String line;\n////            while ((line = br.readLine()) != null) {\n////                text.append(line);\n////                text.append('\\n');\n////            }\n////            br.close();\n//        }\n//        catch (IOException ignored) {\n//        }\n//        return null;\n//    }\n\n    public interface ContentGenerator {\n        void generate(Writer writer) throws IOException;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/utils/FuzzyScore.java",
    "content": "package rocks.tbog.tblauncher.utils;\n\nimport android.util.Pair;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * A Sublime Text inspired fuzzy match algorithm\n * https://github.com/forrestthewoods/lib_fts/blob/master/docs/fuzzy_match.md\n * <p>\n * match(\"otw\", \"Power of the Wild\", info) = true, info.score = 14\n * match(\"otw\", \"Druid of the Claw\", info) = true, info.score = -3\n * match(\"otw\", \"Frostwolf Grunt\", info) = true, info.score = -13\n */\npublic class FuzzyScore {\n    private final int patternLength;\n    private final int[] patternChar;\n    private final int[] patternLower;\n    /**\n     * bonus for adjacent matches\n     */\n    private int adjacency_bonus;\n    /**\n     * bonus if match occurs after a separator\n     */\n    private int separator_bonus;\n    /**\n     * bonus if match is uppercase and prev is lower\n     */\n    private int camel_bonus;\n    /**\n     * penalty applied for every letter in str before the first match\n     */\n    private int leading_letter_penalty;\n    /**\n     * maximum penalty for leading letters\n     */\n    private int max_leading_letter_penalty;\n    /**\n     * penalty for every letter that doesn't matter\n     */\n    private int unmatched_letter_penalty;\n\n    private final MatchInfo matchInfo;\n\n    public FuzzyScore(@NonNull int[] pattern, boolean detailedMatchIndices) {\n        super();\n        patternLength = pattern.length;\n        patternChar = new int[patternLength];\n        patternLower = new int[patternLength];\n        for (int i = 0; i < patternLower.length; i += 1) {\n            patternChar[i] = pattern[i];\n            patternLower[i] = Character.toLowerCase(pattern[i]);\n        }\n        adjacency_bonus = 10;\n        separator_bonus = 5;\n        camel_bonus = 10;\n        leading_letter_penalty = -3;\n        max_leading_letter_penalty = -9;\n        unmatched_letter_penalty = -1;\n        if (detailedMatchIndices) {\n            matchInfo = new MatchInfo(patternLength);\n        } else {\n            matchInfo = new MatchInfo();\n        }\n    }\n\n    public FuzzyScore(@NonNull int[] pattern) {\n        this(pattern, true);\n    }\n\n    public int getPatternLength() {\n        return patternLength;\n    }\n\n    public void setAdjacencyBonus(int adjacency_bonus) {\n        this.adjacency_bonus = adjacency_bonus;\n    }\n\n    public void setSeparatorBonus(int separator_bonus) {\n        this.separator_bonus = separator_bonus;\n    }\n\n    public void setCamelBonus(int camel_bonus) {\n        this.camel_bonus = camel_bonus;\n    }\n\n    public void setLeadingLetterPenalty(int leading_letter_penalty) {\n        this.leading_letter_penalty = leading_letter_penalty;\n    }\n\n    public void setMaxLeadingLetterPenalty(int max_leading_letter_penalty) {\n        this.max_leading_letter_penalty = max_leading_letter_penalty;\n    }\n\n    public void setUnmatchedLetterPenalty(int unmatched_letter_penalty) {\n        this.unmatched_letter_penalty = unmatched_letter_penalty;\n    }\n\n    public static String patternToString(@Nullable int[] pattern) {\n        if (pattern == null)\n            return \"null\";\n        int iMax = pattern.length - 1;\n        if (iMax == -1)\n            return \"[]\";\n\n        StringBuilder b = new StringBuilder();\n        b.append('[');\n        for (int i = 0; ; i++) {\n            b.appendCodePoint(pattern[i]);\n            if (i == iMax)\n                return b.append(']').toString();\n        }\n    }\n\n    @NonNull\n    @Override\n    public String toString() {\n        return \"FuzzyScore{\" +\n            \"patternLength=\" + patternLength +\n            \", patternChar=\" + patternToString(patternChar) +\n            \", patternLower=\" + patternToString(patternLower) +\n            \", adjacency_bonus=\" + adjacency_bonus +\n            \", separator_bonus=\" + separator_bonus +\n            \", camel_bonus=\" + camel_bonus +\n            \", leading_letter_penalty=\" + leading_letter_penalty +\n            \", max_leading_letter_penalty=\" + max_leading_letter_penalty +\n            \", unmatched_letter_penalty=\" + unmatched_letter_penalty +\n            \", matchInfo=\" + matchInfo +\n            '}';\n    }\n\n    /**\n     * @param text string where to search\n     * @return true if each character in pattern is found sequentially within text\n     */\n    @NonNull\n    public MatchInfo match(@NonNull CharSequence text) {\n        int idx = 0;\n        int idxCodepoint = 0;\n        int textLength = text.length();\n        int[] codepoints = new int[Character.codePointCount(text, 0, textLength)];\n        while (idx < textLength) {\n            int codepoint = Character.codePointAt(text, idx);\n            codepoints[idxCodepoint] = codepoint;\n            idx += Character.charCount(codepoint);\n            idxCodepoint += 1;\n        }\n        return match(codepoints);\n    }\n\n    /**\n     * @param text string converted to codepoints\n     * @return true if each character in pattern is found sequentially within text\n     */\n    @NonNull\n    public MatchInfo match(@NonNull int[] text) {\n        // Loop variables\n        int score = 0;\n        int patternIdx = 0;\n        int strIdx = 0;\n        int strLength = text.length;\n        boolean prevMatched = false;\n        boolean prevLower = false;\n        boolean prevSeparator = true;       // true so if first letter match gets separator bonus\n\n        // Use \"best\" matched letter if multiple string letters match the pattern\n        Integer bestLetter = null;\n        Integer bestLower = null;\n        Integer bestLetterIdx = null;\n        int bestLetterScore = 0;\n\n        if (matchInfo.matchedIndices != null) {\n            matchInfo.matchedIndices.clear();\n        }\n\n        // Loop over strings\n        while (strIdx != strLength) {\n            Integer patternChar = null;\n            Integer patternLower = null;\n            if (patternIdx != patternLength) {\n                patternChar = this.patternChar[patternIdx];\n                patternLower = this.patternLower[patternIdx];\n            }\n            int strChar = text[strIdx];\n            int strLower = Character.toLowerCase(strChar);\n            int strUpper = Character.toUpperCase(strChar);\n\n            boolean nextMatch = patternChar != null && patternLower == strLower;\n            boolean rematch = bestLetter != null && bestLower == strLower;\n\n            boolean advanced = nextMatch && bestLetter != null;\n            boolean patternRepeat = bestLetter != null && patternChar != null && patternLower.equals(bestLower);\n            if (advanced || patternRepeat) {\n                score += bestLetterScore;\n                if (matchInfo.matchedIndices != null) {\n                    matchInfo.matchedIndices.add(bestLetterIdx);\n                }\n                bestLetter = null;\n                bestLower = null;\n                bestLetterIdx = null;\n                bestLetterScore = 0;\n            }\n\n            if (nextMatch || rematch) {\n                int newScore = 0;\n\n                // Apply penalty for each letter before the first pattern match\n                // Note: std::max because penalties are negative values. So max is smallest penalty.\n                if (patternIdx == 0) {\n                    int penalty = Math.max(strIdx * leading_letter_penalty, max_leading_letter_penalty);\n                    score += penalty;\n                }\n\n                // Apply bonus for consecutive bonuses\n                if (prevMatched && !rematch)\n                    newScore += adjacency_bonus;\n\n                // Apply bonus for matches after a separator\n                if (prevSeparator)\n                    newScore += separator_bonus;\n\n                // Apply bonus across camel case boundaries. Includes \"clever\" isLetter check.\n                if (prevLower && strChar == strUpper && strLower != strUpper)\n                    newScore += camel_bonus;\n\n                // Update pattern index IF the next pattern letter was matched\n                if (nextMatch)\n                    ++patternIdx;\n\n                // Update best letter in text which may be for a \"next\" letter or a \"rematch\"\n                if (newScore >= bestLetterScore) {\n\n                    // Apply penalty for now skipped letter\n                    if (bestLetter != null)\n                        score += unmatched_letter_penalty;\n\n                    bestLetter = strChar;\n                    bestLower = strLower;\n                    bestLetterIdx = strIdx;\n                    bestLetterScore = newScore;\n                }\n\n                prevMatched = true;\n            } else {\n                score += unmatched_letter_penalty;\n                prevMatched = false;\n            }\n\n            // Includes \"clever\" isLetter check.\n            prevLower = strChar == strLower && strLower != strUpper;\n            prevSeparator = Character.isWhitespace(strChar);\n\n            ++strIdx;\n        }\n\n        // Apply score for last match\n        if (bestLetter != null) {\n            score += bestLetterScore;\n            if (matchInfo.matchedIndices != null) {\n                matchInfo.matchedIndices.add(bestLetterIdx);\n            }\n        }\n\n        matchInfo.match = patternIdx == patternLength;\n        if (matchInfo.match) {\n            matchInfo.score = score;\n        }\n        return matchInfo;\n    }\n\n    public static final class MatchInfo {\n        /**\n         * higher is better match. Value has no intrinsic meaning. Range varies with pattern.\n         * Can only compare scores with same search pattern.\n         */\n        public int score = 0;\n        public boolean match = false;\n        public final ArrayList<Integer> matchedIndices;\n\n        public MatchInfo() {\n            matchedIndices = null;\n        }\n\n        MatchInfo(int patternLength) {\n            matchedIndices = new ArrayList<>(patternLength);\n        }\n\n        public MatchInfo(@NonNull MatchInfo o) {\n            score = o.score;\n            match = o.match;\n            matchedIndices = o.matchedIndices != null ? new ArrayList<>(o.matchedIndices) : null;\n        }\n\n        @NonNull\n        public List<Pair<Integer, Integer>> getMatchedSequences() {\n            return getMatchedSequences(matchedIndices);\n        }\n\n        @NonNull\n        public static List<Pair<Integer, Integer>> getMatchedSequences(@Nullable List<Integer> matchedIndices) {\n            if (matchedIndices == null)\n                return Collections.emptyList();\n            // compute pair match indices\n            List<Pair<Integer, Integer>> positions = new ArrayList<>(matchedIndices.size());\n            int start = matchedIndices.get(0);\n            int end = start + 1;\n            for (int i = 1; i < matchedIndices.size(); i += 1) {\n                if (end == matchedIndices.get(i)) {\n                    end += 1;\n                } else {\n                    positions.add(new Pair<>(start, end));\n                    start = matchedIndices.get(i);\n                    end = start + 1;\n                }\n            }\n            positions.add(new Pair<>(start, end));\n            return positions;\n        }\n\n        public static MatchInfo copyOrNewInstance(@NonNull MatchInfo source, @Nullable MatchInfo destination) {\n            if (destination == null || (destination.matchedIndices == null && source.matchedIndices != null))\n                return new MatchInfo(source);\n            destination.score = source.score;\n            destination.match = source.match;\n            if (destination.matchedIndices != null) {\n                destination.matchedIndices.clear();\n                if (source.matchedIndices != null)\n                    destination.matchedIndices.addAll(source.matchedIndices);\n            }\n            return destination;\n        }\n\n        @NonNull\n        @Override\n        public String toString() {\n            return \"MatchInfo{\" +\n                \"score=\" + score +\n                \", match=\" + match +\n                \", matchedIndices=\" + matchedIndices +\n                '}';\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/utils/GestureDetectorHelper.java",
    "content": "package rocks.tbog.tblauncher.utils;\n\nimport android.util.Log;\nimport android.view.GestureDetector;\n\nimport androidx.core.view.GestureDetectorCompat;\n\nimport java.lang.reflect.Field;\n\npublic final class GestureDetectorHelper {\n    private static final String TAG = \"TBUtil\";\n\n    public static void setGestureDetectorTouchSlop(GestureDetector gestureDetector, int value) {\n        try {\n            Field f_mTouchSlopSquare = GestureDetector.class.getDeclaredField(\"mTouchSlopSquare\");\n            f_mTouchSlopSquare.setAccessible(true);\n            f_mTouchSlopSquare.setInt(gestureDetector, value * value);\n        } catch (NoSuchFieldException | IllegalAccessException | NullPointerException e) {\n            Log.w(TAG, gestureDetector.toString(), e);\n        }\n    }\n\n    public static void setGestureDetectorTouchSlop(GestureDetectorCompat gestureDetector, int value) {\n        try {\n            Field f_mImpl = GestureDetectorCompat.class.getDeclaredField(\"mImpl\");\n            f_mImpl.setAccessible(true);\n            Object mImpl = f_mImpl.get(gestureDetector);\n            if (mImpl == null) {\n                Log.w(TAG, f_mImpl + \" is null\");\n                return;\n            }\n            Class<?> c_GDCIJellybeanMr2 = null;\n            Class<?> c_GDCIBase = null;\n            try {\n                c_GDCIJellybeanMr2 = Class.forName(GestureDetectorCompat.class.getName() + \"$GestureDetectorCompatImplJellybeanMr2\");\n                c_GDCIBase = Class.forName(GestureDetectorCompat.class.getName() + \"$GestureDetectorCompatImplBase\");\n            } catch (ClassNotFoundException ignored) {\n            }\n            if (c_GDCIJellybeanMr2 != null && c_GDCIJellybeanMr2.isInstance(mImpl)) {\n                Field f_mDetector = c_GDCIJellybeanMr2.getDeclaredField(\"mDetector\");\n                f_mDetector.setAccessible(true);\n\n                Object mDetector = f_mDetector.get(mImpl);\n                if (mDetector instanceof GestureDetector)\n                    setGestureDetectorTouchSlop((GestureDetector) mDetector, value);\n            } else if (c_GDCIBase != null) {\n                Field f_mTouchSlopSquare = c_GDCIBase.getDeclaredField(\"mTouchSlopSquare\");\n                f_mTouchSlopSquare.setAccessible(true);\n                f_mTouchSlopSquare.setInt(mImpl, value * value);\n            } else {\n                Log.w(TAG, \"not handled: \" + mImpl.getClass().toString());\n            }\n        } catch (NoSuchFieldException | IllegalAccessException | NullPointerException e) {\n            Log.w(TAG, gestureDetector.getClass().toString(), e);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/utils/GoogleCalendarIcon.java",
    "content": "package rocks.tbog.tblauncher.utils;\n\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.pm.PackageManager;\nimport android.content.res.Resources;\nimport android.content.res.TypedArray;\nimport android.graphics.drawable.Drawable;\nimport android.os.Bundle;\n\nimport androidx.annotation.Nullable;\nimport androidx.core.content.res.ResourcesCompat;\n\nimport java.util.Calendar;\n\n/**\n * This class is used to display a custom icon for Google Calendar\n * Every day, the icon is different and display the current day\n * Credits: https://github.com/LawnchairLauncher/Lawnchair/blob/d12b30d5333c03969ad340eb9b4e1846c12a6a73/src/com/google/android/apps/nexuslauncher/DynamicIconProvider.java\n */\npublic class GoogleCalendarIcon {\n    public static final String GOOGLE_CALENDAR = \"com.google.android.calendar\";\n\n    @Nullable\n    public static Drawable getDrawable(Context context, String activityName) {\n        // retrieve today's icon\n        PackageManager pm = context.getPackageManager();\n        ComponentName cn = new ComponentName(GOOGLE_CALENDAR, activityName);\n        try {\n            Bundle metaData = pm.getActivityInfo(cn, PackageManager.GET_META_DATA | PackageManager.GET_UNINSTALLED_PACKAGES).metaData;\n            Resources resourcesForApplication = pm.getResourcesForApplication(GOOGLE_CALENDAR);\n            int dayResId = getDayResId(metaData, resourcesForApplication);\n            if (dayResId != 0) {\n                return ResourcesCompat.getDrawable(resourcesForApplication, dayResId, context.getTheme());\n            }\n        } catch (PackageManager.NameNotFoundException ignored) {\n        }\n\n        return null;\n    }\n\n    private static int getDayResId(Bundle bundle, Resources resources) {\n        if (bundle != null) {\n            int dateArrayId = bundle.getInt(GOOGLE_CALENDAR + \".dynamic_icons_nexus_round\", 0);\n            if (dateArrayId != 0) {\n                try {\n                    TypedArray dateIds = resources.obtainTypedArray(dateArrayId);\n                    int dateId = dateIds.getResourceId(getDayOfMonth(), 0);\n                    dateIds.recycle();\n                    return dateId;\n                } catch (Resources.NotFoundException ignored) {\n                }\n            }\n        }\n        return 0;\n    }\n\n    private static int getDayOfMonth() {\n        return Calendar.getInstance().get(Calendar.DAY_OF_MONTH) - 1;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/utils/ISparseArray.java",
    "content": "package rocks.tbog.tblauncher.utils;\n\npublic interface ISparseArray<E> {\n    /**\n     * Returns true if the key exists in the array. This is equivalent to\n     * {@link #indexOfKey(int)} >= 0.\n     *\n     * @param key Potential key in the mapping\n     * @return true if the key is defined in the mapping\n     */\n    boolean contains(int key);\n\n    /**\n     * Gets the Object mapped from the specified key, or <code>null</code>\n     * if no such mapping has been made.\n     */\n    E get(int key);\n\n    /**\n     * Gets the Object mapped from the specified key, or the specified Object\n     * if no such mapping has been made.\n     */\n    E get(int key, E valueIfKeyNotFound);\n\n    /**\n     * Removes the mapping from the specified key, if there was any.\n     */\n    void delete(int key);\n\n    /**\n     * Alias for {@link #delete(int)}.\n     */\n    default void remove(int key) {\n        delete(key);\n    }\n\n    /**\n     * Removes the mapping at the specified index.\n     */\n    void removeAt(int index);\n\n    /**\n     * Remove a range of mappings as a batch.\n     *\n     * @param index Index to begin at\n     * @param size  Number of mappings to remove\n     *\n     *              <p>For indices outside of the range <code>0...size()-1</code>,\n     *              the behavior is undefined.</p>\n     */\n    void removeAtRange(int index, int size);\n\n    /**\n     * Alias for {@link #put(int, Object)} to support Kotlin [index]= operator.\n     *\n     * @see #put(int, Object)\n     */\n    default void set(int key, E value) {\n        put(key, value);\n    }\n\n    /**\n     * Adds a mapping from the specified key to the specified value,\n     * replacing the previous mapping from the specified key if there\n     * was one.\n     */\n    void put(int key, E value);\n\n    /**\n     * Returns the number of key-value mappings that this SparseArray\n     * currently stores.\n     */\n    int size();\n\n    /**\n     * Given an index in the range <code>0...size()-1</code>, returns\n     * the key from the <code>index</code>th key-value mapping that this\n     * SparseArray stores.\n     *\n     * <p>The keys corresponding to indices in ascending order are guaranteed to\n     * be in ascending order, e.g., <code>keyAt(0)</code> will return the\n     * smallest key and <code>keyAt(size()-1)</code> will return the largest\n     * key.</p>\n     */\n    int keyAt(int index);\n\n    /**\n     * Given an index in the range <code>0...size()-1</code>, returns\n     * the value from the <code>index</code>th key-value mapping that this\n     * SparseArray stores.\n     *\n     * <p>The values corresponding to indices in ascending order are guaranteed\n     * to be associated with keys in ascending order, e.g.,\n     * <code>valueAt(0)</code> will return the value associated with the\n     * smallest key and <code>valueAt(size()-1)</code> will return the value\n     * associated with the largest key.</p>\n     */\n    E valueAt(int index);\n\n    /**\n     * Given an index in the range <code>0...size()-1</code>, sets a new\n     * value for the <code>index</code>th key-value mapping that this\n     * SparseArray stores.\n     */\n    void setValueAt(int index, E value);\n\n    /**\n     * Returns the index for which {@link #keyAt} would return the\n     * specified key, or a negative number if the specified\n     * key is not mapped.\n     */\n    int indexOfKey(int key);\n\n    /**\n     * Returns an index for which {@link #valueAt} would return the\n     * specified value, or a negative number if no keys map to the\n     * specified value.\n     * <p>Beware that this is a linear search, unlike lookups by key,\n     * and that multiple keys can map to the same value and this will\n     * find only one of them.\n     * <p>Note also that unlike most collections' {@code indexOf} methods,\n     * this method compares values using {@code ==} rather than {@code equals}.\n     */\n    int indexOfValue(E value);\n\n    /**\n     * Removes all key-value mappings from this SparseArray.\n     */\n    void clear();\n\n    /**\n     * Puts a key/value pair into the array, optimizing for the case where\n     * the key is greater than all existing keys in the array.\n     */\n    void append(int key, E value);\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/utils/KeyboardToggleHelper.java",
    "content": "package rocks.tbog.tblauncher.utils;\n\nimport android.view.View;\n\nimport rocks.tbog.tblauncher.ui.WindowInsetsHelper;\n\npublic class KeyboardToggleHelper extends WindowInsetsHelper {\n    public boolean mHiddenByScrolling = false;\n    public boolean mRequestOpen = false;\n    public boolean mLaunchedApp = false;\n\n    public KeyboardToggleHelper(View root) {\n        super(root);\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/utils/KeyboardTriggerBehaviour.java",
    "content": "package rocks.tbog.tblauncher.utils;\n\nimport android.app.Activity;\nimport android.os.Build;\nimport android.util.Log;\nimport android.view.View;\nimport android.view.ViewTreeObserver;\n\nimport androidx.core.view.ViewCompat;\nimport androidx.lifecycle.LiveData;\n\nimport rocks.tbog.tblauncher.TBApplication;\n\npublic class KeyboardTriggerBehaviour extends LiveData<KeyboardTriggerBehaviour.Status> {\n    private static final String TAG = \"KeyTB\";\n\n    private final View contentView;\n    private final ViewTreeObserver.OnGlobalLayoutListener globalLayoutListener;\n\n    public KeyboardTriggerBehaviour(Activity activity) {\n        super(Status.CLOSED);\n        contentView = activity.findViewById(android.R.id.content);\n        globalLayoutListener = () -> {\n            TBApplication.state().syncKeyboardVisibility(contentView);\n            boolean closed = TBApplication.state().isKeyboardHidden();\n            Status status = getValue();\n            Log.d(TAG, \"[listener] state().isKeyboardHidden=\" + closed + \" status=\" + status);\n            if (closed && status != Status.CLOSED)\n                postValue(Status.CLOSED);\n            else if (!closed && status != Status.OPEN)\n                postValue(Status.OPEN);\n        };\n    }\n\n    @Override\n    protected void onActive() {\n        super.onActive();\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            Log.d(TAG, \"onActive - WindowInsetsListener\");\n            ViewCompat.setOnApplyWindowInsetsListener(contentView, (v, insets) -> {\n                globalLayoutListener.onGlobalLayout();\n                return insets;\n            });\n        } else {\n            Log.d(TAG, \"onActive - GlobalLayoutListener\");\n            contentView.getViewTreeObserver().addOnGlobalLayoutListener(globalLayoutListener);\n        }\n        // run the listener to update the current status\n        globalLayoutListener.onGlobalLayout();\n    }\n\n    @Override\n    protected void onInactive() {\n        super.onInactive();\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            Log.d(TAG, \"onInactive - WindowInsetsListener\");\n            ViewCompat.setOnApplyWindowInsetsListener(contentView, null);\n        } else {\n            Log.d(TAG, \"onInactive - GlobalLayoutListener\");\n            contentView.getViewTreeObserver().removeOnGlobalLayoutListener(globalLayoutListener);\n        }\n    }\n\n    public enum Status {OPEN, CLOSED}\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/utils/MapCompat.java",
    "content": "package rocks.tbog.tblauncher.utils;\n\nimport androidx.annotation.NonNull;\n\nimport java.util.Map;\n\npublic class MapCompat {\n\n    public static <K, V> V getOrDefault(@NonNull Map<K, V> map, K key, V defaultValue) {\n        V v = map.get(key);\n        return ((v != null) || map.containsKey(key)) ? v : defaultValue;\n    }\n}"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/utils/MimeTypeUtils.java",
    "content": "package rocks.tbog.tblauncher.utils;\n\nimport static java.util.Collections.emptySet;\n\nimport android.content.ContentUris;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.SharedPreferences;\nimport android.content.pm.PackageManager;\nimport android.content.pm.ResolveInfo;\nimport android.database.Cursor;\nimport android.net.Uri;\nimport android.preference.PreferenceManager;\nimport android.provider.ContactsContract;\nimport android.util.Log;\n\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.TreeSet;\n\nimport rocks.tbog.tblauncher.Permission;\n\npublic class MimeTypeUtils {\n\n    // Known android mime types that are not supported by KISS\n    private static final Set<String> UNSUPPORTED_MIME_TYPES = new HashSet<>(Arrays.asList(\n        ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE,\n        ContactsContract.CommonDataKinds.GroupMembership.CONTENT_ITEM_TYPE,\n        ContactsContract.CommonDataKinds.Identity.CONTENT_ITEM_TYPE,\n        ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE,\n        ContactsContract.CommonDataKinds.Nickname.CONTENT_ITEM_TYPE,\n        ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE,\n        ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE,\n        ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE,\n        ContactsContract.CommonDataKinds.Relation.CONTENT_ITEM_TYPE,\n        ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE,\n        ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE,\n        ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE,\n        ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE\n    ));\n\n    private MimeTypeUtils() {\n    }\n\n    /**\n     * @param context\n     * @return a list of all supported mime types from existing contacts\n     */\n    public static Set<String> getSupportedMimeTypes(Context context) {\n        if (!Permission.checkPermission(context, Permission.PERMISSION_READ_CONTACTS)) {\n            return emptySet();\n        }\n\n        Timer timer = Timer.startNano();\n\n        Set<String> mimeTypes = new HashSet<>();\n\n        Cursor cursor = context.getContentResolver().query(\n            ContactsContract.Data.CONTENT_URI,\n            new String[]{ContactsContract.Data.MIMETYPE}, null, null, null);\n        if (cursor != null) {\n            if (cursor.getCount() > 0) {\n                int mimeTypeIndex = cursor.getColumnIndex(ContactsContract.Data.MIMETYPE);\n                while (cursor.moveToNext()) {\n                    String mimeType = cursor.getString(mimeTypeIndex);\n                    if (isSupportedMimeType(context, mimeType)) {\n                        mimeTypes.add(mimeType);\n                    }\n                }\n            }\n            cursor.close();\n        }\n\n        // always add classic phone contacts\n        mimeTypes.add(ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE);\n\n        Log.i(\"time\", timer + \" to load \" + mimeTypes.size() + \" supported mime types\");\n\n        return mimeTypes;\n    }\n\n    private static boolean isSupportedMimeType(Context context, String mimeType) {\n        if (mimeType == null) {\n            return false;\n        }\n        if (UNSUPPORTED_MIME_TYPES.contains(mimeType)) {\n            return false;\n        }\n        // check if intent for custom mime type is registered\n        Intent intent = getRegisteredIntentByMimeType(context, mimeType, -1, \"\");\n        return intent != null;\n    }\n\n    /**\n     * @param context\n     * @return a list of all mime types that should be shown\n     */\n    public static Set<String> getActiveMimeTypes(Context context) {\n        Timer timer = Timer.startNano();\n\n        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);\n        Set<String> selectedMimeTypes = prefs.getStringSet(\"selected-contact-mime-types\", getDefaultMimeTypes());\n        Set<String> supportedMimeTypes = getSupportedMimeTypes(context);\n\n        supportedMimeTypes.retainAll(selectedMimeTypes);\n\n        Log.i(\"time\", timer + \" to load \" + supportedMimeTypes.size() + \" active mime types\");\n\n        return supportedMimeTypes;\n    }\n\n    /**\n     * Create a new intent to view given row of contact data.\n     *\n     * @param mimeType           mimetype of contact data row\n     * @param id                 id of contact data row\n     * @param schemeSpecificPart\n     * @return intent to view contact by mime type and id, null if no activity is registered for intent\n     */\n    public static Intent getRegisteredIntentByMimeType(Context context, String mimeType, long id, String schemeSpecificPart) {\n        final Intent intent = getIntentByMimeType(mimeType, id, schemeSpecificPart);\n\n        if (isIntentRegistered(context, intent)) {\n            return intent;\n        } else {\n            return null;\n        }\n    }\n\n    /**\n     * create a new intent to view given row of contact data\n     *\n     * @param mimeType           mime type of contact data row\n     * @param id                 id of contact data row\n     * @param schemeSpecificPart\n     * @return intent to view contact by mime type and id\n     */\n    public static Intent getIntentByMimeType(String mimeType, long id, String schemeSpecificPart) {\n        Intent intent;\n        if (ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE.equals(mimeType)) {\n            final Uri phoneUri = Uri.fromParts(\"tel\", Uri.encode(schemeSpecificPart), null);\n            intent = new Intent(Intent.ACTION_CALL, phoneUri);\n        } else if (ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE.equals(mimeType)) {\n            final Uri mailUri = Uri.fromParts(\"mailto\", schemeSpecificPart, null);\n            intent = new Intent(Intent.ACTION_SENDTO, mailUri);\n        } else {\n            intent = new Intent(Intent.ACTION_VIEW);\n            final Uri uri = ContentUris.withAppendedId(ContactsContract.Data.CONTENT_URI, id);\n            intent.setDataAndType(uri, mimeType);\n\n        }\n        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n        return intent;\n    }\n\n    /**\n     * @param context\n     * @param intent\n     * @return true if any activity is registered for given intent\n     */\n    private static boolean isIntentRegistered(Context context, Intent intent) {\n        final PackageManager packageManager = context.getPackageManager();\n        final List<ResolveInfo> receiverList = packageManager.queryIntentActivities(intent,\n            PackageManager.MATCH_DEFAULT_ONLY);\n        return receiverList.size() > 0;\n    }\n\n    /**\n     * strip common vnd.android.cursor.item/ from mimeType\n     *\n     * @param mimeType\n     * @return shortened version of mime type\n     */\n    public static String getShortMimeType(String mimeType) {\n        return mimeType.replaceFirst(\"vnd\\\\.android\\\\.cursor\\\\.item/\", \"\");\n    }\n\n    /**\n     * @return mimeTypes that are shown by default\n     */\n    public static Set<String> getDefaultMimeTypes() {\n        return new TreeSet<>(Collections.singletonList(ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE));\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/utils/PackageManagerUtils.java",
    "content": "package rocks.tbog.tblauncher.utils;\n\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.IntentFilter;\nimport android.content.pm.ApplicationInfo;\nimport android.content.pm.PackageManager;\nimport android.content.pm.ResolveInfo;\nimport android.net.Uri;\n\nimport java.util.List;\n\npublic class PackageManagerUtils {\n\n    /**\n     * Method to enable/disable a specific component\n     */\n    public static void enableComponent(Context ctx, Class component, boolean enabled) {\n        PackageManager pm = ctx.getPackageManager();\n        ComponentName cn = new ComponentName(ctx, component);\n        pm.setComponentEnabledSetting(cn,\n            enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,\n            PackageManager.DONT_KILL_APP);\n    }\n\n    /**\n     * Search best matching app for given intent.\n     *\n     * @param context context\n     * @param intent  intent\n     * @return ResolveInfo for best matching app by intent\n     */\n    public static ResolveInfo getBestResolve(Context context, Intent intent) {\n        if (intent == null) {\n            return null;\n        }\n\n        final PackageManager packageManager = context.getPackageManager();\n        final List<ResolveInfo> matches = packageManager.queryIntentActivities(intent,\n            PackageManager.MATCH_DEFAULT_ONLY);\n\n        final int size = matches.size();\n        if (size == 0) {\n            return null;\n        } else if (size == 1) {\n            return matches.get(0);\n        }\n\n        // Try finding preferred activity, otherwise detect disambiguation\n        final ResolveInfo foundResolve = packageManager.resolveActivity(intent,\n            PackageManager.MATCH_DEFAULT_ONLY);\n        final boolean foundDisambiguation = (foundResolve.match &\n            IntentFilter.MATCH_CATEGORY_MASK) == 0;\n\n        if (!foundDisambiguation) {\n            // Found concrete match, so return directly\n            return foundResolve;\n        }\n\n        // Accept first system app\n        ResolveInfo firstSystem = null;\n        for (ResolveInfo info : matches) {\n            final boolean isSystem = (info.activityInfo.applicationInfo.flags\n                & ApplicationInfo.FLAG_SYSTEM) != 0;\n\n            if (isSystem && firstSystem == null)\n                firstSystem = info;\n        }\n\n        // Return first system found, otherwise first from list\n        return firstSystem != null ? firstSystem : matches.get(0);\n    }\n\n    /**\n     * @param context context\n     * @param intent  intent\n     * @return component name of best matching app for given intent\n     */\n    public static ComponentName getComponentName(Context context, Intent intent) {\n        ResolveInfo resolveInfo = getBestResolve(context, intent);\n        if (resolveInfo != null) {\n            return new ComponentName(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name);\n        }\n        return null;\n    }\n\n    /**\n     * @param context context\n     * @param intent  intent\n     * @return label of best matching app for given intent\n     */\n    public static String getLabel(Context context, Intent intent) {\n        ResolveInfo resolveInfo = PackageManagerUtils.getBestResolve(context, intent);\n        if (resolveInfo != null) {\n            return String.valueOf(resolveInfo.loadLabel(context.getPackageManager()));\n        }\n        return null;\n    }\n\n    /**\n     * @param context       context\n     * @param componentName componentName\n     * @return launching component name for given component\n     */\n    public static ComponentName getLaunchingComponent(Context context, ComponentName componentName) {\n        if (componentName == null) {\n            return null;\n        }\n        ComponentName launchingComponent = getLaunchingComponent(context, componentName.getPackageName());\n        if (launchingComponent != null) {\n            return launchingComponent;\n        }\n        return componentName;\n    }\n\n    /**\n     * @param context     context\n     * @param packageName package name\n     * @return launching component name for given package\n     */\n    public static ComponentName getLaunchingComponent(Context context, String packageName) {\n        if (packageName == null) {\n            return null;\n        }\n        Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(packageName);\n        if (launchIntent != null) {\n            return launchIntent.getComponent();\n        }\n        return null;\n    }\n\n    /**\n     * Creates intent to start activity with given uri.\n     * Uri must have some given criteria to work:\n     * <ul>\n     * <li>it must contain an explicit schema (absolute)</li>\n     * <li>the schema specific part must be longer than 2 (//...) so is some result that can be handled</li>\n     * </ul>\n     *\n     * @param uri\n     * @return intent\n     */\n    public static Intent createUriIntent(Uri uri) {\n        if (uri.isAbsolute() && uri.getSchemeSpecificPart().length() > 2) {\n            Intent intent = new Intent(Intent.ACTION_VIEW, uri);\n            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n            return intent;\n        }\n        return null;\n    }\n\n\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/utils/PrefCache.java",
    "content": "package rocks.tbog.tblauncher.utils;\n\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.content.res.Resources;\nimport android.graphics.Color;\nimport android.graphics.drawable.ColorDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.util.Log;\n\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.LayoutRes;\nimport androidx.annotation.NonNull;\nimport androidx.appcompat.content.res.AppCompatResources;\nimport androidx.collection.ArraySet;\nimport androidx.preference.PreferenceManager;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.preference.ContentLoadHelper;\nimport rocks.tbog.tblauncher.quicklist.QuickList;\n\npublic class PrefCache {\n\n    private final static ArraySet<String> PREF_THAT_REQUIRE_MIGRATION = new ArraySet<>(Arrays.asList(\n        \"result-list-color\", \"result-list-alpha\",\n        \"notification-bar-color\", \"notification-bar-alpha\",\n        \"search-bar-color\", \"search-bar-alpha\",\n        \"quick-list-color\", \"quick-list-alpha\",\n        \"result-list-rounded\", \"search-bar-rounded\"\n    ));\n\n    private static int RESULT_HISTORY_SIZE = 0;\n    private static int RESULT_HISTORY_ADAPTIVE = 0;\n    private static int RESULT_SEARCHER_CAP = -1;\n    private static int LOADING_ICON_RES = 0; // Resources.ID_NULL\n    private static Boolean FUZZY_SEARCH_TAGS = null;\n    private static Boolean TAGS_MENU_ICONS = null;\n    private static Boolean TAGS_MENU_UNTAGGED = null;\n    private static int TAGS_MENU_UNTAGGED_IDX = -1;\n    private static List<ContentLoadHelper.CategoryItem> RESULT_POPUP_ORDER = null;\n\n    private PrefCache() {\n    }\n\n    public static void resetCache() {\n        RESULT_HISTORY_SIZE = 0;\n        RESULT_HISTORY_ADAPTIVE = 0;\n        RESULT_SEARCHER_CAP = -1;\n        LOADING_ICON_RES = 0;\n        FUZZY_SEARCH_TAGS = null;\n        TAGS_MENU_ICONS = null;\n        TAGS_MENU_UNTAGGED = null;\n        TAGS_MENU_UNTAGGED_IDX = -1;\n        RESULT_POPUP_ORDER = null;\n    }\n\n    public static int getResultHistorySize(Context context) {\n        if (RESULT_HISTORY_SIZE == 0) {\n            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n            final int defaultSize = context.getResources().getInteger(R.integer.default_result_history_size);\n            RESULT_HISTORY_SIZE = pref.getInt(\"result-history-size\", defaultSize);\n        }\n        return RESULT_HISTORY_SIZE;\n    }\n\n    public static int getHistoryAdaptive(Context context) {\n        if (RESULT_HISTORY_ADAPTIVE == 0) {\n            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n            final int defaultSize = context.getResources().getInteger(R.integer.default_result_history_adaptive);\n            RESULT_HISTORY_ADAPTIVE = pref.getInt(\"result-history-adaptive\", defaultSize);\n        }\n        return RESULT_HISTORY_ADAPTIVE;\n    }\n\n    public static boolean showWidgetScreenAfterLaunch(SharedPreferences pref) {\n        return pref.getBoolean(\"behaviour-widget-after-launch\", true);\n    }\n\n    public static boolean clearSearchAfterLaunch(SharedPreferences pref) {\n        return pref.getBoolean(\"behaviour-clear-search-after-launch\", true);\n    }\n\n    public static boolean linkKeyboardAndSearchBar(SharedPreferences pref) {\n        return pref.getBoolean(\"behaviour-link-keyboard-search-bar\", true);\n    }\n\n    public static boolean getFuzzySearchTags(Context context) {\n        if (FUZZY_SEARCH_TAGS == null) {\n            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n            FUZZY_SEARCH_TAGS = pref.getBoolean(\"fuzzy-search-tags\", true);\n        }\n        return FUZZY_SEARCH_TAGS;\n    }\n\n    public static boolean showTagsMenuIcons(Context context) {\n        if (TAGS_MENU_ICONS == null) {\n            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n            TAGS_MENU_ICONS = pref.getBoolean(\"tags-menu-icons\", false);\n        }\n        return TAGS_MENU_ICONS;\n    }\n\n    public static boolean showTagsMenuUntagged(Context context) {\n        if (TAGS_MENU_UNTAGGED == null) {\n            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n            TAGS_MENU_UNTAGGED = pref.getBoolean(\"tags-menu-untagged\", false);\n            try {\n                TAGS_MENU_UNTAGGED_IDX = Integer.parseInt(pref.getString(\"tags-menu-untagged-index\", \"0\"));\n            } catch (Exception ignored) {\n            }\n        }\n        return TAGS_MENU_UNTAGGED;\n    }\n\n    public static int getTagsMenuUntaggedIndex(Context context) {\n        if (TAGS_MENU_UNTAGGED_IDX == -1) {\n            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n            try {\n                TAGS_MENU_UNTAGGED_IDX = Integer.parseInt(pref.getString(\"tags-menu-untagged-index\", \"0\"));\n            } catch (Exception ignored) {\n            }\n        }\n        return TAGS_MENU_UNTAGGED_IDX;\n    }\n\n    public static int getResultSearcherCap(Context context) {\n        if (RESULT_SEARCHER_CAP == -1) {\n            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n            final int defaultCap = context.getResources().getInteger(R.integer.default_result_searcher_cap);\n            RESULT_SEARCHER_CAP = pref.getInt(\"result-search-cap\", defaultCap);\n            if (RESULT_SEARCHER_CAP == 0)\n                RESULT_SEARCHER_CAP = Integer.MAX_VALUE;\n        }\n        return RESULT_SEARCHER_CAP;\n    }\n\n    public static boolean modeEmptyQuickListVisible(SharedPreferences preferences) {\n        return preferences.getBoolean(\"dm-empty-quick-list\", false);\n    }\n\n    public static boolean modeEmptyFullscreen(SharedPreferences preferences) {\n        return preferences.getBoolean(\"dm-empty-fullscreen\", true);\n    }\n\n    public static boolean modeSearchQuickListVisible(SharedPreferences preferences) {\n        return preferences.getBoolean(\"dm-search-quick-list\", true);\n    }\n\n    public static boolean modeSearchFullscreen(SharedPreferences preferences) {\n        return preferences.getBoolean(\"dm-search-fullscreen\", false);\n    }\n\n    @NonNull\n    public static String modeSearchOpenResult(SharedPreferences preferences) {\n        String result = preferences.getString(\"dm-search-open-result\", null);\n        return result == null ? \"none\" : result;\n    }\n\n    public static boolean modeWidgetQuickListVisible(SharedPreferences preferences) {\n        return preferences.getBoolean(\"dm-widget-quick-list\", false);\n    }\n\n    public static boolean modeWidgetFullscreen(SharedPreferences preferences) {\n        return preferences.getBoolean(\"dm-widget-fullscreen\", false);\n    }\n\n    public static boolean searchBarAtBottom(SharedPreferences preferences) {\n        return preferences.getBoolean(\"search-bar-at-bottom\", true);\n    }\n\n    @LayoutRes\n    public static int getSearchBarLayout(SharedPreferences pref) {\n        String layout = pref.getString(\"search-bar-layout\", null);\n        if (\"btn-text-menu\".equals(layout))\n            return R.layout.search_bar;\n        if (\"pill-search\".equals(layout))\n            return R.layout.search_pill;\n        return 0;\n    }\n\n    public static boolean searchBarHasTimer(SharedPreferences pref) {\n        String layout = pref.getString(\"search-bar-layout\", null);\n        return \"pill-search\".equals(layout);\n    }\n\n    public static QuickList.QuickListPosition getDockPosition(SharedPreferences pref) {\n        String position = pref.getString(\"quick-list-position\", null);\n        if (position != null) {\n            switch (position) {\n                case \"above-result-list\":\n                    return QuickList.QuickListPosition.POSITION_ABOVE_RESULTS;\n                case \"under-result-list\":\n                    return QuickList.QuickListPosition.POSITION_UNDER_RESULTS;\n                case \"under-search-bar\":\n                    return QuickList.QuickListPosition.POSITION_UNDER_SEARCH_BAR;\n                default:\n                    break;\n            }\n        }\n        return QuickList.QuickListPosition.POSITION_UNDER_RESULTS;\n    }\n\n    public static boolean linkCloseKeyboardToBackButton(SharedPreferences preferences) {\n        return preferences.getBoolean(\"behaviour-link-close-keyboard-back-button\", true);\n    }\n\n    @DrawableRes\n    public static int getLoadingIconRes(@NonNull Context context) {\n        if (LOADING_ICON_RES == 0) {\n            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n            String iconName = pref.getString(\"loading-icon\", null);\n            if (iconName == null)\n                iconName = \"none\";\n            switch (iconName) {\n                case \"arrows\":\n                    LOADING_ICON_RES = R.drawable.ic_loading_arrows;\n                    break;\n                case \"pulse\":\n                    LOADING_ICON_RES = R.drawable.ic_loading_pulse;\n                    break;\n                case \"none\":\n                default:\n                    LOADING_ICON_RES = android.R.color.transparent;\n                    break;\n            }\n        }\n        return LOADING_ICON_RES;\n    }\n\n    @NonNull\n    public static Drawable getLoadingIconDrawable(@NonNull Context context) {\n        @DrawableRes\n        int loadingIconRes = getLoadingIconRes(context);\n        Drawable loadingIcon = AppCompatResources.getDrawable(context, loadingIconRes);\n        if (loadingIcon == null)\n            loadingIcon = new ColorDrawable(Color.TRANSPARENT);\n        return loadingIcon;\n    }\n\n    public static boolean modulateContactIcons(Context context) {\n        SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n        return pref.getBoolean(\"matrix-contacts\", false);\n    }\n\n    public static List<ContentLoadHelper.CategoryItem> getResultPopupOrder(@NonNull Context context) {\n        if (RESULT_POPUP_ORDER == null) {\n            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n            ContentLoadHelper.OrderedMultiSelectListData data = ContentLoadHelper.generateResultPopupContent(context, pref);\n            List<String> orderedValues = data.getOrderedListValues();\n            RESULT_POPUP_ORDER = new ArrayList<>(orderedValues.size());\n            for (String orderValue : orderedValues) {\n                String value = PrefOrderedListHelper.getOrderedValueName(orderValue);\n                for (ContentLoadHelper.CategoryItem categoryItem : ContentLoadHelper.RESULT_POPUP_CATEGORIES) {\n                    if (categoryItem.value.equals(value)) {\n                        RESULT_POPUP_ORDER.add(categoryItem);\n                        break;\n                    }\n                }\n            }\n        }\n        return RESULT_POPUP_ORDER;\n    }\n\n    public static boolean firstAtBottom(SharedPreferences preferences) {\n        return preferences.getBoolean(\"result-first-at-bottom\", true);\n    }\n\n    public static boolean rightToLeft(SharedPreferences preferences) {\n        return preferences.getBoolean(\"result-right-to-left\", true);\n    }\n\n    public static boolean getResultFadeOut(SharedPreferences pref) {\n        return pref.getBoolean(\"result-fading-edge\", false);\n    }\n\n    public static int getDockColumnCount(SharedPreferences pref) {\n        return pref.getInt(\"quick-list-columns\", 1);\n    }\n\n    public static int getDockRowCount(SharedPreferences pref) {\n        return pref.getInt(\"quick-list-rows\", 1);\n    }\n\n    public static boolean isMigrateRequired(@NonNull SharedPreferences pref) {\n        Map<String, ?> allPref = pref.getAll();\n        for (String key : PREF_THAT_REQUIRE_MIGRATION)\n            if (allPref.containsKey(key))\n                return true;\n        return false;\n    }\n\n    public static boolean migratePreferences(@NonNull Context context, @NonNull SharedPreferences pref) {\n        HashMap<String, Object> prefMapCopy = new HashMap<>(pref.getAll());\n        SharedPreferences.Editor editor = pref.edit();\n        boolean changesMade = migratePreferences(context, prefMapCopy, editor);\n        editor.apply();\n        return changesMade;\n    }\n\n    public static boolean migratePreferences(@NonNull Context context, @NonNull HashMap<String, Object> entries, @NonNull SharedPreferences.Editor editor) {\n        Resources res = context.getResources();\n        boolean changesMade;\n        changesMade = migrateColor(entries, editor, \"result-list\");\n        changesMade = migrateColor(entries, editor, \"notification-bar\") || changesMade;\n        changesMade = migrateColor(entries, editor, \"search-bar\") || changesMade;\n        changesMade = migrateColor(entries, editor, \"quick-list\") || changesMade;\n\n        int defaultCornerRadius = UISizes.px2dp(context, res.getDimensionPixelSize(R.dimen.result_corner_radius));\n        changesMade = migrateToggleToValue(entries, editor, \"result-list-rounded\", \"result-list-radius\", 0, defaultCornerRadius) || changesMade;\n        changesMade = migrateToggleToValue(entries, editor, \"search-bar-rounded\", \"search-bar-radius\", 0, defaultCornerRadius) || changesMade;\n\n        return changesMade;\n    }\n\n    private static boolean migrateColor(@NonNull HashMap<String, Object> entries, @NonNull SharedPreferences.Editor editor, String key) {\n        String keyColor = key + \"-color\";\n        String keyAlpha = key + \"-alpha\";\n        Object color = entries.get(keyColor);\n        Object alpha = entries.get(keyAlpha);\n        if (color instanceof Integer && alpha instanceof Integer) {\n            int argb = UIColors.setAlpha((Integer) color, (Integer) alpha);\n            String keyARGB = key + \"-argb\";\n            editor\n                .remove(keyColor)\n                .remove(keyAlpha)\n                .putInt(keyARGB, argb);\n            Log.d(\"pref\", \"migrate `\" + key + \"` from \" +\n                \"(alpha=0x\" + Integer.toHexString((Integer) alpha) + \" color=0x\" + Integer.toHexString((Integer) color) + \")\" +\n                \" to argb=0x\" + Integer.toHexString(argb));\n            return true;\n        }\n        return false;\n    }\n\n    private static boolean migrateToggleToValue(HashMap<String, Object> entries, SharedPreferences.Editor editor, String keyToggle, String keyValue, int valueOff, int valueOn) {\n        Object toggle = entries.get(keyToggle);\n        if (toggle instanceof Boolean) {\n            int value = ((Boolean) toggle) ? valueOn : valueOff;\n            editor\n                .remove(keyToggle)\n                .putInt(keyValue, value);\n            Log.d(\"pref\", \"migrate `\" + keyToggle + \"` from \" +\n                \"value=\" + toggle + \" to `\" + keyValue + \"` value=\" + value);\n            return true;\n        }\n        return false;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/utils/PrefOrderedListHelper.java",
    "content": "package rocks.tbog.tblauncher.utils;\n\nimport android.content.SharedPreferences;\nimport android.util.Log;\n\nimport androidx.annotation.NonNull;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Set;\n\npublic class PrefOrderedListHelper {\n    private static final String TAG = \"Order\";\n\n    /**\n     * Synchronize the toggle list with the order list. Remove toggled off entries and add at the end new ones.\n     *\n     * @param sharedPreferences we get the list from here and apply the changes to\n     * @param listKey           preference key of the list\n     * @param orderKey          preference key of the order list\n     */\n    public static void syncOrderedList(@NonNull SharedPreferences sharedPreferences, @NonNull String listKey, @NonNull String orderKey) {\n        // get list values in a set I can modify\n        Set<String> listSet = new HashSet<>(sharedPreferences.getStringSet(listKey, Collections.emptySet()));\n        final int listSize = listSet.size();\n        // get order\n        final List<String> orderValues;\n        Set<String> orderSet = sharedPreferences.getStringSet(orderKey, null);\n        if (orderSet == null) {\n            // we don't have any order yet\n            orderValues = Collections.emptyList();\n        } else {\n            orderValues = new ArrayList<>(orderSet);\n            Collections.sort(orderValues);\n        }\n\n        // this will be the new order\n        ArrayList<String> newValues = new ArrayList<>(listSize);\n\n        // keep previous order\n        int idx = 0;\n        for (String value : orderValues) {\n            String name = getOrderedValueName(value);\n            if (listSet.remove(name)) {\n                newValues.add(makeOrderedValue(name, idx++));\n            }\n        }\n\n        // add at the end all the new values\n        for (String name : listSet)\n            newValues.add(makeOrderedValue(name, idx++));\n\n        Set<String> newOrderSet = new HashSet<>(newValues);\n        if (!newOrderSet.equals(orderSet))\n            sharedPreferences.edit().putStringSet(orderKey, newOrderSet).apply();\n    }\n\n    public static List<String> getOrderedList(@NonNull SharedPreferences sharedPreferences, @NonNull String listKey, @NonNull String orderKey) {\n        syncOrderedList(sharedPreferences, listKey, orderKey);\n        Set<String> orderSet = sharedPreferences.getStringSet(orderKey, Collections.emptySet());\n        List<String> orderValues = new ArrayList<>(orderSet);\n        Collections.sort(orderValues);\n        return orderValues;\n    }\n\n    public static String getOrderedValueName(String value) {\n        int pos = value.indexOf(\". \");\n        pos = pos >= 0 ? pos + 2 : 0;\n        return value.substring(pos);\n    }\n\n    public static int getOrderedValueIndex(String value) {\n        int pos = value.indexOf(\". \");\n        int order = 0;\n        if (pos > 0) {\n            String hexOrder = value.substring(0, pos);\n            try {\n                order = Integer.parseInt(hexOrder, 16);\n            } catch (Exception e) {\n                Log.e(TAG, \"parse `\" + hexOrder + \"` in base 16\", e);\n            }\n        } else {\n            Log.e(TAG, \"invalid ordered value `\" + value + \"`\");\n        }\n        return order;\n    }\n\n    public static String makeOrderedValue(String name, int position) {\n        return String.format(Locale.US, \"%08x. %s\", position, name);\n    }\n\n    public static ArrayList<String> getOrderedArrayList(CharSequence[] entryValues) {\n        ArrayList<String> orderedValues = new ArrayList<>(entryValues.length);\n        int ord = 0;\n        for (CharSequence value : entryValues) {\n            String orderedValue = makeOrderedValue(value.toString(), ord);\n            orderedValues.add(orderedValue);\n            ord += 1;\n        }\n        return orderedValues;\n    }\n\n    public static ArrayList<String> getOrderedArrayList(List<String> entryValues) {\n        ArrayList<String> orderedValues = new ArrayList<>(entryValues.size());\n        int ord = 0;\n        for (String value : entryValues) {\n            String orderedValue = makeOrderedValue(value, ord);\n            orderedValues.add(orderedValue);\n            ord += 1;\n        }\n        return orderedValues;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/utils/RootHandler.java",
    "content": "package rocks.tbog.tblauncher.utils;\n\nimport android.content.SharedPreferences;\nimport android.util.Log;\n\nimport java.nio.charset.Charset;\nimport java.nio.charset.StandardCharsets;\n\npublic class RootHandler {\n\n    private static final Charset UTF_8 = StandardCharsets.UTF_8;\n\n    private Boolean isRootAvailable = null;\n    private Boolean isRootActivated = null;\n\n    public RootHandler(SharedPreferences prefs) {\n        resetRootHandler(prefs);\n    }\n\n    public boolean isRootActivated() {\n        return this.isRootActivated;\n    }\n\n    public void resetRootHandler(SharedPreferences prefs) {\n        isRootActivated = prefs.getBoolean(\"root-mode\", false);\n        isRootAvailable = null;\n    }\n\n    public boolean isRootAvailable() {\n\n        if (isRootAvailable == null) {\n            try {\n                isRootAvailable = executeRootShell(null);\n            } catch (Exception e) {\n                isRootAvailable = false;\n            }\n        }\n\n        return isRootAvailable;\n    }\n\n    public boolean hibernateApp(String packageName) {\n        try {\n            return executeRootShell(\"am force-stop \" + packageName);\n        } catch (Exception e) {\n            return false;\n        }\n    }\n\n    private boolean executeRootShell(String command) {\n        Process p = null;\n        try {\n            p = Runtime.getRuntime().exec(\"su\");\n            //put command\n            if (command != null && !command.trim().equals(\"\")) {\n                p.getOutputStream().write((command + \"\\n\").getBytes(UTF_8));\n            }\n            //exit from su command\n            p.getOutputStream().write(\"exit\\n\".getBytes(UTF_8));\n            p.getOutputStream().flush();\n            p.getOutputStream().close();\n            int result = p.waitFor();\n            if (result != 0)\n                throw new Exception(\"Command execution failed \" + result);\n            return true;\n        } catch (Exception e) {\n            Log.e(\"RootHandler\", command, e);\n        } finally {\n            if (p != null) {\n                p.destroy();\n            }\n        }\n        return false;\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/utils/SimpleTextWatcher.java",
    "content": "package rocks.tbog.tblauncher.utils;\n\nimport android.text.Editable;\nimport android.text.TextWatcher;\n\npublic abstract class SimpleTextWatcher implements TextWatcher {\n\n    @Override\n    public void onTextChanged(CharSequence s, int start, int before, int count) {\n    }\n\n    @Override\n    public void beforeTextChanged(CharSequence s, int start, int count, int after) {\n    }\n\n    @Override\n    public void afterTextChanged(Editable s) {\n        onTextChanged(s.toString());\n    }\n\n    public abstract void onTextChanged(String newValue);\n}"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/utils/SimpleXmlWriter.java",
    "content": "package rocks.tbog.tblauncher.utils;\n\nimport android.util.Xml;\n\nimport androidx.annotation.Nullable;\n\nimport org.xmlpull.v1.XmlSerializer;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.io.Writer;\nimport java.nio.charset.StandardCharsets;\n\n/**\n * Wrapper class over most probably com.android.org.kxml2.io.KXmlSerializer\n * Because it's a platform dependent class we can't extend.\n */\npublic class SimpleXmlWriter implements XmlSerializer {\n    private final XmlSerializer xmlSerializer;\n    private String namespace = null;\n\n    private SimpleXmlWriter(XmlSerializer serializer) {\n        xmlSerializer = serializer;\n    }\n\n    public static SimpleXmlWriter getNewInstance() {\n        /*\n         * Returns a new instance of the platform default {@link XmlSerializer} more efficiently than\n         * using {@code XmlPullParserFactory.newInstance().newSerializer()}.\n         */\n        XmlSerializer serializer = Xml.newSerializer();\n\n        return new SimpleXmlWriter(serializer);\n    }\n\n    public boolean setIndentation(boolean turnOn, @Nullable String indentString) {\n        try {\n            xmlSerializer.setFeature(\"http://xmlpull.org/v1/doc/features.html#indent-output\", turnOn);\n            if (indentString != null)\n                xmlSerializer.setProperty(\"http://xmlpull.org/v1/doc/properties.html#serializer-indentation\", indentString);\n        } catch (Exception e) {\n            return false;\n        }\n        return true;\n    }\n\n    public boolean setIndentation(boolean turnOn) {\n        return setIndentation(turnOn, null);\n    }\n\n    /**\n     * Most probably not supported\n     *\n     * @param separator line separator\n     * @return true if serializer property exists\n     */\n    public boolean setLineSeparator(String separator) {\n        try {\n            xmlSerializer.setProperty(\"http://xmlpull.org/v1/doc/properties.html#serializer-line-separator\", separator);\n        } catch (Exception e) {\n            return false;\n        }\n        return true;\n    }\n\n    public void setCurrentNamespace(String namespace) {\n        this.namespace = namespace;\n    }\n\n    @Override\n    public void setFeature(String name, boolean state) throws IllegalArgumentException, IllegalStateException {\n        xmlSerializer.setFeature(name, state);\n    }\n\n    @Override\n    public boolean getFeature(String name) {\n        return xmlSerializer.getFeature(name);\n    }\n\n    @Override\n    public void setProperty(String name, Object value) throws IllegalArgumentException, IllegalStateException {\n        xmlSerializer.setProperty(name, value);\n    }\n\n    @Override\n    public Object getProperty(String name) {\n        return xmlSerializer.getProperty(name);\n    }\n\n    @Override\n    public void setOutput(OutputStream os, String encoding) throws IOException, IllegalArgumentException, IllegalStateException {\n        xmlSerializer.setOutput(os, encoding);\n    }\n\n    @Override\n    public void setOutput(Writer writer) throws IOException, IllegalArgumentException, IllegalStateException {\n        xmlSerializer.setOutput(writer);\n    }\n\n    @Override\n    public void startDocument(String encoding, Boolean standalone) throws IOException, IllegalArgumentException, IllegalStateException {\n        xmlSerializer.startDocument(encoding, standalone);\n    }\n\n    public void startDocument() throws IOException, IllegalArgumentException, IllegalStateException {\n        xmlSerializer.startDocument(StandardCharsets.UTF_8.name(), true);\n    }\n\n    @Override\n    public void endDocument() throws IOException, IllegalArgumentException, IllegalStateException {\n        xmlSerializer.endDocument();\n    }\n\n    @Override\n    public void setPrefix(String prefix, String namespace) throws IOException, IllegalArgumentException, IllegalStateException {\n        xmlSerializer.setPrefix(prefix, namespace);\n    }\n\n    @Override\n    public String getPrefix(String namespace, boolean generatePrefix) throws IllegalArgumentException {\n        return xmlSerializer.getPrefix(namespace, generatePrefix);\n    }\n\n    @Override\n    public int getDepth() {\n        return xmlSerializer.getDepth();\n    }\n\n    @Override\n    public String getNamespace() {\n        return xmlSerializer.getNamespace();\n    }\n\n    @Override\n    public String getName() {\n        return xmlSerializer.getName();\n    }\n\n    @Override\n    public XmlSerializer startTag(String namespace, String name) throws IOException, IllegalArgumentException, IllegalStateException {\n        return xmlSerializer.startTag(namespace, name);\n    }\n\n    public SimpleXmlWriter startTag(String name) throws IOException, IllegalArgumentException, IllegalStateException {\n        xmlSerializer.startTag(namespace, name);\n        return this;\n    }\n\n    @Override\n    public XmlSerializer attribute(String namespace, String name, String value) throws IOException, IllegalArgumentException, IllegalStateException {\n        return xmlSerializer.attribute(namespace, name, value);\n    }\n\n    public SimpleXmlWriter attribute(String name, String value) throws IOException, IllegalArgumentException, IllegalStateException {\n        xmlSerializer.attribute(namespace, name, value);\n        return this;\n    }\n\n    public SimpleXmlWriter attribute(String name, int value) throws IOException, IllegalArgumentException, IllegalStateException {\n        xmlSerializer.attribute(namespace, name, Integer.toString(value));\n        return this;\n    }\n\n    public SimpleXmlWriter attribute(String name, long value) throws IOException, IllegalArgumentException, IllegalStateException {\n        xmlSerializer.attribute(namespace, name, Long.toString(value));\n        return this;\n    }\n\n    @Override\n    public XmlSerializer endTag(String namespace, String name) throws IOException, IllegalArgumentException, IllegalStateException {\n        return xmlSerializer.endTag(namespace, name);\n    }\n\n    public SimpleXmlWriter endTag(String name) throws IOException, IllegalArgumentException, IllegalStateException {\n        xmlSerializer.endTag(namespace, name);\n        return this;\n    }\n\n    @Override\n    public XmlSerializer text(String text) throws IOException, IllegalArgumentException, IllegalStateException {\n        return xmlSerializer.text(text);\n    }\n\n    public SimpleXmlWriter content(String text) throws IOException, IllegalArgumentException, IllegalStateException {\n        xmlSerializer.text(text);\n        return this;\n    }\n\n    public SimpleXmlWriter content(int amount) throws IOException, IllegalArgumentException, IllegalStateException {\n        xmlSerializer.text(String.valueOf(amount));\n        return this;\n    }\n\n    @Override\n    public XmlSerializer text(char[] buf, int start, int len) throws IOException, IllegalArgumentException, IllegalStateException {\n        return xmlSerializer.text(buf, start, len);\n    }\n\n    public SimpleXmlWriter content(byte[] buf) throws IOException, IllegalArgumentException, IllegalStateException {\n        final int size = buf.length;\n        char[] text = new char[size];\n        for (int i = 0; i < size; i += 1)\n            text[i] = (char) buf[i];\n        xmlSerializer.text(text, 0, size);\n        return this;\n    }\n\n    /**\n     * Wrap text in <![CDATA[ ... ]]> tags and make sure we have valid chars\n     * boolean valid = (ch >= 0x20 && ch <= 0xd7ff) ||\n     * (ch == '\\t' || ch == '\\n' || ch == '\\r') ||\n     * (ch >= 0xe000 && ch <= 0xfffd);\n     *\n     * @param text to be converted to char[] by calling toCharArray()\n     * @throws IOException              from writer\n     * @throws IllegalArgumentException when an invalid char is found or from writer\n     * @throws IllegalStateException    from writer\n     */\n    @Override\n    public void cdsect(String text) throws IOException, IllegalArgumentException, IllegalStateException {\n        xmlSerializer.cdsect(text);\n    }\n\n    @Override\n    public void entityRef(String text) throws IOException, IllegalArgumentException, IllegalStateException {\n        xmlSerializer.entityRef(text);\n    }\n\n    @Override\n    public void processingInstruction(String text) throws IOException, IllegalArgumentException, IllegalStateException {\n        xmlSerializer.processingInstruction(text);\n    }\n\n    @Override\n    public void comment(String text) throws IOException, IllegalArgumentException, IllegalStateException {\n        xmlSerializer.comment(text);\n    }\n\n    /**\n     * Write <!DOCTYPE text >\n     *\n     * @param text the text inside DOCTYPE\n     * @throws IOException              from writer\n     * @throws IllegalArgumentException from writer\n     * @throws IllegalStateException    from writer\n     */\n    @Override\n    public void docdecl(String text) throws IOException, IllegalArgumentException, IllegalStateException {\n        xmlSerializer.docdecl(text);\n    }\n\n    @Override\n    public void ignorableWhitespace(String text) throws IOException, IllegalArgumentException, IllegalStateException {\n        xmlSerializer.ignorableWhitespace(text);\n    }\n\n    @Override\n    public void flush() throws IOException {\n        xmlSerializer.flush();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/utils/SparseArrayWrapper.java",
    "content": "/*\n * Copyright (C) 2006 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 */\npackage rocks.tbog.tblauncher.utils;\n\nimport android.os.Build;\nimport android.util.SparseArray;\n\npublic class SparseArrayWrapper<E> implements ISparseArray<E> {\n    protected SparseArray<E> mArray;\n    protected int mCapacity;\n\n    /**\n     * Creates a new SparseArray containing no mappings.\n     */\n    public SparseArrayWrapper() {\n        this(0);\n    }\n\n    /**\n     * Creates a new SparseArray containing no mappings that will not\n     * require any additional memory allocation to store the specified\n     * number of mappings.\n     */\n    public SparseArrayWrapper(int initialCapacity) {\n        mCapacity = initialCapacity;\n        mArray = new SparseArray<>(mCapacity);\n    }\n\n    /**\n     * Increases the capacity of this ArrayList instance, if necessary,\n     * to ensure that it can hold at least the number of elements\n     * specified by the minimum capacity argument.\n     * Should only be used with an empty array because the copy\n     * operation is not optimized\n     *\n     * @param capacity the desired minimum capacity\n     */\n    public void ensureCapacity(int capacity) {\n        if (mCapacity < capacity) {\n            SparseArray<E> oldArray = mArray;\n            int size = oldArray.size();\n\n            mCapacity = capacity;\n            mArray = new SparseArray<>(mCapacity);\n            for (int index = 0; index < size; index += 1) {\n                int key = oldArray.keyAt(index);\n                E value = oldArray.valueAt(index);\n                mArray.append(key, value);\n            }\n        }\n    }\n\n    @Override\n    public boolean contains(int key) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {\n            return mArray.contains(key);\n        }\n        return mArray.indexOfKey(key) >= 0;\n    }\n\n    @Override\n    public E get(int key) {\n        return mArray.get(key);\n    }\n\n    @Override\n    public E get(int key, E valueIfKeyNotFound) {\n        return mArray.get(key, valueIfKeyNotFound);\n    }\n\n    @Override\n    public void delete(int key) {\n        mArray.delete(key);\n    }\n\n    @Override\n    public void removeAt(int index) {\n        mArray.removeAt(index);\n    }\n\n    @Override\n    public void removeAtRange(int index, int size) {\n        mArray.removeAtRange(index, size);\n    }\n\n    @Override\n    public void put(int key, E value) {\n        mArray.put(key, value);\n        updateCapacity();\n    }\n\n    @Override\n    public int size() {\n        return mArray.size();\n    }\n\n    public int capacity() {\n        return mCapacity;\n    }\n\n    @Override\n    public int keyAt(int index) {\n        return mArray.keyAt(index);\n    }\n\n    @Override\n    public E valueAt(int index) {\n        return mArray.valueAt(index);\n    }\n\n    @Override\n    public void setValueAt(int index, E value) {\n        mArray.setValueAt(index, value);\n    }\n\n    @Override\n    public int indexOfKey(int key) {\n        return mArray.indexOfKey(key);\n    }\n\n    @Override\n    public int indexOfValue(E value) {\n        return mArray.indexOfValue(value);\n    }\n\n    @Override\n    public void clear() {\n        mArray.clear();\n    }\n\n    @Override\n    public void append(int key, E value) {\n        mArray.append(key, value);\n        updateCapacity();\n    }\n\n    protected void updateCapacity() {\n        int size = mArray.size();\n        if (size > mCapacity)\n            mCapacity = size;\n    }\n}"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/utils/SystemUiVisibility.java",
    "content": "package rocks.tbog.tblauncher.utils;\n\nimport android.os.Build;\nimport android.view.View;\n\nimport androidx.annotation.RequiresApi;\n\npublic class SystemUiVisibility {\n\n    // Note that some of these constants are new as of API 16 (Jelly Bean)\n    // and API 19 (KitKat). It is safe to use them, as they are inlined\n    // at compile-time and do nothing on earlier devices.\n    private static final int REMOVE_STATUS_AND_NAVIGATION = View.SYSTEM_UI_FLAG_LOW_PROFILE\n            | View.SYSTEM_UI_FLAG_FULLSCREEN\n            | View.SYSTEM_UI_FLAG_LAYOUT_STABLE\n            | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY\n            | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION\n            | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;\n\n    private static final int SHOW_SYSTEM_BARS = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN\n            | View.SYSTEM_UI_FLAG_LAYOUT_STABLE\n            | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;\n\n    private static int setFlags(int flags, int visibility) {\n        return flags | visibility;\n    }\n\n    private static int clearFlags(int flags, int visibility) {\n        return flags & ~visibility;\n    }\n\n    @RequiresApi(api = Build.VERSION_CODES.M)\n    public static void setLightStatusBar(View view) {\n        int flags = view.getSystemUiVisibility();\n        flags = setFlags(flags, View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);\n        view.setSystemUiVisibility(flags);\n    }\n\n    @RequiresApi(api = Build.VERSION_CODES.M)\n    public static void clearLightStatusBar(View view) {\n        int flags = view.getSystemUiVisibility();\n        flags = clearFlags(flags, View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);\n        view.setSystemUiVisibility(flags);\n    }\n\n    @RequiresApi(api = Build.VERSION_CODES.O)\n    public static void setLightNavigationBar(View view) {\n        int flags = view.getSystemUiVisibility();\n        flags = setFlags(flags, View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);\n        view.setSystemUiVisibility(flags);\n    }\n\n    @RequiresApi(api = Build.VERSION_CODES.O)\n    public static void clearLightNavigationBar(View view) {\n        int flags = view.getSystemUiVisibility();\n        flags = clearFlags(flags, View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);\n        view.setSystemUiVisibility(flags);\n    }\n\n    public static void setFullscreen(View view) {\n        int flags = view.getSystemUiVisibility();\n        flags = clearFlags(flags, SHOW_SYSTEM_BARS);\n\n        // removal of status and navigation bar\n        flags = setFlags(flags, REMOVE_STATUS_AND_NAVIGATION);\n        view.setSystemUiVisibility(flags);\n    }\n\n    public static void clearFullscreen(View view) {\n        int flags = view.getSystemUiVisibility();\n        flags = clearFlags(flags, REMOVE_STATUS_AND_NAVIGATION);\n\n        // Show the system bar\n        flags = setFlags(flags, SHOW_SYSTEM_BARS);\n        view.setSystemUiVisibility(flags);\n    }\n\n    public static boolean isFullscreenSet(View view) {\n        int currentFlags = view.getSystemUiVisibility();\n        int flags = clearFlags(currentFlags, SHOW_SYSTEM_BARS);\n        flags = setFlags(flags, REMOVE_STATUS_AND_NAVIGATION);\n        return currentFlags == flags;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/utils/Timer.java",
    "content": "package rocks.tbog.tblauncher.utils;\n\nimport androidx.annotation.NonNull;\n\nimport java.util.concurrent.TimeUnit;\n\npublic class Timer {\n    protected long mStart;\n    protected long mStop;\n    protected TimeUnit mUnit;\n\n    public static final StopTimeComparator STOP_TIME_COMPARATOR = new StopTimeComparator();\n\n    public Timer() {\n        this(0, TimeUnit.MILLISECONDS);\n    }\n\n    protected Timer(long now, @NonNull TimeUnit unit) {\n        mStart = now;\n        mStop = now;\n        mUnit = unit;\n    }\n\n    public static Timer startNano() {\n        return new Timer(System.nanoTime(), TimeUnit.NANOSECONDS);\n    }\n\n    public static Timer startMilli() {\n        return new Timer(System.currentTimeMillis(), TimeUnit.MILLISECONDS);\n    }\n\n    public void start() {\n        if (mUnit == TimeUnit.NANOSECONDS) {\n            mStart = System.nanoTime();\n        } else {\n            mStart = System.currentTimeMillis();\n        }\n        mStop = mStart;\n    }\n\n    public void stop() {\n        if (mUnit == TimeUnit.NANOSECONDS) {\n            mStop = System.nanoTime();\n        } else {\n            mStop = System.currentTimeMillis();\n        }\n        //return mStop - mStart;\n    }\n\n    @NonNull\n    @Override\n    public String toString() {\n        if (mUnit == TimeUnit.NANOSECONDS && mUnit.toSeconds(mStop - mStart) == 0) {\n            long deltaTime = mUnit.toNanos(mStop - mStart);\n            long ms = TimeUnit.NANOSECONDS.toMillis(deltaTime);\n            if (ms == 0)\n                return deltaTime + \"ns\";\n            long ns = deltaTime - TimeUnit.MILLISECONDS.toNanos(ms);\n            if (ns > 0)\n                return ms + \"ms \" + ns + \"ns\";\n            return ms + \"ms\";\n        }\n        return toStringSeconds();\n    }\n\n    @NonNull\n    public String toStringSeconds() {\n        long deltaTime = mUnit.toMillis(mStop - mStart);\n        long s = TimeUnit.MILLISECONDS.toSeconds(deltaTime);\n        long ms = deltaTime - TimeUnit.SECONDS.toMillis(s);\n        if (s == 0)\n            return ms + \"ms\";\n        if (ms > 0)\n            return s + \"sec \" + ms + \"ms\";\n        return s + \"sec\";\n    }\n\n    public static class StopTimeComparator implements java.util.Comparator<Timer> {\n        @Override\n        public int compare(Timer o1, Timer o2) {\n            if (o1 == o2)\n                return 0;\n            if (o1 == null)\n                return -1;\n            if (o2 == null)\n                return 1;\n            return (int) (o1.mStop - o2.mStop);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/utils/UIColors.java",
    "content": "package rocks.tbog.tblauncher.utils;\n\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.content.res.Resources;\nimport android.graphics.Color;\nimport android.graphics.ColorFilter;\nimport android.graphics.ColorMatrix;\nimport android.graphics.ColorMatrixColorFilter;\nimport android.graphics.drawable.ColorDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.graphics.drawable.GradientDrawable;\nimport android.os.Build;\nimport android.util.TypedValue;\nimport android.view.Window;\nimport android.view.WindowManager;\n\nimport androidx.annotation.AttrRes;\nimport androidx.annotation.ColorInt;\nimport androidx.annotation.FloatRange;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.appcompat.app.ActionBar;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.appcompat.view.ContextThemeWrapper;\nimport androidx.core.graphics.ColorUtils;\nimport androidx.preference.PreferenceManager;\n\nimport rocks.tbog.tblauncher.R;\n\npublic final class UIColors {\n    public static final int COLOR_DEFAULT = 0xFF3cb371;\n    private static int CACHED_SYSTEM_ACCENT = 0;\n    private static int CACHED_COLOR_HIGHLIGHT = 0;\n    private static int CACHED_COLOR_RESULT_TEXT = 0;\n    private static int CACHED_COLOR_RESULT_TEXT2 = 0;\n    private static int CACHED_COLOR_RESULT_SHADOW = 0;\n    private static int CACHED_COLOR_QL_TOGGLE = 0;\n    private static int CACHED_RIPPLE_QL = 0;\n    private static int CACHED_COLOR_CONTACT_ACTION = 0;\n    private static int CACHED_COLOR_SEARCH_TEXT = 0;\n    private static int CACHED_COLOR_SEARCH_SHADOW = 0;\n    private static Integer CACHED_BACKGROUND_RESULT_LIST = null;\n    private static int CACHED_RIPPLE_RESULT_LIST = 0;\n    private static Integer CACHED_BACKGROUND_ICON = null;\n    private static Integer CACHED_COLOR_POPUP_BORDER = null;\n    private static Integer CACHED_COLOR_POPUP_BACKGROUND = null;\n    private static int CACHED_RIPPLE_POPUP = 0;\n    private static int CACHED_COLOR_POPUP_TEXT = 0;\n    private static int CACHED_COLOR_POPUP_TITLE = 0;\n    private static int CACHED_COLOR_POPUP_SHADOW = 0;\n    private static boolean CACHED_MAT_ICON = false;\n    private static ColorMatrix COLOR_MATRIX_ICON = null;\n\n    private UIColors() {\n    }\n\n    public static void resetCache() {\n        CACHED_SYSTEM_ACCENT = 0;\n        CACHED_COLOR_HIGHLIGHT = 0;\n        CACHED_COLOR_RESULT_TEXT = 0;\n        CACHED_COLOR_RESULT_TEXT2 = 0;\n        CACHED_COLOR_RESULT_SHADOW = 0;\n        CACHED_COLOR_QL_TOGGLE = 0;\n        CACHED_RIPPLE_QL = 0;\n        CACHED_COLOR_CONTACT_ACTION = 0;\n        CACHED_COLOR_SEARCH_TEXT = 0;\n        CACHED_COLOR_SEARCH_SHADOW = 0;\n        CACHED_BACKGROUND_RESULT_LIST = null;\n        CACHED_RIPPLE_RESULT_LIST = 0;\n        CACHED_BACKGROUND_ICON = null;\n        CACHED_COLOR_POPUP_BORDER = null;\n        CACHED_COLOR_POPUP_BACKGROUND = null;\n        CACHED_RIPPLE_POPUP = 0;\n        CACHED_COLOR_POPUP_TEXT = 0;\n        CACHED_COLOR_POPUP_TITLE = 0;\n        CACHED_COLOR_POPUP_SHADOW = 0;\n        CACHED_MAT_ICON = false;\n    }\n\n    public static int getDefaultColor(Context context) {\n        return COLOR_DEFAULT;\n    }\n\n    @ColorInt\n    public static int getThemeColor(Context context, @AttrRes int idRes) {\n        TypedValue typedValue = new TypedValue();\n        Resources.Theme theme = context.getTheme();\n        theme.resolveAttribute(idRes, typedValue, true);\n        return typedValue.data;\n    }\n\n    private static int getSystemAccent(Context context) {\n        int color = COLOR_DEFAULT;\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            ContextThemeWrapper contextThemeWrapper = new ContextThemeWrapper(context, android.R.style.Theme_DeviceDefault);\n            color = getThemeColor(contextThemeWrapper, android.R.attr.colorAccent);\n        }\n        // Oxygen OS accent color, also used by some custom ROMs now\n        String propertyValue = Utilities.getSystemProperty(\"persist.sys.theme.accentcolor\", \"\");\n        if (!propertyValue.isEmpty()) {\n            if (!propertyValue.startsWith(\"#\"))\n                propertyValue = \"#\" + propertyValue;\n            try {\n                color = Color.parseColor(propertyValue);\n            } catch (IllegalArgumentException ignored) {\n            }\n        }\n        return color;\n    }\n\n    @ColorInt\n    public static int getColor(SharedPreferences pref, String key, @ColorInt int defaultColor) {\n        return pref.getInt(key, defaultColor);\n    }\n\n    @ColorInt\n    public static int getColor(SharedPreferences pref, String key) {\n        return getColor(pref, key, COLOR_DEFAULT);\n    }\n\n    public static int getAlpha(SharedPreferences pref, String key) {\n        return pref.getInt(key, 0xFF) & 0xFF;\n    }\n\n    public static int setAlpha(int color, int alpha) {\n        return color & 0x00ffffff | ((alpha & 0xFF) << 24);\n    }\n\n    /**\n     * Returns the relative luminance of a color.\n     * Code adapted from <i>ColorUtils::calculateLuminance(@ColorInt int color)</i>\n     * https://android.googlesource.com/platform/frameworks/base/+/master/core/java/com/android/internal/graphics/ColorUtils.java\n     *\n     * @return a value between 0 (darkest black) and 1 (lightest white)\n     */\n    @FloatRange(from = 0.f, to = 1.f)\n    public static float luminance(@ColorInt int color) {\n        int r = Color.red(color);\n        int g = Color.green(color);\n        int b = Color.blue(color);\n\n        // Convert RGB components to its CIE XYZ representative components.\n        float sr = r / 255f;\n        sr = sr < 0.04045f ? sr / 12.92f : (float) Math.pow((sr + 0.055) / 1.055, 2.4);\n        float sg = g / 255f;\n        sg = sg < 0.04045f ? sg / 12.92f : (float) Math.pow((sg + 0.055) / 1.055, 2.4);\n        float sb = b / 255f;\n        sb = sb < 0.04045f ? sb / 12.92f : (float) Math.pow((sb + 0.055) / 1.055, 2.4);\n\n        return (sr * 0.2126f + sg * 0.7152f + sb * 0.0722f);\n    }\n\n    public static boolean isColorLight(@ColorInt int color) {\n        return luminance(color) > .5f;\n    }\n\n    /**\n     * Darken or lighten the color. For amount 2 the result is white, for 1 color is unchanged, for 0 result is black\n     *\n     * @param color  color to be changed\n     * @param amount [0..2] - less than 1 to darken and grater to lighten\n     */\n    public static int modulateColorLightness(@ColorInt int color, @FloatRange(from = 0.f, to = 2.f) float amount) {\n        float[] hsl = new float[3];\n        ColorUtils.colorToHSL(color, hsl);\n        if (amount <= 1f)\n            hsl[2] = Math.max(0f, hsl[2] * amount);\n        else {\n            final float ratio = amount - 1f;\n            final float inverseRatio = 1f - ratio;\n            hsl[2] = Math.min(1f, hsl[2] * inverseRatio + ratio);\n        }\n        return ColorUtils.HSLToColor(hsl);\n    }\n\n    /**\n     * The Web Content Accessibility Guidelines (WCAG 2.0) level AA requires a 4.5:1 color contrast between text and background for normal text, and 3:1 to large text.\n     *\n     * @param background background color\n     * @return text color for large text\n     */\n    public static int getTextContrastColor(@ColorInt int background) {\n        int result = -1;\n        float lumBack = UIColors.luminance(background);\n\n        float min = 0f;\n        float max = 1f;\n        int count = 0;\n        float ratio;\n        // use binary search to find a text color to satisfy the color contrast\n        while (min < max) {\n            float mid = (min + max) * .5f;\n            float modulateAmount = lumBack < .5f ? (1f + mid) : (1f - mid);\n            int text = UIColors.modulateColorLightness(background, modulateAmount);\n\n            if (++count > 10) {\n                if (result == -1)\n                    result = text;\n                break;\n            }\n\n            float lumText = UIColors.luminance(text);\n            if (lumText >= lumBack) {\n                ratio = (lumText + .05f) / (lumBack + .05f);\n            } else {\n                ratio = (lumBack + .05f) / (lumText + .05f);\n            }\n            if (ratio < 4.5f) // 4.5:1 ratio\n                min = mid;\n            else {\n                max = mid;\n                result = text;\n            }\n        }\n        // return opaque color\n        return result | 0xFF000000;\n    }\n\n    public static void setStatusBarColor(AppCompatActivity compatActivity, @ColorInt int notificationBarColor) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            Window window = compatActivity.getWindow();\n            window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);\n            window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);\n\n            // Update status bar color\n            window.setStatusBarColor(notificationBarColor);\n        }\n\n        ActionBar actionBar = compatActivity.getSupportActionBar();\n        if (actionBar != null) {\n            actionBar.setBackgroundDrawable(new ColorDrawable(notificationBarColor));\n        }\n    }\n\n    public static void setNavigationBarColor(AppCompatActivity activity, int color, int divColor) {\n        Window window = activity.getWindow();\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);\n            window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);\n            window.setNavigationBarColor(color);\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {\n                window.setNavigationBarDividerColor(divColor);\n            }\n        }\n    }\n\n    public static int getSystemAccentColor(Context context) {\n        if (CACHED_SYSTEM_ACCENT == 0) {\n            int accent = getSystemAccent(context);\n            CACHED_SYSTEM_ACCENT = setAlpha(accent, 0xFF);\n        }\n        return CACHED_SYSTEM_ACCENT;\n    }\n\n    public static int getResultHighlightColor(Context context) {\n        if (CACHED_COLOR_HIGHLIGHT == 0) {\n            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n            int highlightColor = getColor(pref, \"result-highlight-color\");\n            CACHED_COLOR_HIGHLIGHT = setAlpha(highlightColor, 0xFF);\n        }\n        return CACHED_COLOR_HIGHLIGHT;\n    }\n\n    public static int getResultTextColor(Context context) {\n        if (CACHED_COLOR_RESULT_TEXT == 0) {\n            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n            int highlightColor = pref.getInt(\"result-text-color\", 0xffffff);\n            CACHED_COLOR_RESULT_TEXT = setAlpha(highlightColor, 0xFF);\n        }\n        return CACHED_COLOR_RESULT_TEXT;\n    }\n\n    public static int getResultText2Color(Context context) {\n        if (CACHED_COLOR_RESULT_TEXT2 == 0) {\n            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n            int highlightColor = pref.getInt(\"result-text2-color\", 0xbbffbb);\n            CACHED_COLOR_RESULT_TEXT2 = setAlpha(highlightColor, 0xFF);\n        }\n        return CACHED_COLOR_RESULT_TEXT2;\n    }\n\n    public static int getResultListShadowColor(Context context) {\n        if (CACHED_COLOR_RESULT_SHADOW == 0) {\n            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n            int color = UIColors.getColor(pref, \"result-shadow-color\");\n            CACHED_COLOR_RESULT_SHADOW = setAlpha(color, 0xFF);\n        }\n        return CACHED_COLOR_RESULT_SHADOW;\n    }\n\n    public static int getQuickListToggleColor(Context context) {\n        if (CACHED_COLOR_QL_TOGGLE == 0) {\n            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n            int highlightColor = getColor(pref, \"quick-list-toggle-color\");\n            CACHED_COLOR_QL_TOGGLE = setAlpha(highlightColor, 0xFF);\n        }\n        return CACHED_COLOR_QL_TOGGLE;\n    }\n\n    public static int getQuickListRipple(Context context) {\n        if (CACHED_RIPPLE_QL == 0) {\n            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n            int color = UIColors.getColor(pref, \"quick-list-ripple-color\");\n            int alpha = 0xFF;\n            CACHED_RIPPLE_QL = setAlpha(color, alpha);\n        }\n        return CACHED_RIPPLE_QL;\n    }\n\n    public static int getContactActionColor(Context context) {\n        if (CACHED_COLOR_CONTACT_ACTION == 0) {\n            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n            int highlightColor = getColor(pref, \"contact-action-color\");\n            CACHED_COLOR_CONTACT_ACTION = setAlpha(highlightColor, 0xFF);\n        }\n        return CACHED_COLOR_CONTACT_ACTION;\n    }\n\n    public static int getSearchTextColor(Context context) {\n        if (CACHED_COLOR_SEARCH_TEXT == 0) {\n            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n            int highlightColor = getColor(pref, \"search-bar-text-color\");\n            CACHED_COLOR_SEARCH_TEXT = setAlpha(highlightColor, 0xFF);\n        }\n        return CACHED_COLOR_SEARCH_TEXT;\n    }\n\n    public static int getSearchShadowColor(Context context) {\n        if (CACHED_COLOR_SEARCH_SHADOW == 0) {\n            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n            int color = UIColors.getColor(pref, \"search-bar-shadow-color\");\n            CACHED_COLOR_SEARCH_SHADOW = setAlpha(color, 0xFF);\n        }\n        return CACHED_COLOR_SEARCH_SHADOW;\n    }\n\n    public static int getResultListBackground(Context context) {\n        if (CACHED_BACKGROUND_RESULT_LIST == null) {\n            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n            return getResultListBackground(pref);\n        }\n        return CACHED_BACKGROUND_RESULT_LIST;\n    }\n\n    public static int getResultListBackground(SharedPreferences pref) {\n        if (CACHED_BACKGROUND_RESULT_LIST == null) {\n            CACHED_BACKGROUND_RESULT_LIST = UIColors.getColor(pref, \"result-list-argb\");\n        }\n        return CACHED_BACKGROUND_RESULT_LIST;\n    }\n\n    public static int getResultListRipple(Context context) {\n        if (CACHED_RIPPLE_RESULT_LIST == 0) {\n            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n            int color = UIColors.getColor(pref, \"result-ripple-color\");\n            int alpha = 0xFF;\n            CACHED_RIPPLE_RESULT_LIST = setAlpha(color, alpha);\n        }\n        return CACHED_RIPPLE_RESULT_LIST;\n    }\n\n    public static int getIconBackground(Context context) {\n        if (CACHED_BACKGROUND_ICON == null) {\n            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n            CACHED_BACKGROUND_ICON = UIColors.getColor(pref, \"icon-background-argb\");\n        }\n        return CACHED_BACKGROUND_ICON;\n    }\n\n    public static int getPopupBorderColor(Context context) {\n        if (CACHED_COLOR_POPUP_BORDER == null) {\n            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n            int color = pref.getInt(\"popup-border-argb\", 0);\n            if (color == 0) {\n                color = getSystemAccentColor(context);\n                pref.edit().putInt(\"popup-border-argb\", color).apply();\n            }\n            CACHED_COLOR_POPUP_BORDER = color;\n        }\n        return CACHED_COLOR_POPUP_BORDER;\n    }\n\n    public static int getPopupBackgroundColor(Context context) {\n        if (CACHED_COLOR_POPUP_BACKGROUND == null) {\n            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n            CACHED_COLOR_POPUP_BACKGROUND = UIColors.getColor(pref, \"popup-background-argb\");\n        }\n        return CACHED_COLOR_POPUP_BACKGROUND;\n    }\n\n    public static int getPopupRipple(Context context) {\n        if (CACHED_RIPPLE_POPUP == 0) {\n            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n            int color = UIColors.getColor(pref, \"popup-ripple-color\");\n            CACHED_RIPPLE_POPUP = setAlpha(color, 0xFF);\n        }\n        return CACHED_RIPPLE_POPUP;\n    }\n\n    public static int getPopupTextColor(Context context) {\n        if (CACHED_COLOR_POPUP_TEXT == 0) {\n            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n            int color = UIColors.getColor(pref, \"popup-text-color\");\n            CACHED_COLOR_POPUP_TEXT = setAlpha(color, 0xFF);\n        }\n        return CACHED_COLOR_POPUP_TEXT;\n    }\n\n    public static int getPopupTitleColor(Context context) {\n        if (CACHED_COLOR_POPUP_TITLE == 0) {\n            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n            int color = UIColors.getColor(pref, \"popup-title-color\");\n            CACHED_COLOR_POPUP_TITLE = setAlpha(color, 0xFF);\n        }\n        return CACHED_COLOR_POPUP_TITLE;\n    }\n\n    public static int getPopupShadowColor(Context context) {\n        if (CACHED_COLOR_POPUP_SHADOW == 0) {\n            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n            int color = UIColors.getColor(pref, \"popup-shadow-color\");\n            CACHED_COLOR_POPUP_SHADOW = setAlpha(color, 0xFF);\n        }\n        return CACHED_COLOR_POPUP_SHADOW;\n    }\n\n    public static Drawable getPreviewDrawable(int color, int border, float radius) {\n        float luminance = UIColors.luminance(color);\n        int borderColor = UIColors.modulateColorLightness(color, 2.f * (1.f - luminance));\n\n        GradientDrawable drawable = new GradientDrawable();\n        drawable.setCornerRadius(radius);\n        drawable.setStroke(border, borderColor);\n        drawable.setColor(color);\n\n        return drawable;\n    }\n\n    public static ColorFilter colorFilterQuickIcon(@NonNull Context context) {\n        return colorFilter(context);\n    }\n\n    @Nullable\n    public static ColorFilter colorFilter(@NonNull Context context) {\n        if (!CACHED_MAT_ICON) {\n            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n            Resources resources = context.getResources();\n            int hue = pref.getInt(\"icon-hue\", resources.getInteger(R.integer.default_icon_hue));\n            int contrast = pref.getInt(\"icon-contrast\", resources.getInteger(R.integer.default_icon_contrast));\n            int brightness = pref.getInt(\"icon-brightness\", resources.getInteger(R.integer.default_icon_brightness));\n            int saturation = pref.getInt(\"icon-saturation\", resources.getInteger(R.integer.default_icon_saturation));\n            int scaleR = pref.getInt(\"icon-scale-red\", resources.getInteger(R.integer.default_icon_scale));\n            int scaleG = pref.getInt(\"icon-scale-green\", resources.getInteger(R.integer.default_icon_scale));\n            int scaleB = pref.getInt(\"icon-scale-blue\", resources.getInteger(R.integer.default_icon_scale));\n            int scaleA = pref.getInt(\"icon-scale-alpha\", resources.getInteger(R.integer.default_icon_scale));\n            final ColorMatrix cm = new ColorMatrix();\n            boolean modified;\n            modified = ColorFilterHelper.adjustScale(cm, scaleR, scaleG, scaleB, scaleA);\n            modified = ColorFilterHelper.adjustHue(cm, hue) || modified;\n            modified = ColorFilterHelper.adjustContrast(cm, contrast) || modified;\n            modified = ColorFilterHelper.adjustBrightness(cm, brightness) || modified;\n            modified = ColorFilterHelper.adjustSaturation(cm, saturation) || modified;\n            CACHED_MAT_ICON = true;\n            COLOR_MATRIX_ICON = modified ? cm : null;\n        }\n        return COLOR_MATRIX_ICON == null ? null : new ColorMatrixColorFilter(COLOR_MATRIX_ICON);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/utils/UISizes.java",
    "content": "package rocks.tbog.tblauncher.utils;\n\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.content.res.Resources;\nimport android.graphics.Rect;\nimport android.os.Build;\nimport android.util.DisplayMetrics;\nimport android.util.TypedValue;\nimport android.view.ViewGroup;\n\nimport androidx.annotation.DimenRes;\nimport androidx.annotation.NonNull;\nimport androidx.preference.PreferenceManager;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.ui.CutoutFactory;\n\npublic final class UISizes {\n    // cached sizes are in pixels so we don't need to convert after each get*\n    private static int CACHED_SIZE_RESULT_TEXT = 0;\n    private static int CACHED_SIZE_RESULT_TEXT2 = 0;\n    private static int CACHED_SIZE_RESULT_ICON = 0;\n    private static int CACHED_SIZE_TAGS_MENU_ICON = 0;\n    private static int CACHED_SIZE_DOCK_ICON = 0;\n    private static int CACHED_SIZE_STATUS_BAR = 0;\n    private static int CACHED_RADIUS_POPUP_CORNER = -1;\n    private static Float CACHED_RADIUS_POPUP_SHADOW = null;\n    private static Float CACHED_DX_POPUP_SHADOW = null;\n    private static Float CACHED_DY_POPUP_SHADOW = null;\n    private static Integer CACHED_HEIGHT_RESULT_LIST_ROW = null;\n    private static Float CACHED_RADIUS_RESULT_LIST_SHADOW = null;\n    private static Float CACHED_DX_RESULT_LIST_SHADOW = null;\n    private static Float CACHED_DY_RESULT_LIST_SHADOW = null;\n    private static final float EPSILON_PX_SIZE = 0.001f;\n\n    private UISizes() {\n    }\n\n    public static void resetCache() {\n        CACHED_SIZE_RESULT_TEXT = 0;\n        CACHED_SIZE_RESULT_TEXT2 = 0;\n        CACHED_SIZE_RESULT_ICON = 0;\n        CACHED_SIZE_TAGS_MENU_ICON = 0;\n        CACHED_SIZE_DOCK_ICON = 0;\n        CACHED_SIZE_STATUS_BAR = 0;\n        CACHED_RADIUS_POPUP_CORNER = -1;\n        CACHED_RADIUS_POPUP_SHADOW = null;\n        CACHED_DX_POPUP_SHADOW = null;\n        CACHED_DY_POPUP_SHADOW = null;\n        CACHED_HEIGHT_RESULT_LIST_ROW = null;\n        CACHED_RADIUS_RESULT_LIST_SHADOW = null;\n        CACHED_DX_RESULT_LIST_SHADOW = null;\n        CACHED_DY_RESULT_LIST_SHADOW = null;\n    }\n\n    public static int sp2px(Context context, int size) {\n        float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, size, context.getResources().getDisplayMetrics());\n        return Math.max(1, (int) (px + .5f));\n    }\n\n    public static int dp2px(Context context, int size) {\n        if (size == 0)\n            return 0;\n        float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, size, context.getResources().getDisplayMetrics());\n        return Math.max(1, (int) (px + .5f));\n    }\n\n    public static int dp2px_float(Context context, float size) {\n        float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, size, context.getResources().getDisplayMetrics());\n        return Math.round(px);\n    }\n\n    public static int px2dp(Context context, int pixelSize) {\n        if (pixelSize == 0)\n            return 0;\n        float dp;\n        DisplayMetrics metrics = context.getResources().getDisplayMetrics();\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE)\n        {\n            // Avoid divide-by-zero, and return 0 since that's what the inverse function will do\n            if (metrics.density == 0) {\n                return 0;\n            }\n            dp = pixelSize / metrics.density;\n        } else {\n            dp = TypedValue.deriveDimension(TypedValue.COMPLEX_UNIT_DIP, pixelSize, metrics);\n        }\n        return Math.max(1, (int) (dp + .5f));\n    }\n\n    public static float px2dp_float(Context context, float size) {\n        DisplayMetrics metrics = context.getResources().getDisplayMetrics();\n        float px;\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE)\n        {\n            // Avoid divide-by-zero, and return 0 since that's what the inverse function will do\n            if (metrics.density == 0) {\n                return 0f;\n            }\n            px = size / metrics.density;\n        } else {\n            px = TypedValue.deriveDimension(TypedValue.COMPLEX_UNIT_DIP, size, metrics);\n        }\n        return Math.round(px * 1000.f) * 0.001f;\n    }\n\n    public static int getResultTextSize(Context context) {\n        if (CACHED_SIZE_RESULT_TEXT == 0) {\n            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n            final int defaultSize = context.getResources().getInteger(R.integer.default_size_text);\n            final int size = pref.getInt(\"result-text-size\", defaultSize);\n            CACHED_SIZE_RESULT_TEXT = sp2px(context, size);\n        }\n        return CACHED_SIZE_RESULT_TEXT;\n    }\n\n    public static int getResultText2Size(Context context) {\n        if (CACHED_SIZE_RESULT_TEXT2 == 0) {\n            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n            final int defaultSize = context.getResources().getInteger(R.integer.default_size_text2);\n            final int size = pref.getInt(\"result-text2-size\", defaultSize);\n            CACHED_SIZE_RESULT_TEXT2 = sp2px(context, size);\n        }\n        return CACHED_SIZE_RESULT_TEXT2;\n    }\n\n    public static int getResultIconSize(Context context) {\n        if (CACHED_SIZE_RESULT_ICON == 0) {\n            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n            final int defaultSize = context.getResources().getInteger(R.integer.default_size_icon);\n            final int size = pref.getInt(\"result-icon-size\", defaultSize);\n            CACHED_SIZE_RESULT_ICON = dp2px(context, Math.max(1, size));\n        }\n        return CACHED_SIZE_RESULT_ICON;\n    }\n\n    public static int getResultListRadius(Context context) {\n        SharedPreferences pref = TBApplication.getApplication(context).preferences();\n        int radius = pref.getInt(\"result-list-radius\", -1);\n        if (radius < 0)\n            return context.getResources().getDimensionPixelSize(R.dimen.result_corner_radius);\n        return dp2px(context, radius);\n    }\n\n    public static Rect getResultListMargin(Context context) {\n        SharedPreferences pref = TBApplication.getApplication(context).preferences();\n        final int marginHorizontal = pref.getInt(\"result-list-margin-horizontal\", 0);\n        final int marginVertical = pref.getInt(\"result-list-margin-vertical\", 0);\n        float marginOffsetX = pref.getFloat(\"result-list-margin-offset-dx\", 0);\n        float marginOffsetY = pref.getFloat(\"result-list-margin-offset-dy\", 0);\n        if (marginOffsetX > marginHorizontal)\n            marginOffsetX = marginHorizontal;\n        if (marginOffsetY > marginVertical)\n            marginOffsetY = marginVertical;\n        Rect margin = new Rect();\n        margin.left = dp2px_float(context, marginHorizontal + marginOffsetX);\n        margin.right = dp2px_float(context, marginHorizontal - marginOffsetX);\n        margin.top = dp2px_float(context, marginVertical + marginOffsetY);\n        margin.bottom = dp2px_float(context, marginVertical - marginOffsetY);\n        return margin;\n    }\n\n    public static int getResultListRowHeight(Context context) {\n        if (CACHED_HEIGHT_RESULT_LIST_ROW == null) {\n            SharedPreferences pref = TBApplication.getApplication(context).preferences();\n            boolean manual = pref.getBoolean(\"result-list-row-height-manual\", false);\n            if (manual) {\n                int height = pref.getInt(\"result-list-row-height\", 0);\n                if (height <= 0)\n                    CACHED_HEIGHT_RESULT_LIST_ROW = ViewGroup.LayoutParams.WRAP_CONTENT;\n                else\n                    CACHED_HEIGHT_RESULT_LIST_ROW = dp2px(context, height);\n            } else {\n                int iconSize = getResultIconSize(context);\n                int resultMargin = context.getResources().getDimensionPixelSize(R.dimen.result_margin_vertical);\n                int iconMargin = context.getResources().getDimensionPixelSize(R.dimen.icon_margin_vertical);\n                CACHED_HEIGHT_RESULT_LIST_ROW = iconSize + 2 * resultMargin + 2 * iconMargin;\n            }\n        }\n        return CACHED_HEIGHT_RESULT_LIST_ROW;\n    }\n\n    public static float getResultListShadowRadius(Context context) {\n        if (CACHED_RADIUS_RESULT_LIST_SHADOW == null) {\n            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n            final float defaultSize = getFloatResource(context.getResources(), R.dimen.default_result_shadow_radius);\n            final float size = pref.getFloat(\"result-shadow-radius\", defaultSize);\n            CACHED_RADIUS_RESULT_LIST_SHADOW = size < EPSILON_PX_SIZE ? 0f : size;\n        }\n        return CACHED_RADIUS_RESULT_LIST_SHADOW;\n    }\n\n    public static float getResultListShadowOffsetHorizontal(Context context) {\n        if (CACHED_DX_RESULT_LIST_SHADOW == null) {\n            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n            final float defaultSize = getFloatResource(context.getResources(), R.dimen.default_result_shadow_dx);\n            final float size = pref.getFloat(\"result-shadow-dx\", defaultSize);\n            CACHED_DX_RESULT_LIST_SHADOW = Math.abs(size) < EPSILON_PX_SIZE ? 0f : size;\n        }\n        return CACHED_DX_RESULT_LIST_SHADOW;\n    }\n\n    public static float getResultListShadowOffsetVertical(Context context) {\n        if (CACHED_DY_RESULT_LIST_SHADOW == null) {\n            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n            final float defaultSize = getFloatResource(context.getResources(), R.dimen.default_result_shadow_dy);\n            final float size = pref.getFloat(\"result-shadow-dy\", defaultSize);\n            CACHED_DY_RESULT_LIST_SHADOW = Math.abs(size) < EPSILON_PX_SIZE ? 0f : size;\n        }\n        return CACHED_DY_RESULT_LIST_SHADOW;\n    }\n\n    public static int getTagsMenuIconSize(Context context) {\n        if (CACHED_SIZE_TAGS_MENU_ICON == 0) {\n            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n            final int defaultSize = context.getResources().getInteger(R.integer.default_size_icon);\n            final int size = pref.getInt(\"tags-menu-icon-size\", defaultSize);\n            CACHED_SIZE_TAGS_MENU_ICON = dp2px(context, Math.max(1, size));\n        }\n        return CACHED_SIZE_TAGS_MENU_ICON;\n    }\n\n    public static int getDockMaxIconSize(Context context) {\n        if (CACHED_SIZE_DOCK_ICON == 0) {\n            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n            final int defaultSize = context.getResources().getInteger(R.integer.default_size_icon);\n            final int size = pref.getInt(\"quick-list-icon-size\", defaultSize);\n            CACHED_SIZE_DOCK_ICON = dp2px(context, Math.max(1, size));\n        }\n        return CACHED_SIZE_DOCK_ICON;\n    }\n\n    public static int getStatusBarSize(Context context) {\n        if (CACHED_SIZE_STATUS_BAR == 0) {\n            CACHED_SIZE_STATUS_BAR = CutoutFactory.StatusBarCutout.getStatusBarHeight(context);\n        }\n        return CACHED_SIZE_STATUS_BAR;\n    }\n\n    public static int getPopupCornerRadius(Context context) {\n        if (CACHED_RADIUS_POPUP_CORNER == -1) {\n            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n            final int defaultSize = context.getResources().getInteger(R.integer.default_corner_radius);\n            final int size = pref.getInt(\"popup-corner-radius\", defaultSize);\n            CACHED_RADIUS_POPUP_CORNER = dp2px(context, size);\n        }\n        return CACHED_RADIUS_POPUP_CORNER;\n    }\n\n    public static float getPopupShadowRadius(Context context) {\n        if (CACHED_RADIUS_POPUP_SHADOW == null) {\n            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n            final float defaultSize = getFloatResource(context.getResources(), R.dimen.default_result_shadow_radius);\n            final float size = pref.getFloat(\"popup-shadow-radius\", defaultSize);\n            CACHED_RADIUS_POPUP_SHADOW = size < EPSILON_PX_SIZE ? 0f : size;\n        }\n        return CACHED_RADIUS_POPUP_SHADOW;\n    }\n\n    public static float getPopupShadowOffsetHorizontal(Context context) {\n        if (CACHED_DX_POPUP_SHADOW == null) {\n            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n            final float defaultSize = getFloatResource(context.getResources(), R.dimen.default_result_shadow_dx);\n            final float size = pref.getFloat(\"popup-shadow-dx\", defaultSize);\n            CACHED_DX_POPUP_SHADOW = Math.abs(size) < EPSILON_PX_SIZE ? 0f : size;\n        }\n        return CACHED_DX_POPUP_SHADOW;\n    }\n\n    public static float getPopupShadowOffsetVertical(Context context) {\n        if (CACHED_DY_POPUP_SHADOW == null) {\n            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n            final float defaultSize = getFloatResource(context.getResources(), R.dimen.default_result_shadow_dy);\n            final float size = pref.getFloat(\"popup-shadow-dy\", defaultSize);\n            CACHED_DY_POPUP_SHADOW = Math.abs(size) < EPSILON_PX_SIZE ? 0f : size;\n        }\n        return CACHED_DY_POPUP_SHADOW;\n    }\n\n//    /**\n//     * Example usage: `int size = UISizes.getTextAppearanceTextSize(context, android.R.attr.textAppearanceMedium);`\n//     *\n//     * @param context        we need the context to get the theme\n//     * @param textAppearance text size of what attribute\n//     * @return text size\n//     */\n//    public static int getTextAppearanceTextSize(Context context, @AttrRes int textAppearance) {\n//        int size = 0;\n//        TypedValue appearance = new TypedValue();\n//        if (context.getTheme().resolveAttribute(textAppearance, appearance, true)) {\n//            TypedArray ta = context.obtainStyledAttributes(appearance.resourceId, new int[]{android.R.attr.textSize});\n//            size = ta.getDimensionPixelSize(0, size);\n//            ta.recycle();\n//        }\n//        return (size == 0) ? sp2px(context, 12) : size;\n//    }\n\n    public static int getSearchBarRadius(Context context) {\n        SharedPreferences pref = TBApplication.getApplication(context).preferences();\n        int radius = pref.getInt(\"search-bar-radius\", 0);\n        return dp2px(context, radius);\n    }\n\n    public static int getSearchBarMarginVertical(Context context) {\n        SharedPreferences pref = TBApplication.getApplication(context).preferences();\n        int margin = pref.getInt(\"search-bar-margin-vertical\", 0);\n        return dp2px(context, margin);\n    }\n\n    public static int getSearchBarMarginHorizontal(Context context) {\n        SharedPreferences pref = TBApplication.getApplication(context).preferences();\n        int margin = pref.getInt(\"search-bar-margin-horizontal\", 0);\n        return dp2px(context, margin);\n    }\n\n    public static float getSearchBarShadowRadius(Context context) {\n        SharedPreferences pref = TBApplication.getApplication(context).preferences();\n        final float defaultSize = getFloatResource(context.getResources(), R.dimen.default_result_shadow_radius);\n        final float size = pref.getFloat(\"search-bar-shadow-radius\", defaultSize);\n        return size < EPSILON_PX_SIZE ? 0f : size;\n    }\n\n    public static float getSearchBarShadowOffsetHorizontal(Context context) {\n        SharedPreferences pref = TBApplication.getApplication(context).preferences();\n        final float defaultSize = getFloatResource(context.getResources(), R.dimen.default_result_shadow_dx);\n        final float size = pref.getFloat(\"search-bar-shadow-dx\", defaultSize);\n        return Math.abs(size) < EPSILON_PX_SIZE ? 0f : size;\n    }\n\n    public static float getSearchBarShadowOffsetVertical(Context context) {\n        SharedPreferences pref = TBApplication.getApplication(context).preferences();\n        final float defaultSize = getFloatResource(context.getResources(), R.dimen.default_result_shadow_dy);\n        final float size = pref.getFloat(\"search-bar-shadow-dy\", defaultSize);\n        return Math.abs(size) < EPSILON_PX_SIZE ? 0f : size;\n    }\n\n    public static int getQuickListMarginVertical(Context context) {\n        SharedPreferences pref = TBApplication.getApplication(context).preferences();\n        int margin = pref.getInt(\"quick-list-margin-vertical\", 0);\n        return dp2px(context, margin);\n    }\n\n    public static int getQuickListMarginHorizontal(Context context) {\n        SharedPreferences pref = TBApplication.getApplication(context).preferences();\n        int margin = pref.getInt(\"quick-list-margin-horizontal\", 0);\n        return dp2px(context, margin);\n    }\n\n    private static float getFloatResource(@NonNull Resources resources, @DimenRes int resId) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n            return resources.getFloat(resId);\n        } else {\n            final TypedValue value = new TypedValue();\n            try {\n                resources.getValue(resId, value, true);\n                if (value.type == TypedValue.TYPE_FLOAT) {\n                    return value.getFloat();\n                }\n                throw new Resources.NotFoundException(\"Resource ID #0x\" + Integer.toHexString(resId)\n                    + \" type #0x\" + Integer.toHexString(value.type) + \" is not valid\");\n            } catch (Resources.NotFoundException e) {\n                return 0f;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/utils/UITheme.java",
    "content": "package rocks.tbog.tblauncher.utils;\n\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.widget.TextView;\n\nimport androidx.annotation.AnyRes;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.StyleRes;\nimport androidx.appcompat.view.ContextThemeWrapper;\nimport androidx.preference.PreferenceManager;\n\nimport rocks.tbog.tblauncher.R;\n\npublic class UITheme {\n    @AnyRes\n    public static final int ID_NULL = 0;\n\n    private static final String[] PREF_BACKGROUND = {\n        \"icon-background-argb\",\n        \"notification-bar-argb\",\n        \"search-bar-argb\",\n        \"result-list-argb\",\n        \"result-shadow-color\",\n        \"popup-shadow-color\",\n        \"search-bar-shadow-color\",\n        \"quick-list-argb\",\n        \"popup-background-argb\",\n    };\n\n    private static final String[] PREF_HIGHLIGHT = {\n        \"search-bar-ripple-color\",\n        \"search-bar-cursor-argb\",\n        \"result-ripple-color\",\n        \"result-highlight-color\",\n        \"quick-list-toggle-color\",\n        \"quick-list-ripple-color\",\n        \"popup-border-argb\",\n        \"popup-ripple-color\",\n    };\n\n    private static final String[] PREF_FOREGROUND = {\n        \"search-bar-text-color\",\n        \"search-bar-icon-color\",\n        \"contact-action-color\",\n        \"result-text-color\",\n        \"popup-text-color\",\n        \"popup-title-color\",\n    };\n\n    private static final String[] PREF_FOREGROUND2 = {\n        \"result-text2-color\",\n    };\n\n    private UITheme() {\n    }\n\n    @StyleRes\n    public static int getSettingsTheme(Context context) {\n        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);\n        String theme = sharedPreferences.getString(\"settings-theme\", null);\n        if (theme != null) {\n            switch (theme) {\n                case \"default\":\n                    return R.style.SettingsTheme_Default;\n                case \"white\":\n                    return R.style.SettingsTheme_White;\n                case \"black\":\n                    return R.style.SettingsTheme_Black;\n                case \"dark\":\n                    return R.style.SettingsTheme_DarkBg;\n                case \"DeepBlues\":\n                    return R.style.SettingsTheme_DeepBlues;\n                default:\n                    return R.style.SettingsTheme;\n            }\n        }\n        return ID_NULL;\n    }\n\n    @StyleRes\n    public static int getDialogTheme(Context context) {\n        return getSettingsTheme(context);\n    }\n\n    @NonNull\n    public static Context getDialogThemedContext(@NonNull Context context) {\n        int theme = getDialogTheme(context);\n        if (theme == ID_NULL)\n            return context;\n        return new ContextThemeWrapper(context, theme);\n    }\n\n    public static void applyColorsThemeSimple(Context context) {\n        SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n        final int colorBg = UIColors.getColor(pref, \"primary-color\");\n        final int colorFg = UIColors.getColor(pref, \"secondary-color\");\n        final int colorHl = UIColors.getTextContrastColor(colorBg);\n        final float lumBg = UIColors.luminance(colorBg);\n        final float lumFg = UIColors.luminance(colorFg);\n        final int colorFg2;\n        if (lumBg > .5f && lumFg > .5f)\n            colorFg2 = UIColors.modulateColorLightness(colorFg, .2f);\n        else if (lumBg > .5f)\n            colorFg2 = UIColors.modulateColorLightness(colorFg, 2.f * (1.f - lumFg));\n        else if (lumFg > .5f)\n            colorFg2 = UIColors.modulateColorLightness(colorFg, 1.9f);\n        else\n            colorFg2 = UIColors.getTextContrastColor(colorBg);\n\n        SharedPreferences.Editor editor = pref.edit();\n\n        setColor(editor, PREF_BACKGROUND, colorBg, 0xCD);\n        setColor(editor, PREF_HIGHLIGHT, colorHl, 0xFF);\n        setColor(editor, PREF_FOREGROUND, colorFg, 0xFF);\n        setColor(editor, PREF_FOREGROUND2, colorFg2, 0xFF);\n\n        editor.apply();\n    }\n\n    public static void applyColorsThemeHighlight(Context context) {\n        SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);\n        final int colorBg = UIColors.getColor(pref, \"primary-color\");\n        final int colorHl = UIColors.getColor(pref, \"secondary-color\");\n        final int colorFg = UIColors.getTextContrastColor(colorBg);\n        final float lumFg = UIColors.luminance(colorFg);\n        final int colorFg2 = UIColors.modulateColorLightness(colorFg, 2.f * (1.f - lumFg));\n\n        SharedPreferences.Editor editor = pref.edit();\n\n        setColor(editor, PREF_BACKGROUND, colorBg, 0xCD);\n        setColor(editor, PREF_HIGHLIGHT, colorHl, 0xFF);\n        setColor(editor, PREF_FOREGROUND, colorFg, 0xFF);\n        setColor(editor, PREF_FOREGROUND2, colorFg2, 0xFF);\n\n        editor.apply();\n    }\n\n    private static void setColor(@NonNull SharedPreferences.Editor editor, String[] colorList, int color, int alpha) {\n        for (String prefName : colorList) {\n            final int prefColor;\n            if (prefName.endsWith(\"-argb\")) {\n                prefColor = UIColors.setAlpha(color, alpha);\n            } else {\n                prefColor = UIColors.setAlpha(color, 0);\n            }\n            editor.putInt(prefName, prefColor);\n        }\n    }\n\n    public static void applySearchBarTextShadow(@NonNull TextView textView) {\n        Context ctx = textView.getContext();\n        float radius = UISizes.getSearchBarShadowRadius(ctx);\n        float dx = UISizes.getSearchBarShadowOffsetHorizontal(ctx);\n        float dy = UISizes.getSearchBarShadowOffsetVertical(ctx);\n        int color = UIColors.getSearchShadowColor(ctx);\n\n        if (radius != textView.getShadowRadius()\n            || dx != textView.getShadowDx()\n            || dy != textView.getShadowDy()\n            || color != textView.getShadowColor()) {\n            textView.setShadowLayer(radius, dx, dy, color);\n        }\n    }\n\n    public static void applyPopupTextShadow(@NonNull TextView textView) {\n        Context ctx = textView.getContext();\n        float radius = UISizes.getPopupShadowRadius(ctx);\n        float dx = UISizes.getPopupShadowOffsetHorizontal(ctx);\n        float dy = UISizes.getPopupShadowOffsetVertical(ctx);\n        int color = UIColors.getPopupShadowColor(ctx);\n\n        if (radius != textView.getShadowRadius()\n            || dx != textView.getShadowDx()\n            || dy != textView.getShadowDy()\n            || color != textView.getShadowColor()) {\n            textView.setShadowLayer(radius, dx, dy, color);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/utils/UserHandleCompat.java",
    "content": "package rocks.tbog.tblauncher.utils;\n\nimport android.annotation.TargetApi;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.os.Build;\nimport android.os.Process;\nimport android.os.UserManager;\n\nimport androidx.annotation.NonNull;\n\n\n/**\n * Wrapper class for `android.os.UserHandle` that works with all Android versions\n */\npublic class UserHandleCompat {\n    public static final UserHandleCompat CURRENT_USER = new UserHandleCompat();\n    private final long serial;\n    private final Object handle; // android.os.UserHandle on Android 4.2 and newer\n\n    public UserHandleCompat() {\n        this(0, null);\n    }\n\n    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)\n    public UserHandleCompat(long serial, android.os.UserHandle user) {\n        if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {\n            // OS does not provide any APIs for multi-user support\n            this.serial = 0;\n            this.handle = null;\n        } else if (user != null && Process.myUserHandle().equals(user)) {\n            // For easier processing the current user is also stored as `null`, even\n            // if there is multi-user support\n            this.serial = 0;\n            this.handle = null;\n        } else {\n            // Store the given user handle\n            this.serial = serial;\n            this.handle = user;\n        }\n    }\n\n    public UserHandleCompat(Context context, android.os.UserHandle userHandle) {\n        final UserManager manager = (UserManager) context.getSystemService(Context.USER_SERVICE);\n        assert manager != null;\n        serial = manager.getSerialNumberForUser(userHandle);\n        handle = userHandle;\n    }\n\n    public static UserHandleCompat fromComponentName(Context ctx, String componentName) {\n        if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            UserManager manager = (UserManager) ctx.getSystemService(Context.USER_SERVICE);\n            assert manager != null;\n            long serial = getUserSerial(componentName);\n            android.os.UserHandle handle = manager.getUserForSerialNumber(serial);\n            return new UserHandleCompat(serial, handle);\n        }\n        return UserHandleCompat.CURRENT_USER;\n    }\n\n    @NonNull\n    public static ComponentName unflattenComponentName(@NonNull String name) {\n        return new ComponentName(getPackageName(name), getActivityName(name));\n    }\n\n    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)\n    public android.os.UserHandle getRealHandle() {\n        if (this.handle != null) {\n            return (android.os.UserHandle) this.handle;\n        } else {\n            return Process.myUserHandle();\n        }\n    }\n\n\n    public boolean isCurrentUser() {\n        return (this.handle == null);\n    }\n\n\n    private String addUserSuffixToString(String base, char separator) {\n        if (this.handle == null) {\n            return base;\n        } else {\n            return base + separator + this.serial;\n        }\n    }\n\n    @SuppressWarnings(\"CatchAndPrintStackTrace\")\n    public boolean hasStringUserSuffix(String string, char separator) {\n        long serial = 0;\n\n        int index = string.lastIndexOf((int) separator);\n        if (index > -1) {\n            String serialText = string.substring(index);\n            try {\n                serial = Long.parseLong(serialText);\n            } catch (NumberFormatException e) {\n                e.printStackTrace();\n            }\n        }\n\n        return (serial == this.serial);\n    }\n\n    public String getUserComponentName(ComponentName component) {\n        return getUserComponentName(component.getPackageName(), component.getClassName());\n    }\n\n    public String getUserComponentName(String packageName, String activityName) {\n        return addUserSuffixToString(packageName + \"/\" + activityName, '#');\n    }\n\n    public static String getPackageName(@NonNull String componentName) {\n        int index = componentName.indexOf('/');\n        if (index > 0)\n            return componentName.substring(0, index);\n        return \"\";\n    }\n\n    public static String getActivityName(@NonNull String componentName) {\n        int start = componentName.indexOf('/') + 1;\n        int end = componentName.lastIndexOf('#');\n        if (end == -1)\n            end = componentName.length();\n        if (start > 0 && start < end) {\n            return componentName.substring(start, end);\n        }\n        return \"\";\n    }\n\n    public static long getUserSerial(@NonNull String componentName) {\n        int index = componentName.indexOf('#') + 1;\n        if (index > 0 && index < componentName.length()) {\n            try {\n                return Long.parseLong(componentName.substring(index));\n            } catch (NumberFormatException ignored) {\n            }\n        }\n        return 0;\n    }\n\n    public String getBadgedLabelForUser(Context context, String label) {\n        if (handle == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)\n            return label;\n        return context.getPackageManager().getUserBadgedLabel(label, (android.os.UserHandle) handle).toString();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/utils/Utilities.java",
    "content": "package rocks.tbog.tblauncher.utils;\n\nimport android.annotation.SuppressLint;\nimport android.app.Activity;\nimport android.app.ActivityOptions;\nimport android.content.Context;\nimport android.content.ContextWrapper;\nimport android.content.Intent;\nimport android.content.pm.PackageManager;\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.graphics.Canvas;\nimport android.graphics.ColorFilter;\nimport android.graphics.PorterDuff;\nimport android.graphics.PorterDuffColorFilter;\nimport android.graphics.Rect;\nimport android.graphics.drawable.Animatable;\nimport android.graphics.drawable.BitmapDrawable;\nimport android.graphics.drawable.ColorDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.graphics.drawable.GradientDrawable;\nimport android.graphics.drawable.ShapeDrawable;\nimport android.graphics.drawable.shapes.RectShape;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.text.SpannableString;\nimport android.text.Spanned;\nimport android.util.Base64;\nimport android.util.Log;\nimport android.view.View;\nimport android.view.ViewTreeObserver;\nimport android.widget.ImageView;\nimport android.widget.TextView;\n\nimport androidx.annotation.ColorInt;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.UiThread;\nimport androidx.annotation.WorkerThread;\nimport androidx.appcompat.content.res.AppCompatResources;\nimport androidx.core.content.res.ResourcesCompat;\nimport androidx.lifecycle.Lifecycle;\n\nimport java.io.ByteArrayOutputStream;\nimport java.lang.ref.WeakReference;\nimport java.lang.reflect.Field;\nimport java.lang.reflect.Method;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.SynchronousQueue;\nimport java.util.concurrent.ThreadFactory;\nimport java.util.concurrent.ThreadPoolExecutor;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport rocks.tbog.tblauncher.WorkAsync.AsyncTask;\nimport rocks.tbog.tblauncher.WorkAsync.RunnableTask;\nimport rocks.tbog.tblauncher.WorkAsync.TaskRunner;\nimport rocks.tbog.tblauncher.result.ResultViewHelper;\nimport rocks.tbog.tblauncher.ui.CenteredImageSpan;\nimport rocks.tbog.tblauncher.ui.CutoutFactory;\nimport rocks.tbog.tblauncher.ui.ICutout;\n\npublic class Utilities {\n    public final static ExecutorService EXECUTOR_RUN_ASYNC;\n    private final static int[] ON_SCREEN_POS = new int[2];\n    private final static Rect ON_SCREEN_RECT = new Rect();\n    private static final String TAG = \"TBUtil\";\n\n    private static final int CORE_POOL_SIZE = 1;\n    private static final int MAXIMUM_POOL_SIZE = 10;\n    private static final int KEEP_ALIVE_SECONDS = 3;\n    private static final ThreadFactory sThreadFactory = new ThreadFactory() {\n        private final AtomicInteger mCount = new AtomicInteger(1);\n\n        public Thread newThread(Runnable r) {\n            return new Thread(r, \"UtilAsync #\" + mCount.getAndIncrement());\n        }\n    };\n\n    private static final Class<?> CLASS_GRADIENT_DRAWABLE_GRADIENT_STATE;\n    private static final Field GRADIENT_DRAWABLE_FIELD_GRADIENT_STATE;\n    private static final Field GRADIENT_DRAWABLE_GRADIENT_STATE_FIELD_POSITIONS;\n\n    static {\n        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(\n            CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,\n            new SynchronousQueue<Runnable>(), sThreadFactory);\n        threadPoolExecutor.setRejectedExecutionHandler((runnable, executor) -> {\n            Log.w(TAG, \"task rejected\");\n            if (!executor.isShutdown()) {\n                runnable.run();\n            }\n        });\n        EXECUTOR_RUN_ASYNC = threadPoolExecutor;\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n            CLASS_GRADIENT_DRAWABLE_GRADIENT_STATE = null;\n            GRADIENT_DRAWABLE_FIELD_GRADIENT_STATE = null;\n            GRADIENT_DRAWABLE_GRADIENT_STATE_FIELD_POSITIONS = null;\n        } else {\n            // make mGradientState accessible\n            Field f_mGradientState = null;\n            try {\n                f_mGradientState = GradientDrawable.class.getDeclaredField(\"mGradientState\");\n                f_mGradientState.setAccessible(true);\n            } catch (Throwable t) {\n                Log.w(TAG, \"make mGradientState from \" + GradientDrawable.class.getSimpleName() + \" accessible\", t);\n            }\n            GRADIENT_DRAWABLE_FIELD_GRADIENT_STATE = f_mGradientState;\n\n            // make mGradientState.mPositions accessible\n            Class<?> c_GradientState = null;\n            try {\n                c_GradientState = Class.forName(GradientDrawable.class.getName() + \"$GradientState\");\n            } catch (ClassNotFoundException ignored) {\n            }\n            CLASS_GRADIENT_DRAWABLE_GRADIENT_STATE = c_GradientState;\n            Field f_mPositions = null;\n            if (c_GradientState != null) {\n                try {\n                    f_mPositions = c_GradientState.getDeclaredField(\"mPositions\");\n                    f_mPositions.setAccessible(true);\n                } catch (Throwable t) {\n                    Log.w(TAG, \"make GradientState.mPositions from \" + c_GradientState + \" accessible\", t);\n                }\n            }\n            GRADIENT_DRAWABLE_GRADIENT_STATE_FIELD_POSITIONS = f_mPositions;\n        }\n    }\n\n    // https://stackoverflow.com/questions/3035692/how-to-convert-a-drawable-to-a-bitmap\n    @NonNull\n    public static Bitmap drawableToBitmap(@Nullable Drawable drawable) {\n        Bitmap bitmap;\n\n        if (drawable instanceof BitmapDrawable) {\n            BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;\n            if (bitmapDrawable.getBitmap() != null) {\n                return bitmapDrawable.getBitmap();\n            }\n        }\n\n        if (drawable == null || drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {\n            bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); // Single color bitmap will be created of 1x1 pixel\n        } else {\n            bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);\n        }\n\n        Canvas canvas = new Canvas(bitmap);\n        if (drawable != null) {\n            drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());\n            drawable.draw(canvas);\n        } else {\n            canvas.drawRGB(255, 255, 255);\n        }\n        return bitmap;\n    }\n\n    @Nullable\n    public static byte[] bitmapToByteArray(@NonNull Bitmap bitmap) {\n        ByteArrayOutputStream stream = null;\n        try {\n            stream = new ByteArrayOutputStream(1024);\n            if (!bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream))\n                stream = null;\n            else {\n                stream.flush();\n                stream.close();\n            }\n        } catch (Exception e) {\n            Log.e(TAG, \"Unable to convert bitmap\", e);\n            stream = null;\n        }\n        return stream != null ? stream.toByteArray() : null;\n    }\n\n    /**\n     * Returns a drawable suitable for the all apps view. If the package or the resource do not\n     * exist, it returns null.\n     */\n    public static Drawable createIconDrawable(Intent.ShortcutIconResource iconRes, Context context) {\n        PackageManager packageManager = context.getPackageManager();\n        // the resource\n        try {\n            Resources resources = packageManager.getResourcesForApplication(iconRes.packageName);\n            final int id = resources.getIdentifier(iconRes.resourceName, null, null);\n            return ResourcesCompat.getDrawableForDensity(resources, id, 0, null);\n        } catch (Exception e) {\n            // Icon not found.\n        }\n        return null;\n    }\n\n    /**\n     * Returns a drawable which is of the appropriate size to be displayed as an icon\n     */\n    public static Drawable createIconDrawable(Bitmap icon, Context context) {\n        return new BitmapDrawable(context.getResources(), icon);\n    }\n\n    @NonNull\n    public static ICutout getNotchCutout(Activity activity) {\n        ICutout cutout;\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)\n            cutout = CutoutFactory.getForAndroidPie(activity);\n        else\n            cutout = CutoutFactory.getByManufacturer(activity, Build.MANUFACTURER);\n\n        return cutout == null ? CutoutFactory.getNoCutout() : cutout;\n    }\n\n    public static void setIconAsync(@NonNull ImageView image, @NonNull GetDrawable callback) {\n        TaskRunner.executeOnExecutor(ResultViewHelper.EXECUTOR_LOAD_ICON,\n            new Utilities.AsyncSetDrawable(image) {\n                @Override\n                protected Drawable getDrawable(Context context) {\n                    return callback.getDrawable(context);\n                }\n            }\n        );\n    }\n\n    public static void setViewAsync(@NonNull View image, @NonNull GetDrawable cbGet, @NonNull SetDrawable cbSet) {\n        TaskRunner.executeOnExecutor(ResultViewHelper.EXECUTOR_LOAD_ICON,\n            new Utilities.AsyncViewSet(image) {\n                @Override\n                protected Drawable getDrawable(Context context) {\n                    return cbGet.getDrawable(context);\n                }\n\n                @Override\n                protected void setDrawable(@NonNull View view, @NonNull Drawable drawable) {\n                    cbSet.setDrawable(view, drawable);\n                }\n            }\n        );\n    }\n\n    public static void setIntentSourceBounds(@NonNull Intent intent, @Nullable View v) {\n        if (v == null)\n            return;\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {\n            v.getLocationOnScreen(ON_SCREEN_POS);\n            ON_SCREEN_RECT.set(ON_SCREEN_POS[0], ON_SCREEN_POS[1], ON_SCREEN_POS[0] + v.getWidth(), ON_SCREEN_POS[1] + v.getHeight());\n            intent.setSourceBounds(ON_SCREEN_RECT);\n        }\n    }\n\n    @Nullable\n    public static Bundle makeStartActivityOptions(@Nullable View source) {\n        if (source == null)\n            return null;\n        Bundle opts = null;\n        // If we got an icon, we create options to get a nice animation\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n            opts = ActivityOptions.makeClipRevealAnimation(source, 0, 0, source.getMeasuredWidth(), source.getMeasuredHeight()).toBundle();\n        }\n        if (opts == null) {\n            opts = ActivityOptions.makeScaleUpAnimation(source, 0, 0, source.getMeasuredWidth(), source.getMeasuredHeight()).toBundle();\n        }\n        return opts;\n    }\n\n    @Nullable\n    public static Rect getOnScreenRect(@Nullable View v) {\n        if (v == null)\n            return null;\n        v.getLocationOnScreen(ON_SCREEN_POS);\n        ON_SCREEN_RECT.set(ON_SCREEN_POS[0], ON_SCREEN_POS[1], ON_SCREEN_POS[0] + v.getWidth(), ON_SCREEN_POS[1] + v.getHeight());\n        return ON_SCREEN_RECT;\n    }\n\n    public static boolean checkFlag(int flags, int flagToCheck) {\n        return (flags & flagToCheck) == flagToCheck;\n    }\n\n    public static boolean checkAnyFlag(int flags, int anyFlag) {\n        return (flags & anyFlag) != 0;\n    }\n\n    /**\n     * Return a valid activity or null given a view\n     *\n     * @param view any view of an activity\n     * @return an activity or null\n     */\n    @Nullable\n    public static Activity getActivity(@Nullable View view) {\n        return view != null ? getActivity(view.getContext()) : null;\n    }\n\n    /**\n     * Return a valid activity or null given a context\n     *\n     * @param ctx context\n     * @return an activity or null\n     */\n    @Nullable\n    public static Activity getActivity(@Nullable Context ctx) {\n        while (ctx instanceof ContextWrapper) {\n            if (ctx instanceof Activity) {\n                Activity act = (Activity) ctx;\n                if (act.isFinishing() || act.isDestroyed())\n                    return null;\n                return act;\n            }\n            ctx = ((ContextWrapper) ctx).getBaseContext();\n        }\n        return null;\n    }\n\n//    public static void positionToast(@NonNull Toast toast, @NonNull View anchor, int offsetX, int offsetY) {\n//        // toasts are positioned relatively to decor view, views relatively to their parents, we have to gather additional data to have a common coordinate system\n//        Rect rect = new Rect();\n//        anchor.getWindowVisibleDisplayFrame(rect);\n//\n//        // covert anchor view absolute position to a position which is relative to decor view\n//        int[] viewLocation = new int[2];\n//        anchor.getLocationOnScreen(viewLocation);\n//        int viewLeft = viewLocation[0] - rect.left;\n//        int viewTop = viewLocation[1] - rect.top;\n//\n//        // measure toast to center it relatively to the anchor view\n//        DisplayMetrics metrics = new DisplayMetrics();\n//        anchor.getDisplay().getMetrics(metrics);\n//        int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(metrics.widthPixels, View.MeasureSpec.UNSPECIFIED);\n//        int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(metrics.heightPixels, View.MeasureSpec.UNSPECIFIED);\n//        toast.getView().measure(widthMeasureSpec, heightMeasureSpec);\n//        int toastWidth = toast.getView().getMeasuredWidth();\n//\n//        // compute toast offsets\n//        int toastX = viewLeft + (anchor.getWidth() - toastWidth) / 2 + offsetX;\n//        int toastY = viewTop + anchor.getHeight() + offsetY;\n//\n//        toast.setGravity(Gravity.START | Gravity.TOP, toastX, toastY);\n//    }\n\n    public static RunnableTask runAsync(@NonNull Lifecycle lifecycle, @NonNull TaskRunner.AsyncRunnable background, @NonNull TaskRunner.AsyncRunnable after) {\n        RunnableTask task = TaskRunner.newTask(lifecycle, background, after);\n        TaskRunner.runOnUiThread(() -> EXECUTOR_RUN_ASYNC.execute(task));\n        return task;\n    }\n\n    public static RunnableTask runAsync(@NonNull TaskRunner.AsyncRunnable background, @Nullable TaskRunner.AsyncRunnable after) {\n        RunnableTask task = TaskRunner.newTask(background, after);\n        TaskRunner.runOnUiThread(() -> EXECUTOR_RUN_ASYNC.execute(task));\n        return task;\n    }\n\n    public static void runAsync(@NonNull Runnable background) {\n        EXECUTOR_RUN_ASYNC.execute(background);\n    }\n\n    public static <I, O> void executeAsync(@NonNull AsyncTask<I, O> task) {\n        TaskRunner.executeOnExecutor(EXECUTOR_RUN_ASYNC, task);\n    }\n\n    public static void setColorFilterMultiply(@NonNull ImageView imageView, int color) {\n        setColorFilterMultiply(imageView.getDrawable(), color);\n    }\n\n    public static void setColorFilterMultiply(@Nullable Drawable drawable, int color) {\n        if (drawable == null)\n            return;\n        ColorFilter cf = new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY);\n        drawable.setColorFilter(cf);\n    }\n\n    @SuppressLint(\"ObsoleteSdkInt\")\n    public static void expandNotificationsPanel(Activity activity) {\n        @SuppressLint(\"WrongConstant\")\n        Object statusBarService = activity.getSystemService(\"statusbar\");\n        if (statusBarService != null) {\n            try {\n                Class<?> statusbarManager = Class.forName(\"android.app.StatusBarManager\");\n                Method expand;\n                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {\n                    expand = statusbarManager.getMethod(\"expandNotificationsPanel\");\n                } else {\n                    expand = statusbarManager.getMethod(\"expand\");\n                }\n                expand.setAccessible(true);\n                expand.invoke(statusBarService);\n            } catch (Exception ignored) {\n            }\n        }\n    }\n\n    @SuppressLint(\"ObsoleteSdkInt\")\n    public static void expandSettingsPanel(Activity activity) {\n        boolean expandCalled = false;\n        @SuppressLint(\"WrongConstant\")\n        Object statusBarService = activity.getSystemService(\"statusbar\");\n        if (statusBarService != null) {\n            try {\n                Class<?> statusbarManager = Class.forName(\"android.app.StatusBarManager\");\n                Method expand;\n                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {\n                    expand = statusbarManager.getMethod(\"expandSettingsPanel\");\n                    expand.setAccessible(true);\n                    expand.invoke(statusBarService);\n                    expandCalled = true;\n                }\n            } catch (Exception ignored) {\n            }\n        }\n        if (!expandCalled) {\n            Intent settings = new Intent(android.provider.Settings.ACTION_SETTINGS);\n            activity.startActivity(settings);\n        }\n    }\n\n    public static void setVerticalScrollbarThumbDrawable(View scrollView, Drawable drawable) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n            scrollView.setVerticalScrollbarThumbDrawable(drawable);\n        } else {\n            try {\n                //noinspection JavaReflectionMemberAccess\n                Field mScrollCacheField = View.class.getDeclaredField(\"mScrollCache\");\n                mScrollCacheField.setAccessible(true);\n                Object mScrollCache = mScrollCacheField.get(scrollView);\n                Field scrollBarField = mScrollCache.getClass().getDeclaredField(\"scrollBar\");\n                scrollBarField.setAccessible(true);\n                Object scrollBar = scrollBarField.get(mScrollCache);\n                Method method = scrollBar.getClass().getDeclaredMethod(\"setVerticalThumbDrawable\", Drawable.class);\n                method.setAccessible(true);\n                method.invoke(scrollBar, drawable);\n            } catch (Exception ignored) {\n            }\n        }\n    }\n\n    public static boolean classContainsDeclaredField(@NonNull Class<?> objectClass, @NonNull String fieldName) {\n        for (Field field : objectClass.getDeclaredFields()) {\n            if (field.getName().equals(fieldName)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    public static void setTextCursorDrawable(@NonNull TextView editText, Drawable drawable) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n            editText.setTextCursorDrawable(drawable);\n        } else {\n            boolean setResToNull = false;\n            if (classContainsDeclaredField(TextView.class, \"mCursorDrawable\")) {\n                try {\n                    @SuppressLint(\"BlockedPrivateApi\")\n                    Field fmCursorDrawable = TextView.class.getDeclaredField(\"mCursorDrawable\");\n                    fmCursorDrawable.setAccessible(true);\n                    fmCursorDrawable.set(editText, drawable);\n                    setResToNull = true;\n                } catch (Throwable t) {\n                    Log.w(TAG, \"set TextView mCursorDrawable\", t);\n                }\n            }\n            if (classContainsDeclaredField(TextView.class, \"mCursorDrawableRes\")) {\n                try {\n                    Field fmCursorDrawableRes = TextView.class.getDeclaredField(\"mCursorDrawableRes\");\n                    fmCursorDrawableRes.setAccessible(true);\n                    if (setResToNull)\n                        fmCursorDrawableRes.setInt(editText, 0);\n                    else if (fmCursorDrawableRes.getInt(editText) == 0) {\n                        // this resource will not get used, we just need something != 0\n                        int res = android.R.drawable.divider_horizontal_dark;\n                        fmCursorDrawableRes.setInt(editText, res);\n                    }\n                } catch (Throwable t) {\n                    Log.w(TAG, \"set TextView mCursorDrawableRes\", t);\n                }\n            }\n            //https://github.com/aosp-mirror/platform_frameworks_base/blob/c46c4a6765196bcabf3ea89771a1f9067b22baad/core/java/android/widget/TextView.java#L4587\n            if (classContainsDeclaredField(TextView.class, \"mEditor\")) {\n                Object mEditor = null;\n                try {\n                    Field fmEditor = TextView.class.getDeclaredField(\"mEditor\");\n                    fmEditor.setAccessible(true);\n                    mEditor = fmEditor.get(editText);\n                } catch (Throwable t) {\n                    Log.w(TAG, \"get TextView mEditor\", t);\n                }\n                if (mEditor == null)\n                    return;\n                if (classContainsDeclaredField(mEditor.getClass(), \"mCursorDrawable\")) {\n                    try {\n                        Field fmCursorDrawable = mEditor.getClass().getDeclaredField(\"mCursorDrawable\");\n                        fmCursorDrawable.setAccessible(true);\n                        fmCursorDrawable.set(mEditor, new Drawable[]{drawable, drawable});\n                    } catch (Throwable t) {\n                        Log.w(TAG, \"set Editor mCursorDrawable[2]\", t);\n                    }\n                }\n            }\n        }\n    }\n\n    private static Drawable getDrawableFromTextViewEditor(@NonNull TextView view, @NonNull String editorField) {\n        Drawable drawable = null;\n        Object editor = null;\n        try {\n            Field f_editor = TextView.class.getDeclaredField(\"mEditor\");\n            f_editor.setAccessible(true);\n            editor = f_editor.get(view);\n        } catch (Throwable t) {\n            Log.w(TAG, \"get Editor from \" + view.getClass(), t);\n        }\n        if (editor != null && classContainsDeclaredField(editor.getClass(), editorField)) {\n            try {\n                Field f_handle = editor.getClass().getDeclaredField(editorField);\n                f_handle.setAccessible(true);\n                if (f_handle.getType().isArray()) {\n                    Object drawables = f_handle.get(editor);\n                    drawable = ((Drawable[]) drawables)[0];\n                } else {\n                    drawable = (Drawable) f_handle.get(editor);\n                }\n            } catch (Throwable t) {\n                Log.w(TAG, \"get `\" + editorField + \"` from \" + editor.getClass(), t);\n            }\n        }\n        return drawable;\n    }\n\n    @Nullable\n    private static Drawable getDrawableFromTextView(@NonNull TextView view, @NonNull String fieldName, @NonNull String editorField) {\n        Context ctx = view.getContext();\n        String resFieldName = fieldName + \"Res\";\n        if (classContainsDeclaredField(TextView.class, resFieldName)) {\n            try {\n                Field f_res = TextView.class.getDeclaredField(resFieldName);\n                f_res.setAccessible(true);\n                int res = f_res.getInt(view);\n                if (res != Resources.ID_NULL) {\n                    Drawable drawable = AppCompatResources.getDrawable(ctx, res);\n                    if (drawable != null)\n                        return drawable;\n                }\n            } catch (Throwable t) {\n                Log.w(TAG, \"get `\" + resFieldName + \"` from \" + TextView.class, t);\n            }\n        }\n        if (classContainsDeclaredField(TextView.class, fieldName)) {\n            try {\n                Field f_drawable = TextView.class.getDeclaredField(fieldName);\n                f_drawable.setAccessible(true);\n                Drawable drawable = (Drawable) f_drawable.get(view);\n                if (drawable != null)\n                    return drawable;\n            } catch (Throwable t) {\n                Log.w(TAG, \"get `\" + fieldName + \"` from \" + TextView.class, t);\n            }\n        }\n\n        return getDrawableFromTextViewEditor(view, editorField);\n    }\n\n    public static void setTextCursorColor(@NonNull TextView editText, @ColorInt int color) {\n        Context ctx = editText.getContext();\n        Drawable drawable = getDrawableFromTextView(editText, \"mCursorDrawable\", \"mCursorDrawable\");\n        if (drawable == null) {\n            drawable = new ShapeDrawable(new RectShape());\n            ((ShapeDrawable) drawable).setIntrinsicWidth(UISizes.dp2px(ctx, 2));\n            ((ShapeDrawable) drawable).getPaint().setColor(color);\n        }\n        drawable.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN));\n        setTextCursorDrawable(editText, drawable);\n    }\n\n    private static void setTextSelectHandle(@NonNull TextView editText, @NonNull String fieldName, @NonNull String editorField, Drawable drawable) {\n//        String fieldNameRes = fieldName + \"Res\";\n//        if (classContainsDeclaredField(TextView.class, fieldNameRes)) {\n//            try {\n//                Field f_handleRes = TextView.class.getDeclaredField(fieldNameRes);\n//                f_handleRes.setAccessible(true);\n//                f_handleRes.setInt(editText, 0);\n//            } catch (Throwable t) {\n//                Log.w(TAG, \"set `\" + fieldNameRes + \"` from \" + editText.getClass(), t);\n//            }\n//        }\n        if (classContainsDeclaredField(TextView.class, fieldName)) {\n            try {\n                Field f_handle = TextView.class.getDeclaredField(fieldName);\n                f_handle.setAccessible(true);\n                f_handle.set(editText, drawable);\n            } catch (Throwable t) {\n                Log.w(TAG, \"set `\" + fieldName + \"` from \" + editText.getClass(), t);\n            }\n        }\n        if (!classContainsDeclaredField(TextView.class, \"mEditor\"))\n            return;\n        Object editor = null;\n        try {\n            Field f_editor = TextView.class.getDeclaredField(\"mEditor\");\n            f_editor.setAccessible(true);\n            editor = f_editor.get(editText);\n        } catch (Throwable t) {\n            Log.w(TAG, \"get Editor from \" + editText.getClass(), t);\n        }\n        if (editor == null)\n            return;\n        try {\n            Field f_handle = editor.getClass().getDeclaredField(editorField);\n            f_handle.setAccessible(true);\n            f_handle.set(editor, drawable);\n        } catch (Throwable t) {\n            Log.w(TAG, \"set `\" + editorField + \"` from \" + editor.getClass(), t);\n        }\n    }\n\n    public static void setTextSelectHandle(@NonNull TextView editText, Drawable left, Drawable right, Drawable center) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n            editText.setTextSelectHandle(center);\n            editText.setTextSelectHandleLeft(left);\n            editText.setTextSelectHandleRight(right);\n        } else {\n            setTextSelectHandle(editText, \"mTextSelectHandleLeft\", \"mSelectHandleLeft\", left);\n            setTextSelectHandle(editText, \"mTextSelectHandleRight\", \"mSelectHandleRight\", right);\n            setTextSelectHandle(editText, \"mTextSelectHandle\", \"mSelectHandleCenter\", center);\n        }\n    }\n\n    public static void setTextSelectHandleColor(@NonNull TextView editText, @ColorInt int color) {\n        Drawable drawableLeft;\n        Drawable drawableRight;\n        Drawable drawableCenter;\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n            drawableLeft = editText.getTextSelectHandleLeft();\n            drawableRight = editText.getTextSelectHandleRight();\n            drawableCenter = editText.getTextSelectHandle();\n        } else {\n            drawableLeft = getDrawableFromTextView(editText, \"mTextSelectHandleLeft\", \"mSelectHandleLeft\");\n            drawableRight = getDrawableFromTextView(editText, \"mTextSelectHandleRight\", \"mSelectHandleRight\");\n            drawableCenter = getDrawableFromTextView(editText, \"mTextSelectHandle\", \"mSelectHandleCenter\");\n        }\n        if (drawableLeft == null)\n            drawableLeft = new ColorDrawable(color);\n        if (drawableRight == null)\n            drawableRight = new ColorDrawable(color);\n        if (drawableCenter == null)\n            drawableCenter = new ColorDrawable(color);\n        PorterDuffColorFilter porterDuffColorFilter = new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN);\n        drawableLeft.setColorFilter(porterDuffColorFilter);\n        drawableRight.setColorFilter(porterDuffColorFilter);\n        drawableCenter.setColorFilter(porterDuffColorFilter);\n        setTextSelectHandle(editText, drawableLeft, drawableRight, drawableCenter);\n    }\n\n    public static boolean setGradientDrawableColors(@NonNull GradientDrawable drawable, @Nullable @ColorInt int[] colors, @Nullable float[] offsets) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n            drawable.setColors(colors, offsets);\n            return true;\n        } else {\n            drawable.setColors(colors);\n            Object mGradientState = null;\n            try {\n                mGradientState = GRADIENT_DRAWABLE_FIELD_GRADIENT_STATE.get(drawable);\n            } catch (IllegalAccessException ignored) {\n            }\n            final Class<?> c_GradientState = CLASS_GRADIENT_DRAWABLE_GRADIENT_STATE;\n            if (c_GradientState != null && c_GradientState.isInstance(mGradientState)) {\n                try {\n                    GRADIENT_DRAWABLE_GRADIENT_STATE_FIELD_POSITIONS.set(mGradientState, offsets);\n                    return true;\n                } catch (IllegalAccessException ignored) {\n                }\n            }\n        }\n        return false;\n    }\n\n    public static int getNextCodePointIndex(CharSequence s, int startPosition) {\n        int codePoint = Character.codePointAt(s, startPosition);\n        int next = startPosition + Character.charCount(codePoint);\n\n        if (next < s.length()) {\n            // skip next character if it's not helpful\n            codePoint = Character.codePointAt(s, next);\n            boolean skip = codePoint == 0x200D;\n            skip = skip || Character.UnicodeBlock.VARIATION_SELECTORS.equals(Character.UnicodeBlock.of(codePoint));\n            if (skip)\n                return getNextCodePointIndex(s, next);\n        }\n\n        return next;\n    }\n\n    public static int codePointsLength(@Nullable CharSequence s) {\n        final int length = s != null ? s.length() : 0;\n        int n = 0;\n        for (int i = 0; i < length; ) {\n            int codePoint = Character.codePointAt(s, i);\n            i += Character.charCount(codePoint);\n            // skip this if it's ZERO WIDTH JOINER\n            if (codePoint == 0x200D)\n                continue;\n            if (Character.UnicodeBlock.VARIATION_SELECTORS.equals(Character.UnicodeBlock.of(codePoint)))\n                continue;\n            ++n;\n        }\n        return n;\n    }\n\n    @Nullable\n    public static byte[] decodeIcon(@Nullable String text, @Nullable String encoding) {\n        if (text != null) {\n            text = text.trim();\n            int size = text.length();\n            if (encoding == null || \"base64\".equals(encoding)) {\n                byte[] base64enc = new byte[size];\n                for (int i = 0; i < size; i += 1) {\n                    char c = text.charAt(i);\n                    base64enc[i] = (byte) (c & 0xff);\n                }\n                return Base64.decode(base64enc, Base64.NO_WRAP);\n            }\n        }\n        return null;\n    }\n\n    public static String getSystemProperty(String property, String defaultValue) {\n        try {\n            @SuppressWarnings(\"rawtypes\") @SuppressLint(\"PrivateApi\")\n            Class clazz = Class.forName(\"android.os.SystemProperties\");\n            @SuppressWarnings(\"unchecked\")\n            Method getter = clazz.getDeclaredMethod(\"get\", String.class);\n            String value = (String) getter.invoke(null, property);\n            if (value != null && !value.isEmpty()) {\n                return value;\n            }\n        } catch (Exception ignored) {\n        }\n        return defaultValue;\n    }\n\n    /**\n     * @param resName\n     * @param c\n     * @return\n     */\n    public static int getResId(String resName, Class<?> c) {\n        try {\n            Field idField = c.getDeclaredField(resName);\n            return idField.getInt(idField);\n        } catch (Exception e) {\n            Log.w(TAG, \"getResId( \" + resName + \" )\", e);\n            return -1;\n        }\n    }\n\n    public static void startAnimatable(ImageView image) {\n        Drawable drawable = image.getDrawable();\n        if (drawable instanceof Animatable)\n            ((Animatable) drawable).start();\n    }\n\n    public static void startAnimatable(TextView textView) {\n        final Runnable startAnimation = () -> {\n            Drawable[] drawables = textView.getCompoundDrawables();\n            for (Drawable drawable : drawables)\n                if (drawable instanceof Animatable)\n                    ((Animatable) drawable).start();\n        };\n        if (textView.isLaidOut()) {\n            startAnimation.run();\n        } else {\n            textView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {\n                @Override\n                public void onGlobalLayout() {\n                    textView.getViewTreeObserver().removeOnGlobalLayoutListener(this);\n                    startAnimation.run();\n                }\n            });\n        }\n    }\n\n    /**\n     * @param text            text we add the icon to\n     * @param icon            drawable to use as an icon\n     * @param layoutDirection will be either View.LAYOUT_DIRECTION_LTR or View.LAYOUT_DIRECTION_RTL.\n     * @return SpannableString with an ImageSpan at the beginning\n     */\n    public static SpannableString addDrawableBeforeString(@NonNull String text, @NonNull Drawable icon, int layoutDirection) {\n        final SpannableString name;\n        final int pos;\n        if (layoutDirection == View.LAYOUT_DIRECTION_RTL) {\n            name = new SpannableString(text + \" #\");\n            pos = name.length() - 1;\n        } else {\n            name = new SpannableString(\"# \" + text);\n            pos = 0;\n        }\n        name.setSpan(new CenteredImageSpan(icon), pos, pos + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);\n        return name;\n    }\n\n    public static SpannableString addDrawableAfterString(@NonNull String text, @NonNull Drawable icon, int layoutDirection) {\n        int dir = layoutDirection != View.LAYOUT_DIRECTION_RTL ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR;\n        return addDrawableBeforeString(text, icon, dir);\n    }\n\n    public static String appendString(@NonNull String textA, @Nullable String glue, @NonNull String textB, int layoutDirection) {\n        int expectedLength = textA.length() + textB.length();\n        if (glue != null)\n            expectedLength += glue.length();\n        StringBuilder builder = new StringBuilder(expectedLength);\n        builder.append(layoutDirection == View.LAYOUT_DIRECTION_RTL ? textB : textA);\n        if (glue != null)\n            builder.append(glue);\n        builder.append(layoutDirection == View.LAYOUT_DIRECTION_RTL ? textA : textB);\n        return builder.toString();\n    }\n\n    public interface GetDrawable {\n        @Nullable\n        Drawable getDrawable(@NonNull Context context);\n    }\n\n    public interface SetDrawable {\n        void setDrawable(@NonNull View view, @NonNull Drawable drawable);\n    }\n\n    public static abstract class AsyncViewSet extends AsyncTask<Void, Drawable> {\n        protected final WeakReference<View> weakView;\n\n        protected AsyncViewSet(View view) {\n            super();\n            this.weakView = new WeakReference<>(view);\n            if (view.getTag() instanceof AsyncViewSet)\n                ((AsyncViewSet) view.getTag()).cancel(true);\n            view.setTag(this);\n        }\n\n        @Override\n        protected Drawable doInBackground(Void param) {\n            View image = weakView.get();\n            Activity act = Utilities.getActivity(image);\n            if (isCancelled() || act == null || image.getTag() != this) {\n                weakView.clear();\n                return null;\n            }\n\n            Context ctx = image.getContext();\n            return getDrawable(ctx);\n        }\n\n        @WorkerThread\n        protected abstract Drawable getDrawable(Context context);\n\n        @UiThread\n        protected abstract void setDrawable(@NonNull View view, @NonNull Drawable drawable);\n\n        @Override\n        protected void onPostExecute(Drawable drawable) {\n            View view = weakView.get();\n            if (view == null || view.getTag() != this)\n                return;\n            Activity act = Utilities.getActivity(view);\n            if (act == null || drawable == null) {\n                weakView.clear();\n                return;\n            }\n            setDrawable(view, drawable);\n            view.setTag(null);\n        }\n\n        public void execute() {\n            TaskRunner.executeOnExecutor(ResultViewHelper.EXECUTOR_LOAD_ICON, this);\n        }\n    }\n\n    public static abstract class AsyncSetDrawable extends AsyncViewSet {\n        protected AsyncSetDrawable(@NonNull ImageView image) {\n            super(image);\n            image.setImageResource(android.R.color.transparent);\n        }\n\n        @Override\n        protected void setDrawable(@NonNull View image, @NonNull Drawable drawable) {\n            ((ImageView) image).setImageDrawable(drawable);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/utils/ViewHolderAdapter.java",
    "content": "package rocks.tbog.tblauncher.utils;\n\nimport android.util.Log;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.BaseAdapter;\n\nimport androidx.annotation.LayoutRes;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport java.util.Collection;\n\nimport rocks.tbog.tblauncher.BuildConfig;\nimport rocks.tbog.tblauncher.WorkAsync.AsyncTask;\n\n/**\n * Adapter class that implements the View holder pattern.\n * The ViewHolder is held as a tag in the list item view.\n *\n * @param <T>  Type of data to send to the ViewHolder\n * @param <VH> ViewHolder class\n */\npublic abstract class ViewHolderAdapter<T, VH extends ViewHolderAdapter.ViewHolder<T>> extends BaseAdapter {\n    @NonNull\n    final Class<? extends VH> mViewHolderClass;\n    @LayoutRes\n    final int mListItemLayout;\n\n    protected ViewHolderAdapter(@NonNull Class<? extends VH> viewHolderClass, @LayoutRes int listItemLayout) {\n        mViewHolderClass = viewHolderClass;\n        mListItemLayout = listItemLayout;\n    }\n\n    @LayoutRes\n    protected int getItemViewTypeLayout(int viewType) {\n        return mListItemLayout;\n    }\n\n    @Override\n    public abstract T getItem(int position);\n\n    @Override\n    public long getItemId(int position) {\n        return getItem(position).hashCode();\n    }\n\n    @Override\n    public boolean hasStableIds() {\n        return true;\n    }\n\n    @Nullable\n    protected VH getNewViewHolder(View view) {\n        VH holder = null;\n        try {\n            holder = mViewHolderClass.getDeclaredConstructor(View.class).newInstance(view);\n        } catch (Exception e) {\n            Log.e(\"VHA\", \"ViewHolder can't be instantiated (make sure class and constructor are public)\", e);\n        }\n        return holder;\n    }\n\n    @Override\n    public View getView(int position, View convertView, ViewGroup parent) {\n        final View view;\n        if (convertView == null) {\n            int viewType = getItemViewType(position);\n            if (BuildConfig.DEBUG) {\n                int viewTypeCount = getViewTypeCount();\n                if (viewType >= viewTypeCount)\n                    throw new IllegalStateException(\"ViewType \" + viewType + \" >= ViewTypeCount \" + viewTypeCount);\n            }\n            @LayoutRes\n            int itemLayout = getItemViewTypeLayout(viewType);\n            view = LayoutInflater.from(parent.getContext()).inflate(itemLayout, parent, false);\n        } else {\n            view = convertView;\n        }\n\n        Object tag = view.getTag();\n        VH holder = mViewHolderClass.isInstance(tag) ? mViewHolderClass.cast(tag) : getNewViewHolder(view);\n        if (holder != null) {\n            T content = getItem(position);\n            holder.setContent(content, position, this);\n        }\n        return view;\n\n    }\n\n    public static abstract class ViewHolder<T> {\n        protected ViewHolder(View view) {\n            view.setTag(this);\n        }\n\n        protected abstract void setContent(T content, int position, @NonNull ViewHolderAdapter<T, ? extends ViewHolder<T>> adapter);\n    }\n\n    public static abstract class LoadAsyncData<T, A extends ViewHolderAdapter<T, ? extends ViewHolder<T>>> extends AsyncTask<Void, Collection<T>> {\n        protected final A adapter;\n        private final LoadInBackground<T> task;\n\n        public interface LoadInBackground<T> {\n            @Nullable\n            Collection<T> loadInBackground();\n        }\n\n        public LoadAsyncData(@NonNull A adapter, @NonNull LoadInBackground<T> loadInBackground) {\n            super();\n            this.adapter = adapter;\n            task = loadInBackground;\n        }\n\n        @Override\n        protected Collection<T> doInBackground(Void param) {\n            return task.loadInBackground();\n        }\n\n        @Override\n        protected void onPostExecute(Collection<T> data) {\n            if (data == null)\n                return;\n            //adapter.addAll(data);\n            onDataLoadFinished(adapter, data);\n        }\n\n        protected abstract void onDataLoadFinished(@NonNull A adapter, @NonNull Collection<T> data);\n\n        public void execute() {\n            Utilities.executeAsync(this);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/utils/ViewHolderListAdapter.java",
    "content": "package rocks.tbog.tblauncher.utils;\n\nimport android.util.Log;\n\nimport androidx.annotation.LayoutRes;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport java.util.Collection;\nimport java.util.List;\n\npublic abstract class ViewHolderListAdapter<T, VH extends ViewHolderAdapter.ViewHolder<T>> extends ViewHolderAdapter<T, VH> {\n    @NonNull\n    protected final List<T> mList;\n\n    protected ViewHolderListAdapter(@NonNull Class<? extends VH> viewHolderClass, int listItemLayout, @NonNull List<T> list) {\n        super(viewHolderClass, listItemLayout);\n        mList = list;\n    }\n\n    @LayoutRes\n    protected int getItemViewTypeLayout(int viewType) {\n        return mListItemLayout;\n    }\n\n    @Override\n    public T getItem(int position) {\n        return mList.get(position);\n    }\n\n    @Override\n    public int getCount() {\n        return mList.size();\n    }\n\n    public void addItems(Collection<? extends T> items) {\n        mList.addAll(items);\n        notifyDataSetChanged();\n    }\n\n    public void addItem(T item) {\n        mList.add(item);\n        notifyDataSetChanged();\n    }\n\n    @Nullable\n    public <L extends LoadAsyncList<T, ?, ?>> L newLoadAsyncList(@NonNull Class<L> loadAsyncClass, @NonNull LoadAsyncData.LoadInBackground<T> loadInBackground) {\n        L loadAsync = null;\n        try {\n            loadAsync = loadAsyncClass.getDeclaredConstructor(this.getClass(), LoadAsyncData.LoadInBackground.class).newInstance(this, loadInBackground);\n        } catch (ReflectiveOperationException e) {\n            Log.e(\"VHLA\", \"LoadAsync can't be instantiated (make sure class and constructor are public)\", e);\n        }\n        return loadAsync;\n    }\n\n    @NonNull\n    public LoadAsyncList<T, ?, ?> newLoadAsyncList(@NonNull LoadAsyncData.LoadInBackground<T> loadInBackground) {\n        return new LoadAsyncList<>(this, loadInBackground);\n    }\n\n    public static class LoadAsyncList<T, VH extends ViewHolderAdapter.ViewHolder<T>, A extends ViewHolderListAdapter<T, VH>> extends LoadAsyncData<T, A> {\n\n        public LoadAsyncList(@NonNull A adapter, @NonNull LoadInBackground<T> loadInBackground) {\n            super(adapter, loadInBackground);\n        }\n\n        @Override\n        protected void onDataLoadFinished(@NonNull A adapter, @NonNull Collection<T> data) {\n            if (!isCancelled())\n                adapter.addItems(data);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/widgets/ItemTitle.java",
    "content": "package rocks.tbog.tblauncher.widgets;\n\nimport androidx.annotation.NonNull;\n\nclass ItemTitle implements MenuItem {\n    @NonNull\n    private final String name;\n\n    ItemTitle(@NonNull String string) {\n        this.name = string;\n    }\n\n    @NonNull\n    @Override\n    public String getName() {\n        return name;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/widgets/ItemWidget.java",
    "content": "package rocks.tbog.tblauncher.widgets;\n\nimport android.content.Context;\nimport android.content.pm.ApplicationInfo;\nimport android.util.Log;\nimport android.view.View;\nimport android.widget.ImageView;\nimport android.widget.TextView;\n\nimport androidx.annotation.NonNull;\nimport androidx.core.text.HtmlCompat;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.utils.UISizes;\nimport rocks.tbog.tblauncher.utils.Utilities;\nimport rocks.tbog.tblauncher.utils.ViewHolderAdapter;\n\nclass ItemWidget implements MenuItem {\n    private static final String TAG = ItemWidget.class.getSimpleName();\n    protected final WidgetInfo info;\n\n    public ItemWidget(@NonNull WidgetInfo info) {\n        this.info = info;\n    }\n\n    @NonNull\n    @Override\n    public String getName() {\n        return info.widgetName;\n    }\n\n    public static class InfoViewHolder extends ViewHolderAdapter.ViewHolder<MenuItem> {\n        TextView text1;\n        ImageView icon;\n\n        public InfoViewHolder(View view) {\n            super(view);\n            text1 = view.findViewById(android.R.id.text1);\n            icon = view.findViewById(android.R.id.icon);\n        }\n\n        @Override\n        protected void setContent(MenuItem content, int position, @NonNull ViewHolderAdapter<MenuItem, ? extends ViewHolderAdapter.ViewHolder<MenuItem>> adapter) {\n            final CharSequence text;\n            if (content instanceof ItemWidget) {\n                WidgetInfo info = ((ItemWidget) content).info;\n                Context ctx = text1.getContext();\n\n                ApplicationInfo widgetAppInfo = null;\n                try {\n                    final String widgetPackage = info.appWidgetInfo.provider.getPackageName();\n                    widgetAppInfo = ctx.getPackageManager().getApplicationInfo(widgetPackage, 0);\n                } catch (Exception e) {\n                    Log.w(TAG, \"widget \" + info.appWidgetInfo.provider, e);\n                }\n                int widgetSdkVer = 0;\n                if (widgetAppInfo != null) {\n                    widgetSdkVer = widgetAppInfo.targetSdkVersion;\n                }\n\n                int cellX = 0;\n                int cellY = 0;\n                if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {\n                    cellX = info.appWidgetInfo.targetCellWidth;\n                    cellY = info.appWidgetInfo.targetCellHeight;\n                }\n                if (cellX == 0 || cellY == 0) {\n                    if (widgetSdkVer >= android.os.Build.VERSION_CODES.S) {\n                        // (73×n-16) × (118×m-16)\n                        cellX = (int) ((UISizes.px2dp_float(ctx, info.appWidgetInfo.minWidth) + 16f) / 73f);\n                        cellY = (int) ((UISizes.px2dp_float(ctx, info.appWidgetInfo.minHeight) + 16f) / 118f);\n                    } else {\n                        // Android 11 and lower\n                        // 70×n−30\n                        cellX = (int) ((UISizes.px2dp(ctx, info.appWidgetInfo.minWidth) + 30f) / 70f);\n                        cellY = (int) ((UISizes.px2dp(ctx, info.appWidgetInfo.minHeight) + 30f) / 70f);\n                    }\n                }\n                cellX = Math.max(1, cellX);\n                cellY = Math.max(1, cellY);\n                final String html;\n                if (info.widgetDesc != null) {\n                    html = text1.getResources().getString(R.string.widget_name_and_desc, info.widgetName, info.widgetDesc, cellX, cellY);\n                } else {\n                    html = text1.getResources().getString(R.string.widget_name, info.widgetName, cellX, cellY);\n                }\n                text = HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_LEGACY);\n                Utilities.setIconAsync(icon, context -> WidgetManager.getWidgetPreview(context, info.appWidgetInfo));\n            } else {\n                text = content.getName();\n                if (icon != null)\n                    icon.setImageDrawable(null);\n            }\n            text1.setText(text);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/widgets/LoadWidgetsAsync.java",
    "content": "package rocks.tbog.tblauncher.widgets;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport java.util.Collection;\n\nimport rocks.tbog.tblauncher.utils.ViewHolderListAdapter;\n\npublic class LoadWidgetsAsync extends ViewHolderListAdapter.LoadAsyncList<MenuItem, ItemWidget.InfoViewHolder, WidgetListAdapter> {\n    @Nullable\n    public Runnable whenDone = null;\n\n    public LoadWidgetsAsync(@NonNull WidgetListAdapter adapter, @NonNull LoadInBackground<MenuItem> loadInBackground) {\n        super(adapter, loadInBackground);\n    }\n\n    @Override\n    protected void onDataLoadFinished(@NonNull WidgetListAdapter adapter, @NonNull Collection<MenuItem> data) {\n        adapter.clearList();\n        super.onDataLoadFinished(adapter, data);\n    }\n\n    @Override\n    protected void onPostExecute(Collection<MenuItem> data) {\n        super.onPostExecute(data);\n        if (whenDone != null)\n            whenDone.run();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/widgets/MenuItem.java",
    "content": "package rocks.tbog.tblauncher.widgets;\n\nimport androidx.annotation.NonNull;\n\ninterface MenuItem {\n    @NonNull\n    String getName();\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/widgets/PickAppWidgetActivity.java",
    "content": "package rocks.tbog.tblauncher.widgets;\n\nimport android.appwidget.AppWidgetManager;\nimport android.appwidget.AppWidgetProviderInfo;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.text.Editable;\nimport android.text.TextUtils;\nimport android.text.TextWatcher;\nimport android.util.Log;\nimport android.view.View;\nimport android.widget.ListView;\nimport android.widget.TextView;\nimport android.widget.Toast;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.WorkerThread;\nimport androidx.appcompat.app.AppCompatActivity;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.Iterator;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.normalizer.StringNormalizer;\nimport rocks.tbog.tblauncher.utils.FuzzyScore;\n\npublic class PickAppWidgetActivity extends AppCompatActivity {\n    private static final String TAG = \"PickAppWidget\";\n    private TextView mSearch;\n    View widgetLoadingGroup;\n    WidgetListAdapter adapter;\n    LoadWidgetsAsync loadWidgetsAsync = null;\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.widget_picker);\n\n        final Context context = getApplicationContext();\n        final ListView list = findViewById(android.R.id.list);\n        adapter = new WidgetListAdapter();\n        widgetLoadingGroup = findViewById(R.id.widgetLoadingGroup);\n\n        list.setAdapter(adapter);\n        list.setOnItemClickListener((parent, view, position, id) -> {\n            Object item = parent.getAdapter().getItem(position);\n            WidgetInfo info = null;\n            if (item instanceof ItemWidget)\n                info = ((ItemWidget) item).info;\n            if (info == null)\n                return;\n            Intent intent = getIntent();\n            var appWidgetManager = AppWidgetManager.getInstance(context);\n            int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, WidgetManager.INVALID_WIDGET_ID);\n            if (appWidgetId != WidgetManager.INVALID_WIDGET_ID) {\n                boolean bindAllowed;\n                if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {\n                    bindAllowed = appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, info.appWidgetInfo.getProfile(), info.appWidgetInfo.provider, null);\n                } else {\n                    bindAllowed = appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, info.appWidgetInfo.provider);\n                }\n                intent.putExtra(WidgetManager.EXTRA_WIDGET_BIND_ALLOWED, bindAllowed);\n                intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, info.appWidgetInfo.provider);\n                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n                    intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE, info.appWidgetInfo.getProfile());\n                }\n                setResult(RESULT_OK, intent);\n            } else {\n                setResult(RESULT_CANCELED, intent);\n            }\n            finish();\n        });\n\n        // set page search bar\n        mSearch = findViewById(R.id.search);\n        mSearch.addTextChangedListener(new TextWatcher() {\n            public void afterTextChanged(Editable s) {\n                // Auto left-trim text.\n                if (s.length() > 0 && s.charAt(0) == ' ')\n                    s.delete(0, 1);\n            }\n\n            public void beforeTextChanged(CharSequence s, int start, int count, int after) {\n            }\n\n            public void onTextChanged(CharSequence s, int start, int before, int count) {\n                mSearch.post(() -> refreshList());\n            }\n        });\n        mSearch.requestFocus();\n        refreshList();\n    }\n\n    private synchronized void refreshList() {\n        final Context context = getApplicationContext();\n        widgetLoadingGroup.setVisibility(View.VISIBLE);\n\n        if (loadWidgetsAsync != null)\n            loadWidgetsAsync.cancel(false);\n\n        loadWidgetsAsync = adapter.newLoadAsyncList(LoadWidgetsAsync.class, () -> {\n            // get widget list\n            var widgetList = getWidgetList(context);\n\n            var text = mSearch.getText();\n            if (text.length() > 0) {\n                StringNormalizer.Result normalized = StringNormalizer.normalizeWithResult(text, true);\n                FuzzyScore fuzzyScore = new FuzzyScore(normalized.codePoints);\n                for (Iterator<WidgetInfo> iterator = widgetList.iterator(); iterator.hasNext(); ) {\n                    WidgetInfo widgetInfo = iterator.next();\n                    var matchAppName = !TextUtils.isEmpty(widgetInfo.appName) && fuzzyScore.match(widgetInfo.appName).match;\n                    var matchWidgetName = !TextUtils.isEmpty(widgetInfo.widgetName) && fuzzyScore.match(widgetInfo.widgetName).match;\n                    var matchDescription = !TextUtils.isEmpty(widgetInfo.widgetDesc) && fuzzyScore.match(widgetInfo.widgetDesc).match;\n\n                    if (!matchAppName && !matchWidgetName && !matchDescription)\n                        iterator.remove();\n                }\n            }\n\n            // sort list\n            Collections.sort(widgetList, Comparator.comparing(o -> o.appName));\n\n            //StringBuilder dbgList = new StringBuilder();\n            // assuming the list is sorted by apps, add titles with app name\n            ArrayList<MenuItem> adapterList = new ArrayList<>(widgetList.size());\n            String lastApp = null;\n            for (var item : widgetList) {\n                if (!item.appName.equals(lastApp)) {\n                    //dbgList\n                    //    .append(\"\\napp=`\")\n                    //    .append(item.appName)\n                    //    .append(\"`\");\n\n                    lastApp = item.appName;\n                    adapterList.add(new ItemTitle(item.appName));\n                }\n                //dbgList\n                //    .append(\"\\n\\twidget=`\")\n                //    .append(item.widgetName)\n                //    .append(\"`\\n\\t\\tdesc=`\")\n                //    .append(item.widgetDesc)\n                //    .append(\"`\");\n                adapterList.add(new ItemWidget(item));\n            }\n            //Log.d(TAG, dbgList.toString());\n            Log.d(TAG, \"list size=\" + adapterList.size());\n            return adapterList;\n        });\n\n        if (loadWidgetsAsync != null) {\n            loadWidgetsAsync.whenDone = () -> {\n                widgetLoadingGroup.setVisibility(View.GONE);\n                synchronized (PickAppWidgetActivity.this) {\n                    loadWidgetsAsync = null;\n                }\n            };\n            loadWidgetsAsync.execute();\n        } else {\n            finish();\n            Toast.makeText(context, R.string.add_widget_failed, Toast.LENGTH_LONG).show();\n        }\n    }\n\n    @WorkerThread\n    private static ArrayList<WidgetInfo> getWidgetList(@NonNull Context context) {\n        var appWidgetManager = AppWidgetManager.getInstance(context);\n        var installedProviders = appWidgetManager.getInstalledProviders();\n        var infoArrayList = new ArrayList<WidgetInfo>(installedProviders.size());\n        var packageManager = context.getPackageManager();\n        for (AppWidgetProviderInfo providerInfo : installedProviders) {\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {\n                if (providerInfo.widgetFeatures == AppWidgetProviderInfo.WIDGET_FEATURE_HIDE_FROM_PICKER) {\n                    // widget is hidden\n                    continue;\n                }\n            }\n            if ((providerInfo.widgetCategory & (AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN | AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX)) == 0) {\n                // widget is not for home screen usage\n                continue;\n            }\n            // get widget name\n            String label = null;\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n                label = providerInfo.loadLabel(packageManager);\n            }\n            if (label == null) {\n                label = providerInfo.label;\n            }\n            if (label == null)\n                label = providerInfo.provider.flattenToShortString();\n\n            // get widget description\n            String description = null;\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {\n                var desc = providerInfo.loadDescription(context);\n                if (desc != null)\n                    description = desc.toString();\n            }\n\n            // it's useless to have the description the same as the label\n            if (label.equals(description))\n                description = null;\n\n            String appName = providerInfo.provider.getPackageName();\n            try {\n                var appInfo = packageManager.getApplicationInfo(providerInfo.provider.getPackageName(), 0);\n                appName = appInfo.loadLabel(packageManager).toString();\n            } catch (Exception e) {\n                Log.e(TAG, \"get `\" + providerInfo.provider.getPackageName() + \"` label\");\n            }\n            infoArrayList.add(new WidgetInfo(appName, label, description, providerInfo));\n        }\n        return infoArrayList;\n    }\n\n    @Override\n    public void onBackPressed() {\n        setResult(RESULT_CANCELED, getIntent());\n        super.onBackPressed();\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/widgets/WidgetInfo.java",
    "content": "package rocks.tbog.tblauncher.widgets;\n\nimport android.appwidget.AppWidgetProviderInfo;\n\nclass WidgetInfo {\n    final String appName;\n    final String widgetName;\n    final String widgetDesc;\n    final AppWidgetProviderInfo appWidgetInfo;\n\n    WidgetInfo(String app, String name, String description, AppWidgetProviderInfo appWidgetInfo) {\n        this.appName = app;\n        this.widgetName = name;\n        this.widgetDesc = description;\n        this.appWidgetInfo = appWidgetInfo;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/widgets/WidgetLayout.java",
    "content": "package rocks.tbog.tblauncher.widgets;\n\nimport android.annotation.SuppressLint;\nimport android.appwidget.AppWidgetHostView;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.graphics.Point;\nimport android.graphics.PointF;\nimport android.graphics.Rect;\nimport android.os.Build;\nimport android.util.AttributeSet;\nimport android.util.Log;\nimport android.view.Gravity;\nimport android.view.LayoutInflater;\nimport android.view.MotionEvent;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.FrameLayout;\nimport android.widget.ImageView;\nimport android.widget.LinearLayout;\n\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.RequiresApi;\n\nimport java.util.ArrayList;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.TBLauncherActivity;\nimport rocks.tbog.tblauncher.utils.ArrayHelper;\n\npublic class WidgetLayout extends ViewGroup {\n    private static final String TAG = \"WdgLayout\";\n    /**\n     * These are used for computing child frames based on their gravity.\n     */\n    private final Rect mTmpContainerRect = new Rect();\n    private final Rect mTmpChildRect = new Rect();\n    private final Point mPageCount = new Point(1, 1);\n    private final ArrayList<OnAfterLayoutTask> mAfterLayoutTaskList = new ArrayList<>(1);\n\n    public interface OnAfterLayoutTask {\n        void onAfterLayout();\n    }\n\n    public enum Handle {\n        MOVE_FREE,\n        MOVE_AXIAL,\n        RESIZE_DIAGONAL,\n        RESIZE_AXIAL,\n        MOVE_FREE_RESIZE_AXIAL,\n        RESIZE_DIAGONAL_MOVE_AXIAL,\n        DISABLED;\n\n        public boolean isMove() {\n            return this == MOVE_FREE || this == MOVE_AXIAL;\n        }\n\n        public boolean isResize() {\n            return this == RESIZE_DIAGONAL || this == RESIZE_AXIAL;\n        }\n\n        public boolean isMoveResize() {\n            return this == MOVE_FREE_RESIZE_AXIAL || this == RESIZE_DIAGONAL_MOVE_AXIAL;\n        }\n    }\n\n\n    public WidgetLayout(Context context) {\n        this(context, null);\n    }\n\n    public WidgetLayout(Context context, AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    public WidgetLayout(Context context, AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n        init();\n    }\n\n    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)\n    public WidgetLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {\n        super(context, attrs, defStyleAttr, defStyleRes);\n        init();\n    }\n\n    private void init() {\n        setClipChildren(true);\n        setClipToPadding(true);\n    }\n\n    public void setPageCount(int horizontal, int vertical) {\n        if (horizontal < 1 || vertical < 1)\n            throw new IllegalStateException(\"setPageCount(\" + horizontal + \",\" + vertical + \") Page count must be >= 1\");\n        mPageCount.set(horizontal, vertical);\n    }\n\n    public int getHorizontalPageCount() {\n        return mPageCount.x;\n    }\n\n    public int getVerticalPageCount() {\n        return mPageCount.y;\n    }\n\n    /**\n     * Add a one time run task\n     *\n     * @param task what to run\n     */\n    public void addOnAfterLayoutTask(OnAfterLayoutTask task) {\n        mAfterLayoutTaskList.add(task);\n    }\n\n    /**\n     * Set current page\n     *\n     * @param pageX horizontal page to show\n     * @param pageY vertical page to show\n     */\n    public void scrollToPage(float pageX, float pageY) {\n        final int pageWidth = getWidth() / mPageCount.x;\n        final int pageHeight = getHeight() / mPageCount.y;\n        final float x = pageWidth * (mPageCount.x - 1) * pageX;\n        final float y = pageHeight * (mPageCount.y - 1) * pageY;\n        scrollTo((int) x, (int) y);\n    }\n\n//    public boolean isHandleEnabled(View widgetView) {\n//        return indexOfChild(widgetView) == -1;\n//    }\n\n    @NonNull\n    public Handle getHandleType(View widgetView) {\n        int viewIndex = indexOfChild(widgetView);\n        if (viewIndex == -1) {\n            for (int idx = 0; idx < getChildCount(); idx += 1) {\n                View child = getChildAt(idx);\n                if (child instanceof ViewGroup) {\n                    viewIndex = ((ViewGroup) child).indexOfChild(widgetView);\n                    if (viewIndex != -1) {\n                        // we keep the handle type in the tag\n                        Object tag = child.getTag();\n                        if (tag instanceof Handle)\n                            return (Handle) child.getTag();\n                        throw new IllegalStateException(\"widget view tag should hold the Handle\");\n                    }\n                }\n            }\n        }\n        return Handle.DISABLED;\n    }\n\n    public void disableHandle(View widgetView) {\n        enableHandle(widgetView, Handle.DISABLED);\n    }\n\n    public void enableHandle(View widgetView, Handle handle) {\n        convertAutoPositionTo(PageLayoutParams.Placement.MARGIN_TL_AS_POSITION);\n\n        ViewGroup widgetHandle = null;\n        // remove widget view from this layout\n        int viewIndex = indexOfChild(widgetView);\n        // if the widget is already wrapped by the handle\n        if (viewIndex == -1) {\n            for (int idx = 0; idx < getChildCount(); idx += 1) {\n                View child = getChildAt(idx);\n                if (child instanceof ViewGroup) {\n                    viewIndex = ((ViewGroup) child).indexOfChild(widgetView);\n                    if (viewIndex != -1) {\n                        widgetHandle = (ViewGroup) child;\n                        break;\n                    }\n                }\n            }\n            // can't find the widget handle\n            if (widgetHandle == null)\n                return;\n        } else {\n            removeViewAt(viewIndex);\n            // inflate the widget handle layout\n            widgetHandle = (ViewGroup) LayoutInflater.from(getContext()).inflate(R.layout.widget_handle, this, false);\n            {\n                PageLayoutParams lp = new PageLayoutParams((PageLayoutParams) widgetView.getLayoutParams());\n                lp.width = ViewGroup.LayoutParams.WRAP_CONTENT;\n                lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;\n                widgetHandle.setLayoutParams(lp);\n            }\n            {\n                PageLayoutParams lp = (PageLayoutParams) widgetView.getLayoutParams();\n                lp.setMargins(0, 0, 0, 0);\n                widgetView.setLayoutParams(lp);\n            }\n            // add the widget view to the handle layout as the first child\n            widgetHandle.addView(widgetView, 0);\n            // add the handle layout to this layout\n            addView(widgetHandle, viewIndex);\n        }\n\n        // use the tag to keep the handle type\n        widgetHandle.setTag(handle);\n\n        switch (handle) {\n            case DISABLED: {\n                final PageLayoutParams lp = (PageLayoutParams) widgetHandle.getLayoutParams();\n                lp.width = widgetView.getWidth();\n                lp.height = widgetView.getHeight();\n\n                int idx = indexOfChild(widgetHandle);\n                widgetHandle.removeViewAt(0);\n                removeViewAt(idx);\n                addView(widgetView, idx);\n                widgetView.setLayoutParams(lp);\n                break;\n            }\n            case MOVE_FREE:\n                setupCornerHandles(widgetHandle, R.drawable.ic_handle_move, sMoveListener, true);\n                break;\n            case MOVE_AXIAL:\n                setupLineHandles(widgetHandle, R.drawable.ic_handle_move, sMoveListener, true);\n                break;\n            case RESIZE_DIAGONAL:\n                setupCornerHandles(widgetHandle, R.drawable.ic_handle_resize_bl, sResizeListener, true);\n                break;\n            case RESIZE_AXIAL:\n                setupLineHandles(widgetHandle, R.drawable.ic_handle_resize_l, sResizeListener, true);\n                break;\n            case MOVE_FREE_RESIZE_AXIAL:\n                setupCornerHandles(widgetHandle, R.drawable.ic_handle_move, sMoveListener, false);\n                setupLineHandles(widgetHandle, R.drawable.ic_handle_resize_l, sResizeListener, false);\n                break;\n            case RESIZE_DIAGONAL_MOVE_AXIAL:\n                setupCornerHandles(widgetHandle, R.drawable.ic_handle_resize_bl, sResizeListener, false);\n                setupLineHandles(widgetHandle, R.drawable.ic_handle_move, sMoveListener, false);\n                break;\n        }\n    }\n\n    @SuppressLint(\"ClickableViewAccessibility\")\n    private static void setupCornerHandles(ViewGroup widgetHandle, @DrawableRes int ic_handle_corner, OnTouchListener touchListener, boolean hideOthers) {\n        if (hideOthers) {\n            widgetHandle.findViewById(R.id.handle_left).setVisibility(GONE);\n            widgetHandle.findViewById(R.id.handle_top).setVisibility(GONE);\n            widgetHandle.findViewById(R.id.handle_right).setVisibility(GONE);\n            widgetHandle.findViewById(R.id.handle_bottom).setVisibility(GONE);\n        }\n        {\n            ImageView image = widgetHandle.findViewById(R.id.handle_top_left);\n            image.setVisibility(VISIBLE);\n            image.setImageResource(ic_handle_corner);\n            image.setOnTouchListener(touchListener);\n        }\n        {\n            ImageView image = widgetHandle.findViewById(R.id.handle_top_right);\n            image.setVisibility(VISIBLE);\n            image.setImageResource(ic_handle_corner);\n            image.setOnTouchListener(touchListener);\n        }\n        {\n            ImageView image = widgetHandle.findViewById(R.id.handle_bottom_right);\n            image.setVisibility(VISIBLE);\n            image.setImageResource(ic_handle_corner);\n            image.setOnTouchListener(touchListener);\n        }\n        {\n            ImageView image = widgetHandle.findViewById(R.id.handle_bottom_left);\n            image.setVisibility(VISIBLE);\n            image.setImageResource(ic_handle_corner);\n            image.setOnTouchListener(touchListener);\n        }\n    }\n\n    @SuppressLint(\"ClickableViewAccessibility\")\n    private static void setupLineHandles(ViewGroup widgetHandle, @DrawableRes int ic_handle, OnTouchListener touchListener, boolean hideOthers) {\n        if (hideOthers) {\n            widgetHandle.findViewById(R.id.handle_top_left).setVisibility(GONE);\n            widgetHandle.findViewById(R.id.handle_top_right).setVisibility(GONE);\n            widgetHandle.findViewById(R.id.handle_bottom_right).setVisibility(GONE);\n            widgetHandle.findViewById(R.id.handle_bottom_left).setVisibility(GONE);\n        }\n        {\n            ImageView image = widgetHandle.findViewById(R.id.handle_left);\n            image.setVisibility(VISIBLE);\n            image.setImageResource(ic_handle);\n            image.setOnTouchListener(touchListener);\n        }\n        {\n            ImageView image = widgetHandle.findViewById(R.id.handle_top);\n            image.setVisibility(VISIBLE);\n            image.setImageResource(ic_handle);\n            image.setOnTouchListener(touchListener);\n        }\n        {\n            ImageView image = widgetHandle.findViewById(R.id.handle_right);\n            image.setVisibility(VISIBLE);\n            image.setImageResource(ic_handle);\n            image.setOnTouchListener(touchListener);\n        }\n        {\n            ImageView image = widgetHandle.findViewById(R.id.handle_bottom);\n            image.setVisibility(VISIBLE);\n            image.setImageResource(ic_handle);\n            image.setOnTouchListener(touchListener);\n        }\n    }\n\n    private void convertAutoPositionTo(PageLayoutParams.Placement placement) {\n        final int childCount = getChildCount();\n        for (int childIdx = 0; childIdx < childCount; childIdx++) {\n            final View child = getChildAt(childIdx);\n            final PageLayoutParams lp = (PageLayoutParams) child.getLayoutParams();\n            if (lp.placement != PageLayoutParams.Placement.AUTO)\n                continue;\n            lp.placement = placement;\n            lp.leftMargin = child.getLeft();\n            lp.topMargin = child.getTop();\n            //lp.rightMargin = child.getRight();\n            //lp.bottomMargin = child.getBottom();\n            child.setLayoutParams(lp);\n        }\n    }\n\n    /**\n     * This prevents the pressed state from appearing when the user is actually trying to scroll the content.\n     *\n     * @return true\n     */\n    @Override\n    public boolean shouldDelayChildPressedState() {\n        return TBApplication.liveWallpaper(getContext()).isPreferenceWPDragAnimate();\n    }\n\n    @Override\n    public PageLayoutParams generateLayoutParams(AttributeSet attrs) {\n        return new PageLayoutParams(getContext(), attrs);\n    }\n\n    @Override\n    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {\n        if (p instanceof LinearLayout.LayoutParams)\n            return new PageLayoutParams((LinearLayout.LayoutParams) p);\n        return new PageLayoutParams(new LinearLayout.LayoutParams(p));\n    }\n\n    @Override\n    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {\n        return new PageLayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);\n    }\n\n    @Override\n    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {\n        return p instanceof PageLayoutParams;\n    }\n\n    @Override\n    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n        int count = getChildCount();\n\n        int width = Math.max(getSuggestedMinimumWidth(), MeasureSpec.getSize(widthMeasureSpec));\n        int height = Math.max(getSuggestedMinimumHeight(), MeasureSpec.getSize(heightMeasureSpec));\n\n        // Iterate through all children and measure them.\n        for (int i = 0; i < count; i++) {\n            final View child = getChildAt(i);\n            if (child.getVisibility() == GONE)\n                continue;\n            ViewGroup.LayoutParams lp = child.getLayoutParams();\n\n            // Measure the child.\n            final int childWidthMeasureSpec;\n            if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT)\n                childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);\n            else\n                childWidthMeasureSpec = ViewGroup.getChildMeasureSpec(widthMeasureSpec, 0, lp.width);\n\n            final int childHeightMeasureSpec;\n            if (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT)\n                childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);\n            else\n                childHeightMeasureSpec = ViewGroup.getChildMeasureSpec(heightMeasureSpec, 0, lp.height);\n            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);\n            //TODO: check that child is not out of bounds\n        }\n\n        for (int pagePosition : PageLayoutParams.PAGE_POSITIONS) {\n            layoutPagePosition(pagePosition, width, height, false);\n            width = Math.max(width, mTmpContainerRect.width());\n            height = Math.max(height, mTmpContainerRect.height());\n        }\n        int resolvedWidth = resolveSize(width, widthMeasureSpec);\n        int resolvedHeight = resolveSize(height, heightMeasureSpec);\n        setMeasuredDimension(resolvedWidth, resolvedHeight);\n    }\n\n    @Override\n    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {\n        final int pageWidth = right - left;\n        final int pageHeight = bottom - top;\n        Log.d(TAG, \"onLayout left=\" + left + \" top=\" + top + \" width=\" + pageWidth + \" height=\" + pageHeight + \" pageCount=\" + mPageCount.x + \"x\" + mPageCount.y);\n        if (mPageCount.x == 1 && mPageCount.y == 1) {\n            layoutPagePosition(PageLayoutParams.PAGE_MIDDLE, pageWidth, pageHeight, true);\n        } else if (mPageCount.x > 1 && mPageCount.y == 1) {\n            for (int pagePos : PageLayoutParams.PAGE_POSITIONS_HORIZONTAL) {\n                layoutPagePosition(pagePos, pageWidth, pageHeight, true);\n            }\n        } else if (mPageCount.x == 1 && mPageCount.y > 1) {\n            for (int pagePos : PageLayoutParams.PAGE_POSITIONS_VERTICAL) {\n                layoutPagePosition(pagePos, pageWidth, pageHeight, true);\n            }\n        } else {\n            for (int pagePos : PageLayoutParams.PAGE_POSITIONS)\n                layoutPagePosition(pagePos, pageWidth, pageHeight, true);\n        }\n        callAfterLayout();\n    }\n\n    protected void callAfterLayout() {\n        for (OnAfterLayoutTask afterLayout : mAfterLayoutTaskList)\n            afterLayout.onAfterLayout();\n        mAfterLayoutTaskList.clear();\n    }\n\n    @Nullable\n    public AppWidgetHostView getWidget(int appWidgetId) {\n        for (int idx = 0; idx < getChildCount(); idx += 1) {\n            View child = getChildAt(idx);\n            if (child instanceof AppWidgetHostView && ((AppWidgetHostView) child).getAppWidgetId() == appWidgetId) {\n                return (AppWidgetHostView) child;\n            }\n            if (child instanceof ViewGroup) {\n                View view = ((ViewGroup) child).getChildAt(0);\n                if (view instanceof AppWidgetHostView && ((AppWidgetHostView) view).getAppWidgetId() == appWidgetId) {\n                    return (AppWidgetHostView) view;\n                }\n            }\n        }\n        return null;\n    }\n\n    @Nullable\n    public View getPlaceholder(@NonNull ComponentName provider) {\n        for (int idx = 0; idx < getChildCount(); idx += 1) {\n            View child = getChildAt(idx);\n            if (provider.equals(child.getTag()))\n                return child;\n        }\n        return null;\n    }\n\n    public void addPlaceholder(View placeholder, ComponentName provider) {\n        addView(placeholder);\n        placeholder.setTag(provider);\n    }\n\n    public void removeWidget(AppWidgetHostView view) {\n        disableHandle(view);\n        removeView(view);\n    }\n\n    public boolean removeWidget(int appWidgetId) {\n        for (int idx = 0; idx < getChildCount(); idx += 1) {\n            View child = getChildAt(idx);\n            if (child instanceof AppWidgetHostView && ((AppWidgetHostView) child).getAppWidgetId() == appWidgetId) {\n                removeView(child);\n                return true;\n            }\n            if (child instanceof ViewGroup) {\n                View view = ((ViewGroup) child).getChildAt(0);\n                if (view instanceof AppWidgetHostView && ((AppWidgetHostView) view).getAppWidgetId() == appWidgetId) {\n                    removeWidget((AppWidgetHostView) view);\n                    return true;\n                }\n            }\n        }\n        return false;\n    }\n\n    private int getLeftMarginForPage(int page, int width) {\n        if (mPageCount.x == 1)\n            return 0;\n        final int pagePos = PageLayoutParams.getPagePosition(page);\n        final int pageIdx = PageLayoutParams.getPageIndex(page);\n        if (pagePos == PageLayoutParams.PAGE_LEFT)\n            return (mPageCount.x / 2 - pageIdx) * width;\n\n        final int center = mPageCount.x / 2 * width;\n        if (pagePos == PageLayoutParams.PAGE_RIGHT)\n            return center + pageIdx * width;\n\n        return center;\n    }\n\n    private int getTopMarginForPage(int page, int height) {\n        if (mPageCount.y == 1)\n            return 0;\n        final int pagePos = PageLayoutParams.getPagePosition(page);\n        final int pageIdx = PageLayoutParams.getPageIndex(page);\n        if (pagePos == PageLayoutParams.PAGE_UP)\n            return (mPageCount.y / 2 - pageIdx) * height;\n\n        final int center = mPageCount.y / 2 * height;\n        if (pagePos == PageLayoutParams.PAGE_DOWN)\n            return center + pageIdx * height;\n\n        return center;\n    }\n\n    private void layoutPagePosition(int pagePosition, int pageWidth, int pageHeight, boolean childLayout) {\n        final int pageStart;\n        final int pageCount;\n        if (pagePosition == PageLayoutParams.PAGE_MIDDLE) {\n            pageStart = 0;\n            pageCount = 1;\n        } else {\n            pageStart = 1;\n            boolean horizontal = ArrayHelper.contains(PageLayoutParams.PAGE_POSITIONS_HORIZONTAL, pagePosition);\n            pageCount = 1 + (horizontal ? mPageCount.x : mPageCount.y) / 2;\n        }\n        int width = pageWidth;\n        int height = pageHeight;\n        for (int pageIdx = pageStart; pageIdx < pageCount; pageIdx += 1) {\n            int page = PageLayoutParams.makePage(pagePosition, pageIdx);\n            pageLayout(page, width, height, childLayout);\n            width = Math.max(width, mTmpContainerRect.width());\n            height = Math.max(height, mTmpContainerRect.height());\n        }\n    }\n\n    private void pageLayout(int page, int width, int height, boolean childLayout) {\n        mTmpContainerRect.setEmpty();\n        final int pageTop = getTopMarginForPage(page, height);\n        final int pageLeft = getLeftMarginForPage(page, width);\n\n        // apply padding\n        final int pageWidth = width - getPaddingLeft() - getPaddingRight();\n        final int pageHeight = height - getPaddingTop() - getPaddingBottom();\n\n        Log.d(TAG, (childLayout ? \"childLayout \" : \"pageLayout \")\n            + PageLayoutParams.debugPage(page) + \" left=\" + pageLeft + \" top=\" + pageTop + \" width=\" + pageWidth + \" height=\" + pageHeight);\n\n        int autoX = 0;\n        int autoY = 0;\n        int maxChildY = 0;\n        StringBuilder debugChild = new StringBuilder();\n        final int childCount = getChildCount();\n        for (int childIdx = 0; childIdx < childCount; childIdx++) {\n            final View child = getChildAt(childIdx);\n            if (child.getVisibility() == GONE)\n                continue;\n\n            final PageLayoutParams lp = (PageLayoutParams) child.getLayoutParams();\n            if (PageLayoutParams.validatedPage(lp.screenPage) != page)\n                continue;\n\n            debugChild.setLength(0);\n            debugChild.append(Integer.toHexString(System.identityHashCode(child)))\n                .append(\" child #\")\n                .append(childIdx)\n                .append(\"\\n\\t\");\n\n            mTmpChildRect.set(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight());\n            debugChild.append(\"measured \")\n                .append(child.getMeasuredWidth())\n                .append(\"×\")\n                .append(child.getMeasuredHeight())\n                .append(\"\\n\\t\");\n\n            switch (lp.placement) {\n                case AUTO:\n                    mTmpChildRect.offset(autoX, autoY);\n                    if (mTmpChildRect.right > pageWidth) {\n                        mTmpChildRect.offset(-autoX, -autoY);\n                        autoX = 0;\n                        autoY = maxChildY;\n                        mTmpChildRect.offset(autoX, autoY);\n                    }\n                    if (mTmpChildRect.bottom > pageHeight) {\n                        mTmpChildRect.offset(-autoX, -autoY);\n                        autoY = 0;\n                        maxChildY = mTmpContainerRect.bottom;\n                        mTmpChildRect.offset(autoX, autoY);\n                    }\n\n\n                    autoX = mTmpChildRect.right;\n\n                    maxChildY = Math.max(maxChildY, mTmpChildRect.bottom);\n                    break;\n                case MARGIN_TL_AS_POSITION:\n                    mTmpChildRect.offset(lp.leftMargin, lp.topMargin);\n            }\n\n            debugChild.append(\"offset in page \")\n                .append(mTmpChildRect.left)\n                .append(\"×\")\n                .append(mTmpChildRect.top)\n                .append(\" size \")\n                .append(mTmpChildRect.width())\n                .append(\"×\")\n                .append(mTmpChildRect.height())\n                .append(\"\\n\\t\");\n            // apply page offset\n            mTmpChildRect.offset(pageLeft, pageTop);\n\n            int initialLeft = mTmpChildRect.left;\n            int initialTop = mTmpChildRect.top;\n            // don't let the child start to the right of this page\n            while (mTmpChildRect.left > (pageLeft + width))\n                mTmpChildRect.offset(-Math.min(width, mTmpChildRect.width()) / 4, 0);\n            // don't let the child end to the left of this page\n            while (mTmpChildRect.right < pageLeft)\n                mTmpChildRect.offset(Math.min(width, mTmpChildRect.width()) / 4, 0);\n            // don't let the child start below this page\n            while (mTmpChildRect.top > (pageTop + height))\n                mTmpChildRect.offset(0, -Math.min(height, mTmpChildRect.height()) / 4);\n            // don't let the child end above this page\n            while (mTmpChildRect.bottom < pageTop)\n                mTmpChildRect.offset(0, Math.min(height, mTmpChildRect.height()) / 4);\n\n            debugChild.append(\"page correction \")\n                .append(initialLeft - mTmpChildRect.left)\n                .append(\"×\")\n                .append(initialTop - mTmpChildRect.top)\n                .append(\" padding \")\n                .append(getPaddingLeft())\n                .append(\"×\")\n                .append(getPaddingTop())\n                .append(\"\\n\\t\");\n\n            // apply page padding\n            mTmpChildRect.offset(getPaddingLeft(), getPaddingTop());\n\n            // Place the child.\n            if (childLayout) {\n                child.layout(mTmpChildRect.left, mTmpChildRect.top, mTmpChildRect.right, mTmpChildRect.bottom);\n                debugChild.append(\"layout\");\n            }\n            Log.d(TAG, debugChild.toString());\n            mTmpContainerRect.union(mTmpChildRect);\n        }\n    }\n\n    private static final OnTouchListener sMoveListener = new OnTouchListener() {\n        final PointF mDownPos = new PointF();\n\n        @SuppressLint({\"RtlHardcoded\", \"ClickableViewAccessibility\"})\n        @Override\n        public boolean onTouch(View v, MotionEvent event) {\n            final int action = event.getActionMasked();\n            final View parent = (View) v.getParent();\n            switch (action) {\n                case MotionEvent.ACTION_DOWN:\n                    mDownPos.set(event.getRawX(), event.getRawY());\n                    return true;\n                case MotionEvent.ACTION_MOVE: {\n                    final float xMove = event.getRawX() - mDownPos.x;\n                    final float yMove = event.getRawY() - mDownPos.y;\n\n                    int gravity = ((FrameLayout.LayoutParams) v.getLayoutParams()).gravity;\n                    boolean horizontal = ((gravity & Gravity.LEFT) == Gravity.LEFT) || ((gravity & Gravity.RIGHT) == Gravity.RIGHT);\n                    boolean vertical = ((gravity & Gravity.TOP) == Gravity.TOP) || ((gravity & Gravity.BOTTOM) == Gravity.BOTTOM);\n                    if (horizontal)\n                        parent.setTranslationX(xMove);\n                    if (vertical)\n                        parent.setTranslationY(yMove);\n                    return true;\n                }\n                case MotionEvent.ACTION_UP: {\n                    final PageLayoutParams lp = (PageLayoutParams) parent.getLayoutParams();\n                    int left = lp.leftMargin;\n                    int top = lp.topMargin;\n                    lp.leftMargin += (int) parent.getTranslationX();\n                    lp.topMargin += (int) parent.getTranslationY();\n                    parent.setTranslationX(0f);\n                    parent.setTranslationY(0f);\n\n                    parent.setLayoutParams(lp);\n                    Log.d(TAG, Integer.toHexString(System.identityHashCode(parent))\n                        + \"\\n\\tbefore pos \" + left + \"×\" + top\n                        + \"\\n\\t after pos \" + lp.leftMargin + \"×\" + lp.topMargin);\n                    return true;\n                }\n                case MotionEvent.ACTION_CANCEL: {\n                    parent.setTranslationX(0f);\n                    parent.setTranslationY(0f);\n                    return true;\n                }\n            }\n            return false;\n        }\n    };\n\n    private static final OnTouchListener sResizeListener = new OnTouchListener() {\n        final Point mDownSize = new Point();\n        final Point mDownMargin = new Point();\n        final PointF mDownPos = new PointF();\n\n        @SuppressLint({\"RtlHardcoded\", \"ClickableViewAccessibility\"})\n        @Override\n        public boolean onTouch(View v, MotionEvent event) {\n            final ViewGroup parent = (ViewGroup) v.getParent();\n            final View widgetView = parent.getChildAt(0);\n            final int action = event.getActionMasked();\n            switch (action) {\n                case MotionEvent.ACTION_DOWN: {\n                    {\n                        final ViewGroup.LayoutParams lp = widgetView.getLayoutParams();\n                        mDownSize.set(lp.width, lp.height);\n                    }\n                    {\n                        final PageLayoutParams lp = (PageLayoutParams) parent.getLayoutParams();\n                        mDownMargin.set(lp.leftMargin, lp.topMargin);\n                    }\n                    mDownPos.set(event.getRawX(), event.getRawY());\n                    return true;\n                }\n                case MotionEvent.ACTION_MOVE: {\n                    int xMove = (int) (event.getRawX() - mDownPos.x + .5f);\n                    int yMove = (int) (event.getRawY() - mDownPos.y + .5f);\n\n                    int gravity = ((FrameLayout.LayoutParams) v.getLayoutParams()).gravity;\n                    // move widget handler\n                    {\n                        final PageLayoutParams lp = (PageLayoutParams) parent.getLayoutParams();\n                        boolean changed = false;\n                        if ((gravity & Gravity.LEFT) == Gravity.LEFT) {\n                            lp.leftMargin = mDownMargin.x + xMove;\n                            xMove = -xMove;\n                            changed = true;\n                        }\n                        if ((gravity & Gravity.TOP) == Gravity.TOP) {\n                            lp.topMargin = mDownMargin.y + yMove;\n                            yMove = -yMove;\n                            changed = true;\n                        }\n                        if (changed)\n                            parent.setLayoutParams(lp);\n                    }\n                    // resize widget\n                    {\n                        final ViewGroup.LayoutParams lp = widgetView.getLayoutParams();\n                        boolean horizontal = ((gravity & Gravity.LEFT) == Gravity.LEFT) || ((gravity & Gravity.RIGHT) == Gravity.RIGHT);\n                        boolean vertical = ((gravity & Gravity.TOP) == Gravity.TOP) || ((gravity & Gravity.BOTTOM) == Gravity.BOTTOM);\n                        if (horizontal)\n                            lp.width = mDownSize.x + xMove;\n                        if (vertical)\n                            lp.height = mDownSize.y + yMove;\n                        widgetView.setLayoutParams(lp);\n                        if (widgetView instanceof AppWidgetHostView hostView) {\n                            TBLauncherActivity activity = TBApplication.launcherActivity(v.getContext());\n                            if (activity != null)\n                                activity.widgetManager.updateWidgetSize(hostView, lp.width, lp.height);\n                        }\n                    }\n                    //requestLayout();\n                    return true;\n                }\n                case MotionEvent.ACTION_UP: {\n                    return true;\n                }\n                case MotionEvent.ACTION_CANCEL: {\n                    {\n                        final ViewGroup.LayoutParams lp = widgetView.getLayoutParams();\n                        lp.width = mDownSize.x;\n                        lp.height = mDownSize.y;\n                        widgetView.setLayoutParams(lp);\n                    }\n                    {\n                        final PageLayoutParams lp = (PageLayoutParams) parent.getLayoutParams();\n                        lp.leftMargin = mDownMargin.x;\n                        lp.topMargin = mDownMargin.y;\n                        parent.setLayoutParams(lp);\n                    }\n                    return true;\n                }\n            }\n            return false;\n        }\n    };\n\n    public static class PageLayoutParams extends ViewGroup.MarginLayoutParams {\n\n        /**\n         * The screen/page to put this view into\n         */\n        public static final int PAGE_MIDDLE = 0;\n        public static final int PAGE_LEFT = 1;\n        public static final int PAGE_RIGHT = 2;\n        public static final int PAGE_UP = 4;\n        public static final int PAGE_DOWN = 8;\n        public static final int PAGE_POSITION_SHIFT = 0;\n        public static final int PAGE_POSITION_MASK = (PAGE_LEFT | PAGE_RIGHT | PAGE_UP | PAGE_DOWN) << PAGE_POSITION_SHIFT; // = 0xf\n        public static final int PAGE_DISTANCE_SHIFT = 4;\n        public static final int PAGE_DISTANCE_MASK = 0xf << PAGE_DISTANCE_SHIFT;\n        public static final int[] PAGE_POSITIONS = new int[]{PAGE_LEFT, PAGE_UP, PAGE_MIDDLE, PAGE_RIGHT, PAGE_DOWN};\n        public static final int[] PAGE_POSITIONS_HORIZONTAL = new int[]{PAGE_LEFT, PAGE_MIDDLE, PAGE_RIGHT};\n        public static final int[] PAGE_POSITIONS_VERTICAL = new int[]{PAGE_UP, PAGE_MIDDLE, PAGE_DOWN};\n\n        public int screenPage = PAGE_MIDDLE;\n        public Placement placement = Placement.AUTO;\n\n        public static int makePage(int pagePosition, int pageIdx) {\n            int pos = (pagePosition << PAGE_POSITION_SHIFT) & PAGE_POSITION_MASK;\n            int idx = (pageIdx << PAGE_DISTANCE_SHIFT) & PAGE_DISTANCE_MASK;\n            return pos | idx;\n        }\n\n        public static int validatedPage(int page) {\n            int pos = getPagePosition(page);\n            int idx = getPageIndex(page);\n            return makePage(pos, idx);\n        }\n\n        public static String debugPage(int page) {\n            int pos = (page & PAGE_POSITION_MASK) >> PAGE_POSITION_SHIFT;\n            int idx = (page & PAGE_DISTANCE_MASK) >> PAGE_DISTANCE_SHIFT;\n            switch (pos) {\n                case PAGE_LEFT:\n                    return idx + \"L\";\n                case PAGE_UP:\n                    return idx + \"U\";\n                case PAGE_RIGHT:\n                    return idx + \"R\";\n                case PAGE_DOWN:\n                    return idx + \"D\";\n                case PAGE_MIDDLE:\n                    return idx + \"M\";\n                default:\n                    return String.valueOf(idx);\n            }\n        }\n\n        public static int getPagePosition(int page) {\n            return (page & PAGE_POSITION_MASK) >> PAGE_POSITION_SHIFT;\n        }\n\n        public static int getPageIndex(int page) {\n            int pos = (page & PAGE_POSITION_MASK) >> PAGE_POSITION_SHIFT;\n            int idx = (page & PAGE_DISTANCE_MASK) >> PAGE_DISTANCE_SHIFT;\n            // middle page is special\n            if (pos == PAGE_MIDDLE)\n                return 0;\n            // only middle page is allowed to have idx 0\n            if (idx == 0)\n                return 1;\n            return idx;\n        }\n\n        public enum Placement {\n            AUTO,\n            MARGIN_TL_AS_POSITION,\n        }\n\n        public PageLayoutParams(Context ctx, AttributeSet attrs) {\n            super(ctx, attrs);\n        }\n\n        public PageLayoutParams(int width, int height) {\n            super(width, height);\n        }\n\n        public PageLayoutParams(ViewGroup.MarginLayoutParams source) {\n            super(source);\n        }\n\n        public PageLayoutParams(PageLayoutParams source) {\n            this((ViewGroup.MarginLayoutParams) source);\n            screenPage = source.screenPage;\n            placement = source.placement;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/widgets/WidgetListAdapter.java",
    "content": "package rocks.tbog.tblauncher.widgets;\n\nimport java.util.ArrayList;\n\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.utils.ViewHolderListAdapter;\n\npublic class WidgetListAdapter extends ViewHolderListAdapter<MenuItem, ItemWidget.InfoViewHolder> {\n\n    public WidgetListAdapter() {\n        super(ItemWidget.InfoViewHolder.class, R.layout.popup_list_item_icon, new ArrayList<>());\n    }\n\n    public void clearList() {\n        mList.clear();\n        notifyDataSetChanged();\n    }\n\n    @Override\n    public boolean isEnabled(int position) {\n        return !(getItem(position) instanceof ItemTitle);\n    }\n\n    @Override\n    protected int getItemViewTypeLayout(int viewType) {\n        if (viewType == 1)\n            return R.layout.popup_title;\n        return super.getItemViewTypeLayout(viewType);\n    }\n\n    public int getItemViewType(int position) {\n        return getItem(position) instanceof ItemTitle ? 1 : 0;\n    }\n\n    public int getViewTypeCount() {\n        return 2;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/widgets/WidgetManager.java",
    "content": "package rocks.tbog.tblauncher.widgets;\n\nimport android.app.Activity;\nimport android.app.ActivityOptions;\nimport android.appwidget.AppWidgetHost;\nimport android.appwidget.AppWidgetHostView;\nimport android.appwidget.AppWidgetManager;\nimport android.appwidget.AppWidgetProviderInfo;\nimport android.content.ActivityNotFoundException;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.pm.PackageManager;\nimport android.content.res.Resources;\nimport android.graphics.PointF;\nimport android.graphics.Rect;\nimport android.graphics.drawable.Drawable;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.os.UserHandle;\nimport android.util.ArrayMap;\nimport android.util.Log;\nimport android.util.SizeF;\nimport android.view.ContextThemeWrapper;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\nimport android.widget.ListAdapter;\nimport android.widget.TextView;\nimport android.widget.Toast;\n\nimport androidx.activity.result.ActivityResultLauncher;\nimport androidx.activity.result.contract.ActivityResultContracts;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.RequiresApi;\nimport androidx.annotation.StringRes;\nimport androidx.annotation.WorkerThread;\nimport androidx.appcompat.app.AlertDialog;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.core.content.res.ResourcesCompat;\n\nimport java.util.ArrayList;\nimport java.util.Iterator;\n\nimport rocks.tbog.tblauncher.Behaviour;\nimport rocks.tbog.tblauncher.LiveWallpaper;\nimport rocks.tbog.tblauncher.R;\nimport rocks.tbog.tblauncher.SettingsActivity;\nimport rocks.tbog.tblauncher.TBApplication;\nimport rocks.tbog.tblauncher.db.DBHelper;\nimport rocks.tbog.tblauncher.db.PlaceholderWidgetRecord;\nimport rocks.tbog.tblauncher.db.WidgetRecord;\nimport rocks.tbog.tblauncher.drawable.DrawableUtils;\nimport rocks.tbog.tblauncher.ui.LinearAdapter;\nimport rocks.tbog.tblauncher.ui.LinearAdapterPlus;\nimport rocks.tbog.tblauncher.ui.ListPopup;\nimport rocks.tbog.tblauncher.utils.DebugInfo;\nimport rocks.tbog.tblauncher.utils.DeviceUtils;\nimport rocks.tbog.tblauncher.utils.UserHandleCompat;\nimport rocks.tbog.tblauncher.utils.Utilities;\n\npublic class WidgetManager {\n    private static final String TAG = \"Wdg\";\n    public static final int INVALID_WIDGET_ID = AppWidgetManager.INVALID_APPWIDGET_ID;\n    private AppWidgetManager mAppWidgetManager;\n    private WidgetHost mAppWidgetHost;\n    private WidgetLayout mLayout;\n    private WidgetLayout.Handle mLastMoveType = WidgetLayout.Handle.MOVE_FREE;\n    private WidgetLayout.Handle mLastResizeType = WidgetLayout.Handle.RESIZE_DIAGONAL;\n    private WidgetLayout.Handle mLastMoveResizeType = WidgetLayout.Handle.MOVE_FREE_RESIZE_AXIAL;\n    private final ArrayMap<Integer, WidgetRecord> mWidgets = new ArrayMap<>(0);\n    private final ArrayList<PlaceholderWidgetRecord> mPlaceholders = new ArrayList<>(0);\n    private static final int APPWIDGET_HOST_ID = 1337;\n    private static final int REQUEST_CONFIGURE_APPWIDGET = 102;\n    public static final String EXTRA_WIDGET_BIND_ALLOWED = \"widgetBindAllowed\";\n\n    // called after widget was picked by the user\n    ActivityResultLauncher<Intent> widgetPickerResult;\n\n    // called after user responded to permission request\n    ActivityResultLauncher<Intent> widgetBindResult;\n\n    /**\n     * Registers the AppWidgetHost to listen for updates to any widgets this app has.\n     */\n    public boolean start(Context context) {\n        Context ctx = context.getApplicationContext();\n        mAppWidgetManager = AppWidgetManager.getInstance(ctx);\n        try {\n            mAppWidgetHost = new WidgetHost(ctx);\n            mAppWidgetHost.startListening();\n        } catch (android.content.res.Resources.NotFoundException e) {\n            Log.e(TAG, \"startListening failed\", e);\n            // Widget app was just updated? See https://github.com/Neamar/KISS/issues/959\n            mAppWidgetHost = null;\n            return false;\n        }\n        return true;\n    }\n\n    public void stop() {\n        mAppWidgetHost.stopListening();\n        mAppWidgetHost = null;\n    }\n\n    /**\n     * Called on the creation of the activity.\n     */\n    public void onCreateActivity(AppCompatActivity activity) {\n        mLayout = activity.findViewById(R.id.widgetContainer);\n        mLayout.setPageCount(LiveWallpaper.SCREEN_COUNT_HORIZONTAL, LiveWallpaper.SCREEN_COUNT_VERTICAL);\n        restoreWidgets();\n        // post the scroll event to happen after the measure and layout phase\n        final LiveWallpaper lw = TBApplication.liveWallpaper(activity);\n        mLayout.addOnAfterLayoutTask(() -> {\n            PointF offset = lw.getWallpaperOffset();\n            WidgetManager.this.scroll(offset.x, offset.y);\n        });\n\n        widgetPickerResult =\n            activity.registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {\n                Log.d(TAG, \"widgetPickerResult \" + result);\n                var data = result.getData();\n                if (result.getResultCode() == Activity.RESULT_OK) {\n                    if (data != null && !data.getBooleanExtra(EXTRA_WIDGET_BIND_ALLOWED, false))\n                        requestBindWidget(data);\n                    else {\n                        createWidget(activity, data);\n                        configureWidget(activity, data);\n                    }\n                } else {\n                    if (data != null) {\n                        int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, INVALID_WIDGET_ID);\n                        if (appWidgetId != INVALID_WIDGET_ID) {\n                            removeWidget(appWidgetId);\n                        }\n                    } else {\n                        Toast.makeText(activity, R.string.add_widget_failed, Toast.LENGTH_LONG).show();\n                    }\n                }\n            });\n\n        widgetBindResult =\n            activity.registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {\n                Log.d(TAG, \"widgetBindResult \" + result);\n                var data = result.getData();\n                if (result.getResultCode() == Activity.RESULT_OK) {\n                    createWidget(activity, data);\n                    configureWidget(activity, data);\n                } else {\n                    if (data != null) {\n                        int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, INVALID_WIDGET_ID);\n                        if (appWidgetId != INVALID_WIDGET_ID) {\n                            removeWidget(appWidgetId);\n                        }\n                    }\n                    Toast.makeText(activity, R.string.bind_widget_failed, Toast.LENGTH_LONG).show();\n                }\n            });\n    }\n\n    private void loadFromDB(Context context) {\n        ArrayList<WidgetRecord> widgets = DBHelper.getWidgets(context);\n        mWidgets.clear();\n        mPlaceholders.clear();\n        // we expect no placeholders\n        mWidgets.ensureCapacity(widgets.size());\n        for (WidgetRecord record : widgets) {\n            if (record instanceof PlaceholderWidgetRecord) {\n                mPlaceholders.add((PlaceholderWidgetRecord) record);\n            } else {\n                mWidgets.put(record.appWidgetId, record);\n            }\n        }\n    }\n\n    private void restoreWidgets() {\n        mLayout.removeAllViews();\n\n        if (mAppWidgetHost == null) {\n            Log.w(TAG, \"`restoreWidgets` called prior to `startListening`\");\n            if (!start(mLayout.getContext())) {\n                Log.w(TAG, \"`start` failed, try `restoreWidgets` after 500ms\");\n                mLayout.postDelayed(this::restoreWidgets, 500);\n                return;\n            }\n        }\n\n        int[] appWidgetIds;\n        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {\n            appWidgetIds = mAppWidgetHost.getAppWidgetIds();\n        } else {\n            appWidgetIds = new int[0];\n        }\n\n        loadFromDB(mLayout.getContext());\n\n        // sync DB with AppWidgetHost\n        for (int appWidgetId : appWidgetIds) {\n            if (!mWidgets.containsKey(appWidgetId)) {\n                // remove widget that has no info in DB\n                removeWidget(appWidgetId);\n            }\n        }\n        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {\n            ArrayList<Integer> toDelete = new ArrayList<>(0);\n            for (WidgetRecord rec : mWidgets.values()) {\n                boolean found = false;\n                for (int appWidgetId : appWidgetIds)\n                    if (appWidgetId == rec.appWidgetId) {\n                        found = true;\n                        break;\n                    }\n                if (!found) {\n                    // remove widget from DB\n                    toDelete.add(rec.appWidgetId);\n                }\n            }\n            for (int appWidgetId : toDelete)\n                removeWidget(appWidgetId);\n        }\n\n        // restore widgets\n        for (WidgetRecord rec : mWidgets.values()) {\n            restoreWidget(rec);\n        }\n\n        // restore placeholders\n        for (PlaceholderWidgetRecord placeholderWidget : mPlaceholders) {\n            addPlaceholderToLayout(placeholderWidget);\n        }\n    }\n\n//    public List<AppWidgetProviderInfo> getWidgetsForPackage(String packageName) {\n//        List<AppWidgetProviderInfo> providers;\n//        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n//            providers = mAppWidgetManager.getInstalledProvidersForPackage(packageName, null);\n//        } else {\n//            providers = new ArrayList<>();\n//            for (AppWidgetProviderInfo prov : mAppWidgetManager.getInstalledProviders()) {\n//                if (prov.provider.getPackageName().equals(packageName)) {\n//                    providers.add(prov);\n//                }\n//            }\n//        }\n//        return providers;\n//    }\n\n    private void restoreWidget(WidgetRecord rec) {\n        final int appWidgetId = rec.appWidgetId;\n        Context ctx = mLayout.getContext().getApplicationContext();\n        AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);\n        if (appWidgetInfo == null)\n            return;\n        AppWidgetHostView hostView = mAppWidgetHost.createView(ctx, appWidgetId, appWidgetInfo);\n\n        addWidgetToLayout(hostView, appWidgetInfo, rec);\n    }\n\n    public void onBeforeRestoreFromBackup(boolean clearAll) {\n        if (mAppWidgetHost == null) {\n            Log.e(TAG, \"`onBeforeRestoreFromBackup` called prior to `startListening`\");\n            return;\n        }\n\n        loadFromDB(mLayout.getContext());\n\n        if (clearAll) {\n            mLayout.removeAllViews();\n            mPlaceholders.clear();\n        }\n    }\n\n    public void onAfterRestoreFromBackup(boolean clearExtra) {\n        if (clearExtra) {\n            // remove all widgets not found in mLayout\n            int[] appWidgetIds = new int[mWidgets.size()];\n            //Arrays.fill(appWidgetIds, INVALID_WIDGET_ID);\n            // copy widget ids we may need to remove\n            {\n                int idx = 0;\n                for (WidgetRecord rec : mWidgets.values())\n                    appWidgetIds[idx++] = rec.appWidgetId;\n            }\n            for (int appWidgetId : appWidgetIds) {\n                AppWidgetHostView widgetHostView = mLayout.getWidget(appWidgetId);\n                if (widgetHostView != null)\n                    removeWidget(widgetHostView);\n            }\n        } else {\n            // restore widgets from mWidgets that are not in mLayout yet\n            for (WidgetRecord rec : mWidgets.values()) {\n                if (mLayout.getWidget(rec.appWidgetId) == null)\n                    restoreWidget(rec);\n            }\n        }\n\n        Context ctx = mLayout.getContext();\n        for (WidgetRecord rec : mWidgets.values()) {\n            DBHelper.setWidgetProperties(ctx, rec);\n        }\n        // remove all placeholders\n        DBHelper.removeWidget(ctx, INVALID_WIDGET_ID);\n        // add back all placeholders\n        for (PlaceholderWidgetRecord placeholder : mPlaceholders) {\n            DBHelper.addWidget(ctx, placeholder);\n        }\n    }\n\n    /**\n     * called when importing from backup / XML\n     *\n     * @param append if true widget information will not be changed / imported / restored\n     * @param record placeholder information\n     */\n    public void restoreFromBackup(boolean append, PlaceholderWidgetRecord record/*, String name, ComponentName provider, byte[] preview, WidgetRecord record*/) {\n        int appWidgetId = record.appWidgetId;\n        boolean bFound = false;\n        {\n            WidgetRecord widgetRecord = mWidgets.get(appWidgetId);\n            AppWidgetProviderInfo info = widgetRecord != null ? getWidgetProviderInfo(widgetRecord.appWidgetId) : null;\n            // check if appWidgetId can be restored\n            if (info != null) {\n                bFound = record.provider.equals(info.provider);\n            }\n        }\n        // check if we can find a provider match\n        if (!bFound) {\n            for (WidgetRecord rec : mWidgets.values()) {\n                // if we already restored this widget, skip\n                if (mLayout.getWidget(rec.appWidgetId) != null)\n                    continue;\n                AppWidgetProviderInfo info = getWidgetProviderInfo(rec.appWidgetId);\n                if (info == null)\n                    continue;\n                if (record.provider.equals(info.provider)) {\n                    appWidgetId = rec.appWidgetId;\n                    bFound = true;\n                    break;\n                }\n            }\n        }\n        //TODO: check if we can recycle a widget based on provider\n\n        if (bFound) {\n            record.appWidgetId = appWidgetId;\n            if (!append) {\n                // widget found, apply the properties\n                mWidgets.put(appWidgetId, record);\n            }\n            mLayout.removeWidget(appWidgetId);\n            restoreWidget(record);\n        } else {\n            // widget not found, add a placeholder\n            record.appWidgetId = INVALID_WIDGET_ID;\n            mPlaceholders.add(record);\n            addPlaceholderToLayout(record);\n        }\n    }\n\n    private void updateAppWidgetOptions(AppWidgetHostView hostView, AppWidgetProviderInfo appWidgetInfo, WidgetRecord rec) {\n        Context ctx = hostView.getContext();\n        Rect padding = new Rect(0, 0, 0, 0);\n        AppWidgetHostView.getDefaultPaddingForWidget(ctx, appWidgetInfo.provider, padding);\n        float density = ctx.getResources().getDisplayMetrics().density;\n\n        float widgetWidthDips = rec.width / density;\n        float widgetHeightDips = rec.height / density;\n\n        float xPaddingDips = (padding.left + padding.right) / density;\n        float yPaddingDips = (padding.top + padding.bottom) / density;\n\n        int minWidth = (int) (widgetWidthDips - xPaddingDips);\n        int maxWidth = (int) (widgetWidthDips - xPaddingDips + .5f);\n\n        int minHeight = (int) (widgetHeightDips - yPaddingDips);\n        int maxHeight = (int) (widgetHeightDips - yPaddingDips + .5f);\n\n        Bundle oldOpt = null;\n        try {\n            oldOpt = mAppWidgetManager.getAppWidgetOptions(rec.appWidgetId);\n        } catch (Exception e) {\n            Log.e(TAG, \"getAppWidgetOptions(\" + rec.appWidgetId + \") \" + appWidgetInfo.provider, e);\n        }\n\n        if (oldOpt == null\n            || minWidth != oldOpt.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH)\n            || minHeight != oldOpt.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT)\n            || maxWidth != oldOpt.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH)\n            || maxHeight != oldOpt.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT)) {\n\n            final Bundle opt;\n            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {\n                opt = oldOpt != null ? oldOpt.deepCopy() : new Bundle();\n            } else {\n                opt = oldOpt != null ? new Bundle(oldOpt) : new Bundle();\n            }\n\n            opt.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, minWidth);\n            opt.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, minHeight);\n            opt.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, maxWidth);\n            opt.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, maxHeight);\n\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {\n                ArrayList<SizeF> sizes = new ArrayList<>();\n                sizes.add(new SizeF(widgetWidthDips, widgetHeightDips));\n\n                opt.putParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES, sizes);\n            }\n\n            // send update\n            hostView.updateAppWidgetOptions(opt);\n        }\n    }\n\n    private void addWidgetToLayout(AppWidgetHostView hostView, AppWidgetProviderInfo appWidgetInfo, WidgetRecord rec) {\n        View placeholder = mLayout.getPlaceholder(appWidgetInfo.provider);\n        WidgetLayout.PageLayoutParams params = null;\n        if (placeholder != null)\n            params = (WidgetLayout.PageLayoutParams) placeholder.getLayoutParams();\n        if (params == null) {\n            params = new WidgetLayout.PageLayoutParams(rec.width, rec.height);\n            params.leftMargin = rec.left;\n            params.topMargin = rec.top;\n            params.screenPage = rec.screen;\n            params.placement = WidgetLayout.PageLayoutParams.Placement.MARGIN_TL_AS_POSITION;\n        }\n        hostView.setMinimumWidth(appWidgetInfo.minWidth);\n        hostView.setMinimumHeight(appWidgetInfo.minHeight);\n\n        hostView.setAppWidget(rec.appWidgetId, appWidgetInfo);\n        updateAppWidgetOptions(hostView, appWidgetInfo, rec);\n\n        hostView.setOnLongClickListener(v -> {\n            if (v instanceof WidgetView) {\n                ListPopup menu = getConfigPopup((WidgetView) v);\n                TBApplication.getApplication(v.getContext()).registerPopup(menu);\n                menu.show(v, 0.f);\n                return true;\n            }\n            return false;\n        });\n\n        // replace placeholder (if it exists) with the widget\n        {\n            int insertPosition = mLayout.indexOfChild(placeholder);\n            if (insertPosition != -1)\n                mLayout.removeViewAt(insertPosition);\n            mLayout.addView(hostView, insertPosition, params);\n        }\n\n        Context context = mLayout.getContext();\n        // remove from `mPlaceholders`\n        {\n            for (Iterator<PlaceholderWidgetRecord> iterator = mPlaceholders.iterator(); iterator.hasNext(); ) {\n                PlaceholderWidgetRecord placeholderWidget = iterator.next();\n                if (placeholderWidget.provider.equals(appWidgetInfo.provider)) {\n                    DBHelper.removeWidgetPlaceholder(context, INVALID_WIDGET_ID, placeholderWidget.provider.flattenToString());\n                    iterator.remove();\n                }\n            }\n        }\n    }\n\n    private void addPlaceholderToLayout(@NonNull PlaceholderWidgetRecord rec) {\n        final Context context = mLayout.getContext();\n        Drawable preview = DrawableUtils.getBitmapDrawable(context, rec.preview);\n\n        View placeholder = LayoutInflater.from(context).inflate(R.layout.widget_placeholder, mLayout, false);\n        {\n            WidgetLayout.PageLayoutParams params = new WidgetLayout.PageLayoutParams(rec.width, rec.height);\n            params.leftMargin = rec.left;\n            params.topMargin = rec.top;\n            params.screenPage = rec.screen;\n            params.placement = WidgetLayout.PageLayoutParams.Placement.MARGIN_TL_AS_POSITION;\n            placeholder.setLayoutParams(params);\n        }\n        {\n            TextView text = placeholder.findViewById(android.R.id.text1);\n            text.setText(context.getString(R.string.widget_placeholder, rec.name));\n        }\n        {\n            ImageView icon = placeholder.findViewById(android.R.id.icon);\n            icon.setImageDrawable(preview);\n        }\n        final ComponentName provider = rec.provider != null ? rec.provider : new ComponentName(\"null\", \"null\");\n        placeholder.setOnClickListener(v -> {\n            Activity activity = Utilities.getActivity(v);\n            if (activity == null)\n                return;\n\n            int appWidgetId = mAppWidgetHost.allocateAppWidgetId();\n            boolean bindAllowed;\n            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {\n                UserHandleCompat userHandle = UserHandleCompat.CURRENT_USER;\n                bindAllowed = mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, userHandle.getRealHandle(), provider, null);\n            } else {\n                bindAllowed = mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, provider);\n            }\n\n            Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);\n            intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);\n            if (bindAllowed) {\n                createWidget(activity, intent);\n                configureWidget(activity, intent);\n            } else {\n                intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, provider);\n                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n                    UserHandleCompat userHandle = UserHandleCompat.CURRENT_USER;\n                    intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE, userHandle.getRealHandle());\n                }\n                requestBindWidget(intent);\n            }\n            //Toast.makeText(activity, provider.flattenToString(), Toast.LENGTH_SHORT).show();\n        });\n        placeholder.setOnLongClickListener(placeholderView -> {\n            Activity activity = Utilities.getActivity(placeholderView);\n            if (activity == null)\n                return false;\n            TextView text = placeholderView.findViewById(android.R.id.text1);\n            final CharSequence placeholderName = text != null ? text.getText() : \"null\";\n            ContextThemeWrapper ctxDialog = new ContextThemeWrapper(activity, R.style.TitleDialogTheme);\n            new AlertDialog.Builder(ctxDialog)\n                .setTitle(R.string.widget_placeholder_remove)\n                .setMessage(placeholderName + \"\\n\" + provider.flattenToShortString())\n                .setPositiveButton(android.R.string.ok, (dialog, which) -> {\n                    mLayout.removeView(placeholderView);\n                    for (Iterator<PlaceholderWidgetRecord> iterator = mPlaceholders.iterator(); iterator.hasNext(); ) {\n                        PlaceholderWidgetRecord placeholderWidgetRecord = iterator.next();\n                        if (provider.equals(placeholderWidgetRecord.provider)) {\n                            DBHelper.removeWidgetPlaceholder(activity, INVALID_WIDGET_ID, placeholderWidgetRecord.provider.flattenToString());\n                            iterator.remove();\n                        }\n                    }\n                    dialog.dismiss();\n                })\n                .setNegativeButton(android.R.string.cancel, (dialog, which) -> dialog.dismiss())\n                .show();\n            return true;\n        });\n\n        mLayout.addPlaceholder(placeholder, provider);\n    }\n\n    /**\n     * Launches the menu to select the widget. The selected widget will be on\n     * the result of the activity.\n     */\n    public void showSelectWidget(AppCompatActivity activity) {\n        Intent pickIntent = new Intent(activity, PickAppWidgetActivity.class);\n        int appWidgetId = mAppWidgetHost.allocateAppWidgetId();\n        pickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);\n        widgetPickerResult.launch(pickIntent);\n    }\n\n    /**\n     * This avoids a bug in the com.android.settings.AppWidgetPickActivity,\n     * which is used to select widgets. This just adds empty extras to the\n     * intent, avoiding the bug.\n     * <p>\n     * See more: http://code.google.com/p/android/issues/detail?id=4272\n     */\n    public static void addEmptyData(Intent pickIntent) {\n        ArrayList<AppWidgetProviderInfo> customInfo = new ArrayList<>(1);\n        pickIntent.putParcelableArrayListExtra(AppWidgetManager.EXTRA_CUSTOM_INFO, customInfo);\n        ArrayList<Bundle> customExtras = new ArrayList<>(1);\n        pickIntent.putParcelableArrayListExtra(AppWidgetManager.EXTRA_CUSTOM_EXTRAS, customExtras);\n    }\n\n    private void requestBindWidget(@NonNull Intent data) {\n        Bundle extras = data.getExtras();\n        if (extras == null)\n            return;\n        final int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, INVALID_WIDGET_ID);\n        final ComponentName provider = extras.getParcelable(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER);\n        final UserHandle profile;\n        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {\n            profile = extras.getParcelable(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE);\n        } else {\n            profile = null;\n        }\n\n        new Handler().postDelayed(() -> {\n            Log.d(TAG, \"asking for permission\");\n\n            Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);\n            intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);\n            intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, provider);\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n                intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE, profile);\n            }\n            addEmptyData(intent);\n\n            widgetBindResult.launch(intent);\n        }, 500);\n    }\n\n    /**\n     * Checks if the widget needs any configuration. If it needs, launches the\n     * configuration activity.\n     */\n    private void configureWidget(Activity activity, Intent data) {\n        Bundle extras = data != null ? data.getExtras() : null;\n        if (extras == null)\n            return;\n        int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, INVALID_WIDGET_ID);\n        AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);\n\n        boolean canConfigure = appWidgetInfo.configure != null;\n        boolean shouldConfigure = true;\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {\n            if ((appWidgetInfo.widgetFeatures & AppWidgetProviderInfo.WIDGET_FEATURE_CONFIGURATION_OPTIONAL) != 0) {\n                shouldConfigure = false;\n            }\n        }\n\n        /* See https://stackoverflow.com/a/40269593\n         * If you use the AppWidgetManager.ACTION_APPWIDGET_PICK intent to pick the intent from the chooser displayed by the Android OS, there is no need to bind as the framework automatically binds the widget.\n         * If you implement a custom chooser (for example, something which shows the preview images of widgets which is implemented in lots of custom launchers), then binding is necessary.\n         */\n//        boolean hasPermission = mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, appWidgetInfo.provider);\n//        if (!hasPermission) {\n//            Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);\n//            intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);\n//            intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, appWidgetInfo.provider);\n//            activity.startActivityForResult(intent, REQUEST_PICK_APPWIDGET/*REQUEST_BIND*/);\n//        }\n\n        if (canConfigure && shouldConfigure) {\n            Log.d(TAG, \"configureWidget \" + appWidgetInfo.configure);\n            launchConfigureWidgetActivity(activity, appWidgetId, appWidgetInfo);\n        }\n    }\n\n    private void launchConfigureWidgetActivity(Activity activity, int appWidgetId, AppWidgetProviderInfo appWidgetInfo) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            startConfigActivity(appWidgetId, activity, REQUEST_CONFIGURE_APPWIDGET);\n        } else {\n            Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE);\n            intent.setComponent(appWidgetInfo.configure);\n            intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);\n            try {\n                activity.startActivityForResult(intent, REQUEST_CONFIGURE_APPWIDGET);\n            } catch (SecurityException e) {\n                Log.e(TAG, \"ACTION_APPWIDGET_CONFIGURE\", e);\n                Toast.makeText(activity, e.getMessage(), Toast.LENGTH_LONG).show();\n            }\n        }\n    }\n\n    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)\n    public void startConfigActivity(int widgetId, Activity activity, int requestCode) {\n        final int flags = 0;\n        final Bundle options;\n        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {\n            options = ActivityOptions\n                .makeBasic()\n                .setPendingIntentBackgroundActivityStartMode(ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)\n                .toBundle();\n        } else {\n            options = null;\n        }\n        try {\n            mAppWidgetHost.startAppWidgetConfigureActivityForResult(activity, widgetId, flags, requestCode, options);\n        } catch (ActivityNotFoundException | SecurityException e) {\n            Toast.makeText(activity, e.getMessage(), Toast.LENGTH_LONG).show();\n        }\n    }\n\n    /**\n     * Creates the widget and adds it to our view layout.\n     */\n    public void createWidget(Activity activity, Intent data) {\n        Bundle extras = data != null ? data.getExtras() : null;\n        if (extras == null)\n            return;\n        int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, INVALID_WIDGET_ID);\n        AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);\n        if (appWidgetInfo == null || mWidgets.containsKey(appWidgetId))\n            return;\n        AppWidgetHostView hostView = mAppWidgetHost.createView(activity.getApplicationContext(), appWidgetId, appWidgetInfo);\n\n        WidgetRecord rec = new WidgetRecord();\n        rec.appWidgetId = appWidgetId;\n        rec.width = Math.max(appWidgetInfo.minWidth, appWidgetInfo.minResizeWidth);\n        rec.height = Math.max(appWidgetInfo.minHeight, appWidgetInfo.minResizeHeight);\n\n        final int screenWidth = DeviceUtils.getScreenWidth(activity);\n        final int screenHeight = DeviceUtils.getScreenHeight(activity);\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {\n            // emulate a cell grid with n×(n+1) cells\n            final int targetCellWidth = Math.max(1, appWidgetInfo.targetCellWidth);\n            final int targetCellHeight = Math.max(1, appWidgetInfo.targetCellHeight);\n            int gridWidth = Math.min(4, targetCellWidth);\n            int gridHeight = Math.min(5, targetCellHeight);\n            if (gridWidth > (gridHeight - 1)) {\n                gridHeight = gridWidth + 1;\n            } else if (gridWidth < (gridHeight - 1)) {\n                gridWidth = gridHeight - 1;\n            }\n            final int cellWidth = screenWidth / gridWidth;\n            final int cellHeight = screenHeight / gridHeight;\n            rec.width = Math.max(rec.width, targetCellWidth * cellWidth);\n            rec.height = Math.max(rec.height, targetCellHeight * cellHeight);\n        } else {\n            // emulate a cell grid with 4×5 cells\n            rec.width = Math.max(rec.width, screenWidth / 4);\n            rec.height = Math.max(rec.height, screenHeight / 5);\n        }\n\n        DBHelper.addWidget(activity, rec);\n        mWidgets.put(rec.appWidgetId, rec);\n\n        addWidgetToLayout(hostView, appWidgetInfo, rec);\n    }\n\n    /**\n     * Removes the widget displayed by this AppWidgetHostView.\n     */\n    public void removeWidget(AppWidgetHostView hostView) {\n        final int appWidgetId = hostView.getAppWidgetId();\n        mLayout.removeWidget(hostView);\n\n        mAppWidgetHost.deleteAppWidgetId(appWidgetId);\n        DBHelper.removeWidget(mLayout.getContext(), appWidgetId);\n        mWidgets.remove(appWidgetId);\n    }\n\n    public void removeWidget(int appWidgetId) {\n        mLayout.removeWidget(appWidgetId);\n\n        mAppWidgetHost.deleteAppWidgetId(appWidgetId);\n        DBHelper.removeWidget(mLayout.getContext(), appWidgetId);\n        mWidgets.remove(appWidgetId);\n    }\n\n    public void updateWidgetSize(AppWidgetHostView hostView, int width, int height) {\n        int appWidgetId = hostView.getAppWidgetId();\n        AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);\n        WidgetRecord rec = new WidgetRecord(mWidgets.get(appWidgetId));\n        rec.width = width;\n        rec.height = height;\n        updateAppWidgetOptions(hostView, appWidgetInfo, rec);\n    }\n\n    /**\n     * Called from the main activity, should return true to stop further processing of this result\n     * If the user has selected an widget, the result will be in the 'data' when this function is called.\n     *\n     * @param activity    The activity that received the result\n     * @param requestCode The integer request code originally supplied to\n     *                    startActivityForResult(), allowing you to identify who this\n     *                    result came from.\n     * @param resultCode  The integer result code returned by the child activity\n     *                    through its setResult().\n     * @param data        An Intent, which can return result data to the caller\n     *                    (various data can be attached to Intent \"extras\").\n     * @return if this result was processed here\n     */\n    public boolean onActivityResult(@NonNull Activity activity, int requestCode, int resultCode, @Nullable Intent data) {\n        switch (resultCode) {\n            case Activity.RESULT_OK:\n                if (requestCode == REQUEST_CONFIGURE_APPWIDGET) {\n                    createWidget(activity, data);\n                    return true;\n                }\n                break;\n            case Activity.RESULT_CANCELED:\n                if (data != null) {\n                    int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, INVALID_WIDGET_ID);\n                    if (appWidgetId != INVALID_WIDGET_ID) {\n                        removeWidget(appWidgetId);\n                        return true;\n                    }\n                }\n                break;\n        }\n        return false;\n    }\n\n    public int widgetCount() {\n        return mWidgets.size() + mPlaceholders.size();\n    }\n\n    /**\n     * A popup with all active widgets to choose one\n     *\n     * @return the menu\n     */\n    public ListPopup getWidgetListPopup(@StringRes int title) {\n        Context ctx = mLayout.getContext();\n        LinearAdapter adapter = new LinearAdapterPlus();\n\n        adapter.add(new LinearAdapter.ItemTitle(ctx, title));\n\n        for (WidgetRecord rec : mWidgets.values()) {\n            adapter.add(WidgetPopupItem.create(ctx, mAppWidgetManager, rec.appWidgetId));\n        }\n\n        for (PlaceholderWidgetRecord placeholder : mPlaceholders) {\n            adapter.add(PlaceholderPopupItem.create(ctx, placeholder));\n        }\n\n        return ListPopup.create(ctx, adapter);\n    }\n\n    /**\n     * Popup with options for all widgets\n     *\n     * @param activity used to start the widget select popup\n     * @return the popup menu\n     */\n    public ListPopup getConfigPopup(AppCompatActivity activity) {\n        LinearAdapter adapter = new LinearAdapter();\n\n        adapter.add(new LinearAdapter.ItemTitle(activity, R.string.menu_widget_title));\n        adapter.add(new LinearAdapter.Item(activity, R.string.menu_widget_add));\n        if (widgetCount() > 0) {\n            adapter.add(new LinearAdapter.Item(activity, R.string.menu_widget_configure));\n            adapter.add(new LinearAdapter.Item(activity, R.string.menu_widget_setup));\n            adapter.add(new LinearAdapter.Item(activity, R.string.menu_widget_remove));\n        }\n        adapter.add(new LinearAdapter.Item(activity, R.string.menu_popup_launcher_settings));\n\n        ListPopup menu = ListPopup.create(activity, adapter);\n        menu.setOnItemClickListener((a, v, pos) -> {\n            LinearAdapter.MenuItem item = ((LinearAdapter) a).getItem(pos);\n            @StringRes int stringId = 0;\n            if (item instanceof LinearAdapter.Item) {\n                stringId = ((LinearAdapter.Item) a.getItem(pos)).stringId;\n            }\n            if (stringId == R.string.menu_widget_add) {\n                TBApplication.widgetManager(activity).showSelectWidget(activity);\n            } else if (stringId == R.string.menu_widget_configure) {\n                ListPopup configWidgetPopup = TBApplication.widgetManager(activity).getWidgetListPopup(R.string.menu_widget_configure);\n                configWidgetPopup.setOnItemClickListener((a1, v1, pos1) -> {\n                    Object item1 = a1.getItem(pos1);\n                    if (item1 instanceof WidgetPopupItem) {\n                        AppWidgetHostView widgetView = mLayout.getWidget(((WidgetPopupItem) item1).appWidgetId);\n                        if (widgetView == null)\n                            return;\n                        ListPopup popup = getConfigPopup((WidgetView) widgetView);\n                        TBApplication.getApplication(mLayout.getContext()).registerPopup(popup);\n                        popup.show(widgetView, 0.f);\n                    } else if (item1 instanceof PlaceholderPopupItem) {\n                        PlaceholderWidgetRecord placeholder = ((PlaceholderPopupItem) item1).placeholder;\n                        View placeholderView = mLayout.getPlaceholder(placeholder.provider);\n                        if (placeholderView != null)\n                            placeholderView.performClick();\n                    }\n                });\n\n                TBApplication.getApplication(activity).registerPopup(configWidgetPopup);\n                configWidgetPopup.showCenter(activity.getWindow().getDecorView());\n            } else if (stringId == R.string.menu_widget_setup) {\n                ListPopup configWidgetPopup = TBApplication.widgetManager(activity).getWidgetListPopup(R.string.menu_widget_setup);\n                configWidgetPopup.setOnItemClickListener((a1, v1, pos1) -> {\n                    Object item1 = a1.getItem(pos1);\n                    if (item1 instanceof WidgetPopupItem) {\n                        AppWidgetHostView widgetView = mLayout.getWidget(((WidgetPopupItem) item1).appWidgetId);\n                        if (widgetView == null)\n                            return;\n                        launchConfigureWidgetActivity(activity, widgetView.getAppWidgetId(), widgetView.getAppWidgetInfo());\n                    } else if (item1 instanceof PlaceholderPopupItem) {\n                        PlaceholderWidgetRecord placeholder = ((PlaceholderPopupItem) item1).placeholder;\n                        View placeholderView = mLayout.getPlaceholder(placeholder.provider);\n                        if (placeholderView != null)\n                            placeholderView.performClick();\n                    }\n                });\n\n                TBApplication.getApplication(activity).registerPopup(configWidgetPopup);\n                configWidgetPopup.showCenter(activity.getWindow().getDecorView());\n            } else if (stringId == R.string.menu_widget_remove) {\n                showRemoveWidgetPopup();\n            } else if (stringId == R.string.menu_popup_launcher_settings) {\n                var intent = new Intent(Utilities.getActivity(mLayout), SettingsActivity.class);\n                Utilities.setIntentSourceBounds(intent, v);\n                Bundle startActivityOptions = Utilities.makeStartActivityOptions(v);\n                mLayout.postDelayed(() -> {\n                    var act = Utilities.getActivity(mLayout);\n                    if (act == null)\n                        return;\n                    try {\n                        act.startActivity(intent, startActivityOptions);\n                    } catch (ActivityNotFoundException ignored) {\n                        // ignored\n                    }\n                }, Behaviour.LAUNCH_DELAY);\n            }\n        });\n        return menu;\n    }\n\n    public void showRemoveWidgetPopup() {\n        Context context = mLayout.getContext();\n        ListPopup removeWidgetPopup = TBApplication.widgetManager(context).getWidgetListPopup(R.string.menu_widget_remove);\n        removeWidgetPopup.setOnItemClickListener((a1, v1, pos1) -> {\n            Object item1 = a1.getItem(pos1);\n            if (item1 instanceof WidgetPopupItem) {\n                removeWidget(((WidgetPopupItem) item1).appWidgetId);\n            } else if (item1 instanceof PlaceholderPopupItem) {\n                PlaceholderWidgetRecord placeholder = ((PlaceholderPopupItem) item1).placeholder;\n                View placeholderView = mLayout.getPlaceholder(placeholder.provider);\n                if (placeholderView != null)\n                    placeholderView.performLongClick();\n            }\n        });\n\n        TBApplication.getApplication(context).registerPopup(removeWidgetPopup);\n        removeWidgetPopup.showCenter(mLayout);\n    }\n\n    private static boolean canMoveToPage(WidgetLayout layout, int from, int to) {\n        if (from != WidgetLayout.PageLayoutParams.PAGE_MIDDLE)\n            return to == WidgetLayout.PageLayoutParams.PAGE_MIDDLE;\n        if (layout == null)\n            return false;\n\n        boolean ok = false;\n        if (layout.getVerticalPageCount() > 1)\n            ok = ok || to == WidgetLayout.PageLayoutParams.PAGE_UP || to == WidgetLayout.PageLayoutParams.PAGE_DOWN;\n        if (layout.getHorizontalPageCount() > 1)\n            ok = ok || to == WidgetLayout.PageLayoutParams.PAGE_LEFT || to == WidgetLayout.PageLayoutParams.PAGE_RIGHT;\n        return ok;\n    }\n\n    /**\n     * Popup with options for the widget in the view\n     *\n     * @param view of the widget\n     * @return the popup menu\n     */\n    protected ListPopup getConfigPopup(WidgetView view) {\n        final int appWidgetId = view.getAppWidgetId();\n        Context ctx = mLayout.getContext();\n        LinearAdapter adapter = new LinearAdapter();\n\n        WidgetRecord widget = mWidgets.get(appWidgetId);\n        if (widget != null) {\n            addConfigPopupItems(mLayout, adapter, view, widget);\n        } else {\n            adapter.add(new LinearAdapter.ItemString(\"ERROR: Not found\"));\n        }\n\n        ListPopup menu = ListPopup.create(ctx, adapter);\n        menu.setOnItemClickListener((a, v, position) -> handleConfigPopupItemClick(a, view, position));\n        return menu;\n    }\n\n    private static void addConfigPopupItems(WidgetLayout widgetLayout, LinearAdapter adapter, WidgetView view, WidgetRecord widget) {\n        Context ctx = widgetLayout.getContext();\n        adapter.add(new LinearAdapter.ItemTitle(getWidgetName(ctx, view.getAppWidgetInfo())));\n        final WidgetLayout.Handle handleType = widgetLayout.getHandleType(view);\n        if (handleType.isMove()) {\n            adapter.add(new WidgetOptionItem(ctx, R.string.cfg_widget_move_switch, WidgetOptionItem.Action.MOVE_SWITCH));\n            adapter.add(new WidgetOptionItem(ctx, R.string.cfg_widget_move_exit, WidgetOptionItem.Action.RESET));\n        } else {\n            adapter.add(new WidgetOptionItem(ctx, R.string.cfg_widget_move, WidgetOptionItem.Action.MOVE));\n        }\n\n        if (handleType.isResize()) {\n            adapter.add(new WidgetOptionItem(ctx, R.string.cfg_widget_resize_switch, WidgetOptionItem.Action.RESIZE_SWITCH));\n            adapter.add(new WidgetOptionItem(ctx, R.string.cfg_widget_resize_exit, WidgetOptionItem.Action.RESET));\n        } else {\n            adapter.add(new WidgetOptionItem(ctx, R.string.cfg_widget_resize, WidgetOptionItem.Action.RESIZE));\n        }\n\n        if (handleType.isMoveResize()) {\n            adapter.add(new WidgetOptionItem(ctx, R.string.cfg_widget_move_resize, WidgetOptionItem.Action.MOVE_RESIZE_SWITCH));\n            adapter.add(new WidgetOptionItem(ctx, R.string.cfg_widget_move_resize_exit, WidgetOptionItem.Action.RESET));\n        } else {\n            adapter.add(new WidgetOptionItem(ctx, R.string.cfg_widget_move_resize, WidgetOptionItem.Action.MOVE_RESIZE));\n        }\n\n        adapter.add(new LinearAdapter.ItemDivider());\n        final ViewGroup.LayoutParams lp = view.getLayoutParams();\n        if (lp instanceof WidgetLayout.PageLayoutParams) {\n            final int screenPage = ((WidgetLayout.PageLayoutParams) lp).screenPage;\n            if (canMoveToPage(widgetLayout, screenPage, WidgetLayout.PageLayoutParams.PAGE_LEFT))\n                adapter.add(new WidgetOptionItem(ctx, R.string.cfg_widget_screen_left, WidgetOptionItem.Action.MOVE2SCREEN_LEFT));\n            if (canMoveToPage(widgetLayout, screenPage, WidgetLayout.PageLayoutParams.PAGE_UP))\n                adapter.add(new WidgetOptionItem(ctx, R.string.cfg_widget_screen_up, WidgetOptionItem.Action.MOVE2SCREEN_UP));\n            if (canMoveToPage(widgetLayout, screenPage, WidgetLayout.PageLayoutParams.PAGE_MIDDLE))\n                adapter.add(new WidgetOptionItem(ctx, R.string.cfg_widget_screen_middle, WidgetOptionItem.Action.MOVE2SCREEN_MIDDLE));\n            if (canMoveToPage(widgetLayout, screenPage, WidgetLayout.PageLayoutParams.PAGE_RIGHT))\n                adapter.add(new WidgetOptionItem(ctx, R.string.cfg_widget_screen_right, WidgetOptionItem.Action.MOVE2SCREEN_RIGHT));\n            if (canMoveToPage(widgetLayout, screenPage, WidgetLayout.PageLayoutParams.PAGE_DOWN))\n                adapter.add(new WidgetOptionItem(ctx, R.string.cfg_widget_screen_down, WidgetOptionItem.Action.MOVE2SCREEN_DOWN));\n            adapter.add(new WidgetOptionItem(ctx, R.string.cfg_widget_back, WidgetOptionItem.Action.MOVE_BELOW));\n            adapter.add(new WidgetOptionItem(ctx, R.string.cfg_widget_front, WidgetOptionItem.Action.MOVE_ABOVE));\n        }\n\n        adapter.add(new WidgetOptionItem(ctx, R.string.cfg_widget_remove, WidgetOptionItem.Action.REMOVE));\n\n        if (DebugInfo.widgetInfo(ctx)) {\n            adapter.add(new LinearAdapter.ItemTitle(\"Debug info\"));\n            adapter.add(new LinearAdapter.ItemString(\"Name: \" + getWidgetName(ctx, view.getAppWidgetInfo())));\n            adapter.add(new LinearAdapter.ItemText(widget.packedProperties()));\n            adapter.add(new LinearAdapter.ItemString(\"ID: \" + widget.appWidgetId));\n        }\n    }\n\n    private void handleConfigPopupItemClick(ListAdapter adapter, WidgetView view, int position) {\n        Object item = adapter.getItem(position);\n        if (item instanceof WidgetOptionItem) {\n            switch (((WidgetOptionItem) item).mAction) {\n                case MOVE:\n                    view.setOnClickListener(v1 -> {\n                        view.setOnClickListener(null);\n                        view.setOnDoubleClickListener(null);\n                        mLayout.disableHandle(view);\n                        saveWidgetProperties(view);\n                    });\n                    view.setOnDoubleClickListener(v1 -> {\n                        if (mLayout.getHandleType(view) == WidgetLayout.Handle.MOVE_FREE) {\n                            mLastMoveType = WidgetLayout.Handle.MOVE_AXIAL;\n                        } else {\n                            mLastMoveType = WidgetLayout.Handle.MOVE_FREE;\n                        }\n                        mLayout.enableHandle(view, mLastMoveType);\n                    });\n                    mLayout.enableHandle(view, mLastMoveType);\n                    break;\n                case MOVE_SWITCH:\n                    if (mLayout.getHandleType(view) == WidgetLayout.Handle.MOVE_FREE) {\n                        mLastMoveType = WidgetLayout.Handle.MOVE_AXIAL;\n                    } else {\n                        mLastMoveType = WidgetLayout.Handle.MOVE_FREE;\n                    }\n                    mLayout.enableHandle(view, mLastMoveType);\n                    break;\n                case RESIZE:\n                    view.setOnClickListener(v1 -> {\n                        view.setOnClickListener(null);\n                        view.setOnDoubleClickListener(null);\n                        mLayout.disableHandle(view);\n                        saveWidgetProperties(view);\n                    });\n                    view.setOnDoubleClickListener(v1 -> {\n                        if (mLayout.getHandleType(view) == WidgetLayout.Handle.RESIZE_DIAGONAL) {\n                            mLastResizeType = WidgetLayout.Handle.RESIZE_AXIAL;\n                        } else {\n                            mLastResizeType = WidgetLayout.Handle.RESIZE_DIAGONAL;\n                        }\n                        mLayout.enableHandle(view, mLastResizeType);\n                    });\n                    mLayout.enableHandle(view, mLastResizeType);\n                    break;\n                case RESIZE_SWITCH:\n                    if (mLayout.getHandleType(view) == WidgetLayout.Handle.RESIZE_DIAGONAL) {\n                        mLastResizeType = WidgetLayout.Handle.RESIZE_AXIAL;\n                    } else {\n                        mLastResizeType = WidgetLayout.Handle.RESIZE_DIAGONAL;\n                    }\n                    mLayout.enableHandle(view, mLastResizeType);\n                    break;\n                case MOVE_RESIZE:\n                    view.setOnClickListener(v1 -> {\n                        view.setOnClickListener(null);\n                        view.setOnDoubleClickListener(null);\n                        mLayout.disableHandle(view);\n                        saveWidgetProperties(view);\n                    });\n                    view.setOnDoubleClickListener(v1 -> {\n                        if (mLayout.getHandleType(view) == WidgetLayout.Handle.MOVE_FREE_RESIZE_AXIAL) {\n                            mLastMoveResizeType = WidgetLayout.Handle.RESIZE_DIAGONAL_MOVE_AXIAL;\n                        } else {\n                            mLastMoveResizeType = WidgetLayout.Handle.MOVE_FREE_RESIZE_AXIAL;\n                        }\n                        mLayout.enableHandle(view, mLastMoveResizeType);\n                    });\n                    mLayout.enableHandle(view, mLastMoveResizeType);\n                case MOVE_RESIZE_SWITCH:\n                    if (mLayout.getHandleType(view) == WidgetLayout.Handle.MOVE_FREE_RESIZE_AXIAL) {\n                        mLastMoveResizeType = WidgetLayout.Handle.RESIZE_DIAGONAL_MOVE_AXIAL;\n                    } else {\n                        mLastMoveResizeType = WidgetLayout.Handle.MOVE_FREE_RESIZE_AXIAL;\n                    }\n                    mLayout.enableHandle(view, mLastMoveResizeType);\n                    break;\n                case RESET:\n                    view.setOnClickListener(null);\n                    view.setOnDoubleClickListener(null);\n                    mLayout.disableHandle(view);\n                    saveWidgetProperties(view);\n                    break;\n                case REMOVE:\n                    removeWidget(view);\n                    break;\n                case MOVE2SCREEN_LEFT: {\n                    final WidgetLayout.PageLayoutParams lp = (WidgetLayout.PageLayoutParams) view.getLayoutParams();\n                    lp.screenPage = WidgetLayout.PageLayoutParams.PAGE_LEFT;\n                    view.setLayoutParams(lp);\n                    saveWidgetProperties(view);\n                    break;\n                }\n                case MOVE2SCREEN_UP: {\n                    final WidgetLayout.PageLayoutParams lp = (WidgetLayout.PageLayoutParams) view.getLayoutParams();\n                    lp.screenPage = WidgetLayout.PageLayoutParams.PAGE_UP;\n                    view.setLayoutParams(lp);\n                    saveWidgetProperties(view);\n                    break;\n                }\n                case MOVE2SCREEN_RIGHT: {\n                    final WidgetLayout.PageLayoutParams lp = (WidgetLayout.PageLayoutParams) view.getLayoutParams();\n                    lp.screenPage = WidgetLayout.PageLayoutParams.PAGE_RIGHT;\n                    view.setLayoutParams(lp);\n                    saveWidgetProperties(view);\n                    break;\n                }\n                case MOVE2SCREEN_DOWN: {\n                    final WidgetLayout.PageLayoutParams lp = (WidgetLayout.PageLayoutParams) view.getLayoutParams();\n                    lp.screenPage = WidgetLayout.PageLayoutParams.PAGE_DOWN;\n                    view.setLayoutParams(lp);\n                    saveWidgetProperties(view);\n                    break;\n                }\n                case MOVE2SCREEN_MIDDLE: {\n                    final WidgetLayout.PageLayoutParams lp = (WidgetLayout.PageLayoutParams) view.getLayoutParams();\n                    lp.screenPage = WidgetLayout.PageLayoutParams.PAGE_MIDDLE;\n                    view.setLayoutParams(lp);\n                    saveWidgetProperties(view);\n                    break;\n                }\n                case MOVE_ABOVE: {\n                    int idx = mLayout.indexOfChild(view);\n                    mLayout.removeViewAt(idx);\n                    mLayout.addView(view);\n                    saveWidgetProperties(view);\n                    break;\n                }\n                case MOVE_BELOW: {\n                    int idx = mLayout.indexOfChild(view);\n                    mLayout.removeViewAt(idx);\n                    mLayout.addView(view, 0);\n                    saveWidgetProperties(view);\n                    break;\n                }\n            }\n        }\n    }\n\n    private void saveWidgetProperties(WidgetView view) {\n        final int appWidgetId = view.getAppWidgetId();\n        mLayout.addOnAfterLayoutTask(() -> {\n            WidgetRecord rec = mWidgets.get(appWidgetId);\n            AppWidgetHostView widgetHostView = mLayout.getWidget(appWidgetId);\n            if (rec != null && widgetHostView != null) {\n                rec.saveProperties(widgetHostView);\n                Utilities.runAsync(() -> DBHelper.setWidgetProperties(mLayout.getContext(), rec));\n\n                AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);\n                if (appWidgetInfo != null)\n                    updateAppWidgetOptions(widgetHostView, appWidgetInfo, rec);\n            }\n        });\n        mLayout.requestLayout();\n    }\n\n    public void setPageCount(int horizontal, int vertical) {\n        if (mLayout == null)\n            return;\n        mLayout.setPageCount(horizontal, vertical);\n    }\n\n    /**\n     * Scroll to page, just like the wallpaper\n     *\n     * @param scrollX horizontal scroll position 0.f .. 1.f\n     * @param scrollY vertical scroll position 0.f .. 1.f\n     */\n    public void scroll(float scrollX, float scrollY) {\n        if (mLayout == null)\n            return;\n\n        final int pageCountX = mLayout.getHorizontalPageCount();\n        final float pageX = pageCountX * scrollX;\n\n        final int pageCountY = mLayout.getVerticalPageCount();\n        final float pageY = pageCountY * scrollY;\n\n        mLayout.scrollToPage(pageX, pageY);\n    }\n\n    @Nullable\n    public static AppWidgetProviderInfo getWidgetProviderInfo(@NonNull Context ctx, int appWidgetId) {\n        AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(ctx);\n        AppWidgetProviderInfo info;\n        try {\n            info = appWidgetManager.getAppWidgetInfo(appWidgetId);\n        } catch (Exception ignored) {\n            return null;\n        }\n        return info;\n    }\n\n    @Nullable\n    public AppWidgetProviderInfo getWidgetProviderInfo(int appWidgetId) {\n        AppWidgetProviderInfo info;\n        try {\n            info = mAppWidgetManager.getAppWidgetInfo(appWidgetId);\n        } catch (Exception ignored) {\n            return null;\n        }\n        return info;\n    }\n\n    @NonNull\n    public static String getWidgetName(@NonNull Context ctx, @Nullable AppWidgetProviderInfo info) {\n        String name = null;\n        if (info != null) {\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n                name = info.loadLabel(ctx.getPackageManager());\n            } else {\n                name = info.label;\n            }\n        }\n        return name == null ? \"[null]\" : name;\n    }\n\n    @WorkerThread\n    @NonNull\n    public static Drawable getWidgetPreview(@NonNull Context context, @NonNull AppWidgetProviderInfo info) {\n        Drawable preview = null;\n        final int density = context.getResources().getDisplayMetrics().densityDpi;\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            preview = info.loadPreviewImage(context, density);\n        }\n        if (preview != null)\n            return preview;\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            preview = info.loadIcon(context, density);\n        }\n        if (preview != null)\n            return preview;\n\n        Resources resources = null;\n        try {\n            resources = context.getPackageManager().getResourcesForApplication(info.provider.getPackageName());\n        } catch (PackageManager.NameNotFoundException e) {\n            Log.w(TAG, \"getResourcesForApplication \" + info.provider.getPackageName(), e);\n        }\n        if (resources != null) {\n            try {\n                preview = ResourcesCompat.getDrawableForDensity(resources, info.previewImage, density, null);\n            } catch (Resources.NotFoundException ignored) {\n                //ignored\n            }\n            if (preview != null)\n                return preview;\n\n            try {\n                preview = ResourcesCompat.getDrawableForDensity(resources, info.icon, density, null);\n            } catch (Resources.NotFoundException ignored) {\n                //ignored\n            }\n            if (preview != null)\n                return preview;\n        }\n\n        UserHandleCompat userHandle = UserHandleCompat.CURRENT_USER;\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            userHandle = new UserHandleCompat(context, info.getProfile());\n        }\n        var icon = TBApplication.iconsHandler(context).getIconForPackage(info.provider, userHandle);\n        return icon.getDrawable();\n    }\n\n    static class WidgetOptionItem extends LinearAdapter.Item {\n        enum Action {\n            MOVE, MOVE_SWITCH,\n            RESIZE, RESIZE_SWITCH,\n            MOVE_RESIZE, MOVE_RESIZE_SWITCH,\n            RESET,\n            REMOVE,\n            MOVE2SCREEN_LEFT, MOVE2SCREEN_UP, MOVE2SCREEN_MIDDLE, MOVE2SCREEN_RIGHT, MOVE2SCREEN_DOWN,\n            MOVE_BELOW, MOVE_ABOVE,\n        }\n\n        final Action mAction;\n\n        public WidgetOptionItem(Context ctx, @StringRes int stringId, Action action) {\n            super(ctx, stringId);\n            mAction = action;\n        }\n    }\n\n    static class PlaceholderPopupItem extends LinearAdapterPlus.ItemStringIcon {\n        final PlaceholderWidgetRecord placeholder;\n\n        @NonNull\n        static PlaceholderPopupItem create(Context ctx, PlaceholderWidgetRecord placeholder) {\n            Drawable icon = DrawableUtils.getBitmapDrawable(ctx, placeholder.preview);\n            String name = ctx.getString(R.string.widget_placeholder, placeholder.name);\n            return new PlaceholderPopupItem(placeholder, name, icon);\n        }\n\n        private PlaceholderPopupItem(@NonNull PlaceholderWidgetRecord placeholder, @NonNull String name, Drawable icon) {\n            super(name, icon);\n            this.placeholder = placeholder;\n        }\n\n        @Override\n        public int getLayoutResource() {\n            return R.layout.popup_list_item_icon;\n        }\n    }\n\n    static class WidgetPopupItem extends LinearAdapterPlus.ItemStringIcon {\n        int appWidgetId;\n\n        @NonNull\n        static WidgetPopupItem create(Context ctx, AppWidgetManager appWidgetManager, int appWidgetId) {\n            AppWidgetProviderInfo info = appWidgetManager.getAppWidgetInfo(appWidgetId);\n            String name = getWidgetName(ctx, info);\n            //TODO: make preview icon loading async\n            Drawable icon = getWidgetPreview(ctx, info);\n            return new WidgetPopupItem(name, appWidgetId, icon);\n        }\n\n        private WidgetPopupItem(@NonNull String string, int appWidgetId, @NonNull Drawable icon) {\n            super(string, icon);\n            this.appWidgetId = appWidgetId;\n        }\n\n        @Override\n        public int getLayoutResource() {\n            return R.layout.popup_list_item_icon;\n        }\n    }\n\n    static class WidgetHost extends AppWidgetHost {\n        public WidgetHost(Context context) {\n            super(context, APPWIDGET_HOST_ID);\n        }\n\n        @Override\n        protected AppWidgetHostView onCreateView(Context context, int appWidgetId, AppWidgetProviderInfo appWidget) {\n            return new WidgetView(context);\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/rocks/tbog/tblauncher/widgets/WidgetView.java",
    "content": "package rocks.tbog.tblauncher.widgets;\n\nimport android.annotation.SuppressLint;\nimport android.appwidget.AppWidgetHostView;\nimport android.content.Context;\nimport android.util.Log;\nimport android.view.GestureDetector;\nimport android.view.MotionEvent;\n\nimport androidx.annotation.Nullable;\nimport androidx.core.view.GestureDetectorCompat;\n\npublic class WidgetView extends AppWidgetHostView {\n    private static final String TAG = \"WdgView\";\n    private final GestureDetectorCompat gestureDetector;\n    private boolean mIntercepted = false;\n    private boolean mJustIntercepted = false;\n    private boolean mSendCancel = false;\n    private boolean mLongClickCalled = false;\n    private OnClickListener mOnClickListener = null;\n    private OnClickListener mOnDoubleClickListener = null;\n    private OnLongClickListener mOnLongClickListener = null;\n\n    public WidgetView(Context context) {\n        super(context);\n        //TODO: make WidgetView implement OnGestureListener and get rid of onGestureListener\n        GestureDetector.SimpleOnGestureListener onGestureListener = new GestureDetector.SimpleOnGestureListener() {\n            @Override\n            public boolean onSingleTapUp(MotionEvent e) {\n                // if we have a double tap listener, wait for onSingleTapConfirmed\n                if (mOnDoubleClickListener != null)\n                    return true;\n                if (mOnClickListener != null) {\n                    mOnClickListener.onClick(WidgetView.this);\n                    return true;\n                }\n                return false;\n            }\n\n            @Override\n            public boolean onSingleTapConfirmed(MotionEvent e) {\n                // if we have both a double tap and click, handle click here\n                if (mOnClickListener != null && mOnDoubleClickListener != null) {\n                    mOnClickListener.onClick(WidgetView.this);\n                    return true;\n                }\n                return false;\n            }\n\n            @Override\n            public void onLongPress(MotionEvent e) {\n                if (mOnLongClickListener != null) {\n                    mLongClickCalled = true;\n                    mOnLongClickListener.onLongClick(WidgetView.this);\n                }\n            }\n\n            @Override\n            public boolean onDoubleTapEvent(MotionEvent e) {\n                //Log.d(TAG, \"onDoubleTapEvent \" + e);\n                if (mOnDoubleClickListener != null) {\n                    final int act = e.getActionMasked();\n                    if (act == MotionEvent.ACTION_UP)\n                        mOnDoubleClickListener.onClick(WidgetView.this);\n                    return true;\n                }\n                return false;\n            }\n\n            @Override\n            public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {\n                //Log.d(TAG, \"onScroll mSendCancel = true\");\n                //mSendCancel = true;\n                //TBApplication.liveWallpaper(context).scroll(e1, e2);\n                //return true;\n\n                return false;\n            }\n\n            @Override\n            public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {\n//                Log.d(TAG, \"onFling mSendCancel = true\" +\n//                        String.format(\"\\r\\nvelocity( %.2f, %.2f )\", velocityX, velocityY) +\n//                        String.format(\"\\r\\ndown pos ( %.2f, %.2f )\", e1.getX(), e1.getY()) +\n//                        String.format(\"\\r\\n up  pos ( %.2f, %.2f )\", e2.getX(), e2.getY())\n//                        );\n//                mSendCancel = true;\n//                return true;\n\n                return false;\n            }\n        };\n        gestureDetector = new GestureDetectorCompat(context, onGestureListener);\n    }\n\n    @Override\n    public void setOnLongClickListener(@Nullable OnLongClickListener listener) {\n        gestureDetector.setIsLongpressEnabled(listener != null);\n        mOnLongClickListener = listener;\n    }\n\n    @Override\n    public void setOnClickListener(@Nullable OnClickListener listener) {\n        mOnClickListener = listener;\n    }\n\n    public void setOnDoubleClickListener(@Nullable OnClickListener listener) {\n        mOnDoubleClickListener = listener;\n    }\n\n    @Override\n    public boolean onInterceptTouchEvent(MotionEvent event) {\n        Log.d(TAG, \"onInterceptTouchEvent\\r\\n\" + event + \"\\r\\nmIntercepted = \" + mIntercepted);\n        if (event.getPointerCount() != 1)\n            return false;\n        final int act = event.getActionMasked();\n        switch (act) {\n            case MotionEvent.ACTION_DOWN:\n                // Throw away all previous state when starting a new touch gesture.\n                // The framework may have dropped the up or cancel event for the previous gesture\n                // due to an app switch, ANR, or some other state change.\n                mIntercepted = false;\n                mJustIntercepted = false;\n                mSendCancel = false;\n                mLongClickCalled = false;\n                break;\n            case MotionEvent.ACTION_UP:\n                if (mLongClickCalled) {\n                    mLongClickCalled = false;\n                    return mJustIntercepted = true;\n                }\n                break;\n        }\n\n        if (mIntercepted)\n            return true;\n        if (gestureDetector.onTouchEvent(event)) {\n            Log.d(TAG, \"mJustIntercepted = \" + true);\n            return mJustIntercepted = true;\n        }\n\n        Log.d(TAG, \"super.onInterceptTouchEvent\");\n        return super.onInterceptTouchEvent(event);\n    }\n\n    @SuppressLint(\"ClickableViewAccessibility\")\n    @Override\n    public boolean onTouchEvent(MotionEvent event) {\n        Log.d(TAG, \"onTouchEvent\\t\\n\" + event +\n                \"\\r\\nmJustIntercepted \" + mJustIntercepted +\n                \" mIntercepted \" + mIntercepted);\n        // first call after the intercept can be ignored\n        if (mJustIntercepted) {\n            mJustIntercepted = false;\n            mIntercepted = true;\n            Log.d(TAG, \"mJustIntercepted = \" + false);\n            return true;\n        }\n        if (event.getPointerCount() != 1)\n            return false;\n        if (gestureDetector.onTouchEvent(event))\n            return true;\n\n        // if we intercepted this gesture, handle all touch events\n        boolean handled = mIntercepted;\n\n        if (mSendCancel) {\n            handled = true;\n            event.setAction(MotionEvent.ACTION_CANCEL);\n        }\n\n        Log.d(TAG, \"super.onTouchEvent\");\n        if (super.onTouchEvent(event)) {\n            Log.d(TAG, \"mIntercepted = \" + false);\n            mIntercepted = false;\n            handled = true;\n        } else {\n            // if no child view handled this event, send cancel to gestureDetector\n            MotionEvent cancel = MotionEvent.obtainNoHistory(event);\n            cancel.setAction(MotionEvent.ACTION_CANCEL);\n            Log.d(TAG, \"gestureDetector CANCEL\");\n            gestureDetector.onTouchEvent(cancel);\n        }\n        return handled;\n    }\n\n    @Override\n    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {\n        // deny this request\n        //super.requestDisallowInterceptTouchEvent(disallowIntercept);\n    }\n}\n"
  },
  {
    "path": "app/src/main/res/anim/popup_in_bottom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <scale\n\n        android:duration=\"@android:integer/config_shortAnimTime\"\n        android:fromXScale=\"0.3\"\n        android:fromYScale=\"0.3\"\n        android:pivotX=\"99%\"\n        android:pivotY=\"99%\"\n        android:toXScale=\"1.0\"\n        android:toYScale=\"1.0\" />\n\n    <translate\n        android:duration=\"@android:integer/config_shortAnimTime\"\n        android:fromXDelta=\"20%\"\n        android:fromYDelta=\"10%\"\n        android:toXDelta=\"0%\"\n        android:toYDelta=\"0%\" />\n\n    <alpha\n        android:duration=\"@android:integer/config_shortAnimTime\"\n        android:fromAlpha=\"0.0\"\n        android:interpolator=\"@android:anim/decelerate_interpolator\"\n        android:toAlpha=\"1.0\" />\n</set>"
  },
  {
    "path": "app/src/main/res/anim/popup_in_top.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <scale\n\n        android:duration=\"@android:integer/config_shortAnimTime\"\n        android:fromXScale=\"0.3\"\n        android:fromYScale=\"0.3\"\n        android:pivotX=\"99%\"\n        android:pivotY=\"1%\"\n        android:toXScale=\"1.0\"\n        android:toYScale=\"1.0\" />\n\n    <translate\n        android:duration=\"@android:integer/config_shortAnimTime\"\n        android:fromXDelta=\"20%\"\n        android:fromYDelta=\"-10%\"\n        android:toXDelta=\"0%\"\n        android:toYDelta=\"0%\" />\n\n    <alpha\n        android:duration=\"@android:integer/config_shortAnimTime\"\n        android:fromAlpha=\"0.0\"\n        android:interpolator=\"@android:anim/decelerate_interpolator\"\n        android:toAlpha=\"1.0\" />\n</set>"
  },
  {
    "path": "app/src/main/res/anim/popup_out.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <scale\n        android:duration=\"@android:integer/config_shortAnimTime\"\n        android:fromXScale=\"1.0\"\n        android:fromYScale=\"1.0\"\n        android:pivotX=\"50%\"\n        android:pivotY=\"50%\"\n        android:toXScale=\"0.9\"\n        android:toYScale=\"0.9\" />\n\n    <alpha\n        android:duration=\"@android:integer/config_shortAnimTime\"\n        android:fromAlpha=\"1.0\"\n        android:interpolator=\"@android:anim/decelerate_interpolator\"\n        android:toAlpha=\"0.0\" />\n</set>"
  },
  {
    "path": "app/src/main/res/color/accent_text_selector.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/colorPrimaryDark\" android:state_enabled=\"false\" />\n    <item android:color=\"@color/colorAccent\" />\n</selector>"
  },
  {
    "path": "app/src/main/res/color/accent_text_selector_black.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/Black_selector_primary_disable\" android:state_enabled=\"false\" />\n    <item android:color=\"@color/Black_selector_primary\" />\n</selector>"
  },
  {
    "path": "app/src/main/res/color/accent_text_selector_deep_blues.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/DeepBlues_2\" android:state_enabled=\"false\" />\n    <item android:color=\"@color/DeepBlues_4\" />\n</selector>"
  },
  {
    "path": "app/src/main/res/color/accent_text_selector_white.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:color=\"#7f000000\" android:state_enabled=\"false\" />\n    <item android:color=\"#ff000000\" />\n</selector>"
  },
  {
    "path": "app/src/main/res/color/primary_text_selector_darkbg.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:color=\"#7Fffffff\" android:state_enabled=\"false\" />\n    <item android:color=\"#FFffffff\" />\n</selector>"
  },
  {
    "path": "app/src/main/res/color/primary_text_selector_lightbg.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:color=\"#FF000000\" android:state_checkable=\"true\" />\n    <item android:color=\"#7F000000\" android:state_enabled=\"false\" />\n    <item android:color=\"#FF000000\" />\n</selector>"
  },
  {
    "path": "app/src/main/res/color/secondary_text_selector_darkbg.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:color=\"#7F77aa77\" android:state_enabled=\"false\" />\n    <item android:color=\"#FF77aa77\" />\n</selector>"
  },
  {
    "path": "app/src/main/res/color/settings_primary_selector_black.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/Black_selector_primary_disable\" android:state_enabled=\"false\" />\n    <item android:color=\"@color/Black_selector_primary\" />\n</selector>"
  },
  {
    "path": "app/src/main/res/color/settings_primary_selector_darkbg.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:color=\"#7FE0FFE0\" android:state_enabled=\"false\" />\n    <item android:color=\"#FFE0FFE0\" />\n</selector>"
  },
  {
    "path": "app/src/main/res/color/settings_primary_selector_deep_blues.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/DeepBlues_selector_primary_disable\" android:state_enabled=\"false\" />\n    <item android:color=\"@color/DeepBlues_selector_primary\" />\n</selector>"
  },
  {
    "path": "app/src/main/res/color/settings_primary_selector_default.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/Default_selector_primary_disable\" android:state_enabled=\"false\" />\n    <item android:color=\"@color/Default_selector_primary\" />\n</selector>"
  },
  {
    "path": "app/src/main/res/color/settings_primary_selector_lightbg.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:color=\"#7F505050\" android:state_enabled=\"false\" />\n    <item android:color=\"#FF505050\" />\n</selector>"
  },
  {
    "path": "app/src/main/res/color/settings_secondary_selector_black.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/Black_selector_secondary_disable\" android:state_enabled=\"false\" />\n    <item android:color=\"@color/Black_selector_secondary\" />\n</selector>"
  },
  {
    "path": "app/src/main/res/color/settings_secondary_selector_deep_blues.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/DeepBlues_selector_secondary_disable\" android:state_enabled=\"false\" />\n    <item android:color=\"@color/DeepBlues_selector_secondary\" />\n</selector>"
  },
  {
    "path": "app/src/main/res/color/settings_secondary_selector_default.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/Default_selector_secondary_disable\" android:state_enabled=\"false\" />\n    <item android:color=\"@color/Default_selector_secondary\" />\n</selector>"
  },
  {
    "path": "app/src/main/res/color/settings_secondary_selector_lightbg.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:color=\"#7F9e9e9e\" android:state_enabled=\"false\" />\n    <item android:color=\"#FF9e9e9e\" />\n</selector>"
  },
  {
    "path": "app/src/main/res/drawable/button_bar_background.xml",
    "content": "<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item\n            android:bottom=\"@dimen/dialog_margin_vertical\"\n            android:left=\"@dimen/dialog_margin_horizontal\"\n            android:right=\"@dimen/dialog_margin_horizontal\">\n        <shape>\n            <gradient\n                    android:angle=\"90\"\n                    android:endColor=\"#00000000\"\n                    android:startColor=\"#FF000000\"\n                    android:type=\"linear\" />\n        </shape>\n    </item>\n</layer-list>"
  },
  {
    "path": "app/src/main/res/drawable/button_bar_background_deep_blues.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item\n        android:bottom=\"2dp\"\n        android:left=\"2dp\"\n        android:right=\"2dp\">\n        <shape>\n            <gradient\n                android:angle=\"90\"\n                android:centerColor=\"@color/DeepBlues_gradient_center\"\n                android:endColor=\"@color/DeepBlues_gradient_finish\"\n                android:startColor=\"@color/DeepBlues_gradient_start\"\n                android:type=\"linear\" />\n            <corners\n                android:bottomLeftRadius=\"@dimen/result_corner_radius\"\n                android:bottomRightRadius=\"@dimen/result_corner_radius\" />\n        </shape>\n    </item>\n</layer-list>"
  },
  {
    "path": "app/src/main/res/drawable/button_bar_background_default.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item\n        android:bottom=\"2dp\"\n        android:left=\"2dp\"\n        android:right=\"2dp\">\n        <shape>\n            <gradient\n                android:angle=\"90\"\n                android:centerColor=\"#A81e1e28\"\n                android:endColor=\"#001e1e28\"\n                android:startColor=\"#FF1e1e28\"\n                android:type=\"linear\" />\n            <corners\n                android:bottomLeftRadius=\"@dimen/result_corner_radius\"\n                android:bottomRightRadius=\"@dimen/result_corner_radius\" />\n        </shape>\n    </item>\n</layer-list>"
  },
  {
    "path": "app/src/main/res/drawable/button_bar_background_light.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item\n        android:bottom=\"2dp\"\n        android:left=\"2dp\"\n        android:right=\"2dp\">\n        <shape>\n            <gradient\n                android:angle=\"90\"\n                android:centerColor=\"#A8ffffff\"\n                android:endColor=\"#00ffffff\"\n                android:startColor=\"#FFffffff\"\n                android:type=\"linear\" />\n            <corners\n                android:bottomLeftRadius=\"@dimen/result_corner_radius\"\n                android:bottomRightRadius=\"@dimen/result_corner_radius\" />\n        </shape>\n    </item>\n</layer-list>"
  },
  {
    "path": "app/src/main/res/drawable/dialog_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!-- fake the dialog margin with padding -->\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item\n        android:bottom=\"@dimen/dialog_margin_vertical\"\n        android:left=\"@dimen/dialog_margin_horizontal\"\n        android:right=\"@dimen/dialog_margin_horizontal\"\n        android:top=\"@dimen/dialog_margin_vertical\">\n        <shape>\n            <solid android:color=\"@android:color/transparent\" />\n            <stroke\n                android:width=\"2dp\"\n                android:color=\"@color/colorAccent\" />\n            <padding\n                android:bottom=\"1dp\"\n                android:left=\"1dp\"\n                android:right=\"1dp\"\n                android:top=\"1dp\" />\n        </shape>\n    </item>\n    <item\n        android:bottom=\"@dimen/dialog_margin_vertical\"\n        android:left=\"@dimen/dialog_margin_horizontal\"\n        android:right=\"@dimen/dialog_margin_horizontal\"\n        android:top=\"@dimen/dialog_margin_vertical\">\n        <shape>\n            <solid android:color=\"@color/black_overlay\" />\n            <padding\n                android:bottom=\"3dp\"\n                android:left=\"3dp\"\n                android:right=\"3dp\"\n                android:top=\"3dp\" />\n        </shape>\n    </item>\n</layer-list>"
  },
  {
    "path": "app/src/main/res/drawable/dialog_background_black.xml",
    "content": "<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item\n        android:bottom=\"4dp\"\n        android:left=\"4dp\"\n        android:right=\"4dp\"\n        android:top=\"4dp\">\n        <shape>\n            <solid android:color=\"@android:color/black\" />\n            <stroke\n                android:width=\"2dp\"\n                android:color=\"@color/Black_selector_primary_disable\" />\n            <corners android:radius=\"@dimen/result_corner_radius\" />\n            <padding\n                android:bottom=\"3dp\"\n                android:left=\"3dp\"\n                android:right=\"3dp\"\n                android:top=\"3dp\" />\n        </shape>\n    </item>\n</layer-list>\n"
  },
  {
    "path": "app/src/main/res/drawable/dialog_background_dark.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!-- fake the dialog margin with padding -->\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item\n        android:bottom=\"@dimen/result_margin_vertical\"\n        android:left=\"@dimen/result_margin_horizontal\"\n        android:right=\"@dimen/result_margin_horizontal\"\n        android:top=\"@dimen/result_margin_vertical\">\n        <shape>\n            <solid android:color=\"@android:color/transparent\" />\n            <stroke\n                android:width=\"2dp\"\n                android:color=\"@color/colorAccent\" />\n            <corners android:radius=\"@dimen/result_corner_radius\" />\n            <padding\n                android:bottom=\"1dp\"\n                android:left=\"1dp\"\n                android:right=\"1dp\"\n                android:top=\"1dp\" />\n        </shape>\n    </item>\n    <item\n        android:bottom=\"@dimen/result_margin_vertical\"\n        android:left=\"@dimen/result_margin_horizontal\"\n        android:right=\"@dimen/result_margin_horizontal\"\n        android:top=\"@dimen/result_margin_vertical\">\n        <shape>\n            <solid android:color=\"@color/black_overlay\" />\n            <corners android:radius=\"@dimen/result_corner_radius\" />\n            <padding\n                android:bottom=\"1dp\"\n                android:left=\"1dp\"\n                android:right=\"1dp\"\n                android:top=\"1dp\" />\n        </shape>\n    </item>\n</layer-list>"
  },
  {
    "path": "app/src/main/res/drawable/dialog_background_deep_blues.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!-- fake the dialog margin with padding -->\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item\n        android:bottom=\"4dp\"\n        android:left=\"4dp\"\n        android:right=\"4dp\"\n        android:top=\"4dp\">\n        <shape>\n            <solid android:color=\"@android:color/transparent\" />\n            <stroke\n                android:width=\"2dp\"\n                android:color=\"@color/DeepBlues_4\" />\n            <corners android:radius=\"@dimen/result_corner_radius\" />\n        </shape>\n    </item>\n    <item\n        android:bottom=\"5dp\"\n        android:left=\"5dp\"\n        android:right=\"5dp\"\n        android:top=\"5dp\">\n        <shape>\n            <solid android:color=\"@color/DeepBlues_window\" />\n            <corners android:radius=\"@dimen/result_corner_radius\" />\n            <padding\n                android:bottom=\"3dp\"\n                android:left=\"3dp\"\n                android:right=\"3dp\"\n                android:top=\"3dp\" />\n        </shape>\n    </item>\n</layer-list>\n"
  },
  {
    "path": "app/src/main/res/drawable/dialog_background_default.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!-- fake the dialog margin with padding -->\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item\n        android:bottom=\"@dimen/dialog_margin_vertical\"\n        android:left=\"@dimen/dialog_margin_horizontal\"\n        android:right=\"@dimen/dialog_margin_horizontal\"\n        android:top=\"@dimen/dialog_margin_vertical\">\n        <shape>\n            <solid android:color=\"@android:color/transparent\" />\n            <stroke\n                android:width=\"2dp\"\n                android:color=\"@color/Default_dark\" />\n            <corners android:radius=\"@dimen/result_corner_radius\" />\n            <padding\n                android:bottom=\"1dp\"\n                android:left=\"1dp\"\n                android:right=\"1dp\"\n                android:top=\"1dp\" />\n        </shape>\n    </item>\n    <item\n        android:bottom=\"@dimen/dialog_margin_vertical\"\n        android:left=\"@dimen/dialog_margin_horizontal\"\n        android:right=\"@dimen/dialog_margin_horizontal\"\n        android:top=\"@dimen/dialog_margin_vertical\">\n        <shape>\n            <solid android:color=\"@color/Default_background\" />\n            <corners android:radius=\"@dimen/result_corner_radius\" />\n            <padding\n                android:bottom=\"3dp\"\n                android:left=\"3dp\"\n                android:right=\"3dp\"\n                android:top=\"3dp\" />\n        </shape>\n    </item>\n</layer-list>"
  },
  {
    "path": "app/src/main/res/drawable/dialog_background_light.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!-- fake the dialog margin with padding -->\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item\n        android:bottom=\"@dimen/dialog_margin_vertical\"\n        android:left=\"@dimen/dialog_margin_horizontal\"\n        android:right=\"@dimen/dialog_margin_horizontal\"\n        android:top=\"@dimen/dialog_margin_vertical\">\n        <shape>\n            <solid android:color=\"@android:color/transparent\" />\n            <stroke\n                android:width=\"2dp\"\n                android:color=\"@android:color/black\" />\n            <corners android:radius=\"@dimen/result_corner_radius\" />\n            <padding\n                android:bottom=\"1dp\"\n                android:left=\"1dp\"\n                android:right=\"1dp\"\n                android:top=\"1dp\" />\n        </shape>\n    </item>\n    <item\n        android:bottom=\"@dimen/dialog_margin_vertical\"\n        android:left=\"@dimen/dialog_margin_horizontal\"\n        android:right=\"@dimen/dialog_margin_horizontal\"\n        android:top=\"@dimen/dialog_margin_vertical\">\n        <shape>\n            <solid android:color=\"@color/white_overlay\" />\n            <corners android:radius=\"@dimen/result_corner_radius\" />\n            <padding\n                android:bottom=\"3dp\"\n                android:left=\"3dp\"\n                android:right=\"3dp\"\n                android:top=\"3dp\" />\n        </shape>\n    </item>\n</layer-list>\n"
  },
  {
    "path": "app/src/main/res/drawable/handle_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n    <item\n        tools:height=\"50dp\"\n        tools:width=\"50dp\">\n        <shape android:shape=\"rectangle\">\n            <solid android:color=\"@android:color/transparent\" />\n        </shape>\n    </item>\n    <item android:gravity=\"left\">\n        <shape android:shape=\"rectangle\">\n            <size android:width=\"10dp\" />\n            <gradient\n                android:angle=\"0\"\n                android:endColor=\"#00000000\"\n                android:startColor=\"@android:color/black\"\n                android:type=\"linear\" />\n        </shape>\n    </item>\n    <item android:gravity=\"top\">\n        <shape android:shape=\"rectangle\">\n            <size android:height=\"10dp\" />\n            <gradient\n                android:angle=\"-90\"\n                android:endColor=\"#00000000\"\n                android:startColor=\"@android:color/black\"\n                android:type=\"linear\" />\n        </shape>\n    </item>\n    <item android:gravity=\"right\">\n        <shape android:shape=\"rectangle\">\n            <size android:width=\"10dp\" />\n            <gradient\n                android:angle=\"180\"\n                android:endColor=\"#00000000\"\n                android:startColor=\"@android:color/black\"\n                android:type=\"linear\" />\n        </shape>\n    </item>\n    <item android:gravity=\"bottom\">\n        <shape android:shape=\"rectangle\">\n            <size android:height=\"10dp\" />\n            <gradient\n                android:angle=\"90\"\n                android:endColor=\"#00000000\"\n                android:startColor=\"@android:color/black\"\n                android:type=\"linear\" />\n        </shape>\n    </item>\n</layer-list>"
  },
  {
    "path": "app/src/main/res/drawable/ic_add_tag.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"12dp\"\n    android:height=\"12dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:fillType=\"evenOdd\"\n        android:pathData=\"\n        M17.63,5.84C17.27,5.33 16.67,5 16,5L5,5.01C3.9,5.01 3,5.9 3,7v10c0,1.1 0.9,1.99 2,1.99L16,19c0.67,0 1.27,-0.33 1.63,-0.84L22,12l-4.37,-6.16z\n        M10,8 v8 a1,1 0 0 0 2,0 v-8 a1,1 0 0 0 -2,0z\n        M7,11 h8 a1,1 0 0 1 0,2 h-8 a1,1 0 0 1 0,-2z\n        M10,11 h2 v2 h-2z\n\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_android.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M6,18c0,0.55 0.45,1 1,1h1v3.5c0,0.83 0.67,1.5 1.5,1.5s1.5,-0.67 1.5,-1.5L11,19h2v3.5c0,0.83 0.67,1.5 1.5,1.5s1.5,-0.67 1.5,-1.5L16,19h1c0.55,0 1,-0.45 1,-1L18,8L6,8v10zM3.5,8C2.67,8 2,8.67 2,9.5v7c0,0.83 0.67,1.5 1.5,1.5S5,17.33 5,16.5v-7C5,8.67 4.33,8 3.5,8zM20.5,8c-0.83,0 -1.5,0.67 -1.5,1.5v7c0,0.83 0.67,1.5 1.5,1.5s1.5,-0.67 1.5,-1.5v-7c0,-0.83 -0.67,-1.5 -1.5,-1.5zM15.53,2.16l1.3,-1.3c0.2,-0.2 0.2,-0.51 0,-0.71 -0.2,-0.2 -0.51,-0.2 -0.71,0l-1.48,1.48C13.85,1.23 12.95,1 12,1c-0.96,0 -1.86,0.23 -2.66,0.63L7.85,0.15c-0.2,-0.2 -0.51,-0.2 -0.71,0 -0.2,0.2 -0.2,0.51 0,0.71l1.31,1.31C6.97,3.26 6,5.01 6,7h12c0,-1.99 -0.97,-3.75 -2.47,-4.84zM10,5L9,5L9,4h1v1zM15,5h-1L14,4h1v1z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_apps.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"512\"\n    android:viewportHeight=\"512\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"m128,456v-344h-16v344c0,22.1 17.9,40 40,40h184v-16h-184c-13.2,0 -24,-10.8 -24,-24z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"m216,448h80v16h-80z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"m144,168c0,4.4 3.6,8 8,8h8.7c1.7,4.7 6.1,8 11.3,8s9.6,-3.3 11.3,-8h17.4c1.7,4.7 6.1,8 11.3,8s9.6,-3.3 11.3,-8h8.7c4.4,0 8,-3.6 8,-8v-24c0,-3 -1.7,-5.8 -4.4,-7.2l-13.6,-6.8 -6.8,-13.6c-1.4,-2.7 -4.1,-4.4 -7.2,-4.4h-32c-3,0 -5.8,1.7 -7.2,4.4l-6.8,13.6 -13.6,6.8c-2.7,1.4 -4.4,4.1 -4.4,7.2zM160,148.9 L171.6,143.1c1.5,-0.8 2.8,-2 3.6,-3.6l5.8,-11.6h22.1l5.8,11.6c0.8,1.5 2,2.8 3.6,3.6l11.6,5.8v11.1h-64v-11.1z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"m336,104v10.6l-11,-8.8c-2.9,-2.3 -7.1,-2.3 -10,0l-40,32c-2.7,2.1 -3.7,5.7 -2.6,8.9s4.2,5.4 7.5,5.4v24c0,4.4 3.6,8 8,8h64c4.4,0 8,-3.6 8,-8v-24c3.4,0 6.4,-2.1 7.5,-5.4 1.1,-3.2 0.1,-6.8 -2.6,-8.9l-13,-10.4v-23.4zM344,144v24h-16v-16h-16v16h-16v-24c0,-0.8 -0.1,-1.6 -0.3,-2.3l24.3,-19.5 24.3,19.5c-0.2,0.7 -0.3,1.5 -0.3,2.3z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"m351.6,234.6 l5.3,-10.6h11.1v-16h-16c-3,0 -5.8,1.7 -7.2,4.4l-5.8,11.6h-59c-2.4,0 -4.7,1.1 -6.2,2.9s-2.1,4.3 -1.7,6.6l8,40c0.3,1.4 1,2.7 1.9,3.8 -1.3,1.9 -2,4.2 -2,6.7 0,6.6 5.4,12 12,12s12,-5.4 12,-12c0,-1.4 -0.3,-2.7 -0.7,-4h17.4c-0.4,1.3 -0.7,2.6 -0.7,4 0,6.6 5.4,12 12,12s12,-5.4 12,-12c0,-2.5 -0.8,-4.8 -2,-6.7 0.9,-1 1.6,-2.3 1.9,-3.8zM334.2,240 L329.4,264h-34.9l-4.8,-24z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"m144,352c0,4.4 3.6,8 8,8h8v40c0,4.4 3.6,8 8,8s8,-3.6 8,-8v-8h40v8c0,4.4 3.6,8 8,8s8,-3.6 8,-8v-19.1l4.4,2.2 7.2,-14.3 -13.9,-7c-3.8,-8.2 -12.1,-13.8 -21.7,-13.8h-19l-0.3,-2c-1.4,-10.3 -10.3,-18 -20.7,-18 -4.4,0 -8,3.6 -8,8v1.4l-9.5,1.8c-3.8,0.7 -6.5,4 -6.5,7.9zM176,361.3c1.5,1.7 3.6,2.7 6,2.7h26c4.4,0 8,3.6 8,8v4h-40z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"m445.8,312.5 l-45.8,-51.5v-205c0,-22.1 -17.9,-40 -40,-40h-208c-22.1,0 -40,17.9 -40,40v40h16v-40c0,-13.2 10.8,-24 24,-24h208c13.2,0 24,10.8 24,24v208,76.7l-26.1,-26.1c-6.9,-6.9 -16,-10.6 -25.7,-10.6 -20,0 -36.3,16.3 -36.3,36.3 0,9.7 3.8,18.8 10.6,25.7l2.1,2.1h-12.6v-16h-16v24c0,4.4 3.6,8 8,8h19.1l-10.2,20.4 14.3,7.2 13.7,-27.4 24.8,24.8c-13,21.3 -14.3,47.7 -2.9,70.4l5.3,10.5v6.1h16v-8c0,-1.2 -0.3,-2.5 -0.8,-3.6l-6.1,-12.2c-9.7,-19.3 -7.6,-42.1 5.4,-59.4 2.4,-3.2 2.1,-7.6 -0.7,-10.5l-47.7,-47.7c-3.8,-3.8 -5.9,-8.9 -5.9,-14.3 0,-11.2 9.1,-20.3 20.3,-20.3 5.4,0 10.5,2.1 14.3,5.9l39.7,39.7 24,24 11.3,-11.3 -21.9,-21.7v-71.7l33.9,38.1c9.1,10.3 14.1,23.5 14.1,37.2v135.7h16v-135.7c0,-17.6 -6.5,-34.6 -18.2,-47.8z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"m144,64h16v16h-16z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"m176,64h16v16h-16z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"m208,64h16v16h-16z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"m200,288v-24h24v-16h-24v-24h-16v24h-24v16h24v24z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_apps_grid_az.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"512\"\n    android:viewportHeight=\"512\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M34,450v-344h-16v344c0,22.1 17.9,40 40,40h184v-16h-184c-13.2,0 -24,-10.8 -24,-24z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M122,442h80v16h-80z\" />\n    <!-- Car -->\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M50,162c0,4.4 3.6,8 8,8h8.7c1.7,4.7 6.1,8 11.3,8s9.6,-3.3 11.3,-8h17.4c1.7,4.7 6.1,8 11.3,8s9.6,-3.3 11.3,-8h8.7c4.4,0 8,-3.6 8,-8v-24c0,-3 -1.7,-5.8 -4.4,-7.2l-13.6,-6.8l-6.8,-13.6c-1.4,-2.7 -4.1,-4.4 -7.2,-4.4h-32c-3,0 -5.8,1.7 -7.2,4.4l-6.8,13.6l-13.6,6.8c-2.7,1.4 -4.4,4.1 -4.4,7.2zM66,142.9l11.6,-5.8c1.5,-0.8 2.8,-2 3.6,-3.6l5.8,-11.6h22.1l5.8,11.6c0.8,1.5 2,2.8 3.6,3.6l11.6,5.8v11.1h-64v-11.1z\" />\n    <!-- House -->\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M242,98v10.6l-11,-8.8c-2.9,-2.3 -7.1,-2.3 -10,0l-40,32c-2.7,2.1 -3.7,5.7 -2.6,8.9s4.2,5.4 7.5,5.4v24c0,4.4 3.6,8 8,8h64c4.4,0 8,-3.6 8,-8v-24c3.4,0 6.4,-2.1 7.5,-5.4c1.1,-3.2 0.1,-6.8 -2.6,-8.9l-13,-10.4v-23.4zM250,138v24h-16v-16h-16v16h-16v-24c0,-0.8 -0.1,-1.6 -0.3,-2.3l24.3,-19.5l24.3,19.5c-0.2,0.7 -0.3,1.5 -0.3,2.3z\" />\n    <!-- Shopping cart -->\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M257.6,228.6l5.3,-10.6h11.1v-16h-16c-3,0 -5.8,1.7 -7.2,4.4l-5.8,11.6h-59c-2.4,0 -4.7,1.1 -6.2,2.9s-2.1,4.3 -1.7,6.6l8,40c0.3,1.4 1,2.7 1.9,3.8c-1.3,1.9 -2,4.2 -2,6.7c0,6.6 5.4,12 12,12s12,-5.4 12,-12c0,-1.4 -0.3,-2.7 -0.7,-4h17.4c-0.4,1.3 -0.7,2.6 -0.7,4c0,6.6 5.4,12 12,12s12,-5.4 12,-12c0,-2.5 -0.8,-4.8 -2,-6.7c0.9,-1 1.6,-2.3 1.9,-3.8zM240.2,234l-4.8,24h-34.9l-4.8,-24z\" />\n    <!-- Couch -->\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M50,346c0,4.4 3.6,8 8,8h8v40c0,4.4 3.6,8 8,8s8,-3.6 8,-8v-8h40v8c0,4.4 3.6,8 8,8s8,-3.6 8,-8v-19.1l4.4,2.2l7.2,-14.3l-13.9,-7c-3.8,-8.2 -12.1,-13.8 -21.7,-13.8h-19l-0.3,-2c-1.4,-10.3 -10.3,-18 -20.7,-18c-4.4,0 -8,3.6 -8,8v1.4l-9.5,1.8c-3.8,0.7 -6.5,4 -6.5,7.9zM82,355.3c1.5,1.7 3.6,2.7 6,2.7h26c4.4,0 8,3.6 8,8v4h-40z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M351.8,306.5l-45.8,-51.5v-205c0,-22.1 -17.9,-40 -40,-40h-208c-22.1,0 -40,17.9 -40,40v40h16v-40c0,-13.2 10.8,-24 24,-24h208c13.2,0 24,10.8 24,24v208v76.7l-26.1,-26.1c-6.9,-6.9 -16,-10.6 -25.7,-10.6c-20,0 -36.3,16.3 -36.3,36.3c0,9.7 3.8,18.8 10.6,25.7l2.1,2.1h-12.6v-16h-16v24c0,4.4 3.6,8 8,8h19.1l-10.2,20.4l14.3,7.2l13.7,-27.4l24.8,24.8c-13,21.3 -14.3,47.7 -2.9,70.4l5.3,10.5v6.1h16v-8c0,-1.2 -0.3,-2.5 -0.8,-3.6l-6.1,-12.2c-9.7,-19.3 -7.6,-42.1 5.4,-59.4c2.4,-3.2 2.1,-7.6 -0.7,-10.5l-47.7,-47.7c-3.8,-3.8 -5.9,-8.9 -5.9,-14.3c0,-11.2 9.1,-20.3 20.3,-20.3c5.4,0 10.5,2.1 14.3,5.9l39.7,39.7l24,24l11.3,-11.3l-21.9,-21.7v-71.7l33.9,38.1c9.1,10.3 14.1,23.5 14.1,37.2v135.7h16v-135.7c0,-17.6 -6.5,-34.6 -18.2,-47.8z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M50,58h16v16h-16z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M82,58h16v16h-16z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M114,58h16v16h-16z\" />\n    <!-- Plus -->\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M106,282v-24h24v-16h-24v-24h-16v24h-24v16h24v24z\" />\n    <!-- letter A -->\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M384.31,43.8c-5,13.4 -16.1,42.9 -24.6,65.7c-8.6,22.7 -15.6,41.5 -15.6,41.7c0,0.2 9.4,0.4 20.8,0.4l20.9,-0l1.2,-4.3c0.7,-2.3 2.2,-7.2 3.3,-11l2,-6.7l23.1,0.2l23.1,0.3l3.4,10.8l3.3,10.7l21.2,-0.2c13.4,-0.2 21.1,-0.7 21,-1.3c-0.2,-1.4 -48.1,-128.4 -48.8,-129.5c-0.4,-0.6 -9.7,-1 -22.9,-1l-22.3,-0l-9.1,24.2zM423.11,78.5l6.7,22.1l-14.3,-0l-14.3,-0l0.9,-2.8c0.5,-1.5 3.4,-11 6.5,-21.2c6.9,-22.9 6.4,-21.4 7.1,-20.7c0.3,0.3 3.6,10.5 7.4,22.6z\" />\n    <!-- letter Z -->\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M360.41,178l0,14l32.7,0.2l32.8,0.3l-36.8,38.2l-36.8,38.3l0.3,13.2l0.3,13.3l62.8,0.3l62.8,0.2l-0.3,-14.2l-0.3,-14.3l-37.6,-0.5l-37.6,-0.5l36.3,-37.9l36.4,-37.9l0,-13.3l0,-13.4l-57.5,-0l-57.5,-0l0,14z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_apps_grid_za.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"512\"\n    android:viewportHeight=\"512\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M34,450v-344h-16v344c0,22.1 17.9,40 40,40h184v-16h-184c-13.2,0 -24,-10.8 -24,-24z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M122,442h80v16h-80z\" />\n    <!-- Car -->\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M50,162c0,4.4 3.6,8 8,8h8.7c1.7,4.7 6.1,8 11.3,8s9.6,-3.3 11.3,-8h17.4c1.7,4.7 6.1,8 11.3,8s9.6,-3.3 11.3,-8h8.7c4.4,0 8,-3.6 8,-8v-24c0,-3 -1.7,-5.8 -4.4,-7.2l-13.6,-6.8l-6.8,-13.6c-1.4,-2.7 -4.1,-4.4 -7.2,-4.4h-32c-3,0 -5.8,1.7 -7.2,4.4l-6.8,13.6l-13.6,6.8c-2.7,1.4 -4.4,4.1 -4.4,7.2zM66,142.9l11.6,-5.8c1.5,-0.8 2.8,-2 3.6,-3.6l5.8,-11.6h22.1l5.8,11.6c0.8,1.5 2,2.8 3.6,3.6l11.6,5.8v11.1h-64v-11.1z\" />\n    <!-- House -->\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M242,98v10.6l-11,-8.8c-2.9,-2.3 -7.1,-2.3 -10,0l-40,32c-2.7,2.1 -3.7,5.7 -2.6,8.9s4.2,5.4 7.5,5.4v24c0,4.4 3.6,8 8,8h64c4.4,0 8,-3.6 8,-8v-24c3.4,0 6.4,-2.1 7.5,-5.4c1.1,-3.2 0.1,-6.8 -2.6,-8.9l-13,-10.4v-23.4zM250,138v24h-16v-16h-16v16h-16v-24c0,-0.8 -0.1,-1.6 -0.3,-2.3l24.3,-19.5l24.3,19.5c-0.2,0.7 -0.3,1.5 -0.3,2.3z\" />\n    <!-- Shopping cart -->\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M257.6,228.6l5.3,-10.6h11.1v-16h-16c-3,0 -5.8,1.7 -7.2,4.4l-5.8,11.6h-59c-2.4,0 -4.7,1.1 -6.2,2.9s-2.1,4.3 -1.7,6.6l8,40c0.3,1.4 1,2.7 1.9,3.8c-1.3,1.9 -2,4.2 -2,6.7c0,6.6 5.4,12 12,12s12,-5.4 12,-12c0,-1.4 -0.3,-2.7 -0.7,-4h17.4c-0.4,1.3 -0.7,2.6 -0.7,4c0,6.6 5.4,12 12,12s12,-5.4 12,-12c0,-2.5 -0.8,-4.8 -2,-6.7c0.9,-1 1.6,-2.3 1.9,-3.8zM240.2,234l-4.8,24h-34.9l-4.8,-24z\" />\n    <!-- Couch -->\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M50,346c0,4.4 3.6,8 8,8h8v40c0,4.4 3.6,8 8,8s8,-3.6 8,-8v-8h40v8c0,4.4 3.6,8 8,8s8,-3.6 8,-8v-19.1l4.4,2.2l7.2,-14.3l-13.9,-7c-3.8,-8.2 -12.1,-13.8 -21.7,-13.8h-19l-0.3,-2c-1.4,-10.3 -10.3,-18 -20.7,-18c-4.4,0 -8,3.6 -8,8v1.4l-9.5,1.8c-3.8,0.7 -6.5,4 -6.5,7.9zM82,355.3c1.5,1.7 3.6,2.7 6,2.7h26c4.4,0 8,3.6 8,8v4h-40z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M351.8,306.5l-45.8,-51.5v-205c0,-22.1 -17.9,-40 -40,-40h-208c-22.1,0 -40,17.9 -40,40v40h16v-40c0,-13.2 10.8,-24 24,-24h208c13.2,0 24,10.8 24,24v208v76.7l-26.1,-26.1c-6.9,-6.9 -16,-10.6 -25.7,-10.6c-20,0 -36.3,16.3 -36.3,36.3c0,9.7 3.8,18.8 10.6,25.7l2.1,2.1h-12.6v-16h-16v24c0,4.4 3.6,8 8,8h19.1l-10.2,20.4l14.3,7.2l13.7,-27.4l24.8,24.8c-13,21.3 -14.3,47.7 -2.9,70.4l5.3,10.5v6.1h16v-8c0,-1.2 -0.3,-2.5 -0.8,-3.6l-6.1,-12.2c-9.7,-19.3 -7.6,-42.1 5.4,-59.4c2.4,-3.2 2.1,-7.6 -0.7,-10.5l-47.7,-47.7c-3.8,-3.8 -5.9,-8.9 -5.9,-14.3c0,-11.2 9.1,-20.3 20.3,-20.3c5.4,0 10.5,2.1 14.3,5.9l39.7,39.7l24,24l11.3,-11.3l-21.9,-21.7v-71.7l33.9,38.1c9.1,10.3 14.1,23.5 14.1,37.2v135.7h16v-135.7c0,-17.6 -6.5,-34.6 -18.2,-47.8z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M50,58h16v16h-16z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M82,58h16v16h-16z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M114,58h16v16h-16z\" />\n    <!-- Plus -->\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M106,282v-24h24v-16h-24v-24h-16v24h-24v16h24v24z\" />\n    <!-- Letter A -->\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M385.31,184.8c-5,13.4 -16.1,42.9 -24.6,65.7c-8.6,22.7 -15.6,41.5 -15.6,41.7c0,0.2 9.4,0.4 20.8,0.4l20.9,-0l1.2,-4.3c0.7,-2.3 2.2,-7.2 3.3,-11l2,-6.7l23.1,0.2l23.1,0.3l3.4,10.8l3.3,10.7l21.2,-0.2c13.4,-0.2 21.1,-0.7 21,-1.3c-0.2,-1.4 -48.1,-128.4 -48.8,-129.5c-0.4,-0.6 -9.7,-1 -22.9,-1l-22.3,-0l-9.1,24.2zM424.11,219.5l6.7,22.1l-14.3,-0l-14.3,-0l0.9,-2.8c0.5,-1.5 3.4,-11 6.5,-21.2c6.9,-22.9 6.4,-21.4 7.1,-20.7c0.3,0.3 3.6,10.5 7.4,22.6z\" />\n    <!-- Letter Z -->\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M362.41,30l0,14l32.7,0.2l32.8,0.3l-36.8,38.2l-36.8,38.3l0.3,13.2l0.3,13.3l62.8,0.3l62.8,0.2l-0.3,-14.2l-0.3,-14.3l-37.6,-0.5l-37.6,-0.5l36.3,-37.9l36.4,-37.9l0,-13.3l0,-13.4l-57.5,-0l-57.5,-0l0,14z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_apps_list_az.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"512\"\n    android:viewportHeight=\"512\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M34,450v-344h-16v344c0,22.1 17.9,40 40,40h184v-16h-184c-13.2,0 -24,-10.8 -24,-24z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M122,442h80v16h-80z\" />\n    <!-- Car -->\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M50,162c0,4.4 3.6,8 8,8h8.7c1.7,4.7 6.1,8 11.3,8s9.6,-3.3 11.3,-8h17.4c1.7,4.7 6.1,8 11.3,8s9.6,-3.3 11.3,-8h8.7c4.4,0 8,-3.6 8,-8v-24c0,-3 -1.7,-5.8 -4.4,-7.2l-13.6,-6.8l-6.8,-13.6c-1.4,-2.7 -4.1,-4.4 -7.2,-4.4h-32c-3,0 -5.8,1.7 -7.2,4.4l-6.8,13.6l-13.6,6.8c-2.7,1.4 -4.4,4.1 -4.4,7.2zM66,142.9l11.6,-5.8c1.5,-0.8 2.8,-2 3.6,-3.6l5.8,-11.6h22.1l5.8,11.6c0.8,1.5 2,2.8 3.6,3.6l11.6,5.8v11.1h-64v-11.1z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M175,120 h90 a5,5 0 0 1 5,5 v11 a5,5 0 0 1 -5,5 h-90 a5,5 0 0 1 -5,-5 v-11 a5,5 0 0 1 5,-5 z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M170,150 h30v10h-30z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M205,150 h50v10h-50z\" />\n\n    <group\n        android:translateX=\"-125\"\n        android:translateY=\"110\">\n        <!-- House -->\n        <path\n            android:fillColor=\"#FFF\"\n            android:pathData=\"M242,98v10.6l-11,-8.8c-2.9,-2.3 -7.1,-2.3 -10,0l-40,32c-2.7,2.1 -3.7,5.7 -2.6,8.9s4.2,5.4 7.5,5.4v24c0,4.4 3.6,8 8,8h64c4.4,0 8,-3.6 8,-8v-24c3.4,0 6.4,-2.1 7.5,-5.4c1.1,-3.2 0.1,-6.8 -2.6,-8.9l-13,-10.4v-23.4zM250,138v24h-16v-16h-16v16h-16v-24c0,-0.8 -0.1,-1.6 -0.3,-2.3l24.3,-19.5l24.3,19.5c-0.2,0.7 -0.3,1.5 -0.3,2.3z\" />\n        <path\n            android:fillColor=\"#FFF\"\n            android:pathData=\"M300,120 h90 a5,5 0 0 1 5,5 v11 a5,5 0 0 1 -5,5 h-90 a5,5 0 0 1 -5,-5 v-11 a5,5 0 0 1 5,-5 z\" />\n        <path\n            android:fillColor=\"#FFF\"\n            android:pathData=\"M295,150 h40v10h-40z\" />\n\n        <path\n            android:fillColor=\"#FFF\"\n            android:pathData=\"M340,150 h30v10h-30z\" />\n\n        <!-- Shopping cart -->\n        <path\n            android:fillColor=\"#FFF\"\n            android:pathData=\"M257.6,228.6l5.3,-10.6h11.1v-16h-16c-3,0 -5.8,1.7 -7.2,4.4l-5.8,11.6h-59c-2.4,0 -4.7,1.1 -6.2,2.9s-2.1,4.3 -1.7,6.6l8,40c0.3,1.4 1,2.7 1.9,3.8c-1.3,1.9 -2,4.2 -2,6.7c0,6.6 5.4,12 12,12s12,-5.4 12,-12c0,-1.4 -0.3,-2.7 -0.7,-4h17.4c-0.4,1.3 -0.7,2.6 -0.7,4c0,6.6 5.4,12 12,12s12,-5.4 12,-12c0,-2.5 -0.8,-4.8 -2,-6.7c0.9,-1 1.6,-2.3 1.9,-3.8zM240.2,234l-4.8,24h-34.9l-4.8,-24z\" />\n    </group>\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M351.8,306.5l-45.8,-51.5v-205c0,-22.1 -17.9,-40 -40,-40h-208c-22.1,0 -40,17.9 -40,40v40h16v-40c0,-13.2 10.8,-24 24,-24h208c13.2,0 24,10.8 24,24v208v76.7l-26.1,-26.1c-6.9,-6.9 -16,-10.6 -25.7,-10.6c-20,0 -36.3,16.3 -36.3,36.3c0,9.7 3.8,18.8 10.6,25.7l2.1,2.1h-12.6v-16h-16v24c0,4.4 3.6,8 8,8h19.1l-10.2,20.4l14.3,7.2l13.7,-27.4l24.8,24.8c-13,21.3 -14.3,47.7 -2.9,70.4l5.3,10.5v6.1h16v-8c0,-1.2 -0.3,-2.5 -0.8,-3.6l-6.1,-12.2c-9.7,-19.3 -7.6,-42.1 5.4,-59.4c2.4,-3.2 2.1,-7.6 -0.7,-10.5l-47.7,-47.7c-3.8,-3.8 -5.9,-8.9 -5.9,-14.3c0,-11.2 9.1,-20.3 20.3,-20.3c5.4,0 10.5,2.1 14.3,5.9l39.7,39.7l24,24l11.3,-11.3l-21.9,-21.7v-71.7l33.9,38.1c9.1,10.3 14.1,23.5 14.1,37.2v135.7h16v-135.7c0,-17.6 -6.5,-34.6 -18.2,-47.8z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M50,58h16v16h-16z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M82,58h16v16h-16z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M114,58h16v16h-16z\" />\n    <!-- letter A -->\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M384.31,43.8c-5,13.4 -16.1,42.9 -24.6,65.7c-8.6,22.7 -15.6,41.5 -15.6,41.7c0,0.2 9.4,0.4 20.8,0.4l20.9,-0l1.2,-4.3c0.7,-2.3 2.2,-7.2 3.3,-11l2,-6.7l23.1,0.2l23.1,0.3l3.4,10.8l3.3,10.7l21.2,-0.2c13.4,-0.2 21.1,-0.7 21,-1.3c-0.2,-1.4 -48.1,-128.4 -48.8,-129.5c-0.4,-0.6 -9.7,-1 -22.9,-1l-22.3,-0l-9.1,24.2zM423.11,78.5l6.7,22.1l-14.3,-0l-14.3,-0l0.9,-2.8c0.5,-1.5 3.4,-11 6.5,-21.2c6.9,-22.9 6.4,-21.4 7.1,-20.7c0.3,0.3 3.6,10.5 7.4,22.6z\"\n        android:strokeColor=\"#00000000\" />\n    <!-- letter Z -->\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M360.41,178l0,14l32.7,0.2l32.8,0.3l-36.8,38.2l-36.8,38.3l0.3,13.2l0.3,13.3l62.8,0.3l62.8,0.2l-0.3,-14.2l-0.3,-14.3l-37.6,-0.5l-37.6,-0.5l36.3,-37.9l36.4,-37.9l0,-13.3l0,-13.4l-57.5,-0l-57.5,-0l0,14z\"\n        android:strokeColor=\"#00000000\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_apps_list_za.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"512\"\n    android:viewportHeight=\"512\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M34,450v-344h-16v344c0,22.1 17.9,40 40,40h184v-16h-184c-13.2,0 -24,-10.8 -24,-24z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M122,442h80v16h-80z\" />\n    <!-- Car -->\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M50,162c0,4.4 3.6,8 8,8h8.7c1.7,4.7 6.1,8 11.3,8s9.6,-3.3 11.3,-8h17.4c1.7,4.7 6.1,8 11.3,8s9.6,-3.3 11.3,-8h8.7c4.4,0 8,-3.6 8,-8v-24c0,-3 -1.7,-5.8 -4.4,-7.2l-13.6,-6.8l-6.8,-13.6c-1.4,-2.7 -4.1,-4.4 -7.2,-4.4h-32c-3,0 -5.8,1.7 -7.2,4.4l-6.8,13.6l-13.6,6.8c-2.7,1.4 -4.4,4.1 -4.4,7.2zM66,142.9l11.6,-5.8c1.5,-0.8 2.8,-2 3.6,-3.6l5.8,-11.6h22.1l5.8,11.6c0.8,1.5 2,2.8 3.6,3.6l11.6,5.8v11.1h-64v-11.1z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M175,120 h90 a5,5 0 0 1 5,5 v11 a5,5 0 0 1 -5,5 h-90 a5,5 0 0 1 -5,-5 v-11 a5,5 0 0 1 5,-5 z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M170,150 h30v10h-30z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M205,150 h50v10h-50z\" />\n\n    <group\n        android:translateX=\"-125\"\n        android:translateY=\"110\">\n        <!-- House -->\n        <path\n            android:fillColor=\"#FFF\"\n            android:pathData=\"M242,98v10.6l-11,-8.8c-2.9,-2.3 -7.1,-2.3 -10,0l-40,32c-2.7,2.1 -3.7,5.7 -2.6,8.9s4.2,5.4 7.5,5.4v24c0,4.4 3.6,8 8,8h64c4.4,0 8,-3.6 8,-8v-24c3.4,0 6.4,-2.1 7.5,-5.4c1.1,-3.2 0.1,-6.8 -2.6,-8.9l-13,-10.4v-23.4zM250,138v24h-16v-16h-16v16h-16v-24c0,-0.8 -0.1,-1.6 -0.3,-2.3l24.3,-19.5l24.3,19.5c-0.2,0.7 -0.3,1.5 -0.3,2.3z\" />\n        <path\n            android:fillColor=\"#FFF\"\n            android:pathData=\"M300,120 h90 a5,5 0 0 1 5,5 v11 a5,5 0 0 1 -5,5 h-90 a5,5 0 0 1 -5,-5 v-11 a5,5 0 0 1 5,-5 z\" />\n        <path\n            android:fillColor=\"#FFF\"\n            android:pathData=\"M295,150 h40v10h-40z\" />\n\n        <path\n            android:fillColor=\"#FFF\"\n            android:pathData=\"M340,150 h30v10h-30z\" />\n\n        <!-- Shopping cart -->\n        <path\n            android:fillColor=\"#FFF\"\n            android:pathData=\"M257.6,228.6l5.3,-10.6h11.1v-16h-16c-3,0 -5.8,1.7 -7.2,4.4l-5.8,11.6h-59c-2.4,0 -4.7,1.1 -6.2,2.9s-2.1,4.3 -1.7,6.6l8,40c0.3,1.4 1,2.7 1.9,3.8c-1.3,1.9 -2,4.2 -2,6.7c0,6.6 5.4,12 12,12s12,-5.4 12,-12c0,-1.4 -0.3,-2.7 -0.7,-4h17.4c-0.4,1.3 -0.7,2.6 -0.7,4c0,6.6 5.4,12 12,12s12,-5.4 12,-12c0,-2.5 -0.8,-4.8 -2,-6.7c0.9,-1 1.6,-2.3 1.9,-3.8zM240.2,234l-4.8,24h-34.9l-4.8,-24z\" />\n    </group>\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M351.8,306.5l-45.8,-51.5v-205c0,-22.1 -17.9,-40 -40,-40h-208c-22.1,0 -40,17.9 -40,40v40h16v-40c0,-13.2 10.8,-24 24,-24h208c13.2,0 24,10.8 24,24v208v76.7l-26.1,-26.1c-6.9,-6.9 -16,-10.6 -25.7,-10.6c-20,0 -36.3,16.3 -36.3,36.3c0,9.7 3.8,18.8 10.6,25.7l2.1,2.1h-12.6v-16h-16v24c0,4.4 3.6,8 8,8h19.1l-10.2,20.4l14.3,7.2l13.7,-27.4l24.8,24.8c-13,21.3 -14.3,47.7 -2.9,70.4l5.3,10.5v6.1h16v-8c0,-1.2 -0.3,-2.5 -0.8,-3.6l-6.1,-12.2c-9.7,-19.3 -7.6,-42.1 5.4,-59.4c2.4,-3.2 2.1,-7.6 -0.7,-10.5l-47.7,-47.7c-3.8,-3.8 -5.9,-8.9 -5.9,-14.3c0,-11.2 9.1,-20.3 20.3,-20.3c5.4,0 10.5,2.1 14.3,5.9l39.7,39.7l24,24l11.3,-11.3l-21.9,-21.7v-71.7l33.9,38.1c9.1,10.3 14.1,23.5 14.1,37.2v135.7h16v-135.7c0,-17.6 -6.5,-34.6 -18.2,-47.8z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M50,58h16v16h-16z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M82,58h16v16h-16z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M114,58h16v16h-16z\" />\n    <!-- Letter A -->\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M385.31,184.8c-5,13.4 -16.1,42.9 -24.6,65.7c-8.6,22.7 -15.6,41.5 -15.6,41.7c0,0.2 9.4,0.4 20.8,0.4l20.9,-0l1.2,-4.3c0.7,-2.3 2.2,-7.2 3.3,-11l2,-6.7l23.1,0.2l23.1,0.3l3.4,10.8l3.3,10.7l21.2,-0.2c13.4,-0.2 21.1,-0.7 21,-1.3c-0.2,-1.4 -48.1,-128.4 -48.8,-129.5c-0.4,-0.6 -9.7,-1 -22.9,-1l-22.3,-0l-9.1,24.2zM424.11,219.5l6.7,22.1l-14.3,-0l-14.3,-0l0.9,-2.8c0.5,-1.5 3.4,-11 6.5,-21.2c6.9,-22.9 6.4,-21.4 7.1,-20.7c0.3,0.3 3.6,10.5 7.4,22.6z\" />\n    <!-- Letter Z -->\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M362.41,30l0,14l32.7,0.2l32.8,0.3l-36.8,38.2l-36.8,38.3l0.3,13.2l0.3,13.3l62.8,0.3l62.8,0.2l-0.3,-14.2l-0.3,-14.3l-37.6,-0.5l-37.6,-0.5l36.3,-37.9l36.4,-37.9l0,-13.3l0,-13.4l-57.5,-0l-57.5,-0l0,14z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_arrow_back.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_backup.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"12dp\"\n    android:height=\"12dp\"\n    android:viewportWidth=\"496\"\n    android:viewportHeight=\"496\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M152,248c-13.232,0 -24,10.768 -24,24s10.768,24 24,24s24,-10.768 24,-24S165.232,248 152,248zM152,280c-4.416,0 -8,-3.592 -8,-8c0,-4.408 3.584,-8 8,-8s8,3.592 8,8C160,276.408 156.416,280 152,280z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M152,184c13.232,0 24,-10.768 24,-24s-10.768,-24 -24,-24s-24,10.768 -24,24S138.768,184 152,184zM152,152c4.416,0 8,3.592 8,8s-3.584,8 -8,8s-8,-3.592 -8,-8S147.584,152 152,152z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M152,72c13.232,0 24,-10.768 24,-24s-10.768,-24 -24,-24s-24,10.768 -24,24S138.768,72 152,72zM152,40c4.416,0 8,3.592 8,8s-3.584,8 -8,8s-8,-3.592 -8,-8S147.584,40 152,40z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M304,256v-32h-16v-16h16v-96h-16V96h16V0H0v96h16v16H0v96h16v16H0v96h256v176h240V256H304zM304,272h112v64h16v-64h16v80H304V272zM16,16h272v64h-16V32h-16v48h-16V32h-16v48h-16V32h-16v48h-80V32H96v48H80V32H64v48H48V32H32v48H16V16zM272,96v16H32V96H272zM16,128h272v64h-16v-48h-16v48h-16v-48h-16v48h-16v-48h-16v48h-80v-48H96v48H80v-48H64v48H48v-48H32v48H16V128zM272,208v16H32v-16H272zM256,304h-16v-48h-16v48h-16v-48h-16v48h-80v-48H96v48H80v-48H64v48H48v-48H32v48H16v-64h272v16h-11.312L256,276.688V304zM448,480H304v-56c0,-4.408 3.584,-8 8,-8h128c4.416,0 8,3.592 8,8V480zM480,480h-16v-56c0,-13.232 -10.768,-24 -24,-24H312c-13.232,0 -24,10.768 -24,24v56h-16V283.312L283.312,272H288v96h176v-96h16V480z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M148.152,372.944C150.56,369.2 152,364.776 152,360c0,-13.232 -10.768,-24 -24,-24s-24,10.768 -24,24s10.768,24 24,24c2.624,0 5.112,-0.528 7.48,-1.312c26.08,32.224 62.144,54.152 103.008,62.04l3.032,-15.712C204.52,421.872 171.84,402.064 148.152,372.944zM128,368c-4.416,0 -8,-3.592 -8,-8c0,-4.408 3.584,-8 8,-8s8,3.592 8,8C136,364.408 132.416,368 128,368z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M414.416,199.792C410.472,204.072 408,209.736 408,216c0,13.232 10.768,24 24,24s24,-10.768 24,-24s-10.768,-24 -24,-24c-1.136,0 -2.216,0.184 -3.304,0.336c-21.864,-42.928 -60.368,-75.24 -106.416,-88.952l-4.56,15.336C359.608,131.192 394.552,160.688 414.416,199.792zM432,208c4.416,0 8,3.592 8,8s-3.584,8 -8,8c-4.416,0 -8,-3.592 -8,-8S427.584,208 432,208z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_behaviour.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"12dp\"\n    android:height=\"12dp\"\n    android:viewportWidth=\"64\"\n    android:viewportHeight=\"64\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M58,1L28,1a5,5 0,0 0,-5 5v13.2c-3.4,0.4 -6.6,1.7 -9.3,3.8L1,23v19h12.7a22,22 0,0 0,9.3 3v13a5,5 0,0 0,5 5h30a5,5 0,0 0,5 -5L63,6a5,5 0,0 0,-5 -5zM24.8,43a20,20 0,0 1,-9.8 -2.6L15,24.5c3,-2.3 6.7,-3.5 10.4,-3.5L35,21a2,2 0,0 1,-2 2h-6a3,3 0,0 0,0 6h15a1,1 0,0 1,0 2L26,31v2h10a1,1 0,0 1,0 2L26,35v2h8a1,1 0,0 1,0 2h-8v2h6a1,1 0,0 1,0 2zM33,19v-6h6v6zM29,45h3a3,3 0,0 0,2.8 -4.1,3 3,0 0,0 2,-4 3,3 0,0 0,2 -3.9L42,33a3,3 0,0 0,0 -6L27,27a1,1 0,0 1,0 -2h6a4,4 0,0 0,4 -4h4L41,11L31,11v8h-2L29,9h28v40L29,49zM44,7L44,5h-2v2h-2L40,3h6v4zM25,6a3,3 0,0 1,3 -3h10v4h-9a2,2 0,0 0,-2 2v10h-2zM3,25h10v15L3,40zM61,58a3,3 0,0 1,-3 3L28,61a3,3 0,0 1,-3 -3L25,45h2v4c0,1.1 0.9,2 2,2h28a2,2 0,0 0,2 -2L59,9a2,2 0,0 0,-2 -2h-9L48,3h10a3,3 0,0 1,3 3z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M47,53h-6a2,2 0,0 0,-2 2v2c0,1.1 0.9,2 2,2h6a2,2 0,0 0,2 -2v-2a2,2 0,0 0,-2 -2zM41,57v-2h6v2zM49,30a5,5 0,0 0,-5 -5h-1v2h1a3,3 0,0 1,0 6h-1v2h1a5,5 0,0 0,5 -5z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M44,21h-1v2h1a7,7 0,0 1,0 14h-1v2h1a9,9 0,0 0,0 -18zM43,45h2v2h-2zM47,45h2v2h-2zM39,45h2v2h-2zM43,17h12v2H43zM43,13h2v2h-2zM47,13h8v2h-8z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_browse_add_icon.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"894.8\"\n    android:viewportHeight=\"894.8\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M679,590a391,391 0,0 1,-89 89l209,209c10,9 26,9 35,0l54,-54c9,-9 9,-25 0,-35L679,590z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M364,727a362,362 0,0 0,363 -363,364 364,0 1,0 -363,363zM183,333c0,-11 9,-20 20,-20h111V203c0,-11 9,-20 20,-20h60c11,0 20,9 20,20v110h110c11,0 20,9 20,20v60c0,11 -9,20 -20,20H414v111c0,11 -9,20 -20,20h-60c-11,0 -20,-9 -20,-20V413H203c-11,0 -20,-9 -20,-20v-60z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_bug.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"12dp\"\n    android:height=\"12dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M20,8h-2.81c-0.45,-0.78 -1.07,-1.45 -1.82,-1.96L17,4.41 15.59,3l-2.17,2.17C12.96,5.06 12.49,5 12,5c-0.49,0 -0.96,0.06 -1.41,0.17L8.41,3 7,4.41l1.62,1.63C7.88,6.55 7.26,7.22 6.81,8L4,8v2h2.09c-0.05,0.33 -0.09,0.66 -0.09,1v1L4,12v2h2v1c0,0.34 0.04,0.67 0.09,1L4,16v2h2.81c1.04,1.79 2.97,3 5.19,3s4.15,-1.21 5.19,-3L20,18v-2h-2.09c0.05,-0.33 0.09,-0.66 0.09,-1v-1h2v-2h-2v-1c0,-0.34 -0.04,-0.67 -0.09,-1L20,10L20,8zM14,16h-4v-2h4v2zM14,12h-4v-2h4v2z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_clear.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"6dp\"\n    android:height=\"6dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_contact_placeholder.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path android:fillColor=\"#888\"\n        android:pathData=\"M0,0H24V24H0z\"/>\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M6,17C6,15 10,13.9 12,13.9C14,13.9 18,15 18,17V18H6M15,9A3,3 0 0,1 12,12A3,3 0 0,1 9,9A3,3 0 0,1 12,6A3,3 0 0,1 15,9M3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3H5C3.89,3 3,3.9 3,5Z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_contacts.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"512\"\n    android:viewportHeight=\"512\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"m424.308,0h-354.308c-27.57,0 -50,22.43 -50,50v161c0,5.523 4.477,10 10,10s10,-4.477 10,-10v-161c0,-16.542 13.458,-30 30,-30h222v103.5c0,4.044 2.436,7.691 6.173,9.239s8.038,0.691 10.898,-2.167l24.929,-24.93 24.929,24.929c1.913,1.913 4.471,2.929 7.073,2.929 1.288,-0.001 2.588,-0.249 3.825,-0.762 3.736,-1.548 6.173,-5.194 6.173,-9.239v-103.499h6c16.542,0 30,13.458 30,30v412c0,16.542 -13.458,30 -30,30h-312c-16.542,0 -30,-13.458 -30,-30v-161c0,-5.523 -4.477,-10 -10,-10s-10,4.477 -10,10v161c0,27.57 22.43,50 50,50h354.308c37.326,0 67.692,-30.367 67.692,-67.693v-376.614c0,-37.326 -30.366,-67.693 -67.692,-67.693zM356,99.358 L341.071,84.429c-1.953,-1.953 -4.511,-2.929 -7.071,-2.929s-5.118,0.976 -7.071,2.929l-14.929,14.929v-79.358h44zM472,444.307c0,26.298 -21.395,47.693 -47.692,47.693h-2.335c6.292,-8.362 10.027,-18.752 10.027,-30v-412c0,-11.248 -3.735,-21.638 -10.027,-30h2.335c26.297,0 47.692,21.395 47.692,47.693z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"m231.481,156.216 l-23.566,-40.819c-9.039,-15.655 -29.13,-21.04 -44.789,-12.001l-8.266,4.772c-48.265,27.866 -64.86,89.802 -36.995,138.065l67.098,116.216c13.498,23.38 35.293,40.104 61.37,47.091 8.708,2.333 17.544,3.487 26.323,3.487 17.513,0 34.798,-4.593 50.371,-13.585l8.266,-4.772c7.585,-4.379 13.01,-11.449 15.276,-19.909 2.268,-8.46 1.104,-17.296 -3.275,-24.88l-23.317,-40.388c-4.379,-7.585 -11.449,-13.01 -19.909,-15.277 -8.459,-2.266 -17.296,-1.104 -24.88,3.276l-25.137,14.513 -55.707,-96.488 25.137,-14.513c15.656,-9.039 21.04,-29.131 12,-44.788zM285.188,314.814c2.959,-1.709 6.404,-2.161 9.704,-1.278 3.299,0.884 6.057,3 7.765,5.958l23.317,40.388c1.708,2.958 2.162,6.404 1.277,9.704 -0.884,3.299 -3,6.057 -5.958,7.765 0,0 -13.306,7.499 -15.897,8.655l-35.879,-62.144zM234.261,324.659c3.609,6.255 10.634,9.318 17.375,8.233l34.081,59.03c-11.249,1.865 -22.863,1.341 -34.208,-1.699 -20.916,-5.604 -38.399,-19.019 -49.226,-37.772l-67.098,-116.216c-17.328,-30.013 -13.209,-66.609 7.551,-91.961l34.312,59.429c-4.309,5.294 -5.169,12.908 -1.559,19.163zM209.48,183.685 L193.809,192.732 157.681,130.158c2.286,-1.662 15.444,-9.441 15.444,-9.441 6.105,-3.526 13.943,-1.426 17.469,4.681l23.566,40.819c3.526,6.106 1.426,13.942 -4.68,17.468z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"m30,266c2.63,0 5.21,-1.07 7.07,-2.93s2.93,-4.44 2.93,-7.07 -1.07,-5.21 -2.93,-7.07 -4.44,-2.93 -7.07,-2.93 -5.21,1.07 -7.07,2.93 -2.93,4.44 -2.93,7.07 1.07,5.21 2.93,7.07 4.44,2.93 7.07,2.93z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_contacts_az.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"512\"\n    android:viewportHeight=\"512\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M424.3,0h-354.3c-27.6,0 -50,22.4 -50,50v161c0,5.5 4.5,10 10,10s10,-4.5 10,-10v-161c0,-16.5 13.5,-30 30,-30h222v103.5c0,4 2.4,7.7 6.2,9.2s8,0.7 10.9,-2.2l24.9,-24.9l24.9,24.9c1.9,1.9 4.5,2.9 7.1,2.9c1.3,0 2.6,-0.2 3.8,-0.8c3.7,-1.5 6.2,-5.2 6.2,-9.2v-103.5h6c16.5,0 30,13.5 30,30v412c0,16.5 -13.5,30 -30,30h-312c-16.5,0 -30,-13.5 -30,-30v-161c0,-5.5 -4.5,-10 -10,-10s-10,4.5 -10,10v161c0,27.6 22.4,50 50,50h354.3c37.3,0 67.7,-30.4 67.7,-67.7v-376.6c0,-37.3 -30.4,-67.7 -67.7,-67.7zM356,99.4l-14.9,-14.9c-2,-2 -4.5,-2.9 -7.1,-2.9s-5.1,1 -7.1,2.9l-14.9,14.9v-79.4h44zM472,444.3c0,26.3 -21.4,47.7 -47.7,47.7h-2.3c6.3,-8.4 10,-18.8 10,-30v-412c0,-11.2 -3.7,-21.6 -10,-30h2.3c26.3,0 47.7,21.4 47.7,47.7z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M30,266c2.6,0 5.2,-1.1 7.1,-2.9s2.9,-4.4 2.9,-7.1s-1.1,-5.2 -2.9,-7.1s-4.4,-2.9 -7.1,-2.9s-5.2,1.1 -7.1,2.9s-2.9,4.4 -2.9,7.1s1.1,5.2 2.9,7.1s4.4,2.9 7.1,2.9z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M194.18,138.9l-15.44,-45.52c-5.86,-17.5 -24.9,-26.82 -42.4,-20.96l-9.26,3.14c-53.88,18.24 -82.86,76.82 -64.62,130.7l43.86,129.62c8.82,26.1 27.28,47.16 51.98,59.38c8.24,4.04 16.8,7 25.6,8.76c17.5,3.5 35.72,2.36 53.12,-3.52l9.26,-3.14c8.48,-2.88 15.28,-8.8 19.28,-16.84c4,-8.04 4.56,-17.08 1.68,-25.56l-15.22,-45.06c-2.88,-8.48 -8.8,-15.28 -16.84,-19.28c-8.04,-4 -17.08,-4.56 -25.56,-1.68l-28,9.48l-36.4,-107.64l28,-9.48c17.5,-5.86 26.82,-24.9 20.96,-42.4zM216.16,308.24c3.34,-1.1 6.84,-0.92 9.96,0.64c3.12,1.56 5.5,4.22 6.6,7.56l15.22,45.06c1.1,3.34 0.92,6.84 -0.64,9.96c-1.56,3.12 -4.22,5.5 -7.56,6.6c0,0 -14.8,4.84 -17.64,5.52l-23.48,-69.28zM163.3,307.86c2.34,7.02 8.74,11.42 15.76,11.68l22.3,65.82c-11.58,-0.34 -23.16,-3.28 -33.86,-8.54c-19.78,-9.78 -34.6,-26.68 -41.64,-47.64l-43.86,-129.62c-11.3,-33.46 0.12,-69.24 26,-90.48l22.42,66.26c-5.36,4.44 -7.78,11.86 -5.44,18.88zM166.7,161.9l-17.5,5.86l-23.58,-69.82c2.64,-1.24 17.28,-6.32 17.28,-6.32c6.8,-2.28 14.18,1.38 16.56,8.2l15.44,45.52c2.28,6.8 -1.38,14.18 -8.2,16.56z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M303.31,175.8c-5,13.4 -16.1,42.9 -24.6,65.7c-8.6,22.7 -15.6,41.5 -15.6,41.7c0,0.2 9.4,0.4 20.8,0.4l20.9,-0l1.2,-4.3c0.7,-2.3 2.2,-7.2 3.3,-11l2,-6.7l23.1,0.2l23.1,0.3l3.4,10.8l3.3,10.7l21.2,-0.2c13.4,-0.2 21.1,-0.7 21,-1.3c-0.2,-1.4 -48.1,-128.4 -48.8,-129.5c-0.4,-0.6 -9.7,-1 -22.9,-1l-22.3,-0l-9.1,24.2zM342.11,210.5l6.7,22.1l-14.3,-0l-14.3,-0l0.9,-2.8c0.5,-1.5 3.4,-11 6.5,-21.2c6.9,-22.9 6.4,-21.4 7.1,-20.7c0.3,0.3 3.6,10.5 7.4,22.6z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M276.41,328l0,14l32.7,0.2l32.8,0.3l-36.8,38.2l-36.8,38.3l0.3,13.2l0.3,13.3l62.8,0.3l62.8,0.2l-0.3,-14.2l-0.3,-14.3l-37.6,-0.5l-37.6,-0.5l36.3,-37.9l36.4,-37.9l0,-13.3l0,-13.4l-57.5,-0l-57.5,-0l0,14z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_contacts_za.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"512\"\n    android:viewportHeight=\"512\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M424.3,0h-354.3c-27.6,0 -50,22.4 -50,50v161c0,5.5 4.5,10 10,10s10,-4.5 10,-10v-161c0,-16.5 13.5,-30 30,-30h222v103.5c0,4 2.4,7.7 6.2,9.2s8,0.7 10.9,-2.2l24.9,-24.9l24.9,24.9c1.9,1.9 4.5,2.9 7.1,2.9c1.3,0 2.6,-0.2 3.8,-0.8c3.7,-1.5 6.2,-5.2 6.2,-9.2v-103.5h6c16.5,0 30,13.5 30,30v412c0,16.5 -13.5,30 -30,30h-312c-16.5,0 -30,-13.5 -30,-30v-161c0,-5.5 -4.5,-10 -10,-10s-10,4.5 -10,10v161c0,27.6 22.4,50 50,50h354.3c37.3,0 67.7,-30.4 67.7,-67.7v-376.6c0,-37.3 -30.4,-67.7 -67.7,-67.7zM356,99.4l-14.9,-14.9c-2,-2 -4.5,-2.9 -7.1,-2.9s-5.1,1 -7.1,2.9l-14.9,14.9v-79.4h44zM472,444.3c0,26.3 -21.4,47.7 -47.7,47.7h-2.3c6.3,-8.4 10,-18.8 10,-30v-412c0,-11.2 -3.7,-21.6 -10,-30h2.3c26.3,0 47.7,21.4 47.7,47.7z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M30,266c2.6,0 5.2,-1.1 7.1,-2.9s2.9,-4.4 2.9,-7.1s-1.1,-5.2 -2.9,-7.1s-4.4,-2.9 -7.1,-2.9s-5.2,1.1 -7.1,2.9s-2.9,4.4 -2.9,7.1s1.1,5.2 2.9,7.1s4.4,2.9 7.1,2.9z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M194.18,138.9l-15.44,-45.52c-5.86,-17.5 -24.9,-26.82 -42.4,-20.96l-9.26,3.14c-53.88,18.24 -82.86,76.82 -64.62,130.7l43.86,129.62c8.82,26.1 27.28,47.16 51.98,59.38c8.24,4.04 16.8,7 25.6,8.76c17.5,3.5 35.72,2.36 53.12,-3.52l9.26,-3.14c8.48,-2.88 15.28,-8.8 19.28,-16.84c4,-8.04 4.56,-17.08 1.68,-25.56l-15.22,-45.06c-2.88,-8.48 -8.8,-15.28 -16.84,-19.28c-8.04,-4 -17.08,-4.56 -25.56,-1.68l-28,9.48l-36.4,-107.64l28,-9.48c17.5,-5.86 26.82,-24.9 20.96,-42.4zM216.16,308.24c3.34,-1.1 6.84,-0.92 9.96,0.64c3.12,1.56 5.5,4.22 6.6,7.56l15.22,45.06c1.1,3.34 0.92,6.84 -0.64,9.96c-1.56,3.12 -4.22,5.5 -7.56,6.6c0,0 -14.8,4.84 -17.64,5.52l-23.48,-69.28zM163.3,307.86c2.34,7.02 8.74,11.42 15.76,11.68l22.3,65.82c-11.58,-0.34 -23.16,-3.28 -33.86,-8.54c-19.78,-9.78 -34.6,-26.68 -41.64,-47.64l-43.86,-129.62c-11.3,-33.46 0.12,-69.24 26,-90.48l22.42,66.26c-5.36,4.44 -7.78,11.86 -5.44,18.88zM166.7,161.9l-17.5,5.86l-23.58,-69.82c2.64,-1.24 17.28,-6.32 17.28,-6.32c6.8,-2.28 14.18,1.38 16.56,8.2l15.44,45.52c2.28,6.8 -1.38,14.18 -8.2,16.56z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M296.31,338.8c-5,13.4 -16.1,42.9 -24.6,65.7c-8.6,22.7 -15.6,41.5 -15.6,41.7c0,0.2 9.4,0.4 20.8,0.4l20.9,-0l1.2,-4.3c0.7,-2.3 2.2,-7.2 3.3,-11l2,-6.7l23.1,0.2l23.1,0.3l3.4,10.8l3.3,10.7l21.2,-0.2c13.4,-0.2 21.1,-0.7 21,-1.3c-0.2,-1.4 -48.1,-128.4 -48.8,-129.5c-0.4,-0.6 -9.7,-1 -22.9,-1l-22.3,-0l-9.1,24.2zM335.11,373.5l6.7,22.1l-14.3,-0l-14.3,-0l0.9,-2.8c0.5,-1.5 3.4,-11 6.5,-21.2c6.9,-22.9 6.4,-21.4 7.1,-20.7c0.3,0.3 3.6,10.5 7.4,22.6z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M273.41,172l0,14l32.7,0.2l32.8,0.3l-36.8,38.2l-36.8,38.3l0.3,13.2l0.3,13.3l62.8,0.3l62.8,0.2l-0.3,-14.2l-0.3,-14.3l-37.6,-0.5l-37.6,-0.5l36.3,-37.9l36.4,-37.9l0,-13.3l0,-13.4l-57.5,-0l-57.5,-0l0,14z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_dots.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"6dp\"\n    android:height=\"6dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_edit.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M14.06,9.02l0.92,0.92L5.92,19L5,19v-0.92l9.06,-9.06M17.66,3c-0.25,0 -0.51,0.1 -0.7,0.29l-1.83,1.83 3.75,3.75 1.83,-1.83c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.2,-0.2 -0.45,-0.29 -0.71,-0.29zM14.06,6.19L3,17.25L3,21h3.75L17.81,9.94l-3.75,-3.75z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_eye_crossed.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"223.5\"\n    android:viewportHeight=\"223.5\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M222,102c-9,-16 -21,-30 -36,-41a8,8 0,0 0,-9 13c12,8 22,19 30,32 -20,32 -57,52 -95,52a8,8 0,0 0,0 15c45,0 88,-24 110,-64 2,-2 2,-5 0,-7z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M112,150a45,45 0,0 0,43 -54,7 7,0 1,0 -14,3v7a30,30 0,0 1,-44 25L205,23a7,7 0,1 0,-10 -11L86,120c-2,-4 -4,-9 -4,-14a30,30 0,0 1,37 -29,8 8,0 0,0 3,-15 45,45 0,0 0,-55 44c0,9 3,18 8,25l-14,15c-19,-9 -34,-23 -45,-40a113,113 0,0 1,120 -51,7 7,0 1,0 3,-14A129,129 0,0 0,1 102v7c12,20 28,37 49,48L6,201a7,7 0,1 0,10 11l70,-70c7,5 16,8 26,8z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_favorites.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M16.5,3c-1.74,0 -3.41,0.81 -4.5,2.09C10.91,3.81 9.24,3 7.5,3 4.42,3 2,5.42 2,8.5c0,3.78 3.4,6.86 8.55,11.54L12,21.35l1.45,-1.32C18.6,15.36 22,12.28 22,8.5 22,5.42 19.58,3 16.5,3zM12.1,18.55l-0.1,0.1 -0.1,-0.1C7.14,14.24 4,11.39 4,8.5 4,6.5 5.5,5 7.5,5c1.54,0 3.04,0.99 3.57,2.36h1.87C13.46,5.99 14.96,5 16.5,5c2,0 3.5,1.5 3.5,3.5 0,2.89 -3.14,5.74 -7.9,10.05z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_features.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"12dp\"\n    android:height=\"12dp\"\n    android:viewportWidth=\"937.5\"\n    android:viewportHeight=\"937\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M62.5,781.3h31.3L93.8,750L62.5,750c-17.3,0 -31.3,-14 -31.3,-31.3v-625c0,-17.2 14,-31.2 31.3,-31.2L750,62.5c17.3,0 31.3,14 31.3,31.3L781.3,125h31.2L812.5,93.7c0,-34.5 -28,-62.5 -62.5,-62.5L62.5,31.2C28,31.3 0,59.3 0,93.8v625c0,34.6 28,62.5 62.5,62.5zM62.5,781.3\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M62.5,93.8h31.3L93.8,125L62.5,125zM62.5,93.8M125,93.8h31.3L156.3,125L125,125zM125,93.8M187.5,93.8h31.3L218.8,125h-31.3zM187.5,93.8M125,843.8c0,34.5 28,62.5 62.5,62.5L875,906.3c34.5,0 62.5,-28 62.5,-62.5v-625c0,-34.6 -28,-62.6 -62.5,-62.6L187.5,156.2c-34.5,0 -62.5,28 -62.5,62.5zM156.3,218.8c0,-17.3 14,-31.3 31.2,-31.3L875,187.5c17.3,0 31.3,14 31.3,31.3v625c0,17.2 -14,31.2 -31.3,31.2L187.5,875c-17.3,0 -31.3,-14 -31.3,-31.3zM156.3,218.8\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M187.5,218.8h31.3L218.8,250h-31.3zM187.5,218.8M250,218.8h31.3L281.3,250L250,250zM250,218.8M312.5,218.8h31.3L343.8,250h-31.3zM312.5,218.8M187.5,281.3L875,281.3v31.2L187.5,312.5zM187.5,281.3M187.5,359.4h125v31.2h-125zM187.5,359.4M187.5,421.9h93.8L281.3,453h-93.8zM187.5,421.9M531.3,484.4a109.4,109.4 0,1 0,0 218.7,109.4 109.4,0 0,0 0,-218.7zM531.3,671.9a78.1,78.1 0,1 1,0 -156.3,78.1 78.1,0 0,1 0,156.3zM531.3,671.9\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M781.3,546.9c0,-8.7 -7,-15.6 -15.7,-15.6h-41.3a198.8,198.8 0,0 0,-12.3 -29.9l29.2,-29.2c6,-6 6,-16 0,-22l-66.3,-66.4c-6.1,-6 -16,-6 -22.1,0L623.6,413c-9.6,-4.9 -19.6,-9 -29.9,-12.3v-41.3c0,-8.7 -7,-15.6 -15.6,-15.6h-93.7c-8.7,0 -15.6,7 -15.6,15.6v41.3a202.2,202.2 0,0 0,-29.9 12.3l-29.2,-29.2c-6,-6 -16,-6 -22,0l-66.4,66.3c-6,6.1 -6,16 0,22.1l29.2,29.2a203.3,203.3 0,0 0,-12.3 29.9h-41.3c-8.7,0 -15.6,7 -15.6,15.6v93.7c0,8.7 7,15.6 15.6,15.6h41.3c3.3,10.3 7.4,20.3 12.3,29.9l-29.2,29.2c-6,6 -6,16 0,22l66.3,66.4c6.1,6 16,6 22.1,0l29.2,-29.2c9.6,4.9 19.6,9 29.9,12.3v41.3c0,8.7 7,15.6 15.6,15.6L578,843.7c8.7,0 15.6,-7 15.6,-15.6v-41.3c10.3,-3.3 20.3,-7.4 29.9,-12.3l29.2,29.2c6,6 16,6 22,0l66.4,-66.3c6,-6.1 6,-16 0,-22.1L712,686c4.9,-9.6 9,-19.6 12.3,-29.9h41.3c8.7,0 15.6,-7 15.6,-15.6zM750,625h-37.4c-7.1,0 -13.4,4.8 -15.1,11.7 -4,15.5 -10.2,30.4 -18.3,44.2 -3.6,6.1 -2.6,14 2.4,19l26.4,26.4 -44.2,44.2 -26.4,-26.4c-5,-5 -12.9,-6 -19,-2.4a170.7,170.7 0,0 1,-44.2 18.3c-6.9,1.8 -11.7,8 -11.7,15v37.5L500,812.5v-37.4c0,-7.1 -4.8,-13.4 -11.7,-15.1 -15.5,-4 -30.4,-10.2 -44.2,-18.3 -6.1,-3.6 -14,-2.6 -19,2.4l-26.4,26.4 -44.2,-44.2 26.4,-26.4c5,-5 6,-12.9 2.4,-19a170.8,170.8 0,0 1,-18.3 -44.2c-1.8,-6.9 -8,-11.7 -15,-11.7h-37.5v-62.5h37.4c7.1,0 13.4,-4.8 15.1,-11.7a169.7,169.7 0,0 1,18.3 -44.3c3.6,-6.1 2.6,-14 -2.4,-19l-26.4,-26.4 44.2,-44.2 26.4,26.5c5,5 12.9,6 19,2.3a171,171 0,0 1,44.2 -18.2c6.9,-1.8 11.7,-8 11.7,-15.2L500,375h62.5v37.4c0,7.1 4.8,13.4 11.7,15.1 15.5,4 30.4,10.2 44.2,18.3 6.1,3.6 14,2.6 19,-2.4l26.4,-26.4 44.2,44.2 -26.4,26.4c-5,5 -6,12.9 -2.4,19a170.8,170.8 0,0 1,18.3 44.2c1.8,6.9 8,11.7 15,11.7L750,562.5zM750,625M781.3,812.5L875,812.5v31.3h-93.8zM781.3,812.5\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_functions.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M18,4H6v2l6.5,6L6,18v2h12v-3h-7l5,-5 -5,-5h7z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_gesture.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"12dp\"\n    android:height=\"12dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M3.72,6.04c0.47,0.46 1.21,0.48 1.71,0.06 0.37,-0.32 0.69,-0.51 0.87,-0.43 0.5,0.2 0,1.03 -0.3,1.52 -0.25,0.42 -2.86,3.89 -2.86,6.31 0,1.28 0.48,2.34 1.34,2.98 0.75,0.56 1.74,0.73 2.64,0.46 1.07,-0.31 1.95,-1.4 3.06,-2.77 1.21,-1.49 2.83,-3.44 4.08,-3.44 1.63,0 1.65,1.01 1.76,1.79 -3.78,0.64 -5.38,3.67 -5.38,5.37 0,1.7 1.44,3.09 3.21,3.09 1.63,0 4.29,-1.33 4.69,-6.1h1.21c0.69,0 1.25,-0.56 1.25,-1.25s-0.56,-1.25 -1.25,-1.25h-1.22c-0.15,-1.65 -1.09,-4.2 -4.03,-4.2 -2.25,0 -4.18,1.91 -4.94,2.84 -0.58,0.73 -2.06,2.48 -2.29,2.72 -0.25,0.3 -0.68,0.84 -1.11,0.84 -0.45,0 -0.72,-0.83 -0.36,-1.92 0.35,-1.09 1.4,-2.86 1.85,-3.52 0.78,-1.14 1.3,-1.92 1.3,-3.28C8.95,3.69 7.31,3 6.44,3c-1.09,0 -2.04,0.63 -2.7,1.22 -0.53,0.48 -0.53,1.32 -0.02,1.82zM13.88,18.55c-0.31,0 -0.74,-0.26 -0.74,-0.72 0,-0.6 0.73,-2.2 2.87,-2.76 -0.3,2.69 -1.43,3.48 -2.13,3.48z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_grid.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"6dp\"\n    android:height=\"6dp\"\n    android:viewportWidth=\"13\"\n    android:viewportHeight=\"21\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"\n        M1,1 h3v3h-3z\n        M5,1 h3v3h-3z\n        M9,1 h3v3h-3z\n\n        M1,5 h3v1h-3z\n        M5,5 h3v1h-3z\n        M9,5 h3v1h-3z\n\n\n        M1,8 h3v3h-3z\n        M5,8 h3v3h-3z\n        M9,8 h3v3h-3z\n\n        M1,12h3v1h-3z\n        M5,12h3v1h-3z\n        M9,12h3v1h-3z\n\n\n        M1,15h3v3h-3z\n        M5,15h3v3h-3z\n        M9,15h3v3h-3z\n\n        M1,19h3v1h-3z\n        M5,19h3v1h-3z\n        M9,19h3v1h-3z\n\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_handle_move.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M3,13h2v-2L3,11v2zM7,21h2v-2L7,19v2zM3,9h2L5,7L3,7v2zM13,3h-2v2h2L13,3zM3,17h2v-2L3,15v2zM9,3L7,3v2h2L9,3zM5,3L3,3v2h2L5,3zM11,21h2v-2h-2v2zM19,13h2v-2h-2v2zM19,9h2L21,7h-2v2zM19,17h2v-2h-2v2zM15,21h2v-2h-2v2zM15,5h2L17,3h-2v2zM19,5h2L21,3h-2v2zM19,21h2v-2h-2v2zM3,21h2v-2L3,19v2zM7,17h10L17,7L7,7v10zM9,9h6v6L9,15L9,9z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_handle_resize_bl.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M9,7L7,7v2h2L9,7zM9,11L7,11v2h2v-2zM13,15h-2v2h2v-2zM13,3h-2v2h2L13,3zM9,3L7,3v2h2L9,3zM21,3h-2v2h2L21,3zM21,15h-2v2h2v-2zM9,15L7,15v2h2v-2zM19,13h2v-2h-2v2zM19,9h2L21,7h-2v2zM5,7L3,7v14h14v-2L5,19L5,7zM15,5h2L17,3h-2v2zM15,17h2v-2h-2v2z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_handle_resize_l.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M15,21h2v-2h-2v2zM19,9h2L21,7h-2v2zM3,3v18h6v-2L5,19L5,5h4L9,3L3,3zM19,3v2h2L21,3h-2zM11,23h2L13,1h-2v22zM19,17h2v-2h-2v2zM15,5h2L17,3h-2v2zM19,13h2v-2h-2v2zM19,21h2v-2h-2v2z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_history.xml",
    "content": "<vector android:height=\"12dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"12dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_icon.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M19,5v14L5,19L5,5h14m0,-2L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM14.14,11.86l-3,3.87L9,13.14 6,17h12l-3.86,-5.14z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_keyboard.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"12dp\"\n    android:height=\"12dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M20,7v10L4,17L4,7h16m0,-2L4,5c-1.1,0 -1.99,0.9 -1.99,2L2,17c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,7c0,-1.1 -0.9,-2 -2,-2zM11,8h2v2h-2zM11,11h2v2h-2zM8,8h2v2L8,10zM8,11h2v2L8,13zM5,11h2v2L5,13zM5,8h2v2L5,10zM8,14h8v2L8,16zM14,11h2v2h-2zM14,8h2v2h-2zM17,11h2v2h-2zM17,8h2v2h-2z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_list.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"12dp\"\n    android:height=\"12dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M19,5v14L5,19L5,5h14m1.1,-2L3.9,3c-0.5,0 -0.9,0.4 -0.9,0.9v16.2c0,0.4 0.4,0.9 0.9,0.9h16.2c0.4,0 0.9,-0.5 0.9,-0.9L21,3.9c0,-0.5 -0.5,-0.9 -0.9,-0.9zM11,7h6v2h-6L11,7zM11,11h6v2h-6v-2zM11,15h6v2h-6zM7,7h2v2L7,9zM7,11h2v2L7,13zM7,15h2v2L7,17z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_loading_arrows.xml",
    "content": "<animated-vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    tools:ignore=\"NewApi\">\n\n    <aapt:attr name=\"android:drawable\">\n        <vector\n            android:width=\"6dp\"\n            android:height=\"6dp\"\n            android:viewportWidth=\"24\"\n            android:viewportHeight=\"24\">\n            <group\n                android:name=\"rotationGroup\"\n                android:pivotX=\"12\"\n                android:pivotY=\"12\"\n                android:scaleX=\"-1\">\n                <path\n                    android:name=\"arrowBottom\"\n                    android:fillColor=\"#FFF\"\n                    android:pathData=\"M7.52,21.48C4.25,19.94 1.91,16.76 1.55,13L0.05,13C0.56,19.16 5.71,24 12,24l0.66,-0.03 -3.81,-3.81 -1.33,1.32z\" />\n                <path\n                    android:name=\"arrowTop\"\n                    android:fillColor=\"#FFF\"\n                    android:pathData=\"M12,0l-0.66,0.03 3.81,3.81 1.33,-1.33c3.27,1.55 5.61,4.72 5.96,8.48h1.5C23.44,4.84 18.29,0 12,0z\" />\n            </group>\n        </vector>\n    </aapt:attr>\n\n    <target android:name=\"rotationGroup\">\n\n        <aapt:attr name=\"android:animation\">\n            <objectAnimator\n                android:duration=\"1000\"\n                android:interpolator=\"@android:interpolator/linear\"\n                android:propertyName=\"rotation\"\n                android:repeatCount=\"infinite\"\n                android:repeatMode=\"restart\"\n                android:valueFrom=\"0\"\n                android:valueTo=\"360\" />\n        </aapt:attr>\n\n    </target>\n</animated-vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_loading_pulse.xml",
    "content": "<animated-vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    tools:ignore=\"NewApi\">\n\n    <aapt:attr name=\"android:drawable\">\n        <vector\n            android:width=\"6dp\"\n            android:height=\"6dp\"\n            android:viewportWidth=\"18\"\n            android:viewportHeight=\"18\">\n            <path\n                android:name=\"discPath\"\n                android:fillColor=\"#FFF\"\n                android:pathData=\"\n                    M 9, 9\n                    m -2, 0\n                    a2,2 0 1,0 4,0\n                     2,2 0 1,0 -4,0\n                    z\"\n                android:strokeWidth=\"1\"\n                android:strokeColor=\"#FFF\" />\n\n            <path\n                android:name=\"circlePath\"\n                android:fillColor=\"#FFF\"\n                android:fillType=\"evenOdd\"\n                android:pathData=\"\n                    M 9, 9\n                    m -3, 0\n                    a3,3 0 1,0 6,0\n                     3,3 0 1,0 -6,0\n                    z\n\n                    M 9, 9\n                    m -2, 0\n                    a2,2 0 1,0 4,0\n                     2,2 0 1,0 -4,0\n                    z\" />\n        </vector>\n    </aapt:attr>\n\n    <target android:name=\"circlePath\">\n        <aapt:attr name=\"android:animation\">\n            <set android:shareInterpolator=\"false\">\n                <objectAnimator\n                    android:duration=\"500\"\n                    android:interpolator=\"@android:interpolator/accelerate_cubic\"\n                    android:propertyName=\"pathData\"\n                    android:repeatCount=\"infinite\"\n                    android:repeatMode=\"reverse\"\n                    android:valueFrom=\"\n                    M 9, 9\n                    m -3, 0\n                    a3,3 0 1,0 6,0\n                     3,3 0 1,0 -6,0\n                    z\n\n                    M 9, 9\n                    m -2, 0\n                    a2,2 0 1,0 4,0\n                     2,2 0 1,0 -4,0\n                    z\"\n                    android:valueTo=\"\n                    M 9, 9\n                    m -9, 0\n                    a9,9 0 1,0 18,0\n                     9,9 0 1,0 -18,0\n                    z\n\n                    M 9, 9\n                    m -8, 0\n                    a8,8 0 1,0 16,0\n                     8,8 0 1,0 -16,0\n                    z\"\n                    android:valueType=\"pathType\" />\n\n                <objectAnimator\n                    android:duration=\"500\"\n                    android:interpolator=\"@android:interpolator/accelerate_decelerate\"\n                    android:propertyName=\"fillAlpha\"\n                    android:repeatCount=\"infinite\"\n                    android:repeatMode=\"restart\"\n                    android:valueFrom=\"1\"\n                    android:valueTo=\"0\"\n                    android:valueType=\"floatType\" />\n            </set>\n        </aapt:attr>\n    </target>\n\n    <target android:name=\"discPath\">\n        <aapt:attr name=\"android:animation\">\n            <set android:shareInterpolator=\"true\">\n                <objectAnimator\n                    android:duration=\"1000\"\n                    android:interpolator=\"@android:interpolator/accelerate_decelerate\"\n                    android:propertyName=\"fillAlpha\"\n                    android:repeatCount=\"infinite\"\n                    android:repeatMode=\"reverse\"\n                    android:valueFrom=\"0\"\n                    android:valueTo=\"1\"\n                    android:valueType=\"floatType\" />\n\n                <objectAnimator\n                    android:duration=\"1000\"\n                    android:interpolator=\"@android:interpolator/accelerate_decelerate\"\n                    android:propertyName=\"strokeAlpha\"\n                    android:repeatCount=\"infinite\"\n                    android:repeatMode=\"reverse\"\n                    android:valueFrom=\"1\"\n                    android:valueTo=\"0\"\n                    android:valueType=\"floatType\" />\n            </set>\n        </aapt:attr>\n    </target>\n</animated-vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_memory.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"12dp\"\n    android:height=\"12dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M15,9L9,9v6h6L15,9zM13,13h-2v-2h2v2zM21,11L21,9h-2L19,7c0,-1.1 -0.9,-2 -2,-2h-2L15,3h-2v2h-2L11,3L9,3v2L7,5c-1.1,0 -2,0.9 -2,2v2L3,9v2h2v2L3,13v2h2v2c0,1.1 0.9,2 2,2h2v2h2v-2h2v2h2v-2h2c1.1,0 2,-0.9 2,-2v-2h2v-2h-2v-2h2zM17,17L7,17L7,7h10v10z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_message.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM18,14L6,14v-2h12v2zM18,11L6,11L6,9h12v2zM18,8L6,8L6,6h12v2z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_phone.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M6.62,10.79c1.44,2.83 3.76,5.14 6.59,6.59l2.2,-2.2c0.27,-0.27 0.67,-0.36 1.02,-0.24 1.12,0.37 2.33,0.57 3.57,0.57 0.55,0 1,0.45 1,1V20c0,0.55 -0.45,1 -1,1 -9.39,0 -17,-7.61 -17,-17 0,-0.55 0.45,-1 1,-1h3.5c0.55,0 1,0.45 1,1 0,1.25 0.2,2.45 0.57,3.57 0.11,0.35 0.03,0.74 -0.25,1.02l-2.2,2.2z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_phone_ui.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"12dp\"\n    android:height=\"12dp\"\n    android:viewportWidth=\"512\"\n    android:viewportHeight=\"512\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"m187.734,42.668h-85.336c-4.707,0 -8.531,-3.824 -8.531,-8.535 0,-4.711 3.824,-8.531 8.531,-8.531h85.336c4.711,0 8.531,3.82 8.531,8.531 0,4.711 -3.82,8.535 -8.531,8.535zM187.734,42.668\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"m247.465,42.668h-8.531c-4.711,0 -8.535,-3.824 -8.535,-8.535 0,-4.711 3.824,-8.531 8.535,-8.531h8.531c4.711,0 8.535,3.82 8.535,8.531 0,4.711 -3.824,8.535 -8.535,8.535zM247.465,42.668\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"m155.656,486.398h-21.188c-12.98,0 -23.535,-10.555 -23.535,-23.535v-4.121c0,-12.977 10.555,-23.543 23.535,-23.543h21.188c12.98,0 23.543,10.566 23.543,23.543v4.121c0,12.98 -10.563,23.535 -23.543,23.535zM134.469,452.266c-3.566,0 -6.469,2.902 -6.469,6.477v4.121c0,3.57 2.902,6.469 6.469,6.469h21.188c3.574,0 6.477,-2.898 6.477,-6.469v-4.121c0,-3.574 -2.902,-6.477 -6.477,-6.477zM134.469,452.266\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"m281.602,426.668h-273.066c-4.711,0 -8.535,-3.824 -8.535,-8.535v-358.398c0,-4.711 3.824,-8.535 8.535,-8.535h273.066c4.707,0 8.531,3.824 8.531,8.535v128c0,4.711 -3.824,8.531 -8.531,8.531 -4.711,0 -8.535,-3.82 -8.535,-8.531v-119.469h-256v341.336h256v-119.469c0,-4.711 3.824,-8.531 8.535,-8.531 4.707,0 8.531,3.82 8.531,8.531v128c0,4.711 -3.824,8.535 -8.531,8.535zM281.602,426.668\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"m259.285,512h-228.438c-17.008,0 -30.848,-13.84 -30.848,-30.848v-63.02c0,-4.711 3.824,-8.531 8.535,-8.531h273.066c4.707,0 8.531,3.82 8.531,8.531v63.02c0,17.008 -13.84,30.848 -30.848,30.848zM17.066,426.668v54.484c0,7.594 6.188,13.781 13.781,13.781h228.445c7.586,0 13.773,-6.188 13.773,-13.781v-54.484zM17.066,426.668\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"m281.602,68.266h-273.066c-4.711,0 -8.535,-3.82 -8.535,-8.531v-28.887c0,-17.008 13.84,-30.848 30.848,-30.848h228.445c17,0 30.84,13.84 30.84,30.848v28.887c0,4.711 -3.824,8.531 -8.531,8.531zM17.066,51.199h256v-20.352c0,-7.594 -6.188,-13.781 -13.781,-13.781h-228.438c-7.594,0 -13.781,6.188 -13.781,13.781zM17.066,51.199\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"m145.066,332.801c-51.754,0 -93.867,-42.113 -93.867,-93.867s42.113,-93.867 93.867,-93.867 93.867,42.113 93.867,93.867 -42.113,93.867 -93.867,93.867zM145.066,162.133c-42.344,0 -76.801,34.457 -76.801,76.801s34.457,76.801 76.801,76.801 76.801,-34.457 76.801,-76.801 -34.457,-76.801 -76.801,-76.801zM145.066,162.133\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"m145.066,281.602c-4.711,0 -8.531,-3.824 -8.531,-8.535v-68.266c0,-4.711 3.82,-8.535 8.531,-8.535 4.711,0 8.535,3.824 8.535,8.535v68.266c0,4.711 -3.824,8.535 -8.535,8.535zM145.066,281.602\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"m179.199,247.465h-68.266c-4.711,0 -8.535,-3.82 -8.535,-8.531 0,-4.711 3.824,-8.535 8.535,-8.535h68.266c4.711,0 8.535,3.824 8.535,8.535 0,4.711 -3.824,8.531 -8.535,8.531zM179.199,247.465\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"m469.332,85.332h-34.133c-4.711,0 -8.531,-3.82 -8.531,-8.531v-34.133c0,-4.711 3.82,-8.535 8.531,-8.535h34.133c4.711,0 8.535,3.824 8.535,8.535v34.133c0,4.711 -3.824,8.531 -8.535,8.531zM443.734,68.266h17.066v-17.066h-17.066zM443.734,68.266\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"m503.465,119.465h-102.398c-4.711,0 -8.531,-3.82 -8.531,-8.531v-102.398c0,-4.711 3.82,-8.535 8.531,-8.535h102.398c4.711,0 8.535,3.824 8.535,8.535v102.398c0,4.711 -3.824,8.531 -8.535,8.531zM409.602,102.398h85.332v-85.332h-85.332zM409.602,102.398\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"m469.332,443.734h-34.133c-4.711,0 -8.531,-3.824 -8.531,-8.535v-34.133c0,-4.711 3.82,-8.531 8.531,-8.531h34.133c4.711,0 8.535,3.82 8.535,8.531v34.133c0,4.711 -3.824,8.535 -8.535,8.535zM443.734,426.668h17.066v-17.066h-17.066zM443.734,426.668\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"m503.465,477.867h-102.398c-4.711,0 -8.531,-3.824 -8.531,-8.535v-102.398c0,-4.711 3.82,-8.535 8.531,-8.535h102.398c4.711,0 8.535,3.824 8.535,8.535v102.398c0,4.711 -3.824,8.535 -8.535,8.535zM409.602,460.801h85.332v-85.336h-85.332zM409.602,460.801\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"m469.332,264.535h-34.133c-4.711,0 -8.531,-3.824 -8.531,-8.535v-34.133c0,-4.711 3.82,-8.535 8.531,-8.535h34.133c4.711,0 8.535,3.824 8.535,8.535v34.133c0,4.711 -3.824,8.535 -8.535,8.535zM443.734,247.465h17.066v-17.066h-17.066zM443.734,247.465\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"m503.465,298.668h-102.398c-4.711,0 -8.531,-3.824 -8.531,-8.535v-102.398c0,-4.711 3.82,-8.535 8.531,-8.535h102.398c4.711,0 8.535,3.824 8.535,8.535v102.398c0,4.711 -3.824,8.535 -8.535,8.535zM409.602,281.602h85.332v-85.336h-85.332zM409.602,281.602\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"m196.273,179.199c-3.027,0 -5.965,-1.613 -7.508,-4.469 -2.242,-4.141 -0.707,-9.32 3.438,-11.566l204.801,-110.93c4.148,-2.254 9.32,-0.711 11.563,3.438 2.246,4.137 0.711,9.316 -3.438,11.563l-204.801,110.934c-1.289,0.699 -2.68,1.031 -4.055,1.031zM196.273,179.199\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"m401.059,426.668c-1.375,0 -2.766,-0.336 -4.055,-1.035l-204.801,-110.934c-4.145,-2.242 -5.691,-7.422 -3.438,-11.563 2.246,-4.145 7.426,-5.684 11.563,-3.438l204.801,110.934c4.148,2.242 5.691,7.422 3.438,11.563 -1.543,2.859 -4.48,4.473 -7.508,4.473zM401.059,426.668\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"m401.066,247.465h-34.133c-4.711,0 -8.535,-3.82 -8.535,-8.531 0,-4.711 3.824,-8.535 8.535,-8.535h34.133c4.711,0 8.535,3.824 8.535,8.535 0,4.711 -3.824,8.531 -8.535,8.531zM332.801,247.465h-34.133c-4.711,0 -8.535,-3.82 -8.535,-8.531 0,-4.711 3.824,-8.535 8.535,-8.535h34.133c4.711,0 8.531,3.824 8.531,8.535 0,4.711 -3.82,8.531 -8.531,8.531zM264.535,247.465h-34.137c-4.707,0 -8.531,-3.82 -8.531,-8.531 0,-4.711 3.824,-8.535 8.531,-8.535h34.137c4.707,0 8.531,3.824 8.531,8.535 0,4.711 -3.824,8.531 -8.531,8.531zM264.535,247.465\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_popup.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"12dp\"\n    android:height=\"12dp\"\n    android:viewportWidth=\"512\"\n    android:viewportHeight=\"512\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"m308.613,464h-92.803c-4.142,0 -7.5,3.358 -7.5,7.5s3.358,7.5 7.5,7.5h92.803c4.142,0 7.5,-3.358 7.5,-7.5s-3.358,-7.5 -7.5,-7.5z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"m445,107.5h-33.29v-12c0,-4.142 -3.358,-7.5 -7.5,-7.5s-7.5,3.358 -7.5,7.5v12h-204.469c-4.142,0 -7.5,3.358 -7.5,7.5s3.358,7.5 7.5,7.5h249.259v147.258h-158.072c-4.307,0 -8.216,2.374 -10.203,6.195l-17.225,33.125 -17.225,-33.125c-1.987,-3.821 -5.896,-6.195 -10.203,-6.195h-158.072v-147.258h89.555c4.142,0 7.5,-3.358 7.5,-7.5s-3.358,-7.5 -7.5,-7.5h-32.345v-70c0,-12.407 10.093,-22.5 22.5,-22.5h28.354v22.5c0,9.649 7.851,17.5 17.5,17.5h132c9.649,0 17.5,-7.851 17.5,-17.5v-22.5h28.646c12.407,0 22.5,10.093 22.5,22.5v26c0,4.142 3.358,7.5 7.5,7.5s7.5,-3.358 7.5,-7.5v-26c0,-20.678 -16.822,-37.5 -37.5,-37.5h-224c-20.678,0 -37.5,16.822 -37.5,37.5v70h-45.71c-6.341,0 -11.5,5.159 -11.5,11.5v154.258c0,6.341 5.159,11.5 11.5,11.5h45.71v98.742c0,4.142 3.358,7.5 7.5,7.5s7.5,-3.358 7.5,-7.5v-98.742h98.737l21.125,40.625c1.641,3.157 4.871,5.118 8.428,5.118s6.787,-1.961 8.428,-5.117l21.125,-40.625h111.157v147.741h-269v-16.07c0,-4.142 -3.358,-7.5 -7.5,-7.5s-7.5,3.358 -7.5,7.5v58.07c0,20.678 16.822,37.5 37.5,37.5h224c20.678,0 37.5,-16.822 37.5,-37.5v-34.345c0.001,-0.052 0.002,-0.103 0.002,-0.155s-0.001,-0.103 -0.002,-0.155v-155.087h33.29c6.341,0 11.5,-5.159 11.5,-11.5v-154.258c0,-6.341 -5.159,-11.5 -11.5,-11.5zM330.563,15v22.5c0,1.378 -1.122,2.5 -2.5,2.5h-132c-1.378,0 -2.5,-1.122 -2.5,-2.5v-22.5zM374.21,497h-224c-12.407,0 -22.5,-10.093 -22.5,-22.5v-27h269v27c0,12.407 -10.093,22.5 -22.5,22.5z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"m106.5,159v68c0,7.444 6.056,13.5 13.5,13.5h68c7.444,0 13.5,-6.056 13.5,-13.5v-68c0,-7.444 -6.056,-13.5 -13.5,-13.5h-68c-7.444,0 -13.5,6.056 -13.5,13.5zM121.5,160.5h65v65h-65z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"m227,178.5h160c4.142,0 7.5,-3.358 7.5,-7.5s-3.358,-7.5 -7.5,-7.5h-160c-4.142,0 -7.5,3.358 -7.5,7.5s3.358,7.5 7.5,7.5z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"m227,224.5h160c4.142,0 7.5,-3.358 7.5,-7.5s-3.358,-7.5 -7.5,-7.5h-160c-4.142,0 -7.5,3.358 -7.5,7.5s3.358,7.5 7.5,7.5z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_quick.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"12dp\"\n    android:height=\"12dp\"\n    android:viewportWidth=\"512\"\n    android:viewportHeight=\"512\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M510.4,179.3a7.5,7.5 0,0 0,-5.9 -3L296.1,176.3a7.5,7.5 0,0 0,-7.3 5.6l-30,112.2a7.5,7.5 0,0 0,7.2 9.5h133.1l-4.6,17L201.6,320.6l26.1,-97.2L256,223.4a7.5,7.5 0,0 0,0 -15h-34a7.5,7.5 0,0 0,-7.3 5.6l-30,112.2a7.5,7.5 0,0 0,7.2 9.4h208.4c3.4,0 6.4,-2.2 7.2,-5.5l7.1,-26.5h59.8c3.4,0 6.4,-2.3 7.3,-5.6l30,-112.2c0.6,-2.3 0.2,-4.7 -1.2,-6.5zM301.8,191.3h88.7l-26,97.3h-88.7l26,-97.2zM468.7,288.6L380,288.6l26,-97.2h88.7l-26,97.2z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M319.3,216.7c-4,-1 -8,1.3 -9.1,5.3l-8.6,32a7.5,7.5 0,1 0,14.5 4l8.6,-32.1c1,-4 -1.4,-8.1 -5.4,-9.2zM451.2,263.2a7.5,7.5 0,0 0,9.1 -5.3l8.6,-32a7.5,7.5 0,0 0,-14.4 -4l-8.6,32.2c-1.1,4 1.3,8 5.3,9.1zM236,254l-8.6,32.1A7.5,7.5 0,0 0,242 290l8.6,-32a7.5,7.5 0,0 0,-14.5 -4zM127.7,191.4H264a7.5,7.5 0,0 0,0 -15H127.7a7.5,7.5 0,0 0,0 15zM127.2,216c0,-4.2 -3.3,-7.6 -7.5,-7.6H7.5a7.5,7.5 0,0 0,0 15h112.2c4.2,0 7.5,-3.3 7.5,-7.5zM191.4,216c0,-4.2 -3.4,-7.6 -7.5,-7.6h-32.1a7.5,7.5 0,0 0,0 15h32c4.2,0 7.6,-3.3 7.6,-7.5zM175.8,240.5H63.6a7.5,7.5 0,0 0,0 15h112.2a7.5,7.5 0,0 0,0 -15zM175.8,272.5h-64a7.5,7.5 0,0 0,0 15h64a7.5,7.5 0,0 0,0 -15z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_refresh.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"12dp\"\n    android:height=\"12dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_remove_tag.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M7,11v2h10v-2L7,11zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_search.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"12dp\"\n    android:height=\"12dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_search_bar.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"12dp\"\n    android:height=\"12dp\"\n    android:viewportWidth=\"512\"\n    android:viewportHeight=\"512\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"m467,53.178h-82.961c-0.013,0 -0.025,-0.002 -0.039,-0.002s-0.025,0.002 -0.039,0.002h-338.961c-24.813,0 -45,20.187 -45,45v68c0,24.813 20.187,45 45,45h82.427c-6.507,14.399 -10.132,30.368 -10.132,47.169 0,63.362 51.549,114.911 114.911,114.911 16.983,0 33.115,-3.707 47.636,-10.349l54.594,88.771c2.833,4.605 7.753,7.144 12.792,7.144 2.68,0 5.393,-0.718 7.844,-2.225 7.056,-4.34 9.259,-13.579 4.919,-20.636l-54.725,-88.983c25.545,-21.094 41.853,-52.995 41.853,-88.633 0,-16.801 -3.625,-32.77 -10.133,-47.169h130.014c24.813,0 45,-20.187 45,-45v-68c0,-24.813 -20.187,-45 -45,-45zM232.206,343.258c-46.82,0 -84.911,-38.091 -84.911,-84.911s38.091,-84.912 84.911,-84.912 84.912,38.092 84.912,84.912 -38.092,84.911 -84.912,84.911zM232.206,143.435c-33.688,0 -64.035,14.572 -85.073,37.743h-102.133c-8.271,0 -15,-6.729 -15,-15v-68c0,-8.271 6.729,-15 15,-15h324v98h-51.721c-21.037,-23.171 -51.385,-37.743 -85.073,-37.743zM482,166.178c0,8.271 -6.729,15 -15,15h-68v-98h68c8.271,0 15,6.729 15,15z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_send.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_settings.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"12dp\"\n    android:height=\"12dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98 0,-0.34 -0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.09,-0.16 -0.26,-0.25 -0.44,-0.25 -0.06,0 -0.12,0.01 -0.17,0.03l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.06,-0.02 -0.12,-0.03 -0.18,-0.03 -0.17,0 -0.34,0.09 -0.43,0.25l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98 0,0.33 0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.09,0.16 0.26,0.25 0.44,0.25 0.06,0 0.12,-0.01 0.17,-0.03l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.06,0.02 0.12,0.03 0.18,0.03 0.17,0 0.34,-0.09 0.43,-0.25l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM17.45,11.27c0.04,0.31 0.05,0.52 0.05,0.73 0,0.21 -0.02,0.43 -0.05,0.73l-0.14,1.13 0.89,0.7 1.08,0.84 -0.7,1.21 -1.27,-0.51 -1.04,-0.42 -0.9,0.68c-0.43,0.32 -0.84,0.56 -1.25,0.73l-1.06,0.43 -0.16,1.13 -0.2,1.35h-1.4l-0.19,-1.35 -0.16,-1.13 -1.06,-0.43c-0.43,-0.18 -0.83,-0.41 -1.23,-0.71l-0.91,-0.7 -1.06,0.43 -1.27,0.51 -0.7,-1.21 1.08,-0.84 0.89,-0.7 -0.14,-1.13c-0.03,-0.31 -0.05,-0.54 -0.05,-0.74s0.02,-0.43 0.05,-0.73l0.14,-1.13 -0.89,-0.7 -1.08,-0.84 0.7,-1.21 1.27,0.51 1.04,0.42 0.9,-0.68c0.43,-0.32 0.84,-0.56 1.25,-0.73l1.06,-0.43 0.16,-1.13 0.2,-1.35h1.39l0.19,1.35 0.16,1.13 1.06,0.43c0.43,0.18 0.83,0.41 1.23,0.71l0.91,0.7 1.06,-0.43 1.27,-0.51 0.7,1.21 -1.07,0.85 -0.89,0.7 0.14,1.13zM12,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM12,14c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_shortcuts.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"204\"\n    android:viewportHeight=\"204\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M142,160c-6.431,0 -12.871,0.446 -18.962,1.305l-54.289,-43.057C81.888,115.646 92,110.173 92,102c0,-9.71 -14.272,-15.608 -30.939,-17.404L117.656,28l-0.042,-0.043C118.41,27.98 119.207,28 120,28c15.412,0 32,-4.381 32,-14S135.412,0 120,0S88,4.381 88,14c0,6.969 8.716,11.175 19.373,12.971L50.338,84.006C50.225,84.006 50.112,84 50,84c-20.871,0 -42,6.184 -42,18s21.129,18 42,18c2.585,0 5.174,-0.096 7.726,-0.285l54.927,43.562C98.39,166.785 88,173.073 88,182c0,14.443 27.166,22 54,22s54,-7.557 54,-22S168.834,160 142,160zM95.992,14.072C96.488,12.398 104.623,8 120,8s23.512,4.398 24.008,5.928C143.512,15.602 135.377,20 120,20S96.488,15.602 95.992,14.072zM50,112c-21.092,0 -34,-6.475 -34,-10s12.908,-10 34,-10s34,6.475 34,10S71.092,112 50,112zM142,196c-28.08,0 -46,-8.291 -46,-14s17.92,-14 46,-14s46,8.291 46,14S170.08,196 142,196z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M129.798,52.968l-1.706,-15.91l7.952,-0.853l1.706,15.91z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M146.275,131.661l1.705,15.908l-7.952,0.853l-1.705,-15.908z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M139.455,68.025l1.705,15.908l-7.952,0.853l-1.705,-15.908z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M142.866,99.843l1.705,15.908l-7.952,0.853l-1.705,-15.908z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_shortcuts_az.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"512\"\n    android:viewportHeight=\"512\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M280.5,390.68c-12.8,0 -25.8,0.96 -38,3.12l-108.6,-103.44C160.3,284.12 180.5,271.16 180.5,251.48c0,-23.28 -28.6,-37.44 -61.8,-41.76L231.7,73.88l0,0C233.3,73.88 234.9,73.88 236.5,73.88c30.8,0 64,-10.56 64,-33.6S267.3,6.68 236.5,6.68S172.5,17.24 172.5,40.28c0,16.8 17.4,26.88 38.8,31.2L97.1,208.28C96.9,208.28 96.7,208.28 96.5,208.28c-41.8,0 -84,14.88 -84,43.2s42.2,43.2 84,43.2c5.2,0 10.4,-0.24 15.4,-0.72l109.8,104.64C193.3,407 172.5,422.12 172.5,443.48c0,34.56 54.4,52.8 108,52.8s108,-18.24 108,-52.8S334.1,390.68 280.5,390.68zM188.5,40.52C189.5,36.44 205.7,25.88 236.5,25.88s47,10.56 48,14.16C283.5,44.12 267.3,54.68 236.5,54.68S189.5,44.12 188.5,40.52zM96.5,275.48c-42.2,0 -68,-15.6 -68,-24s25.8,-24 68,-24s68,15.6 68,24S138.7,275.48 96.5,275.48zM280.5,477.08c-56.2,0 -92,-19.92 -92,-33.6s35.8,-33.6 92,-33.6s92,19.92 92,33.6S336.7,477.08 280.5,477.08z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M246,144.65l-4.8,-38.4l16,-2.4l4.8,38.4z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M265,170.71l4.8,38.4l-16,2.4l-4.8,-38.4z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M274,246.53l4.8,38.4l-16,2.4l-4.8,-38.4z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M281.7,325.03l4.8,38.4l-16,2.4l-4.8,-38.4z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M367.31,89.8c-5,13.4 -16.1,42.9 -24.6,65.7c-8.6,22.7 -15.6,41.5 -15.6,41.7c0,0.2 9.4,0.4 20.8,0.4l20.9,-0l1.2,-4.3c0.7,-2.3 2.2,-7.2 3.3,-11l2,-6.7l23.1,0.2l23.1,0.3l3.4,10.8l3.3,10.7l21.2,-0.2c13.4,-0.2 21.1,-0.7 21,-1.3c-0.2,-1.4 -48.1,-128.4 -48.8,-129.5c-0.4,-0.6 -9.7,-1 -22.9,-1l-22.3,-0l-9.1,24.2zM406.11,124.5l6.7,22.1l-14.3,-0l-14.3,-0l0.9,-2.8c0.5,-1.5 3.4,-11 6.5,-21.2c6.9,-22.9 6.4,-21.4 7.1,-20.7c0.3,0.3 3.6,10.5 7.4,22.6z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M341.41,255l0,14l32.7,0.2l32.8,0.3l-36.8,38.2l-36.8,38.3l0.3,13.2l0.3,13.3l62.8,0.3l62.8,0.2l-0.3,-14.2l-0.3,-14.3l-37.6,-0.5l-37.6,-0.5l36.3,-37.9l36.4,-37.9l0,-13.3l0,-13.4l-57.5,-0l-57.5,-0l0,14z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_shortcuts_za.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"512\"\n    android:viewportHeight=\"512\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M280.5,390.68c-12.8,0 -25.8,0.96 -38,3.12l-108.6,-103.44C160.3,284.12 180.5,271.16 180.5,251.48c0,-23.28 -28.6,-37.44 -61.8,-41.76L231.7,73.88l0,0C233.3,73.88 234.9,73.88 236.5,73.88c30.8,0 64,-10.56 64,-33.6S267.3,6.68 236.5,6.68S172.5,17.24 172.5,40.28c0,16.8 17.4,26.88 38.8,31.2L97.1,208.28C96.9,208.28 96.7,208.28 96.5,208.28c-41.8,0 -84,14.88 -84,43.2s42.2,43.2 84,43.2c5.2,0 10.4,-0.24 15.4,-0.72l109.8,104.64C193.3,407 172.5,422.12 172.5,443.48c0,34.56 54.4,52.8 108,52.8s108,-18.24 108,-52.8S334.1,390.68 280.5,390.68zM188.5,40.52C189.5,36.44 205.7,25.88 236.5,25.88s47,10.56 48,14.16C283.5,44.12 267.3,54.68 236.5,54.68S189.5,44.12 188.5,40.52zM96.5,275.48c-42.2,0 -68,-15.6 -68,-24s25.8,-24 68,-24s68,15.6 68,24S138.7,275.48 96.5,275.48zM280.5,477.08c-56.2,0 -92,-19.92 -92,-33.6s35.8,-33.6 92,-33.6s92,19.92 92,33.6S336.7,477.08 280.5,477.08z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M246,144.65l-4.8,-38.4l16,-2.4l4.8,38.4z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M265,170.71l4.8,38.4l-16,2.4l-4.8,-38.4z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M274,246.53l4.8,38.4l-16,2.4l-4.8,-38.4z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M281.7,325.03l4.8,38.4l-16,2.4l-4.8,-38.4z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M368.31,253.8c-5,13.4 -16.1,42.9 -24.6,65.7c-8.6,22.7 -15.6,41.5 -15.6,41.7c0,0.2 9.4,0.4 20.8,0.4l20.9,-0l1.2,-4.3c0.7,-2.3 2.2,-7.2 3.3,-11l2,-6.7l23.1,0.2l23.1,0.3l3.4,10.8l3.3,10.7l21.2,-0.2c13.4,-0.2 21.1,-0.7 21,-1.3c-0.2,-1.4 -48.1,-128.4 -48.8,-129.5c-0.4,-0.6 -9.7,-1 -22.9,-1l-22.3,-0l-9.1,24.2zM407.11,288.5l6.7,22.1l-14.3,-0l-14.3,-0l0.9,-2.8c0.5,-1.5 3.4,-11 6.5,-21.2c6.9,-22.9 6.4,-21.4 7.1,-20.7c0.3,0.3 3.6,10.5 7.4,22.6z\" />\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M343.41,81l0,14l32.7,0.2l32.8,0.3l-36.8,38.2l-36.8,38.3l0.3,13.2l0.3,13.3l62.8,0.3l62.8,0.2l-0.3,-14.2l-0.3,-14.3l-37.6,-0.5l-37.6,-0.5l36.3,-37.9l36.4,-37.9l0,-13.3l0,-13.4l-57.5,-0l-57.5,-0l0,14z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_tags.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"12dp\"\n    android:height=\"12dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M17.63,5.84C17.27,5.33 16.67,5 16,5L5,5.01C3.9,5.01 3,5.9 3,7v10c0,1.1 0.9,1.99 2,1.99L16,19c0.67,0 1.27,-0.33 1.63,-0.84L22,12l-4.37,-6.16z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_undo.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M12.5,8c-2.65,0 -5.05,0.99 -6.9,2.6L2,7v9h9l-3.62,-3.62c1.39,-1.16 3.16,-1.88 5.12,-1.88 3.54,0 6.55,2.31 7.6,5.5l2.37,-0.78C21.08,11.03 17.15,8 12.5,8z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_untagged.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"12dp\"\n    android:height=\"12dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:fillType=\"evenOdd\"\n        android:pathData=\"m17.63,5.84c-0.36,-0.51 -0.96,-0.84 -1.63,-0.84l-11,0.01c-1.1,0 -2,0.89 -2,1.99l0,10c0,1.1 0.9,1.99 2,1.99l11,0.01c0.67,0 1.27,-0.33 1.63,-0.84l4.37,-6.16l-4.37,-6.16zM1.63,21.84c-0.384,-0.384 -0.384,-1.03 0,-1.414l17.089,-17.089c0.384,-0.384 1.03,-0.384 1.414,0l0,0c0.384,0.384 0.384,1.03 0,1.414l-17.089,17.089c-0.384,0.384 -1.03,0.384 -1.414,0l0,0z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_wallpaper.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"12dp\"\n    android:height=\"12dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M4,4h7L11,2L4,2c-1.1,0 -2,0.9 -2,2v7h2L4,4zM10,13l-4,5h12l-3,-4 -2.03,2.71L10,13zM17,8.5c0,-0.83 -0.67,-1.5 -1.5,-1.5S14,7.67 14,8.5s0.67,1.5 1.5,1.5S17,9.33 17,8.5zM20,2h-7v2h7v7h2L22,4c0,-1.1 -0.9,-2 -2,-2zM20,20h-7v2h7c1.1,0 2,-0.9 2,-2v-7h-2v7zM4,13L2,13v7c0,1.1 0.9,2 2,2h7v-2L4,20v-7z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/launcher_pill.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"15dp\"\n    android:height=\"10dp\"\n    android:viewportWidth=\"15\"\n    android:viewportHeight=\"10\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:fillType=\"evenOdd\"\n        android:pathData=\"\n        M11,0\n        a4,4 0 0 1 4,4\n        v2\n        a4,4 0 0 1 -4,4\n        h-6\n        a4,4 0 0 1 -4,-4\n        v-2\n        a4,4 0 0 1 4,-4\n        z\n        M4,1h2v2h-2z m0.2,0.2h1.6v1.6h-1.6z\n        M4,4h2v2h-2z m0.2,0.2h1.6v1.6h-1.6z\n        M4,7h2v2h-2z m0.2,0.2h1.6v1.6h-1.6z\n        M7,1h2v2h-2z m0.2,0.2h1.6v1.6h-1.6z\n        M7,4h2v2h-2z m0.2,0.2h1.6v1.6h-1.6z\n        M7,7h2v2h-2z m0.2,0.2h1.6v1.6h-1.6z\n        M10,1h2v2h-2z m0.2,0.2h1.6v1.6h-1.6z\n        M10,4h2v2h-2z m0.2,0.2h1.6v1.6h-1.6z\n        M10,7h2v2h-2z m0.2,0.2h1.6v1.6h-1.6z\n        \" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/launcher_pill_background.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"15dp\"\n    android:height=\"10dp\"\n    android:viewportWidth=\"15\"\n    android:viewportHeight=\"10\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"\n        M5,0\n        a4,4 0 0 0 -4,4\n        v2\n        a4,4 0 0 0 4,4\n        h-5 v-10 z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/launcher_white.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"#FFF\"\n        android:pathData=\"M4,8h4L8,4L4,4v4zM10,20h4v-4h-4v4zM4,20h4v-4L4,16v4zM4,14h4v-4L4,10v4zM10,14h4v-4h-4v4zM16,4v4h4L20,4h-4zM10,8h4L14,4h-4v4zM16,14h4v-4h-4v4zM16,20h4v-4h-4v4z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/list_separator_dark.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item>\n        <shape android:shape=\"rectangle\">\n            <size android:height=\"3dp\" />\n            <solid android:color=\"@color/kiss_divider_light\" />\n            <padding\n                android:bottom=\"1dp\"\n                android:left=\"7dp\"\n                android:right=\"7dp\"\n                android:top=\"1dp\" />\n        </shape>\n    </item>\n    <item>\n        <shape android:shape=\"rectangle\">\n            <solid android:color=\"@color/kiss_divider_dark\" />\n        </shape>\n    </item>\n</layer-list>"
  },
  {
    "path": "app/src/main/res/drawable/list_separator_deep_blues.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item>\n        <shape android:shape=\"rectangle\">\n            <size android:height=\"3dp\" />\n            <solid android:color=\"@color/DeepBlues_4\" />\n            <padding\n                android:bottom=\"1dp\"\n                android:left=\"7dp\"\n                android:right=\"7dp\"\n                android:top=\"1dp\" />\n        </shape>\n    </item>\n    <item>\n        <shape android:shape=\"rectangle\">\n            <solid android:color=\"@color/DeepBlues_5\" />\n        </shape>\n    </item>\n</layer-list>"
  },
  {
    "path": "app/src/main/res/drawable/list_separator_default.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n    <size android:height=\"3dp\" />\n    <gradient\n        android:centerColor=\"@color/Default_dark\"\n        android:endColor=\"@android:color/transparent\"\n        android:startColor=\"@android:color/transparent\" />\n    <padding\n        android:left=\"7dp\"\n        android:right=\"7dp\" />\n</shape>\n"
  },
  {
    "path": "app/src/main/res/drawable/list_separator_light.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item>\n        <shape android:shape=\"rectangle\">\n            <size android:height=\"3dp\" />\n            <solid android:color=\"@color/kiss_divider_dark\" />\n            <padding\n                android:bottom=\"1dp\"\n                android:left=\"7dp\"\n                android:right=\"7dp\"\n                android:top=\"1dp\" />\n        </shape>\n    </item>\n    <item>\n        <shape android:shape=\"rectangle\">\n            <solid android:color=\"@color/kiss_divider_light\" />\n        </shape>\n    </item>\n</layer-list>"
  },
  {
    "path": "app/src/main/res/drawable/mm2d_cc_ic_check.xml",
    "content": "<vector\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24.0\"\n    android:viewportHeight=\"24.0\"\n    >\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM10,17l-5,-5 1.41,-1.41L10,14.17l7.59,-7.59L19,8l-9,9z\"\n        />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/notification_bar_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <gradient\n        android:angle=\"90\"\n        android:endColor=\"#FFffffff\"\n        android:centerColor=\"#A8ffffff\"\n        android:startColor=\"#00ffffff\"\n        android:type=\"linear\" />\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/notification_dot.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item>\n        <shape>\n            <solid android:color=\"@android:color/transparent\" />\n            <size\n                android:width=\"48dp\"\n                android:height=\"48dp\" />\n        </shape>\n    </item>\n    <item\n        android:gravity=\"top|right\"\n        android:right=\"2dp\"\n        android:top=\"2dp\">\n        <shape\n            android:shape=\"oval\">\n            <solid android:color=\"#666666\" />\n            <size\n                android:width=\"14dp\"\n                android:height=\"14dp\" />\n        </shape>\n    </item>\n</layer-list>"
  },
  {
    "path": "app/src/main/res/drawable/popup_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!-- move the 1dp stoke outside -->\n<inset xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:insetLeft=\"1dp\"\n    android:insetTop=\"1dp\"\n    android:insetRight=\"1dp\"\n    android:insetBottom=\"1dp\">\n    <inset\n        android:insetLeft=\"-1dp\"\n        android:insetTop=\"-1dp\"\n        android:insetRight=\"-1dp\"\n        android:insetBottom=\"-1dp\">\n        <shape>\n            <solid android:color=\"#8f000000\" />\n            <stroke\n                android:width=\"1dp\"\n                android:color=\"@android:color/black\" />\n            <corners android:radius=\"@dimen/popup_corner_radius\" />\n        </shape>\n    </inset>\n</inset>\n"
  },
  {
    "path": "app/src/main/res/drawable/tab_background_black.xml",
    "content": "<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:state_selected=\"true\">\n        <layer-list>\n            <item android:bottom=\"@dimen/edit_tag_padding\" android:left=\"@dimen/edit_tag_padding\" android:right=\"@dimen/edit_tag_padding\" android:top=\"@dimen/edit_tag_padding\">\n                <shape>\n                    <solid android:color=\"@color/Black_selector_primary_disable\" />\n                    <stroke android:width=\"1dp\" android:color=\"@color/Black_selector_primary\" />\n                    <corners android:radius=\"@dimen/popup_corner_radius\" />\n                </shape>\n            </item>\n        </layer-list>\n    </item>\n    <item>\n        <layer-list>\n            <item android:bottom=\"@dimen/edit_tag_padding\" android:left=\"@dimen/edit_tag_padding\" android:right=\"@dimen/edit_tag_padding\" android:top=\"@dimen/edit_tag_padding\">\n                <shape>\n                    <solid android:color=\"@color/Black_selector_secondary_disable\" />\n                    <stroke android:width=\"1dp\" android:color=\"@color/Black_selector_secondary\" />\n                    <corners android:radius=\"@dimen/popup_corner_radius\" />\n                </shape>\n            </item>\n        </layer-list>\n    </item>\n</selector>"
  },
  {
    "path": "app/src/main/res/drawable/tab_background_deep_blues.xml",
    "content": "<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:state_selected=\"true\">\n        <layer-list>\n            <item android:bottom=\"@dimen/edit_tag_padding\" android:left=\"@dimen/edit_tag_padding\" android:right=\"@dimen/edit_tag_padding\" android:top=\"@dimen/edit_tag_padding\">\n                <shape>\n                    <solid android:color=\"@color/DeepBlues_2\" />\n                    <stroke android:width=\"1dp\" android:color=\"@color/DeepBlues_4\" />\n                    <corners android:radius=\"@dimen/popup_corner_radius\" />\n                </shape>\n            </item>\n        </layer-list>\n    </item>\n    <item>\n        <layer-list>\n            <item android:bottom=\"@dimen/edit_tag_padding\" android:left=\"@dimen/edit_tag_padding\" android:right=\"@dimen/edit_tag_padding\" android:top=\"@dimen/edit_tag_padding\">\n                <shape>\n                    <solid android:color=\"@color/DeepBlues_5\" />\n                    <stroke android:width=\"1dp\" android:color=\"@color/DeepBlues_2\" />\n                    <corners android:radius=\"@dimen/popup_corner_radius\" />\n                </shape>\n            </item>\n        </layer-list>\n    </item>\n</selector>"
  },
  {
    "path": "app/src/main/res/drawable/tab_background_default.xml",
    "content": "<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:state_selected=\"true\">\n        <layer-list>\n            <item android:bottom=\"@dimen/edit_tag_padding\" android:left=\"@dimen/edit_tag_padding\" android:right=\"@dimen/edit_tag_padding\" android:top=\"@dimen/edit_tag_padding\">\n                <shape>\n                    <solid android:color=\"@color/Default_dark\" />\n                    <stroke android:width=\"1dp\" android:color=\"@color/Default_background\" />\n                </shape>\n            </item>\n        </layer-list>\n    </item>\n    <item>\n        <layer-list>\n            <item android:bottom=\"@dimen/edit_tag_padding\" android:left=\"@dimen/edit_tag_padding\" android:right=\"@dimen/edit_tag_padding\" android:top=\"@dimen/edit_tag_padding\">\n                <shape>\n                    <solid android:color=\"@color/Default_background\" />\n                    <stroke android:width=\"1dp\" android:color=\"@color/Default_dark\" />\n                </shape>\n            </item>\n        </layer-list>\n    </item>\n</selector>"
  },
  {
    "path": "app/src/main/res/drawable/tab_background_light.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!-- move the 1dp stoke outside -->\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item\n        android:bottom=\"@dimen/edit_tag_padding\"\n        android:left=\"@dimen/edit_tag_padding\"\n        android:right=\"@dimen/edit_tag_padding\"\n        android:top=\"@dimen/edit_tag_padding\">\n        <shape>\n            <solid android:color=\"#8Fffffff\" />\n            <stroke\n                android:width=\"1dp\"\n                android:color=\"@android:color/white\" />\n            <corners android:radius=\"@dimen/popup_corner_radius\" />\n        </shape>\n    </item>\n</layer-list>"
  },
  {
    "path": "app/src/main/res/drawable/window_title_background.xml",
    "content": "<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item\n            android:left=\"@dimen/dialog_margin_horizontal\"\n            android:right=\"@dimen/dialog_margin_horizontal\"\n            android:top=\"@dimen/dialog_margin_vertical\">\n        <shape>\n            <gradient\n                    android:angle=\"90\"\n                    android:endColor=\"#FF000000\"\n                    android:startColor=\"#00000000\"\n                    android:type=\"linear\" />\n        </shape>\n    </item>\n</layer-list>"
  },
  {
    "path": "app/src/main/res/drawable/window_title_background_deep_blues.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item\n        android:left=\"2dp\"\n        android:right=\"2dp\"\n        android:top=\"2dp\">\n        <shape>\n            <gradient\n                android:angle=\"270\"\n                android:centerColor=\"@color/DeepBlues_gradient_center\"\n                android:endColor=\"@color/DeepBlues_gradient_finish\"\n                android:startColor=\"@color/DeepBlues_gradient_start\"\n                android:type=\"linear\" />\n            <corners\n                android:topLeftRadius=\"@dimen/result_corner_radius\"\n                android:topRightRadius=\"@dimen/result_corner_radius\" />\n            <padding\n                android:left=\"@dimen/result_margin_horizontal\"\n                android:right=\"@dimen/result_margin_horizontal\" />\n        </shape>\n    </item>\n</layer-list>"
  },
  {
    "path": "app/src/main/res/drawable/window_title_background_default.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item\n        android:left=\"2dp\"\n        android:right=\"2dp\"\n        android:top=\"2dp\">\n        <shape>\n            <gradient\n                android:angle=\"90\"\n                android:centerColor=\"#A81e1e28\"\n                android:endColor=\"#FF1e1e28\"\n                android:startColor=\"#001e1e28\"\n                android:type=\"linear\" />\n            <corners\n                android:topLeftRadius=\"@dimen/result_corner_radius\"\n                android:topRightRadius=\"@dimen/result_corner_radius\" />\n            <padding\n                android:left=\"@dimen/result_margin_horizontal\"\n                android:right=\"@dimen/result_margin_horizontal\" />\n        </shape>\n    </item>\n</layer-list>"
  },
  {
    "path": "app/src/main/res/drawable/window_title_background_light.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item\n        android:left=\"2dp\"\n        android:right=\"2dp\"\n        android:top=\"2dp\">\n        <shape>\n            <gradient\n                android:angle=\"90\"\n                android:centerColor=\"#A8ffffff\"\n                android:endColor=\"#FFffffff\"\n                android:startColor=\"#00ffffff\"\n                android:type=\"linear\" />\n            <corners\n                android:topLeftRadius=\"@dimen/result_corner_radius\"\n                android:topRightRadius=\"@dimen/result_corner_radius\" />\n            <padding\n                android:left=\"@dimen/result_margin_horizontal\"\n                android:right=\"@dimen/result_margin_horizontal\" />\n        </shape>\n    </item>\n</layer-list>"
  },
  {
    "path": "app/src/main/res/drawable-v23/button_bar_background.xml",
    "content": "<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item\n            android:bottom=\"@dimen/dialog_margin_vertical\"\n            android:left=\"@dimen/dialog_margin_horizontal\"\n            android:right=\"@dimen/dialog_margin_horizontal\">\n        <shape>\n            <size\n                    android:width=\"16dp\"\n                    android:height=\"16dp\" />\n            <gradient\n                    android:angle=\"270\"\n                    android:endColor=\"#FF000000\"\n                    android:startColor=\"#00000000\"\n                    android:type=\"linear\" />\n        </shape>\n    </item>\n\n    <item\n            android:bottom=\"@dimen/dialog_margin_vertical\"\n            android:gravity=\"bottom\"\n            android:left=\"@dimen/dialog_margin_horizontal\"\n            android:right=\"@dimen/dialog_margin_horizontal\">\n        <shape>\n            <size android:height=\"1dp\" />\n            <solid android:color=\"@color/colorAccent\" />\n        </shape>\n    </item>\n\n    <item\n            android:bottom=\"@dimen/dialog_margin_vertical\"\n            android:gravity=\"left\"\n            android:left=\"@dimen/dialog_margin_horizontal\"\n            android:right=\"@dimen/dialog_margin_horizontal\">\n        <shape>\n            <size android:width=\"1dp\" />\n            <gradient\n                    android:angle=\"90\"\n                    android:endColor=\"#00000000\"\n                    android:startColor=\"@color/colorAccent\"\n                    android:type=\"linear\" />\n        </shape>\n    </item>\n\n    <item\n            android:bottom=\"@dimen/dialog_margin_vertical\"\n            android:gravity=\"right\"\n            android:left=\"@dimen/dialog_margin_horizontal\"\n            android:right=\"@dimen/dialog_margin_horizontal\">\n        <shape>\n            <size android:width=\"1dp\" />\n            <gradient\n                    android:angle=\"90\"\n                    android:endColor=\"#00000000\"\n                    android:startColor=\"@color/colorAccent\"\n                    android:type=\"linear\" />\n        </shape>\n    </item>\n\n</layer-list>"
  },
  {
    "path": "app/src/main/res/drawable-v23/window_title_background.xml",
    "content": "<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item\n        android:left=\"@dimen/dialog_margin_horizontal\"\n        android:right=\"@dimen/dialog_margin_horizontal\"\n        android:top=\"@dimen/dialog_margin_vertical\">\n        <shape>\n            <size\n                android:width=\"32dp\"\n                android:height=\"64dp\" />\n            <gradient\n                android:angle=\"90\"\n                android:endColor=\"#FF000000\"\n                android:startColor=\"#00000000\"\n                android:type=\"linear\" />\n        </shape>\n    </item>\n\n    <item\n        android:gravity=\"top\"\n        android:left=\"@dimen/dialog_margin_horizontal\"\n        android:right=\"@dimen/dialog_margin_horizontal\"\n        android:top=\"@dimen/dialog_margin_vertical\">\n        <shape>\n            <size android:height=\"1dp\" />\n            <solid android:color=\"@color/colorAccent\" />\n        </shape>\n    </item>\n\n    <item\n        android:gravity=\"left\"\n        android:left=\"@dimen/dialog_margin_horizontal\"\n        android:right=\"@dimen/dialog_margin_horizontal\"\n        android:top=\"@dimen/dialog_margin_vertical\">\n        <shape>\n            <size android:width=\"1dp\" />\n            <gradient\n                android:angle=\"90\"\n                android:endColor=\"@color/colorAccent\"\n                android:startColor=\"#00000000\"\n                android:type=\"linear\" />\n        </shape>\n    </item>\n\n    <item\n        android:gravity=\"right\"\n        android:left=\"@dimen/dialog_margin_horizontal\"\n        android:right=\"@dimen/dialog_margin_horizontal\"\n        android:top=\"@dimen/dialog_margin_vertical\">\n        <shape>\n            <size android:width=\"1dp\" />\n            <gradient\n                android:angle=\"90\"\n                android:endColor=\"@color/colorAccent\"\n                android:startColor=\"#00000000\"\n                android:type=\"linear\" />\n        </shape>\n    </item>\n\n</layer-list>"
  },
  {
    "path": "app/src/main/res/layout/activity_fullscreen.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    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/root_layout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@null\"\n    android:fitsSystemWindows=\"false\"\n    tools:context=\".TBLauncherActivity\">\n\n    <!-- Views in this Layout are drawn under the status bar and nav bar\n    -->\n    <ImageView\n        android:id=\"@+id/notificationBackground\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"30dp\"\n        android:layout_gravity=\"top\"\n        android:contentDescription=\"@null\"\n        android:src=\"@drawable/notification_bar_background\" />\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:clipToPadding=\"false\"\n        android:fitsSystemWindows=\"true\"\n        tools:layout_marginTop=\"30dp\">\n        <!-- This Layout insets its children based on system windows using\n             android:fitsSystemWindows.\n             This is the only way to resize the result list and move the\n             search bar when the soft keyboard is displayed\n        -->\n\n        <rocks.tbog.tblauncher.ui.ViewStubPreview\n            android:id=\"@+id/stubSearchTop\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"@dimen/bar_height\"\n            android:layout_gravity=\"bottom|center_horizontal\"\n            android:background=\"@null\"\n            android:clipChildren=\"false\"\n            android:gravity=\"center\"\n            app:layout=\"@layout/search_bar\"\n            android:orientation=\"horizontal\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\"\n            tools:visibility=\"visible\" />\n\n        <rocks.tbog.tblauncher.ui.ViewStubPreview\n            android:id=\"@+id/dockAboveResults\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"@dimen/large_bar_height\"\n            android:background=\"@null\"\n            android:gravity=\"center\"\n            android:measureWithLargestChild=\"true\"\n            android:orientation=\"horizontal\"\n            android:visibility=\"gone\"\n            app:layout=\"@layout/quick_list\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toBottomOf=\"@id/stubSearchTop\"\n            tools:background=\"@drawable/tab_background_light\"\n            tools:backgroundTint=\"@color/colorAccent\" />\n\n        <androidx.constraintlayout.widget.Barrier\n            android:id=\"@+id/barrierTop\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            app:barrierDirection=\"bottom\"\n            app:constraint_referenced_ids=\"stubSearchTop,dockAboveResults\" />\n\n        <rocks.tbog.tblauncher.ui.ViewStubPreview\n            android:id=\"@+id/resultLayout\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"0dp\"\n            android:layout_marginHorizontal=\"@dimen/result_margin_horizontal\"\n            android:layout_marginVertical=\"@dimen/result_margin_vertical\"\n            android:background=\"?attr/listBackgroundColor\"\n            android:elevation=\"2dp\"\n            app:inflatedId=\"@id/resultLayout\"\n            app:layout=\"@layout/result_list\"\n            app:layout_constraintBottom_toTopOf=\"@id/barrierFooter\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toBottomOf=\"@id/barrierTop\"\n            tools:visibility=\"visible\" />\n\n        <rocks.tbog.tblauncher.widgets.WidgetLayout\n            android:id=\"@+id/widgetContainer\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"0dp\"\n            android:gravity=\"center\"\n            android:orientation=\"vertical\"\n            android:visibility=\"gone\"\n            app:layout_constraintBottom_toTopOf=\"@id/barrierFooter\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toBottomOf=\"@id/barrierTop\"\n            tools:background=\"@drawable/popup_background\"\n            tools:visibility=\"invisible\" />\n        <!--            <ImageView-->\n        <!--                android:importantForAccessibility=\"no\"-->\n        <!--                android:layout_width=\"50dp\"-->\n        <!--                android:layout_height=\"50dp\"-->\n        <!--                android:src=\"@drawable/ic_android\"-->\n        <!--                />-->\n        <!--        </rocks.tbog.tblauncher.widgets.WidgetLayout>-->\n\n        <androidx.constraintlayout.widget.Barrier\n            android:id=\"@+id/barrierFooter\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            app:barrierDirection=\"top\"\n            app:constraint_referenced_ids=\"debugText,dockUnderResults,stubSearchBottom,dockAtBottom\" />\n\n        <TextView\n            android:id=\"@+id/debugText\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:background=\"#000\"\n            android:textColor=\"#666\"\n            android:visibility=\"gone\"\n            android:elevation=\"10dp\"\n            app:layout_constraintBottom_toTopOf=\"@id/dockUnderResults\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            tools:visibility=\"visible\" />\n\n        <rocks.tbog.tblauncher.ui.ViewStubPreview\n            android:id=\"@+id/dockUnderResults\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"@dimen/large_bar_height\"\n            android:background=\"@null\"\n            android:gravity=\"center\"\n            android:measureWithLargestChild=\"true\"\n            android:orientation=\"horizontal\"\n            android:visibility=\"gone\"\n            app:layout=\"@layout/quick_list\"\n            app:layout_constraintBottom_toTopOf=\"@id/stubSearchBottom\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            tools:background=\"@drawable/tab_background_light\"\n            tools:backgroundTint=\"@color/colorAccent\"\n            tools:visibility=\"visible\" />\n\n        <rocks.tbog.tblauncher.ui.ViewStubPreview\n            android:id=\"@+id/stubSearchBottom\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"@dimen/large_bar_height\"\n            android:layout_gravity=\"bottom|center_horizontal\"\n            android:background=\"@null\"\n            android:clipChildren=\"false\"\n            android:gravity=\"center\"\n            app:layout=\"@layout/search_bar\"\n            android:orientation=\"horizontal\"\n            app:layout_constraintBottom_toTopOf=\"@id/dockAtBottom\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            tools:visibility=\"visible\" />\n\n        <rocks.tbog.tblauncher.ui.ViewStubPreview\n            android:id=\"@+id/dockAtBottom\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"@dimen/large_bar_height\"\n            android:background=\"@null\"\n            android:gravity=\"center\"\n            android:measureWithLargestChild=\"true\"\n            android:orientation=\"horizontal\"\n            android:visibility=\"gone\"\n            app:layout=\"@layout/quick_list\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            tools:background=\"@drawable/tab_background_light\"\n            tools:backgroundTint=\"@color/colorAccent\" />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n    <!--    <ImageView-->\n    <!--        android:id=\"@+id/keyboardBackground\"-->\n    <!--        android:layout_width=\"match_parent\"-->\n    <!--        android:layout_height=\"0dp\"-->\n    <!--        android:contentDescription=\"@null\"-->\n    <!--        android:layout_gravity=\"bottom\"-->\n    <!--        android:src=\"@android:color/black\" />-->\n\n\n</FrameLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_settings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/settings_container\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:fitsSystemWindows=\"true\"\n    tools:context=\".SettingsActivity\" />\n\n"
  },
  {
    "path": "app/src/main/res/layout/add_search_engine.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    android:paddingHorizontal=\"@dimen/dialog_padding_horizontal\"\n    android:paddingVertical=\"@dimen/dialog_padding_vertical\"\n    tools:theme=\"@style/SettingsTheme.White\">\n\n    <com.google.android.material.textfield.TextInputLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"10dp\"\n        android:layout_marginBottom=\"10dp\"\n        android:background=\"?android:attr/windowBackground\"\n        android:padding=\"5dp\">\n\n        <com.google.android.material.textfield.TextInputEditText\n            android:id=\"@android:id/text1\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:hint=\"@string/label_search_engine_name\"\n            android:importantForAutofill=\"no\"\n            android:inputType=\"textAutoCorrect\"\n            android:textAppearance=\"?android:attr/textAppearance\" />\n\n    </com.google.android.material.textfield.TextInputLayout>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"10dp\"\n        android:layout_marginBottom=\"10dp\"\n        android:background=\"?android:attr/windowBackground\"\n        android:orientation=\"vertical\">\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_margin=\"5dp\"\n            android:text=\"@string/label_search_engine_url\" />\n\n        <com.google.android.material.textfield.TextInputLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"10dp\"\n            android:layout_marginBottom=\"10dp\"\n            android:background=\"?android:attr/windowBackground\"\n            android:padding=\"5dp\">\n\n            <com.google.android.material.textfield.TextInputEditText\n                android:id=\"@android:id/text2\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:hint=\"@string/hint_add_search_engine_url\"\n                android:importantForAutofill=\"no\"\n                android:inputType=\"textUri\" />\n        </com.google.android.material.textfield.TextInputLayout>\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_margin=\"5dp\"\n            android:text=\"@string/search_engine_url_help\" />\n\n    </LinearLayout>\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/add_search_hint.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:paddingHorizontal=\"@dimen/dialog_padding_horizontal\"\n    android:paddingVertical=\"@dimen/dialog_padding_vertical\"\n    tools:theme=\"@style/SettingsTheme\">\n\n    <com.google.android.material.textfield.TextInputLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"?android:attr/windowBackground\"\n        android:padding=\"5dp\">\n\n        <com.google.android.material.textfield.TextInputEditText\n            android:id=\"@android:id/text1\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:hint=\"@string/label_search_hint\"\n            android:importantForAutofill=\"no\"\n            android:inputType=\"textAutoCorrect\" />\n\n    </com.google.android.material.textfield.TextInputLayout>\n\n</FrameLayout>"
  },
  {
    "path": "app/src/main/res/layout/custom_icon_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ImageView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@android:id/icon\"\n    android:layout_width=\"@dimen/icon_size\"\n    android:layout_height=\"@dimen/icon_size\"\n    android:layout_gravity=\"center\"\n    android:layout_marginHorizontal=\"@dimen/result_margin_horizontal\"\n    android:layout_marginVertical=\"@dimen/result_margin_vertical\"\n    android:contentDescription=\"@null\"\n    android:scaleType=\"centerCrop\"\n    tools:showIn=\"@layout/dialog_icon_select_page\"\n    tools:src=\"@android:drawable/ic_menu_call\">\n\n</ImageView>"
  },
  {
    "path": "app/src/main/res/layout/dialog_custom_shape_icon_select_page.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:animateLayoutChanges=\"true\"\n    android:orientation=\"vertical\"\n    android:paddingHorizontal=\"@dimen/dialog_padding_horizontal\"\n    android:paddingVertical=\"@dimen/dialog_padding_vertical\">\n\n    <TextView\n        android:id=\"@+id/backgroundColor\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"@dimen/color_preview_size\"\n        android:layout_marginVertical=\"1dp\"\n        android:background=\"?attr/selectableItemBackground\"\n        android:focusable=\"true\"\n        android:gravity=\"start|center_vertical\"\n        android:text=\"@string/choose_background_color\"\n        android:textAlignment=\"gravity\"\n        tools:drawableEnd=\"@color/colorPrimary\" />\n\n    <TextView\n        android:id=\"@+id/lettersToggle\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"@dimen/color_preview_size\"\n        android:background=\"?attr/selectableItemBackground\"\n        android:focusable=\"true\"\n        android:gravity=\"start|center_vertical\"\n        android:text=\"@string/letters_toggle\"\n        tools:drawableEnd=\"@android:drawable/arrow_up_float\" />\n\n    <LinearLayout\n        android:id=\"@+id/lettersGroup\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginVertical=\"1dp\"\n        android:background=\"?android:attr/windowBackground\"\n        android:orientation=\"vertical\"\n        android:paddingHorizontal=\"@dimen/dialog_padding_horizontal\"\n        android:paddingVertical=\"@dimen/dialog_padding_vertical\"\n        tools:ignore=\"RtlSymmetry\">\n\n        <com.google.android.material.textfield.TextInputLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\">\n\n            <com.google.android.material.textfield.TextInputEditText\n                android:id=\"@+id/letters\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:autofillHints=\"application,text\"\n                android:hint=\"@string/static_icon_letters_label\"\n                android:inputType=\"textAutoCorrect\"\n                android:textAppearance=\"?android:attr/textAppearance\" />\n\n        </com.google.android.material.textfield.TextInputLayout>\n\n        <TextView\n            android:id=\"@+id/lettersColor\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:background=\"?attr/selectableItemBackground\"\n            android:focusable=\"true\"\n            android:gravity=\"start|center_vertical\"\n            android:paddingHorizontal=\"5dp\"\n            android:text=\"@string/choose_letter_color\"\n            android:textAlignment=\"gravity\"\n            tools:drawableEnd=\"@color/colorPrimary\" />\n\n    </LinearLayout>\n\n    <TextView\n        android:id=\"@+id/scaleBarToggle\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"@dimen/color_preview_size\"\n        android:background=\"?attr/selectableItemBackground\"\n        android:focusable=\"true\"\n        android:gravity=\"start|center_vertical\"\n        android:labelFor=\"@+id/scaleBar\"\n        android:text=\"@string/choose_icon_scale\"\n        tools:drawableEnd=\"@android:drawable/arrow_up_float\" />\n\n    <SeekBar\n        android:id=\"@+id/scaleBar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:max=\"200\"\n        android:paddingVertical=\"10dp\"\n        android:progress=\"100\" />\n\n    <TextView\n        android:id=\"@+id/shapeGridToggle\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"@dimen/color_preview_size\"\n        android:background=\"?attr/selectableItemBackground\"\n        android:focusable=\"true\"\n        android:gravity=\"start|center_vertical\"\n        android:labelFor=\"@id/shapeGrid\"\n        android:text=\"@string/choose_icon_shape\"\n        tools:drawableEnd=\"@android:drawable/arrow_up_float\" />\n\n    <GridView\n        android:id=\"@+id/shapeGrid\"\n        style=\"@style/ItemGrid\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\"\n        tools:itemCount=\"20\"\n        tools:listitem=\"@layout/item_grid\" />\n\n    <TextView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:labelFor=\"@id/iconGrid\"\n        android:text=\"@string/choose_icon\" />\n\n    <GridView\n        android:id=\"@+id/iconGrid\"\n        style=\"@style/ItemGrid\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\"\n        tools:itemCount=\"10\"\n        tools:listitem=\"@layout/item_grid\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_edit_tags.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\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=\"0dp\"\n    tools:background=\"@drawable/dialog_background_dark\"\n    tools:theme=\"@style/TitleDialogTheme\">\n\n    <FrameLayout\n        android:id=\"@+id/previewWrapper\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginHorizontal=\"@dimen/result_margin_horizontal\"\n        android:layout_marginVertical=\"@dimen/result_margin_vertical\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        android:padding=\"@dimen/edit_tag_padding\">\n\n        <!-- Code will inflate a preview of the entry here -->\n        <!--        <include-->\n        <!--            android:id=\"@+id/entry\"-->\n        <!--            layout=\"@layout/item_app\"-->\n        <!--            android:layout_width=\"match_parent\"-->\n        <!--            android:layout_height=\"wrap_content\" />-->\n\n    </FrameLayout>\n\n    <GridView\n        android:id=\"@+id/grid\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        app:layout_constraintHeight_default=\"wrap\"\n        android:layout_marginHorizontal=\"@dimen/result_margin_horizontal\"\n        android:layout_marginVertical=\"@dimen/result_margin_vertical\"\n        android:clipToPadding=\"false\"\n        android:gravity=\"center\"\n        android:horizontalSpacing=\"@dimen/edit_tag_padding\"\n        android:numColumns=\"auto_fit\"\n        android:stretchMode=\"columnWidth\"\n        android:verticalSpacing=\"@dimen/edit_tag_padding\"\n        app:layout_constraintBottom_toTopOf=\"@+id/newTag\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/previewWrapper\"\n        tools:itemCount=\"9\"\n        tools:listitem=\"@layout/edit_tag_item\" />\n\n    <!--    <LinearLayout-->\n    <!--        android:layout_width=\"match_parent\"-->\n    <!--        android:layout_height=\"wrap_content\"-->\n    <!--        android:gravity=\"center\"-->\n    <!--        android:paddingHorizontal=\"@dimen/result_margin_horizontal\"-->\n    <!--        android:paddingVertical=\"@dimen/result_margin_vertical\">-->\n\n    <androidx.appcompat.widget.AppCompatAutoCompleteTextView\n        android:id=\"@+id/newTag\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginHorizontal=\"@dimen/result_margin_horizontal\"\n        android:layout_marginVertical=\"@dimen/result_margin_vertical\"\n        android:autofillHints=\"application,text\"\n        android:completionThreshold=\"1\"\n        android:dropDownWidth=\"wrap_content\"\n        android:dropDownHeight=\"wrap_content\"\n        android:hint=\"@string/hint_new_tag\"\n        android:imeOptions=\"actionSend\"\n        android:inputType=\"textAutoCorrect\"\n        android:maxLines=\"1\"\n        app:layout_constraintBottom_toTopOf=\"@+id/buttonPanel\"\n        app:layout_constraintEnd_toStartOf=\"@+id/addTag\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/grid\" />\n\n    <ImageButton\n        android:id=\"@+id/addTag\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:layout_marginHorizontal=\"@dimen/result_margin_horizontal\"\n        android:background=\"?attr/appSelectableItemBackground\"\n        android:contentDescription=\"@string/cd_add_tag\"\n        android:scaleType=\"fitCenter\"\n        app:layout_constraintBottom_toBottomOf=\"@+id/newTag\"\n        app:layout_constraintDimensionRatio=\"1:1\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"@id/newTag\"\n        app:srcCompat=\"@drawable/ic_add_tag\"\n        app:tint=\"?attr/colorControlHighlight\" />\n\n    <!-- Alert dialog style buttons along the bottom. -->\n    <include\n        android:id=\"@+id/buttonPanel\"\n        layout=\"@layout/ok_cancel_button_bar\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_icon_select.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    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/root_layout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:orientation=\"vertical\"\n    tools:theme=\"@style/NoTitleDialogTheme\">\n\n    <FrameLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\">\n\n        <View\n            style=\"?android:attr/windowTitleStyle\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"48dp\" />\n\n        <androidx.viewpager.widget.ViewPager\n            android:id=\"@+id/viewPager\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:paddingHorizontal=\"@dimen/dialog_padding_horizontal\"\n            android:paddingTop=\"@dimen/dialog_padding_vertical\">\n\n            <com.google.android.material.tabs.TabLayout\n                android:id=\"@+id/tabLayout\"\n                style=\"?attr/tabLayoutStyle\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                app:tabMode=\"scrollable\" />\n\n        </androidx.viewpager.widget.ViewPager>\n\n    </FrameLayout>\n\n    <LinearLayout\n        style=\"?android:attr/buttonBarStyle\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"bottom\"\n        android:gravity=\"center\"\n        android:orientation=\"horizontal\">\n\n        <Button\n            android:id=\"@android:id/button2\"\n            style=\"?android:attr/buttonBarButtonStyle\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@android:string/cancel\"\n            android:textAllCaps=\"false\" />\n\n        <TextView\n            android:id=\"@+id/previewLabel\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginVertical=\"2dp\"\n            android:layout_weight=\"1\"\n            android:drawablePadding=\"@dimen/icon_margin_right\"\n            android:gravity=\"center_vertical|end\"\n            android:lines=\"2\"\n            android:paddingVertical=\"@dimen/icon_margin_vertical\"\n            android:text=\"@string/current_icon_preview_label\"\n            android:textAlignment=\"textEnd\"\n            android:textColor=\"?android:attr/textColorSecondary\"\n            android:textSize=\"@dimen/result_small_size\"\n            tools:drawableRight=\"@drawable/ic_android\"\n            tools:text=\"Current\\n icon\" />\n\n        <Button\n            android:id=\"@android:id/button1\"\n            style=\"?android:attr/buttonBarButtonStyle\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@android:string/ok\"\n            android:textAllCaps=\"false\" />\n\n    </LinearLayout>\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_icon_select_page.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    android:paddingHorizontal=\"@dimen/dialog_padding_horizontal\"\n    android:paddingVertical=\"@dimen/dialog_padding_vertical\"\n    tools:theme=\"@style/NoTitleDialogTheme\">\n\n    <TextView\n        android:id=\"@android:id/text1\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"@dimen/app_grid_vertical_padding\"\n        android:gravity=\"center_horizontal|bottom\"\n        android:text=\"@string/icon_pack_content_list\"\n        android:textColor=\"?android:attr/textColorPrimary\"\n        tools:drawableLeft=\"@drawable/ic_android\" />\n\n    <ProgressBar\n        android:id=\"@+id/iconLoadingBar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\"\n        android:indeterminate=\"true\"\n        android:indeterminateOnly=\"true\"\n        android:visibility=\"gone\"\n        tools:visibility=\"visible\" />\n\n    <GridView\n        android:id=\"@+id/iconGrid\"\n        style=\"@style/ItemGrid\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\"\n        android:columnWidth=\"@dimen/icon_size\"\n        tools:itemCount=\"20\"\n        tools:listitem=\"@layout/custom_icon_item\" />\n\n    <com.google.android.material.textfield.TextInputLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n\n        <com.google.android.material.textfield.TextInputEditText\n            android:id=\"@+id/search\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:autofillHints=\"application,text\"\n            android:hint=\"@string/hint_custom_icon\"\n            android:inputType=\"textAutoCorrect\" />\n\n    </com.google.android.material.textfield.TextInputLayout>\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_preference_color_chooser.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout 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\n    <ProgressBar\n        android:id=\"@+id/iconLoadingBar\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:indeterminate=\"true\"\n        android:indeterminateOnly=\"true\"\n        android:visibility=\"visible\"\n        app:layout_constraintDimensionRatio=\"1:1\"\n        app:layout_constraintBottom_toTopOf=\"@id/buttonPanel\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <!-- Alert dialog style buttons along the bottom. -->\n    <LinearLayout\n        android:id=\"@+id/buttonPanel\"\n        style=\"?android:attr/buttonBarStyle\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"bottom\"\n        android:baselineAligned=\"false\"\n        android:measureWithLargestChild=\"true\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/iconLoadingBar\">\n\n        <Button\n            android:id=\"@android:id/button3\"\n            style=\"?attr/buttonBarNeutralButtonStyle\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:clickable=\"true\"\n            android:focusable=\"true\"\n            tools:text=\"@android:string/unknownName\"\n            tools:visibility=\"gone\" />\n\n        <android.widget.Space\n            android:id=\"@+id/spacer\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"0dp\"\n            android:layout_weight=\"1\"\n            android:visibility=\"invisible\" />\n\n        <Button\n            android:id=\"@android:id/button2\"\n            style=\"?attr/buttonBarNegativeButtonStyle\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:clickable=\"true\"\n            android:focusable=\"true\"\n            tools:text=\"@android:string/cancel\" />\n\n        <Button\n            android:id=\"@android:id/button1\"\n            style=\"?attr/buttonBarPositiveButtonStyle\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:clickable=\"true\"\n            android:focusable=\"true\"\n            tools:text=\"@android:string/ok\" />\n    </LinearLayout>\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_rename.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:layout_margin=\"0dp\"\n    android:orientation=\"vertical\"\n    tools:background=\"@drawable/dialog_background_dark\"\n    tools:theme=\"@style/TitleDialogTheme\">\n\n    <TextView\n        android:id=\"@android:id/title\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginHorizontal=\"@dimen/result_margin_horizontal\"\n        android:layout_marginVertical=\"@dimen/result_margin_vertical\"\n        android:text=\"\"\n        android:textAppearance=\"?android:attr/textAppearanceMedium\" />\n\n    <com.google.android.material.textfield.TextInputLayout\n        android:id=\"@android:id/hint\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginHorizontal=\"@dimen/result_margin_horizontal\"\n        android:layout_marginVertical=\"@dimen/result_margin_vertical\">\n\n        <com.google.android.material.textfield.TextInputEditText\n            android:id=\"@+id/rename\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:autofillHints=\"application,name\"\n            android:inputType=\"textAutoCorrect\"\n            tools:ignore=\"LabelFor\" />\n\n    </com.google.android.material.textfield.TextInputLayout>\n\n    <!-- Alert dialog style buttons along the bottom. Automatically added if not present -->\n    <!--    <include layout=\"@layout/abc_alert_dialog_button_bar_material\" />-->\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_title.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<TextView 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:id=\"@android:id/title\"\n    style=\"?android:attr/windowTitleStyle\"\n    android:layout_height=\"wrap_content\"\n    android:layout_width=\"match_parent\"\n    android:lines=\"1\"\n    app:autoSizeTextType=\"uniform\"\n    tools:text=\"Background color and opacity\" />\n"
  },
  {
    "path": "app/src/main/res/layout/edit_search_engines.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    tools:theme=\"@style/SettingsDialogTheme\">\n\n    <ListView\n        android:id=\"@android:id/list\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\"\n        android:background=\"@null\"\n        android:cacheColorHint=\"@android:color/transparent\"\n        android:clipChildren=\"false\"\n        android:clipToPadding=\"false\"\n        android:divider=\"?attr/dividerDrawable\"\n        android:dividerHeight=\"1dp\"\n        tools:listitem=\"@android:layout/simple_list_item_checked\" />\n\n    <include\n        android:id=\"@+id/ok_cancel_button_bar\"\n        layout=\"@layout/ok_cancel_button_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:visibility=\"gone\"\n        tools:visibility=\"visible\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/edit_tag_item.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:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:clipChildren=\"false\"\n    android:background=\"?android:attr/windowBackground\"\n    android:orientation=\"horizontal\">\n\n    <TextView\n        android:id=\"@android:id/text1\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"match_parent\"\n        android:gravity=\"center\"\n        android:padding=\"@dimen/edit_tag_padding\"\n        android:text=\"@string/app_name\"\n        android:textAlignment=\"center\"\n        android:textColor=\"?android:attr/textColorPrimary\" />\n\n    <ImageView\n        android:id=\"@android:id/button1\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"match_parent\"\n        android:contentDescription=\"@string/cd_remove_tag\"\n        android:padding=\"@dimen/edit_tag_padding\"\n        app:tint=\"?attr/colorControlHighlight\"\n        app:srcCompat=\"@drawable/ic_remove_tag\" />\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_app.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout 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:id=\"@+id/linearLayout2\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:gravity=\"center_vertical\"\n    android:orientation=\"horizontal\"\n    tools:layout_height=\"58dp\">\n\n\n    <ImageView\n        android:id=\"@android:id/icon\"\n        android:layout_width=\"@dimen/icon_size\"\n        android:layout_height=\"@dimen/icon_size\"\n        android:layout_marginHorizontal=\"@dimen/result_margin_horizontal\"\n        android:importantForAccessibility=\"no\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        tools:src=\"@drawable/ic_android\" />\n\n    <View\n        android:id=\"@+id/dummyGuide\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        app:layout_constraintBottom_toBottomOf=\"@android:id/icon\"\n        app:layout_constraintHorizontal_weight=\"2\"\n        app:layout_constraintLeft_toLeftOf=\"@android:id/icon\"\n        app:layout_constraintRight_toLeftOf=\"@android:id/icon2\"\n        app:layout_constraintTop_toTopOf=\"@android:id/icon\" />\n\n    <ImageView\n        android:id=\"@android:id/icon2\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:importantForAccessibility=\"no\"\n        android:scaleType=\"fitXY\"\n        app:layout_constraintBottom_toBottomOf=\"@android:id/icon\"\n        app:layout_constraintDimensionRatio=\"1:1\"\n        app:layout_constraintHorizontal_weight=\"1\"\n        app:layout_constraintLeft_toRightOf=\"@id/dummyGuide\"\n        app:layout_constraintRight_toRightOf=\"@android:id/icon\"\n        tools:src=\"@drawable/ic_android\"\n        tools:tint=\"#9acd32\" />\n\n    <ImageView\n        android:id=\"@+id/item_notification_dot\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:importantForAccessibility=\"no\"\n        app:srcCompat=\"@drawable/notification_dot\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toBottomOf=\"@android:id/icon\"\n        app:layout_constraintLeft_toLeftOf=\"@android:id/icon\"\n        app:layout_constraintRight_toRightOf=\"@android:id/icon\"\n        app:layout_constraintTop_toTopOf=\"@android:id/icon\"\n        tools:visibility=\"visible\" />\n\n    <TextView\n        android:id=\"@+id/item_app_name\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/icon_margin_right\"\n        android:ellipsize=\"end\"\n        android:maxLines=\"1\"\n        android:paddingEnd=\"2px\"\n        android:paddingBottom=\"4px\"\n        android:textColor=\"?attr/resultColor\"\n        android:textSize=\"@dimen/result_title_size\"\n        app:layout_constraintBottom_toTopOf=\"@id/item_app_tag\"\n        app:layout_constraintStart_toEndOf=\"@android:id/icon\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintVertical_chainStyle=\"packed\"\n        tools:ignore=\"PxUsage,RtlSymmetry\"\n        tools:text=\"@string/stub_application\" />\n\n    <TextView\n        android:id=\"@+id/item_app_tag\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:ellipsize=\"end\"\n        android:paddingEnd=\"2px\"\n        android:paddingBottom=\"4px\"\n        android:textColor=\"?android:attr/textColorSecondary\"\n        android:textSize=\"@dimen/result_small_size\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"@id/item_app_name\"\n        app:layout_constraintTop_toBottomOf=\"@id/item_app_name\"\n        tools:ignore=\"PxUsage,RtlSymmetry\"\n        tools:text=\"@string/stub_app_tag\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_builtin.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    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:gravity=\"center_vertical\"\n    android:orientation=\"horizontal\"\n    android:paddingHorizontal=\"@dimen/result_margin_horizontal\"\n    tools:layout_height=\"58dp\">\n\n    <ImageView\n        android:id=\"@android:id/icon\"\n        android:layout_width=\"@dimen/icon_size\"\n        android:layout_height=\"@dimen/icon_size\"\n        android:layout_gravity=\"start|center_vertical\"\n        android:layout_marginStart=\"@dimen/icon_margin_left\"\n        android:layout_marginEnd=\"@dimen/icon_margin_right\"\n        android:importantForAccessibility=\"no\"\n        android:scaleType=\"fitCenter\"\n        app:srcCompat=\"@drawable/ic_android\" />\n\n    <TextView\n        android:id=\"@android:id/text1\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center_vertical\"\n        android:ellipsize=\"end\"\n        android:maxLines=\"1\"\n        android:paddingEnd=\"2dp\"\n        android:textColor=\"?attr/resultColor\"\n        android:textSize=\"@dimen/result_title_size\"\n        tools:ignore=\"RtlSymmetry\"\n        tools:text=\"Apps\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_contact.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout 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:id=\"@+id/relativeLayout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:descendantFocusability=\"blocksDescendants\"\n    android:paddingHorizontal=\"@dimen/result_margin_horizontal\"\n    tools:layout_height=\"58dp\">\n\n    <ImageView\n        android:id=\"@android:id/icon\"\n        android:layout_width=\"@dimen/icon_size\"\n        android:layout_height=\"@dimen/icon_size\"\n        android:layout_marginStart=\"@dimen/icon_margin_left\"\n        android:layout_marginEnd=\"@dimen/icon_margin_right\"\n        android:importantForAccessibility=\"no\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:srcCompat=\"@drawable/ic_contact_placeholder\" />\n\n    <View\n        android:id=\"@+id/dummyGuide\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        app:layout_constraintBottom_toBottomOf=\"@android:id/icon\"\n        app:layout_constraintHorizontal_weight=\"2\"\n        app:layout_constraintLeft_toLeftOf=\"@android:id/icon\"\n        app:layout_constraintRight_toLeftOf=\"@android:id/icon2\"\n        app:layout_constraintTop_toTopOf=\"@android:id/icon\" />\n\n    <ImageView\n        android:id=\"@android:id/icon2\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:importantForAccessibility=\"no\"\n        android:scaleType=\"fitXY\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toBottomOf=\"@android:id/icon\"\n        app:layout_constraintDimensionRatio=\"1:1\"\n        app:layout_constraintHorizontal_weight=\"1\"\n        app:layout_constraintLeft_toRightOf=\"@id/dummyGuide\"\n        app:layout_constraintRight_toRightOf=\"@android:id/icon\"\n        tools:src=\"@drawable/ic_android\"\n        tools:tint=\"#9acd32\" />\n\n    <TextView\n        android:id=\"@+id/item_contact_name\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/icon_margin_right\"\n        android:ellipsize=\"end\"\n        android:paddingEnd=\"2dp\"\n        android:singleLine=\"true\"\n        android:textColor=\"?attr/resultColor\"\n        android:textSize=\"@dimen/result_title_size\"\n        app:layout_constraintBottom_toTopOf=\"@id/item_contact_phone\"\n        app:layout_constraintStart_toEndOf=\"@android:id/icon\"\n        app:layout_constraintTop_toTopOf=\"@android:id/icon\"\n        app:layout_constraintVertical_chainStyle=\"packed\"\n        tools:ignore=\"RtlSymmetry\"\n        tools:text=\"Contact name\" />\n\n    <TextView\n        android:id=\"@+id/item_contact_phone\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:gravity=\"center_vertical\"\n        android:importantForAccessibility=\"no\"\n        android:paddingEnd=\"2dp\"\n        android:textColor=\"?android:attr/textColorSecondary\"\n        android:textSize=\"@dimen/result_small_size\"\n        app:layout_constraintBottom_toBottomOf=\"@android:id/icon\"\n        app:layout_constraintEnd_toStartOf=\"@id/item_contact_nickname\"\n        app:layout_constraintHorizontal_bias=\"0\"\n        app:layout_constraintHorizontal_chainStyle=\"packed\"\n        app:layout_constraintStart_toStartOf=\"@id/item_contact_name\"\n        app:layout_constraintTop_toBottomOf=\"@id/item_contact_name\"\n        tools:ignore=\"RtlSymmetry\"\n        tools:text=\"+1 330 1234 5678\" />\n\n    <TextView\n        android:id=\"@+id/item_contact_nickname\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"5dp\"\n        android:ellipsize=\"end\"\n        android:maxLines=\"1\"\n        android:paddingEnd=\"2dp\"\n        android:singleLine=\"true\"\n        android:textColor=\"?android:attr/textColorSecondary\"\n        android:textSize=\"@dimen/result_title_size\"\n        app:layout_constraintBaseline_toBaselineOf=\"@id/item_contact_phone\"\n        app:layout_constraintEnd_toStartOf=\"@+id/item_contact_action_message\"\n        app:layout_constraintStart_toEndOf=\"@+id/item_contact_phone\"\n        tools:ignore=\"RtlSymmetry\"\n        tools:text=\"Nickname\" />\n\n    <ImageButton\n        android:id=\"@+id/item_contact_action_message\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:paddingVertical=\"@dimen/result_margin_vertical\"\n        android:background=\"?attr/appSelectableItemBackground\"\n        android:contentDescription=\"@string/cd_item_contact_message\"\n        android:scaleType=\"fitCenter\"\n        app:layout_constraintDimensionRatio=\"1:1\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toStartOf=\"@+id/item_contact_action_phone\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:srcCompat=\"@drawable/ic_message\" />\n\n    <ImageButton\n        android:id=\"@+id/item_contact_action_phone\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:paddingVertical=\"@dimen/result_margin_vertical\"\n        android:background=\"?attr/appSelectableItemBackground\"\n        android:contentDescription=\"@string/cd_item_contact_call\"\n        android:scaleType=\"fitCenter\"\n        app:layout_constraintDimensionRatio=\"1:1\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toStartOf=\"@id/item_contact_action_open\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:srcCompat=\"@drawable/ic_phone\" />\n\n    <ImageButton\n        android:id=\"@+id/item_contact_action_open\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:paddingVertical=\"@dimen/result_margin_vertical\"\n        android:background=\"?attr/appSelectableItemBackground\"\n        android:contentDescription=\"@string/cd_item_contact_open\"\n        android:scaleType=\"fitCenter\"\n        android:visibility=\"gone\"\n        app:layout_constraintDimensionRatio=\"1:1\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:srcCompat=\"@drawable/ic_send\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_dock.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout 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:layout_marginHorizontal=\"@dimen/quick_list_margin_horizontal\"\n    android:layout_marginVertical=\"@dimen/quick_list_margin_vertical\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    tools:layout_height=\"394px\"\n    tools:layout_width=\"146px\"\n    tools:theme=\"@style/AppThemeTransparent\">\n    <!--    android:background=\"@color/Chocolate\"-->\n\n    <ImageView\n        android:id=\"@android:id/icon\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:importantForAccessibility=\"no\"\n        android:scaleType=\"fitXY\"\n        app:layout_constraintBottom_toTopOf=\"@android:id/text1\"\n        app:layout_constraintDimensionRatio=\"1:1\"\n        app:layout_constraintHeight_max=\"@dimen/icon_size\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintVertical_chainStyle=\"packed\"\n        app:layout_constraintWidth_max=\"@dimen/icon_size\"\n        tools:src=\"@drawable/ic_android\" />\n\n    <View\n        android:id=\"@+id/dummyGuide\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        app:layout_constraintBottom_toBottomOf=\"@android:id/icon\"\n        app:layout_constraintHorizontal_weight=\"2\"\n        app:layout_constraintLeft_toLeftOf=\"@android:id/icon\"\n        app:layout_constraintRight_toLeftOf=\"@android:id/icon2\"\n        app:layout_constraintTop_toTopOf=\"@android:id/icon\" />\n\n    <ImageView\n        android:id=\"@android:id/icon2\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:importantForAccessibility=\"no\"\n        android:scaleType=\"fitXY\"\n        app:layout_constraintBottom_toBottomOf=\"@android:id/icon\"\n        app:layout_constraintDimensionRatio=\"1:1\"\n        app:layout_constraintHorizontal_weight=\"1\"\n        app:layout_constraintLeft_toRightOf=\"@id/dummyGuide\"\n        app:layout_constraintRight_toRightOf=\"@android:id/icon\"\n        tools:src=\"@drawable/ic_android\"\n        tools:tint=\"#9acd32\" />\n\n    <TextView\n        android:id=\"@android:id/text1\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:gravity=\"top|center_horizontal\"\n        android:maxLines=\"3\"\n        android:paddingHorizontal=\"2dp\"\n        android:textAlignment=\"center\"\n        android:textColor=\"?android:attr/textColorSecondary\"\n        android:textSize=\"@dimen/result_small_size\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@android:id/icon\"\n        tools:ignore=\"RtlSymmetry\"\n        tools:text=\"Apps for fun\"\n        tools:visibility=\"gone\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_dock_shortcut.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout 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:layout_marginHorizontal=\"@dimen/quick_list_margin_horizontal\"\n    android:layout_marginVertical=\"@dimen/quick_list_margin_vertical\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    tools:layout_height=\"90dp\"\n    tools:layout_width=\"90dp\"\n    tools:theme=\"@style/AppThemeTransparent\">\n    <!--    android:background=\"@color/Crimson\"-->\n\n    <ImageView\n        android:id=\"@android:id/icon1\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:importantForAccessibility=\"no\"\n        android:scaleType=\"fitXY\"\n        app:layout_constraintBottom_toTopOf=\"@android:id/text1\"\n        app:layout_constraintDimensionRatio=\"1:1\"\n        app:layout_constraintHeight_max=\"@dimen/icon_size\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintVertical_chainStyle=\"packed\"\n        app:layout_constraintWidth_max=\"@dimen/icon_size\"\n        tools:layout_height=\"@dimen/icon_size\"\n        tools:layout_width=\"@dimen/icon_size\"\n        tools:src=\"@drawable/ic_android\" />\n\n    <View\n        android:id=\"@+id/dummyGuide\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        app:layout_constraintBottom_toBottomOf=\"@android:id/icon1\"\n        app:layout_constraintHorizontal_weight=\"2\"\n        app:layout_constraintLeft_toLeftOf=\"@android:id/icon1\"\n        app:layout_constraintRight_toLeftOf=\"@android:id/icon2\"\n        app:layout_constraintTop_toTopOf=\"@android:id/icon1\" />\n\n    <ImageView\n        android:id=\"@android:id/icon2\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:importantForAccessibility=\"no\"\n        android:scaleType=\"fitXY\"\n        app:layout_constraintBottom_toBottomOf=\"@android:id/icon1\"\n        app:layout_constraintDimensionRatio=\"1:1\"\n        app:layout_constraintHorizontal_weight=\"1\"\n        app:layout_constraintLeft_toRightOf=\"@id/dummyGuide\"\n        app:layout_constraintRight_toRightOf=\"@android:id/icon1\"\n        tools:src=\"@drawable/ic_android\"\n        tools:tint=\"#9acd32\" />\n\n    <TextView\n        android:id=\"@android:id/text1\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:gravity=\"top|center_horizontal\"\n        android:maxLines=\"3\"\n        android:paddingHorizontal=\"2dp\"\n        android:textAlignment=\"center\"\n        android:textColor=\"?android:attr/textColorSecondary\"\n        android:textSize=\"@dimen/result_small_size\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@android:id/icon1\"\n        tools:ignore=\"RtlSymmetry\"\n        tools:text=\"Shortcut for apps\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_grid.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    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginHorizontal=\"@dimen/result_margin_horizontal\"\n    android:layout_marginVertical=\"@dimen/result_margin_vertical\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:orientation=\"vertical\"\n    tools:layout_height=\"90dp\"\n    tools:layout_width=\"90dp\"\n    tools:theme=\"@style/SettingsTheme\">\n    <!--    android:background=\"@color/BlanchedAlmond\"-->\n\n    <ImageView\n        android:id=\"@android:id/icon\"\n        android:layout_width=\"@dimen/icon_size\"\n        android:layout_height=\"@dimen/icon_size\"\n        android:layout_gravity=\"top|center_horizontal\"\n        android:importantForAccessibility=\"no\"\n        android:scaleType=\"fitCenter\"\n        app:srcCompat=\"@drawable/ic_android\" />\n\n    <TextView\n        android:id=\"@android:id/text1\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"bottom|center_horizontal\"\n        android:gravity=\"top|center_horizontal\"\n        android:maxLines=\"3\"\n        android:paddingHorizontal=\"2dp\"\n        android:textAlignment=\"center\"\n        android:textColor=\"?android:attr/textColorSecondary\"\n        android:textSize=\"@dimen/result_small_size\"\n        tools:ignore=\"RtlSymmetry\"\n        tools:text=\"@tools:sample/full_names\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_grid_shortcut.xml",
    "content": "<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\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_marginVertical=\"@dimen/result_margin_vertical\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    tools:layout_height=\"180dp\"\n    tools:layout_width=\"180dp\"\n    tools:theme=\"@style/AppThemeTransparent\">\n\n    <ImageView\n        android:id=\"@android:id/icon1\"\n        android:layout_width=\"@dimen/icon_size\"\n        android:layout_height=\"@dimen/icon_size\"\n        android:layout_marginHorizontal=\"@dimen/result_margin_horizontal\"\n        android:importantForAccessibility=\"no\"\n        android:scaleType=\"fitCenter\"\n        app:layout_constraintBottom_toTopOf=\"@android:id/text1\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintVertical_bias=\"0\"\n        app:layout_constraintVertical_chainStyle=\"packed\"\n        tools:src=\"@drawable/ic_android\" />\n\n    <View\n        android:id=\"@+id/dummyGuide\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        app:layout_constraintBottom_toBottomOf=\"@android:id/icon1\"\n        app:layout_constraintHorizontal_weight=\"2\"\n        app:layout_constraintLeft_toLeftOf=\"@android:id/icon1\"\n        app:layout_constraintRight_toLeftOf=\"@android:id/icon2\"\n        app:layout_constraintTop_toTopOf=\"@android:id/icon1\" />\n\n    <ImageView\n        android:id=\"@android:id/icon2\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:importantForAccessibility=\"no\"\n        android:scaleType=\"fitXY\"\n        app:layout_constraintBottom_toBottomOf=\"@android:id/icon1\"\n        app:layout_constraintDimensionRatio=\"1:1\"\n        app:layout_constraintHorizontal_weight=\"1\"\n        app:layout_constraintLeft_toRightOf=\"@id/dummyGuide\"\n        app:layout_constraintRight_toRightOf=\"@android:id/icon1\"\n        tools:src=\"@drawable/ic_android\"\n        tools:tint=\"#9acd32\" />\n\n    <TextView\n        android:id=\"@android:id/text1\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginHorizontal=\"@dimen/result_margin_horizontal\"\n        android:gravity=\"top|center_horizontal\"\n        android:maxLines=\"3\"\n        android:paddingHorizontal=\"2dp\"\n        android:textAlignment=\"center\"\n        android:textColor=\"?android:attr/textColorSecondary\"\n        android:textSize=\"@dimen/result_small_size\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@android:id/icon1\"\n        tools:ignore=\"RtlSymmetry\"\n        tools:text=\"Shortcut for apps\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_shortcut.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout 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:id=\"@+id/linearLayout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:paddingHorizontal=\"@dimen/result_margin_horizontal\"\n    tools:layout_height=\"58dp\">\n\n    <ImageView\n        android:id=\"@android:id/icon1\"\n        android:layout_width=\"@dimen/icon_size\"\n        android:layout_height=\"@dimen/icon_size\"\n        android:layout_marginStart=\"@dimen/icon_margin_left\"\n        android:layout_marginEnd=\"@dimen/icon_margin_right\"\n        android:importantForAccessibility=\"no\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        tools:src=\"@drawable/ic_android\" />\n\n    <View\n        android:id=\"@+id/dummyGuide\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        app:layout_constraintBottom_toBottomOf=\"@android:id/icon1\"\n        app:layout_constraintHorizontal_weight=\"2\"\n        app:layout_constraintLeft_toLeftOf=\"@android:id/icon1\"\n        app:layout_constraintRight_toLeftOf=\"@android:id/icon2\"\n        app:layout_constraintTop_toTopOf=\"@android:id/icon1\" />\n\n    <ImageView\n        android:id=\"@android:id/icon2\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:importantForAccessibility=\"no\"\n        android:scaleType=\"fitXY\"\n        app:layout_constraintBottom_toBottomOf=\"@android:id/icon1\"\n        app:layout_constraintDimensionRatio=\"1:1\"\n        app:layout_constraintHorizontal_weight=\"1\"\n        app:layout_constraintLeft_toRightOf=\"@id/dummyGuide\"\n        app:layout_constraintRight_toRightOf=\"@android:id/icon1\"\n        tools:src=\"@drawable/ic_android\"\n        tools:tint=\"#9acd32\" />\n\n\n    <TextView\n        android:id=\"@+id/item_app_name\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/icon_margin_right\"\n        android:ellipsize=\"end\"\n        android:maxLines=\"1\"\n        android:paddingEnd=\"2dp\"\n        android:paddingBottom=\"3dp\"\n        android:textColor=\"?attr/resultColor\"\n        android:textSize=\"@dimen/result_title_size\"\n        app:layout_constraintBottom_toTopOf=\"@id/item_app_tag\"\n        app:layout_constraintStart_toEndOf=\"@android:id/icon1\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintVertical_chainStyle=\"packed\"\n        tools:ignore=\"RtlSymmetry\"\n        tools:text=\"@string/stub_application\" />\n\n    <TextView\n        android:id=\"@+id/item_app_tag\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:ellipsize=\"end\"\n        android:paddingEnd=\"2dp\"\n        android:textColor=\"?android:attr/textColorSecondary\"\n        android:textSize=\"@dimen/result_small_size\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"@id/item_app_name\"\n        app:layout_constraintTop_toBottomOf=\"@id/item_app_name\"\n        tools:ignore=\"RtlSymmetry\"\n        tools:text=\"@string/stub_app_tag\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/mm2d_cc_color_chooser.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<net.mm2d.color.chooser.ColorChooserView\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:theme=\"?attr/alertDialogTheme\"\n    />\n"
  },
  {
    "path": "app/src/main/res/layout/mm2d_cc_item_palette.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"horizontal\"\n    >\n\n    <net.mm2d.color.chooser.element.PaletteCell\n        android:id=\"@+id/sample_00\"\n        style=\"@style/palette_cell\"\n        />\n\n    <net.mm2d.color.chooser.element.PaletteCell\n        android:id=\"@+id/sample_01\"\n        style=\"@style/palette_cell\"\n        />\n\n    <net.mm2d.color.chooser.element.PaletteCell\n        android:id=\"@+id/sample_02\"\n        style=\"@style/palette_cell\"\n        />\n\n    <net.mm2d.color.chooser.element.PaletteCell\n        android:id=\"@+id/sample_03\"\n        style=\"@style/palette_cell\"\n        />\n\n    <net.mm2d.color.chooser.element.PaletteCell\n        android:id=\"@+id/sample_04\"\n        style=\"@style/palette_cell\"\n        />\n\n    <net.mm2d.color.chooser.element.PaletteCell\n        android:id=\"@+id/sample_05\"\n        style=\"@style/palette_cell\"\n        />\n\n    <net.mm2d.color.chooser.element.PaletteCell\n        android:id=\"@+id/sample_06\"\n        style=\"@style/palette_cell\"\n        />\n\n    <net.mm2d.color.chooser.element.PaletteCell\n        android:id=\"@+id/sample_07\"\n        style=\"@style/palette_cell\"\n        />\n\n    <net.mm2d.color.chooser.element.PaletteCell\n        android:id=\"@+id/sample_08\"\n        style=\"@style/palette_cell\"\n        />\n\n    <net.mm2d.color.chooser.element.PaletteCell\n        android:id=\"@+id/sample_09\"\n        style=\"@style/palette_cell\"\n        />\n\n    <net.mm2d.color.chooser.element.PaletteCell\n        android:id=\"@+id/sample_10\"\n        style=\"@style/palette_cell\"\n        />\n\n    <net.mm2d.color.chooser.element.PaletteCell\n        android:id=\"@+id/sample_11\"\n        style=\"@style/palette_cell\"\n        />\n\n    <net.mm2d.color.chooser.element.PaletteCell\n        android:id=\"@+id/sample_12\"\n        style=\"@style/palette_cell\"\n        />\n\n    <net.mm2d.color.chooser.element.PaletteCell\n        android:id=\"@+id/sample_13\"\n        style=\"@style/palette_cell\"\n        />\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/mm2d_cc_view_control.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge 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    tools:layout_height=\"100dp\"\n    tools:parentTag=\"androidx.constraintlayout.widget.ConstraintLayout\">\n\n    <net.mm2d.color.chooser.element.ColorSliderView\n        android:id=\"@+id/seek_alpha\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/mm2d_cc_page_margin\"\n        app:alphaMode=\"true\"\n        app:baseColor=\"#ffffff\"\n        app:layout_constraintBottom_toTopOf=\"@id/color_preview\"\n        app:layout_constraintEnd_toStartOf=\"@id/text_alpha\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintVertical_chainStyle=\"spread\"\n        app:maxColor=\"#000000\"\n        tools:progress=\"128\" />\n\n    <TextView\n        android:id=\"@+id/text_alpha\"\n        android:layout_width=\"@dimen/mm2d_cc_rgb_size\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginEnd=\"@dimen/mm2d_cc_page_margin\"\n        android:fontFamily=\"monospace\"\n        android:gravity=\"end\"\n        android:text=\"0\"\n        android:textColor=\"?android:attr/textColorPrimary\"\n        android:textSize=\"@dimen/mm2d_cc_color_text\"\n        app:layout_constraintBottom_toBottomOf=\"@id/seek_alpha\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toEndOf=\"@id/seek_alpha\"\n        app:layout_constraintTop_toTopOf=\"@id/seek_alpha\"\n        tools:ignore=\"HardcodedText,SpUsage\" />\n\n    <net.mm2d.color.chooser.element.PreviewView\n        android:id=\"@+id/color_preview\"\n        android:layout_width=\"@dimen/mm2d_cc_preview_width\"\n        android:layout_height=\"@dimen/mm2d_cc_preview_height\"\n        android:layout_marginHorizontal=\"@dimen/mm2d_cc_sample_horizontal_margin\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toStartOf=\"@id/edit_hex\"\n        app:layout_constraintTop_toBottomOf=\"@id/seek_alpha\" />\n\n    <EditText\n        android:id=\"@+id/edit_hex\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginHorizontal=\"@dimen/mm2d_cc_page_margin\"\n        android:ems=\"5\"\n        android:fontFamily=\"monospace\"\n        android:inputType=\"textNoSuggestions\"\n        android:textSize=\"@dimen/mm2d_cc_color_text\"\n        app:layout_constraintBottom_toBottomOf=\"@id/color_preview\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"@id/color_preview\"\n        tools:ignore=\"Autofill,LabelFor,SpUsage\" />\n</merge>\n"
  },
  {
    "path": "app/src/main/res/layout/mm2d_cc_view_dialog.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge 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    tools:parentTag=\"androidx.constraintlayout.widget.ConstraintLayout\"\n    tools:theme=\"@style/SettingsDialogTheme\">\n\n    <com.google.android.material.tabs.TabLayout\n        android:id=\"@+id/tab_layout\"\n        style=\"?attr/tabLayoutStyle\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginHorizontal=\"@dimen/mm2d_cc_panel_margin\"\n        app:layout_constraintBottom_toTopOf=\"@id/view_pager\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintVertical_chainStyle=\"spread_inside\" />\n\n    <androidx.viewpager2.widget.ViewPager2\n        android:id=\"@+id/view_pager\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginHorizontal=\"@dimen/mm2d_cc_panel_margin\"\n        app:layout_constrainedHeight=\"true\"\n        app:layout_constraintBottom_toTopOf=\"@id/control_view\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintHeight_max=\"@dimen/mm2d_cc_panel_height\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/tab_layout\" />\n\n    <net.mm2d.color.chooser.ControlView\n        android:id=\"@+id/control_view\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center_horizontal\"\n        android:layout_marginHorizontal=\"@dimen/mm2d_cc_panel_margin\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/view_pager\" />\n</merge>\n"
  },
  {
    "path": "app/src/main/res/layout/mm2d_cc_view_hsv.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n    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    tools:parentTag=\"androidx.constraintlayout.widget.ConstraintLayout\"\n    >\n\n    <net.mm2d.color.chooser.element.SvView\n        android:id=\"@+id/sv_view\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/mm2d_cc_hsv_margin\"\n        android:layout_marginEnd=\"@dimen/mm2d_cc_hsv_margin\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toStartOf=\"@id/hue_view\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        />\n\n    <net.mm2d.color.chooser.element.HueView\n        android:id=\"@+id/hue_view\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"0dp\"\n        app:layout_constraintBottom_toBottomOf=\"@id/sv_view\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toEndOf=\"@id/sv_view\"\n        app:layout_constraintTop_toTopOf=\"@id/sv_view\"\n        />\n</merge>\n"
  },
  {
    "path": "app/src/main/res/layout/mm2d_cc_view_slider.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge 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    tools:layout_height=\"130dp\"\n    tools:parentTag=\"androidx.constraintlayout.widget.ConstraintLayout\">\n\n    <net.mm2d.color.chooser.element.ColorSliderView\n        android:id=\"@+id/seek_red\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/mm2d_cc_page_margin\"\n        app:alphaMode=\"false\"\n        app:baseColor=\"#000000\"\n        app:layout_constraintBottom_toTopOf=\"@id/seek_green\"\n        app:layout_constraintEnd_toStartOf=\"@id/text_red\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintVertical_chainStyle=\"spread\"\n        app:maxColor=\"#ff0000\"\n        tools:progress=\"128\" />\n\n    <TextView\n        android:id=\"@+id/text_red\"\n        android:layout_width=\"@dimen/mm2d_cc_rgb_size\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginEnd=\"@dimen/mm2d_cc_page_margin\"\n        android:fontFamily=\"monospace\"\n        android:gravity=\"end\"\n        android:text=\"0\"\n        android:textColor=\"?android:attr/textColorPrimary\"\n        android:textSize=\"@dimen/mm2d_cc_color_text\"\n        app:layout_constraintBottom_toBottomOf=\"@id/seek_red\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toEndOf=\"@id/seek_red\"\n        app:layout_constraintTop_toTopOf=\"@id/seek_red\"\n        tools:ignore=\"HardcodedText,SpUsage\" />\n\n    <net.mm2d.color.chooser.element.ColorSliderView\n        android:id=\"@+id/seek_green\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/mm2d_cc_page_margin\"\n        app:alphaMode=\"false\"\n        app:baseColor=\"#000000\"\n        app:layout_constraintBottom_toTopOf=\"@id/seek_blue\"\n        app:layout_constraintEnd_toStartOf=\"@id/text_green\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/seek_red\"\n        app:maxColor=\"#00ff00\"\n        tools:progress=\"128\" />\n\n    <TextView\n        android:id=\"@+id/text_green\"\n        android:layout_width=\"@dimen/mm2d_cc_rgb_size\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center_vertical\"\n        android:layout_marginEnd=\"@dimen/mm2d_cc_page_margin\"\n        android:fontFamily=\"monospace\"\n        android:gravity=\"end\"\n        android:text=\"0\"\n        android:textColor=\"?android:attr/textColorPrimary\"\n        android:textSize=\"@dimen/mm2d_cc_color_text\"\n        app:layout_constraintBottom_toBottomOf=\"@id/seek_green\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toEndOf=\"@id/seek_green\"\n        app:layout_constraintTop_toTopOf=\"@id/seek_green\"\n        tools:ignore=\"HardcodedText,SpUsage\" />\n\n    <net.mm2d.color.chooser.element.ColorSliderView\n        android:id=\"@+id/seek_blue\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"@dimen/mm2d_cc_page_margin\"\n        app:alphaMode=\"false\"\n        app:baseColor=\"#000000\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toStartOf=\"@id/text_blue\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/seek_green\"\n        app:maxColor=\"#0000ff\"\n        tools:progress=\"128\" />\n\n    <TextView\n        android:id=\"@+id/text_blue\"\n        android:layout_width=\"@dimen/mm2d_cc_rgb_size\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center_vertical\"\n        android:layout_marginEnd=\"@dimen/mm2d_cc_page_margin\"\n        android:fontFamily=\"monospace\"\n        android:gravity=\"end\"\n        android:text=\"0\"\n        android:textColor=\"?android:attr/textColorPrimary\"\n        android:textSize=\"@dimen/mm2d_cc_color_text\"\n        app:layout_constraintBottom_toBottomOf=\"@id/seek_blue\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toEndOf=\"@id/seek_blue\"\n        app:layout_constraintTop_toTopOf=\"@id/seek_blue\"\n        tools:ignore=\"HardcodedText,SpUsage\" />\n</merge>\n"
  },
  {
    "path": "app/src/main/res/layout/ok_cancel_button_bar.xml",
    "content": "<!-- Alert dialog style buttons. Layout similar to @layout/abc_alert_dialog_button_bar_material -->\n<!-- TODO: copy functionality from androidx.appcompat.widget.ButtonBarLayout to allow buttons to be stacked -->\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        xmlns:tools=\"http://schemas.android.com/tools\"\n        android:id=\"@+id/buttonPanel\"\n        style=\"?android:attr/buttonBarStyle\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"bottom\"\n        android:baselineAligned=\"false\"\n        android:measureWithLargestChild=\"true\">\n\n    <Button\n            android:id=\"@android:id/button3\"\n            style=\"?attr/buttonBarNeutralButtonStyle\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:clickable=\"true\"\n            android:focusable=\"true\"\n            tools:text=\"@android:string/unknownName\"\n            tools:visibility=\"gone\" />\n\n    <android.widget.Space\n            android:id=\"@+id/spacer\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"0dp\"\n            android:layout_weight=\"1\"\n            android:visibility=\"invisible\" />\n\n    <Button\n            android:id=\"@android:id/button2\"\n            style=\"?attr/buttonBarNegativeButtonStyle\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:clickable=\"true\"\n            android:focusable=\"true\"\n            tools:text=\"@android:string/cancel\" />\n\n    <Button\n            android:id=\"@android:id/button1\"\n            style=\"?attr/buttonBarPositiveButtonStyle\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:clickable=\"true\"\n            android:focusable=\"true\"\n            tools:text=\"@android:string/ok\" />\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/order_list_item.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        xmlns:tools=\"http://schemas.android.com/tools\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:descendantFocusability=\"blocksDescendants\"\n        android:gravity=\"center_vertical\"\n        android:paddingHorizontal=\"@dimen/result_margin_horizontal\"\n        android:paddingVertical=\"@dimen/result_margin_vertical\"\n        tools:layout_gravity=\"center\"\n        tools:theme=\"@style/SettingsDialogTheme\">\n\n    <ImageView\n            android:id=\"@android:id/icon\"\n            android:layout_width=\"@dimen/icon_size\"\n            android:layout_height=\"@dimen/icon_size\"\n            android:layout_marginVertical=\"@dimen/icon_margin_vertical\"\n            android:layout_marginStart=\"@dimen/icon_margin_left\"\n            android:layout_marginEnd=\"@dimen/icon_margin_right\"\n            android:importantForAccessibility=\"no\"\n            android:src=\"@null\"\n            android:visibility=\"gone\"\n            tools:src=\"@tools:sample/avatars\"\n            tools:visibility=\"visible\" />\n\n    <TextView\n            android:id=\"@android:id/text1\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:ellipsize=\"end\"\n            android:singleLine=\"true\"\n            android:textColor=\"?attr/resultColor\"\n            android:textSize=\"@dimen/result_title_size\"\n            tools:text=\"@tools:sample/last_names\" />\n\n    <ImageView\n            android:id=\"@android:id/button1\"\n            android:layout_width=\"@dimen/icon_size\"\n            android:layout_height=\"@dimen/icon_size\"\n            android:layout_gravity=\"center\"\n            android:background=\"?attr/appSelectableItemBackground\"\n            android:contentDescription=\"@string/cd_remove_tag\"\n            android:scaleType=\"center\"\n            app:srcCompat=\"@android:drawable/arrow_up_float\" />\n\n    <ImageView\n            android:id=\"@android:id/button2\"\n            android:layout_width=\"@dimen/icon_size\"\n            android:layout_height=\"@dimen/icon_size\"\n            android:layout_gravity=\"center\"\n            android:background=\"?attr/appSelectableItemBackground\"\n            android:contentDescription=\"@string/cd_rename_tag\"\n            android:scaleType=\"center\"\n            app:srcCompat=\"@android:drawable/arrow_down_float\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/pin_shortcut_confirm.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        xmlns:tools=\"http://schemas.android.com/tools\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:gravity=\"center_horizontal\"\n        android:orientation=\"vertical\"\n        tools:theme=\"@style/TitleDialogTheme\">\n\n    <TextView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginHorizontal=\"16dp\"\n            android:layout_marginTop=\"16dp\"\n            android:labelFor=\"@+id/shortcutName\"\n            android:text=\"@string/pin_shortcut_icon\"\n            android:textAppearance=\"?android:attr/textAppearanceSmall\" />\n\n    <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"@dimen/icon_size\"\n            android:layout_marginHorizontal=\"16dp\"\n            android:layout_marginBottom=\"16dp\"\n            android:baselineAligned=\"false\"\n            android:gravity=\"center\"\n            android:measureWithLargestChild=\"true\"\n            android:orientation=\"horizontal\">\n\n        <!-- TODO: let user choose if he wants badge or not -->\n        <include\n                android:id=\"@+id/image\"\n                layout=\"@layout/item_grid_shortcut\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"match_parent\"\n                android:layout_weight=\"1\"\n                android:visibility=\"gone\" />\n\n        <include\n                android:id=\"@+id/imageWithBadge\"\n                layout=\"@layout/item_grid_shortcut\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"match_parent\"\n                android:layout_weight=\"1\" />\n\n    </LinearLayout>\n\n    <TextView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginHorizontal=\"16dp\"\n            android:layout_marginTop=\"16dp\"\n            android:labelFor=\"@+id/shortcutName\"\n            android:text=\"@string/pin_shortcut_label\"\n            android:textAppearance=\"?android:attr/textAppearanceSmall\" />\n\n    <EditText\n            android:id=\"@+id/shortcutName\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginHorizontal=\"16dp\"\n            android:layout_marginBottom=\"16dp\"\n            android:importantForAutofill=\"no\"\n            android:inputType=\"text\"\n            android:textAppearance=\"?android:attr/textAppearanceMedium\" />\n\n    <TextView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_margin=\"16dp\"\n            android:text=\"@string/pin_shortcut_message\"\n            android:textAppearance=\"?android:attr/textAppearanceMedium\" />\n\n    <TextView\n            android:id=\"@+id/shortcutDetails\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_margin=\"16dp\"\n            android:minLines=\"6\"\n            android:textAppearance=\"?android:attr/textAppearance\"\n            tools:layout_weight=\"1\"\n            tools:text=\"&lt;text&gt;\\n\\t&lt;second line of text &#x2F;&gt;\\n\\t&lt;some more text &#x2F;&gt;\\n&lt;&#x2F;text&gt;\" />\n\n    <!-- Alert dialog style buttons along the bottom. -->\n    <include layout=\"@layout/ok_cancel_button_bar\" />\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/popup_divider.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<View style=\"@style/SeparatorHorizontal\" />\r\n"
  },
  {
    "path": "app/src/main/res/layout/popup_list_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<TextView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@android:id/text1\"\n    style=\"@style/ListPopupItem\"\n    tools:text=\"@string/menu_quick_list_add\" />\n"
  },
  {
    "path": "app/src/main/res/layout/popup_list_item_icon.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    style=\"@style/ListPopupItem\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:paddingVertical=\"@dimen/widget_preview_padding_vertical\">\n\n    <ImageView\n        android:id=\"@android:id/icon\"\n        android:layout_width=\"@dimen/widget_preview_width\"\n        android:layout_height=\"@dimen/widget_preview_height\"\n        android:importantForAccessibility=\"no\"\n        tools:src=\"@drawable/dialog_background_light\" />\n\n    <TextView\n        android:id=\"@android:id/text1\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:maxLines=\"3\"\n        android:minLines=\"1\"\n        android:paddingStart=\"?android:attr/listPreferredItemPaddingStart\"\n        tools:ignore=\"RtlSymmetry\"\n        tools:text=\"Placeholder \\n Widget \\n name\" />\n</LinearLayout>\n\n"
  },
  {
    "path": "app/src/main/res/layout/popup_list_text.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<TextView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@android:id/text1\"\n    style=\"@style/ListPopupItem\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:gravity=\"center_vertical\"\n    android:lineSpacingMultiplier=\"0.75\"\n    android:minLines=\"1\"\n    tools:text=\"&lt;text&gt;\\n\\t&lt;second line of text &#x2F;&gt;\\n\\t&lt;some more text &#x2F;&gt;\\n&lt;&#x2F;text&gt;\" />\n"
  },
  {
    "path": "app/src/main/res/layout/popup_title.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n    xmlns:tools=\"http://schemas.android.com/tools\"\r\n    android:layout_width=\"match_parent\"\r\n    android:layout_height=\"?android:attr/listPreferredItemHeightSmall\"\r\n    android:gravity=\"center\"\r\n    android:orientation=\"vertical\">\r\n\r\n    <TextView\r\n        android:id=\"@android:id/text1\"\r\n        android:layout_width=\"match_parent\"\r\n        android:layout_height=\"wrap_content\"\r\n        android:layout_gravity=\"center\"\r\n        android:layout_marginStart=\"?android:attr/listPreferredItemPaddingLeft\"\r\n        android:layout_marginEnd=\"?android:attr/listPreferredItemPaddingRight\"\r\n        android:gravity=\"center\"\r\n        android:textAppearance=\"@style/ListPopupTitle\"\r\n        tools:text=\"Pref title gravity\" />\r\n\r\n    <include\r\n        android:id=\"@+id/title_divider\"\r\n        layout=\"@layout/popup_divider\" />\r\n\r\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/pref_alpha_preview.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<TextView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/prefAlphaPreview\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:gravity=\"center\"\n    tools:text=\"100%\" />\n"
  },
  {
    "path": "app/src/main/res/layout/pref_amount_preview.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<TextView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/prefAmountPreview\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:gravity=\"center\"\n    tools:text=\"+100\" />\n"
  },
  {
    "path": "app/src/main/res/layout/pref_color_preview.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<rocks.tbog.tblauncher.ui.SquareImageView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/prefColorPreview\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginVertical=\"@dimen/icon_margin_vertical\"\n    android:importantForAccessibility=\"no\"\n    android:minWidth=\"@dimen/color_preview_size\"\n    android:minHeight=\"@dimen/color_preview_size\"\n    android:scaleType=\"fitXY\"\n    android:src=\"@color/colorPrimary\" />\n\n"
  },
  {
    "path": "app/src/main/res/layout/pref_confirm.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"fill_parent\"\n    android:layout_height=\"fill_parent\"\n    android:orientation=\"vertical\"\n    tools:theme=\"@style/SettingsDialogTheme\">\n\n    <TextView\n        android:id=\"@android:id/text1\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginHorizontal=\"@dimen/dialog_padding_horizontal\"\n        android:layout_marginVertical=\"@dimen/dialog_padding_vertical\"\n        android:textAppearance=\"?android:attr/textAppearanceMedium\"\n        tools:text=\"Are you sure you want to ... ?\" />\n\n    <TextView\n        android:id=\"@android:id/text2\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginHorizontal=\"@dimen/dialog_padding_horizontal\"\n        android:layout_marginVertical=\"@dimen/dialog_padding_vertical\"\n        android:textAppearance=\"?android:attr/textAppearanceSmall\"\n        tools:text=\"This will do such and such...\" />\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/pref_margin_offset.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.core.widget.NestedScrollView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:paddingHorizontal=\"@dimen/dialog_padding_horizontal\"\n    android:paddingVertical=\"@dimen/dialog_padding_vertical\"\n    tools:layout_width=\"wrap_content\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\">\n\n        <TextView\n            android:id=\"@android:id/text1\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/shadow_offset\"\n            android:textColor=\"?android:attr/textColorPrimary\" />\n\n        <rocks.tbog.tblauncher.ui.CustomizeMarginView\n            android:id=\"@+id/viewXY\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center_horizontal\" />\n\n        <TextView\n            android:id=\"@+id/textValueXY\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/value_float_xy\"\n            android:textAlignment=\"center\"\n            android:textColor=\"?android:attr/textColorPrimary\" />\n\n    </LinearLayout>\n</androidx.core.widget.NestedScrollView>"
  },
  {
    "path": "app/src/main/res/layout/pref_matrix_preview.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<GridView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/grid\"\n    style=\"@style/ItemGrid\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:clickable=\"false\"\n    android:focusable=\"false\"\n    android:minHeight=\"@dimen/icon_size\">\n\n</GridView>"
  },
  {
    "path": "app/src/main/res/layout/pref_offset_preview.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<TextView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/prefOffsetPreview\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:gravity=\"center\"\n    tools:text=\"&lt;0.1&#215;0.2&gt;\" />\n"
  },
  {
    "path": "app/src/main/res/layout/pref_shadow.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.core.widget.NestedScrollView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:paddingHorizontal=\"@dimen/dialog_padding_horizontal\"\n    android:paddingVertical=\"@dimen/dialog_padding_vertical\"\n    tools:layout_width=\"wrap_content\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\">\n\n        <TextView\n            android:id=\"@android:id/text1\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/shadow_offset\"\n            android:textColor=\"?android:attr/textColorPrimary\" />\n\n        <rocks.tbog.tblauncher.ui.CustomizeShadowView\n            android:id=\"@+id/viewXY\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center_horizontal\" />\n\n        <TextView\n            android:id=\"@+id/textValueXY\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/value_float_xy\"\n            android:textAlignment=\"center\"\n            android:textColor=\"?android:attr/textColorPrimary\" />\n\n        <TextView\n            android:id=\"@android:id/text2\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:paddingTop=\"10dp\"\n            android:text=\"@string/shadow_radius\"\n            android:textColor=\"?android:attr/textColorPrimary\" />\n\n        <SeekBar\n            android:id=\"@+id/seekBar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:paddingVertical=\"10dp\" />\n\n        <TextView\n            android:id=\"@+id/textValueSlider\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/value\"\n            android:textColor=\"?android:attr/textColorPrimary\" />\n\n    </LinearLayout>\n</androidx.core.widget.NestedScrollView>"
  },
  {
    "path": "app/src/main/res/layout/pref_shadow_preview.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<TextView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/prefShadowPreview\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:gravity=\"center_vertical|end\"\n    android:textAlignment=\"gravity\"\n    tools:text=\"&lt;4.5&gt;\\n&lt;0.1&#215;0.2&gt;\" />\n"
  },
  {
    "path": "app/src/main/res/layout/pref_size_preview.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<TextView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/prefSizePreview\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:gravity=\"center\"\n    tools:text=\"&lt;14&gt;\" />\n"
  },
  {
    "path": "app/src/main/res/layout/pref_slider.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"fill_parent\"\n    android:layout_height=\"fill_parent\"\n    android:orientation=\"vertical\"\n    android:paddingHorizontal=\"@dimen/dialog_padding_horizontal\"\n    android:paddingVertical=\"@dimen/dialog_padding_vertical\">\n\n    <TextView\n        android:id=\"@android:id/text1\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/title_select_size\"\n        android:textColor=\"?android:attr/textColorPrimary\" />\n\n    <SeekBar\n        android:id=\"@+id/seekBar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:paddingVertical=\"10dp\" />\n\n    <TextView\n        android:id=\"@android:id/text2\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/value\"\n        android:textColor=\"?android:attr/textColorPrimary\" />\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/preference_switch.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.appcompat.widget.SwitchCompat xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@android:id/switch_widget\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@null\"\n    android:clickable=\"false\"\n    android:focusable=\"false\"\n    android:thumb=\"@drawable/abc_switch_thumb_material\"\n    android:track=\"@drawable/abc_switch_track_mtrl_alpha\"\n    tools:ignore=\"NewApi\" />\n"
  },
  {
    "path": "app/src/main/res/layout/quick_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<rocks.tbog.tblauncher.ui.RecyclerList xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/quickList\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"@dimen/large_bar_height\"\n    android:background=\"@null\"\n    android:gravity=\"center\"\n    android:measureWithLargestChild=\"true\"\n    android:orientation=\"horizontal\"\n    android:visibility=\"gone\"\n    tools:background=\"@drawable/tab_background_light\"\n    tools:backgroundTint=\"@color/colorAccent\"\n    tools:showIn=\"@layout/activity_fullscreen\"\n    tools:visibility=\"visible\" />"
  },
  {
    "path": "app/src/main/res/layout/quick_list_editor.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    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    tools:theme=\"@style/SettingsDialogTheme\">\n\n    <TextView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginHorizontal=\"@dimen/dialog_padding_horizontal\"\n        android:layout_marginTop=\"@dimen/dialog_padding_vertical\"\n        android:text=\"@string/edit_quick_list_preview\" />\n\n    <!-- The preview will get the same margin as the real thing (25% of the height) -->\n    <rocks.tbog.tblauncher.ui.RecyclerList\n        android:id=\"@+id/dockPreview\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"@dimen/large_bar_height\"\n        android:background=\"@null\"\n        android:gravity=\"center\"\n        android:orientation=\"horizontal\"\n        tools:background=\"@drawable/tab_background_light\"\n        tools:backgroundTint=\"@color/colorAccent\"/>\n<!--    <LinearLayout-->\n<!--        android:id=\"@+id/preview\"-->\n<!--        android:layout_width=\"match_parent\"-->\n<!--        android:layout_height=\"@dimen/large_bar_height\"-->\n<!--        android:layout_marginHorizontal=\"@dimen/dialog_padding_horizontal\"-->\n<!--        android:background=\"@null\"-->\n<!--        android:gravity=\"center\"-->\n<!--        android:measureWithLargestChild=\"true\"-->\n<!--        android:orientation=\"horizontal\"-->\n<!--        tools:background=\"@drawable/tab_background_light\"-->\n<!--        tools:backgroundTint=\"@color/colorAccent\" />-->\n\n    <androidx.viewpager.widget.ViewPager\n        android:id=\"@+id/viewPager\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_marginHorizontal=\"@dimen/dialog_padding_horizontal\"\n        android:layout_weight=\"1\">\n\n        <com.google.android.material.tabs.TabLayout\n            android:id=\"@+id/tabLayout\"\n            style=\"?attr/tabLayoutStyle\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            app:tabGravity=\"fill\"\n            app:tabMode=\"fixed\" />\n\n    </androidx.viewpager.widget.ViewPager>\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/quick_list_editor_page.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<GridView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/grid\"\n    style=\"@style/ItemGrid\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:layout_marginHorizontal=\"@dimen/dialog_padding_horizontal\"\n    android:columnWidth=\"@dimen/app_grid_column_width\"\n    android:stretchMode=\"columnWidth\"\n    android:verticalSpacing=\"@dimen/app_grid_vertical_spacing\"\n    tools:layout_height=\"200dp\"\n    tools:listitem=\"@layout/item_grid\" />\n"
  },
  {
    "path": "app/src/main/res/layout/result_list.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    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/resultLayout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"0dp\"\n    android:layout_marginHorizontal=\"@dimen/result_margin_horizontal\"\n    android:layout_marginVertical=\"@dimen/result_margin_vertical\"\n    android:layout_weight=\"1\"\n    android:background=\"?attr/listBackgroundColor\"\n    android:elevation=\"2dp\"\n    android:visibility=\"gone\"\n    app:layout_constraintBottom_toTopOf=\"@id/debugText\"\n    app:layout_constraintTop_toTopOf=\"parent\"\n    app:layout_constraintVertical_bias=\"0.0\"\n    tools:ignore=\"UnusedAttribute\"\n    tools:parentTag=\"androidx.constraintlayout.widget.ConstraintLayout\"\n    tools:showIn=\"@layout/activity_fullscreen\"\n    tools:visibility=\"visible\">\n\n    <rocks.tbog.tblauncher.ui.RecyclerList\n        android:id=\"@+id/resultList\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:background=\"@null\"\n        android:cacheColorHint=\"@android:color/transparent\"\n        android:clipChildren=\"false\"\n        android:clipToPadding=\"false\"\n        android:divider=\"?attr/dividerDrawable\"\n        android:dividerHeight=\"1dp\"\n        android:orientation=\"vertical\"\n        android:scrollbars=\"vertical\"\n        android:stackFromBottom=\"true\"\n        android:transcriptMode=\"alwaysScroll\"\n        tools:listitem=\"@layout/item_app\" />\n\n    <rocks.tbog.tblauncher.ui.BottomPullEffectView\n        android:id=\"@+id/listEdgeEffect\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        tools:visibility=\"gone\" />\n\n</FrameLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/search_bar.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    xmlns:tools=\"http://schemas.android.com/tools\"\n\n    android:id=\"@+id/searchBarContainer\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"@dimen/bar_height\"\n    android:layout_gravity=\"bottom|center_horizontal\"\n    android:background=\"@null\"\n    android:clipChildren=\"false\"\n    android:gravity=\"center\"\n    android:orientation=\"horizontal\"\n    android:visibility=\"gone\"\n    tools:background=\"@color/colorPrimaryDark\"\n    tools:visibility=\"visible\">\n\n    <ImageView\n        android:id=\"@+id/launcherButton\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"match_parent\"\n        android:adjustViewBounds=\"true\"\n        android:background=\"?attr/selectableItemBackgroundBorderless\"\n        android:clickable=\"true\"\n        android:contentDescription=\"@string/cd_show_all_apps\"\n        android:focusable=\"true\"\n        android:paddingVertical=\"1dp\"\n        android:paddingLeft=\"@dimen/launcher_button_padding\"\n        android:paddingRight=\"@dimen/launcher_button_padding\"\n        android:scaleType=\"fitCenter\"\n        app:srcCompat=\"@drawable/launcher_white\"\n        app:tint=\"?attr/searchColor\" />\n\n    <rocks.tbog.tblauncher.ui.SearchEditText\n        android:id=\"@+id/launcherSearch\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"match_parent\"\n        android:layout_weight=\"1\"\n        android:background=\"?attr/searchBackgroundColor\"\n        android:gravity=\"start|center_vertical\"\n        android:hint=\"@string/hint_ui_search\"\n        android:imeOptions=\"flagNoExtractUi|actionSearch\"\n        android:importantForAutofill=\"no\"\n        android:inputType=\"textVisiblePassword|textNoSuggestions\"\n        android:singleLine=\"true\"\n        android:textAlignment=\"textStart\"\n        android:textColor=\"?attr/searchColor\"\n        android:textColorHint=\"?android:attr/textColorSecondary\"\n        android:textCursorDrawable=\"@null\"\n        android:textSize=\"16sp\" />\n\n    <FrameLayout\n        android:layout_width=\"@dimen/launcher_button_width\"\n        android:layout_height=\"match_parent\"\n        android:clipChildren=\"false\">\n\n        <ImageView\n            android:id=\"@+id/menuButton\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:background=\"?attr/selectableItemBackgroundBorderless\"\n            android:clickable=\"true\"\n            android:contentDescription=\"@string/cd_main_menu\"\n            android:focusable=\"true\"\n            android:paddingLeft=\"@dimen/launcher_button_padding\"\n            android:paddingRight=\"@dimen/launcher_button_padding\"\n            android:scaleType=\"fitCenter\"\n            android:textAlignment=\"center\"\n            app:srcCompat=\"@drawable/ic_dots\" />\n\n        <ImageView\n            android:id=\"@+id/clearButton\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:background=\"?attr/selectableItemBackgroundBorderless\"\n            android:clickable=\"true\"\n            android:contentDescription=\"@string/cd_main_clear\"\n            android:focusable=\"true\"\n            android:paddingLeft=\"@dimen/launcher_button_padding\"\n            android:paddingRight=\"@dimen/launcher_button_padding\"\n            android:scaleType=\"fitCenter\"\n            android:textAlignment=\"center\"\n            android:visibility=\"invisible\"\n            app:srcCompat=\"@drawable/ic_clear\" />\n\n    </FrameLayout>\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/search_pill.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.motion.widget.MotionLayout 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\n    android:id=\"@+id/searchBarContainer\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"50dp\"\n    android:background=\"@null\"\n    android:clipChildren=\"false\"\n    android:visibility=\"gone\"\n    app:layoutDescription=\"@xml/search_pill_scene\"\n    tools:visibility=\"visible\">\n\n    <rocks.tbog.tblauncher.ui.SearchEditText\n        android:id=\"@+id/launcherSearch\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:hint=\"@string/hint_ui_search\"\n        android:imeOptions=\"flagNoExtractUi|actionSearch\"\n        android:importantForAutofill=\"no\"\n        android:inputType=\"textVisiblePassword|textNoSuggestions\"\n        android:singleLine=\"true\"\n        android:textAlignment=\"textStart\"\n        android:padding=\"0dp\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintHorizontal_chainStyle=\"packed\"\n        app:layout_constraintHorizontal_weight=\"1\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toLeftOf=\"@id/launcherButton\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        tools:background=\"@color/Default_light\"\n        tools:backgroundTintMode=\"multiply\"\n        tools:textColor=\"@color/Default_text\"\n        tools:textColorHint=\"@color/Default_text\"\n        tools:textSize=\"16sp\" />\n\n    <ImageView\n        android:id=\"@+id/launcherButton\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:adjustViewBounds=\"true\"\n        android:clickable=\"true\"\n        android:contentDescription=\"@string/cd_show_all_apps\"\n        android:focusable=\"true\"\n        android:scaleType=\"fitXY\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintDimensionRatio=\"1.5:1\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:srcCompat=\"@drawable/launcher_pill\"\n        tools:background=\"?attr/selectableItemBackgroundBorderless\"\n        tools:ignore=\"ImageContrastCheck\"\n        tools:tint=\"@color/Default_text\"\n        tools:tintMode=\"multiply\" />\n\n    <ImageView\n        android:id=\"@+id/bkgBehindButton\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:scaleType=\"fitXY\"\n        app:layout_constraintBottom_toBottomOf=\"@id/launcherButton\"\n        app:layout_constraintDimensionRatio=\"1.5:1\"\n        app:layout_constraintEnd_toEndOf=\"@id/launcherButton\"\n        app:layout_constraintStart_toStartOf=\"@id/launcherButton\"\n        app:layout_constraintTop_toTopOf=\"@id/launcherButton\"\n        app:srcCompat=\"@drawable/launcher_pill_background\"\n        tools:background=\"?attr/selectableItemBackgroundBorderless\"\n        tools:ignore=\"ContentDescription,ImageContrastCheck\"\n        tools:tint=\"@color/Default_light\"\n        tools:tintMode=\"multiply\" />\n\n    <TextView\n        android:id=\"@+id/launcherTime\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:textAlignment=\"center\"\n        android:gravity=\"center\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toRightOf=\"@id/launcherButton\"\n        app:layout_constraintRight_toLeftOf=\"@id/menuButton\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        tools:background=\"@color/Default_background\"\n        tools:text=\"@tools:sample/date/hhmmss\"\n        tools:textColor=\"@color/Default_text\"\n        tools:textColorHint=\"@color/Default_text\"\n        tools:textSize=\"16sp\" />\n\n    <ImageView\n        android:id=\"@+id/menuButton\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:background=\"?attr/selectableItemBackgroundBorderless\"\n        android:clickable=\"true\"\n        android:contentDescription=\"@string/cd_main_menu\"\n        android:focusable=\"true\"\n        android:scaleType=\"fitXY\"\n        android:textAlignment=\"center\"\n        app:layout_constraintDimensionRatio=\"1:1\"\n        app:layout_constraintBottom_toBottomOf=\"@id/launcherButton\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"@id/launcherButton\"\n        app:srcCompat=\"@drawable/ic_dots\" />\n\n    <ImageView\n        android:id=\"@+id/clearButton\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:background=\"?attr/selectableItemBackgroundBorderless\"\n        android:clickable=\"true\"\n        android:contentDescription=\"@string/cd_main_clear\"\n        android:focusable=\"true\"\n        android:scaleType=\"fitCenter\"\n        android:textAlignment=\"center\"\n        android:visibility=\"invisible\"\n        app:layout_constraintBottom_toBottomOf=\"@id/menuButton\"\n        app:layout_constraintEnd_toEndOf=\"@id/menuButton\"\n        app:layout_constraintStart_toStartOf=\"@id/menuButton\"\n        app:layout_constraintTop_toTopOf=\"@id/menuButton\"\n        app:srcCompat=\"@drawable/ic_clear\" />\n\n</androidx.constraintlayout.motion.widget.MotionLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/tags_manager.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    tools:theme=\"@style/SettingsDialogTheme\"\n    tools:background=\"@drawable/dialog_background_dark\">\n\n    <ListView\n        android:id=\"@android:id/list\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\"\n        android:background=\"@null\"\n        android:cacheColorHint=\"@android:color/transparent\"\n        android:clipChildren=\"false\"\n        android:clipToPadding=\"false\"\n        android:divider=\"?attr/dividerDrawable\"\n        android:dividerHeight=\"1dp\"\n        tools:listitem=\"@layout/tags_manager_item\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/tags_manager_item.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    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/relativeLayout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:descendantFocusability=\"blocksDescendants\"\n    android:gravity=\"center_vertical\"\n    android:paddingHorizontal=\"@dimen/result_margin_horizontal\"\n    android:paddingVertical=\"@dimen/result_margin_vertical\"\n    tools:layout_gravity=\"center\"\n    tools:theme=\"@style/SettingsDialogTheme\">\n\n    <ImageView\n        android:id=\"@android:id/icon\"\n        android:layout_width=\"@dimen/icon_size\"\n        android:layout_height=\"@dimen/icon_size\"\n        android:layout_marginVertical=\"@dimen/icon_margin_vertical\"\n        android:layout_marginStart=\"@dimen/icon_margin_left\"\n        android:layout_marginEnd=\"@dimen/icon_margin_right\"\n        android:importantForAccessibility=\"no\"\n        android:src=\"@null\" />\n\n    <LinearLayout\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_weight=\"1\"\n        android:orientation=\"vertical\">\n\n        <TextView\n            android:id=\"@android:id/text1\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:ellipsize=\"end\"\n            android:singleLine=\"true\"\n            android:textColor=\"?attr/resultColor\"\n            android:textSize=\"@dimen/result_title_size\"\n            tools:text=\"@tools:sample/last_names\" />\n\n        <TextView\n            android:id=\"@android:id/text2\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"center_vertical\"\n            android:importantForAccessibility=\"no\"\n            android:paddingEnd=\"2dp\"\n            android:textColor=\"?android:attr/textColorSecondary\"\n            android:textSize=\"@dimen/result_small_size\"\n            tools:ignore=\"RtlSymmetry\"\n            tools:text=\"@tools:sample/first_names\" />\n    </LinearLayout>\n\n    <ImageView\n        android:id=\"@android:id/button1\"\n        android:layout_width=\"@dimen/icon_size\"\n        android:layout_height=\"@dimen/icon_size\"\n        android:layout_gravity=\"center\"\n        android:background=\"?attr/appSelectableItemBackground\"\n        android:contentDescription=\"@string/cd_remove_tag\"\n        android:scaleType=\"center\"\n        app:srcCompat=\"@drawable/ic_remove_tag\" />\n\n    <ImageView\n        android:id=\"@android:id/button2\"\n        android:layout_width=\"@dimen/icon_size\"\n        android:layout_height=\"@dimen/icon_size\"\n        android:layout_gravity=\"center\"\n        android:background=\"?attr/appSelectableItemBackground\"\n        android:contentDescription=\"@string/cd_rename_tag\"\n        android:scaleType=\"center\"\n        app:srcCompat=\"@drawable/ic_edit\" />\n\n    <ImageView\n        android:id=\"@android:id/button3\"\n        android:layout_width=\"@dimen/icon_size\"\n        android:layout_height=\"@dimen/icon_size\"\n        android:layout_gravity=\"center\"\n        android:background=\"?attr/appSelectableItemBackground\"\n        android:contentDescription=\"@string/cd_icon_tag\"\n        android:scaleType=\"center\"\n        app:srcCompat=\"@drawable/ic_icon\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/tags_manager_item_deleted.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    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:gravity=\"center_vertical\"\n    android:orientation=\"horizontal\"\n    android:paddingHorizontal=\"@dimen/result_margin_horizontal\"\n    android:paddingVertical=\"@dimen/result_margin_vertical\"\n    tools:layout_gravity=\"center\"\n    tools:theme=\"@style/SettingsDialogTheme\">\n\n    <View\n        android:layout_width=\"@dimen/icon_size\"\n        android:layout_height=\"0dp\"\n        android:layout_marginStart=\"@dimen/icon_margin_left\"\n        android:layout_marginEnd=\"@dimen/icon_margin_right\" />\n\n    <TextView\n        android:id=\"@android:id/text1\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_weight=\"1\"\n        android:singleLine=\"true\"\n        android:textColor=\"?attr/resultColor\"\n        android:textSize=\"@dimen/result_title_size\"\n        tools:text=\"@tools:sample/full_names\" />\n\n    <ImageView\n        android:id=\"@android:id/button1\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:contentDescription=\"@string/cd_undo_remove_tag\"\n        app:srcCompat=\"@drawable/ic_undo\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/widget_handle.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    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@drawable/handle_background\"\n    tools:layout_height=\"200dp\"\n    tools:layout_width=\"100dp\">\n\n    <ImageView\n        android:id=\"@+id/handle_top_left\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"top|left\"\n        android:importantForAccessibility=\"no\"\n        android:rotation=\"90\"\n        app:srcCompat=\"@drawable/ic_handle_resize_bl\"\n        tools:ignore=\"RtlHardcoded\" />\n\n    <ImageView\n        android:id=\"@+id/handle_top_right\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"top|right\"\n        android:importantForAccessibility=\"no\"\n        android:rotation=\"180\"\n        app:srcCompat=\"@drawable/ic_handle_resize_bl\"\n        tools:ignore=\"RtlHardcoded\" />\n\n    <ImageView\n        android:id=\"@+id/handle_bottom_right\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"bottom|right\"\n        android:importantForAccessibility=\"no\"\n        android:rotation=\"-90\"\n        app:srcCompat=\"@drawable/ic_handle_resize_bl\"\n        tools:ignore=\"RtlHardcoded\" />\n\n    <ImageView\n        android:id=\"@+id/handle_bottom_left\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"bottom|left\"\n        android:importantForAccessibility=\"no\"\n        app:srcCompat=\"@drawable/ic_handle_resize_bl\"\n        tools:ignore=\"RtlHardcoded\" />\n\n    <ImageView\n        android:id=\"@+id/handle_left\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"left|center_vertical\"\n        android:importantForAccessibility=\"no\"\n        app:srcCompat=\"@drawable/ic_handle_resize_l\"\n        tools:ignore=\"RtlHardcoded\" />\n\n    <ImageView\n        android:id=\"@+id/handle_top\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"top|center_horizontal\"\n        android:importantForAccessibility=\"no\"\n        android:rotation=\"90\"\n        app:srcCompat=\"@drawable/ic_handle_resize_l\"\n        tools:ignore=\"RtlHardcoded\" />\n\n    <ImageView\n        android:id=\"@+id/handle_right\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"right|center_vertical\"\n        android:importantForAccessibility=\"no\"\n        android:rotation=\"180\"\n        app:srcCompat=\"@drawable/ic_handle_resize_l\"\n        tools:ignore=\"RtlHardcoded\" />\n\n    <ImageView\n        android:id=\"@+id/handle_bottom\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"bottom|center_horizontal\"\n        android:importantForAccessibility=\"no\"\n        android:rotation=\"-90\"\n        app:srcCompat=\"@drawable/ic_handle_resize_l\"\n        tools:ignore=\"RtlHardcoded\" />\n\n</FrameLayout>"
  },
  {
    "path": "app/src/main/res/layout/widget_picker.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout 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\n    <TextView\n        android:id=\"@android:id/title\"\n        style=\"?android:attr/windowTitleStyle\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/menu_widget_add\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <androidx.constraintlayout.widget.Group\n        android:id=\"@+id/widgetLoadingGroup\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        app:constraint_referenced_ids=\"widgetLoading,widgetLoadingText\" />\n\n    <ProgressBar\n        android:id=\"@+id/widgetLoading\"\n        style=\"?android:attr/progressBarStyleLarge\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        app:layout_constraintBottom_toTopOf=\"@+id/widgetLoadingText\"\n        app:layout_constraintDimensionRatio=\"1:1\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintVertical_chainStyle=\"packed\" />\n\n    <TextView\n        android:id=\"@+id/widgetLoadingText\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:paddingTop=\"4dip\"\n        android:singleLine=\"true\"\n        android:text=\"@string/loading\"\n        android:textAppearance=\"?android:attr/textAppearanceSmall\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/widgetLoading\" />\n\n    <ListView\n        android:id=\"@android:id/list\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"match_parent\"\n        app:layout_constraintHeight_default=\"wrap\"\n        app:layout_constrainedHeight=\"true\"\n        app:layout_constraintBottom_toTopOf=\"@id/search_container\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@android:id/title\"\n        tools:listitem=\"@layout/popup_list_item_icon\" />\n\n    <com.google.android.material.textfield.TextInputLayout\n        android:id=\"@+id/search_container\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@android:id/list\">\n\n        <com.google.android.material.textfield.TextInputEditText\n            android:id=\"@+id/search\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:autofillHints=\"application,text\"\n            android:hint=\"@string/hint_custom_icon\"\n            android:inputType=\"textAutoCorrect\" />\n\n    </com.google.android.material.textfield.TextInputLayout>\n\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/widget_placeholder.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@drawable/handle_background\"\n    tools:layout_height=\"200dp\"\n    tools:layout_width=\"100dp\">\n\n    <ImageView\n        android:id=\"@android:id/icon\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_gravity=\"center\"\n        android:importantForAccessibility=\"no\"\n        android:scaleType=\"fitCenter\"\n        tools:src=\"@drawable/ic_android\" />\n\n    <TextView\n        android:id=\"@android:id/text1\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:background=\"@color/black_overlay\"\n        android:gravity=\"center\"\n        android:shadowColor=\"@android:color/black\"\n        android:shadowRadius=\"4\"\n        android:maxLines=\"3\"\n        android:minLines=\"1\"\n        android:textAppearance=\"?android:attr/textAppearance\"\n        android:text=\"@string/widget_placeholder\" />\n\n</FrameLayout>"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@mipmap/ic_launcher_background\"/>\n    <foreground android:drawable=\"@mipmap/ic_launcher_foreground\"/>\n</adaptive-icon>"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@mipmap/ic_launcher_background\"/>\n    <foreground android:drawable=\"@mipmap/ic_launcher_foreground\"/>\n</adaptive-icon>"
  },
  {
    "path": "app/src/main/res/values/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\" tools:ignore=\"MissingTranslation\">\n\n    <string-array name=\"settingsThemeEntries\">\n        <item>@string/settings_default</item>\n        <item>@string/settings_8bit</item>\n        <item>@string/settings_white</item>\n        <item>@string/settings_black</item>\n        <item>@string/settings_dark</item>\n        <item>@string/settings_deep_blues</item>\n    </string-array>\n\n    <string-array name=\"settingsThemeValues\" translatable=\"false\">\n        <item>default</item>\n        <item>8bit</item>\n        <item>white</item>\n        <item>black</item>\n        <item>dark</item>\n        <item>DeepBlues</item>\n    </string-array>\n\n    <string-array name=\"loadingIconEntries\">\n        <item>None / Transparent</item>\n        <item>Rotating arrows</item>\n        <item>Pulse circle</item>\n    </string-array>\n\n    <string-array name=\"loadingIconValues\" translatable=\"false\">\n        <item>none</item>\n        <item>arrows</item>\n        <item>pulse</item>\n    </string-array>\n\n    <string-array name=\"searchBarLayoutEntries\">\n        <item>Default text box</item>\n        <item>Pill-like button</item>\n    </string-array>\n\n    <string-array name=\"searchBarLayoutValues\" translatable=\"false\">\n        <item>btn-text-menu</item>\n        <item>pill-search</item>\n    </string-array>\n\n    <string-array name=\"dockPositionEntries\">\n        <item>Above the &#171;Result list&#187;</item>\n        <item>Under the &#171;Result list&#187;</item>\n        <item>Under the &#171;Search bar&#187;</item>\n    </string-array>\n\n    <string-array name=\"dockPositionValues\" translatable=\"false\">\n        <item>above-result-list</item>\n        <item>under-result-list</item>\n        <item>under-search-bar</item>\n    </string-array>\n\n    <string-array name=\"adaptiveEntries\">\n        <item>None / Default</item>\n        <item>Circle</item>\n        <item>Square</item>\n        <item>Squircle</item>\n        <item>Rounded rectangle</item>\n        <item>Teardrop bottom-right</item>\n        <item>Teardrop bottom-left</item>\n        <item>Teardrop top-left</item>\n        <item>Teardrop top-right</item>\n        <!-- <item>Teardrop random</item>-->\n        <item>Hexagon</item>\n        <item>Octagon</item>\n        <item>Rounded hexagon</item>\n        <item>Rounded octagon</item>\n    </string-array>\n\n    <!-- Values come from DrawableUtils.SHAPE_*    -->\n    <string-array name=\"adaptiveValues\" translatable=\"false\">\n        <item>0</item>  <!-- SHAPE_NONE -->\n        <item>1</item>  <!-- SHAPE_CIRCLE -->\n        <item>2</item>  <!-- SHAPE_SQUARE -->\n        <item>3</item>  <!-- SHAPE_SQUIRCLE -->\n        <item>4</item>  <!-- SHAPE_ROUND_RECT -->\n        <item>5</item>  <!-- SHAPE_TEARDROP_BR -->\n        <item>6</item>  <!-- SHAPE_TEARDROP_BL -->\n        <item>7</item>  <!-- SHAPE_TEARDROP_TL -->\n        <item>8</item>  <!-- SHAPE_TEARDROP_TR -->\n        <!-- <item>9</item>  SHAPE_TEARDROP_RND -->\n        <item>10</item> <!-- SHAPE_HEXAGON -->\n        <item>11</item> <!-- SHAPE_OCTAGON -->\n        <item>12</item> <!-- SHAPE_ROUND_HEXAGON -->\n        <item>13</item> <!-- SHAPE_ROUND_OCTAGON -->\n    </string-array>\n\n    <string-array name=\"defaultSearchProviders\" tools:ignore=\"InconsistentArrays\">\n        <item>Bing|https://www.bing.com/search?q=%s</item>\n        <item>Ecosia|https://www.ecosia.org/search?q=%s</item>\n        <item>DuckDuckGo|https://start.duckduckgo.com/?q=%s</item>\n        <item>Google|https://encrypted.google.com/search?q=%s</item>\n        <item>Google Maps|https://www.google.com/maps/search/?api=1&amp;query=%s</item>\n        <item>Google Play Store|https://play.google.com/store/search?q=%s</item>\n        <item>YouTube|https://www.youtube.com/results?search_query=%s</item>\n    </string-array>\n\n    <string-array name=\"defaultContactMimeTypes\" translatable=\"false\">\n        <item>vnd.android.cursor.item/phone_v2</item>\n    </string-array>\n\n    <array name=\"defaultSearchHints\">\n        <item>@string/hint_ui_search</item>\n    </array>\n\n    <string-array name=\"gestureEntries\">\n        <item>Do nothing</item>\n        <item>Lock screen</item>\n        <item>Show notifications panel</item>\n        <item>Show settings panel</item>\n        <item>Show &#171;Search&#187; desktop</item>\n        <item>Show &#171;Search&#187; desktop with keyboard</item>\n        <item>Show &#171;Widget&#187; desktop</item>\n        <item>Show &#171;Widget&#187; desktop center</item>\n        <item>Show &#171;Empty&#187; desktop</item>\n        <item>Toggle between Search and Widget</item>\n        <item>Toggle Search, Widget, Empty</item>\n        <item>@string/action_reload</item>\n        <item>@string/action_toggle_grid</item>\n        <item>@string/action_show_apps</item>\n        <item>@string/action_show_apps_reversed</item>\n        <item>@string/action_show_contacts</item>\n        <item>@string/action_show_contacts_reversed</item>\n        <item>@string/action_show_shortcuts</item>\n        <item>@string/action_show_shortcuts_reversed</item>\n        <item>@string/action_show_history_recency</item>\n        <item>@string/action_show_history_frequency</item>\n        <item>@string/action_show_history_frecency</item>\n        <item>@string/action_show_history_adaptive</item>\n        <item>@string/show_tags_menu</item>\n        <item>@string/show_tags_list</item>\n        <item>@string/show_tags_list_reversed</item>\n        <item>Run app…</item>\n        <item>Run shortcut…</item>\n        <item>Show tagged…</item>\n    </string-array>\n    <array name=\"gestureValues\" translatable=\"false\">\n        <item>none</item>\n        <item>lockScreen</item>\n        <item>expandNotificationsPanel</item>\n        <item>expandSettingsPanel</item>\n        <item>showSearchBar</item>\n        <item>showSearchBarAndKeyboard</item>\n        <item>showWidgets</item>\n        <item>showWidgetsCenter</item>\n        <item>showEmpty</item>\n        <item>toggleSearchAndWidget</item>\n        <item>toggleSearchWidgetEmpty</item>\n        <item>reloadProviders</item>\n        <item>toggleGrid</item>\n        <item>showAllAppsAZ</item>\n        <item>showAllAppsZA</item>\n        <item>showContactsAZ</item>\n        <item>showContactsZA</item>\n        <item>showShortcutsAZ</item>\n        <item>showShortcutsZA</item>\n        <item>showHistoryByRecency</item>\n        <item>showHistoryByFrequency</item>\n        <item>showHistoryByFrecency</item>\n        <item>showHistoryByAdaptive</item>\n        <item>showTagsMenu</item>\n        <item>showTagsList</item>\n        <item>showTagsListReversed</item>\n        <item>runApp</item>\n        <item>runShortcut</item>\n        <item>showEntry</item>\n    </array>\n\n    <string-array name=\"resultEntries\">\n        <item>Empty</item>\n        <item>@string/action_show_apps</item>\n        <item>@string/action_show_apps_reversed</item>\n        <item>@string/action_show_contacts</item>\n        <item>@string/action_show_contacts_reversed</item>\n        <item>@string/action_show_shortcuts</item>\n        <item>@string/action_show_shortcuts_reversed</item>\n        <item>@string/action_show_history_recency</item>\n        <item>@string/action_show_history_frequency</item>\n        <item>@string/action_show_history_frecency</item>\n        <item>@string/action_show_history_adaptive</item>\n        <item>@string/show_tags_list</item>\n        <item>@string/show_tags_list_reversed</item>\n        <item>Show tagged…</item>\n    </string-array>\n    <array name=\"resultValues\" translatable=\"false\">\n        <item>none</item>\n        <item>showAllAppsAZ</item>\n        <item>showAllAppsZA</item>\n        <item>showContactsAZ</item>\n        <item>showContactsZA</item>\n        <item>showShortcutsAZ</item>\n        <item>showShortcutsZA</item>\n        <item>showHistoryByRecency</item>\n        <item>showHistoryByFrequency</item>\n        <item>showHistoryByFrecency</item>\n        <item>showHistoryByAdaptive</item>\n        <item>showTagsList</item>\n        <item>showTagsListReversed</item>\n        <item>showEntry</item>\n    </array>\n\n    <string-array name=\"desktopEntries\">\n        <item>@string/title_desktop_last</item>\n        <item>@string/title_desktop_mode_search</item>\n        <item>Desktop &#171;Search&#187; with keyboard</item>\n        <item>@string/title_desktop_mode_widget</item>\n        <item>@string/title_desktop_mode_empty</item>\n    </string-array>\n    <array name=\"desktopValues\" translatable=\"false\">\n        <item>none</item>\n        <item>showSearchBar</item>\n        <item>showSearchBarAndKeyboard</item>\n        <item>showWidgets</item>\n        <item>showEmpty</item>\n    </array>\n\n    <string-array name=\"lwpPageCountVerticalEntries\">\n        <item>@string/lwp_pages_vertical_1</item>\n        <item>@string/lwp_pages_vertical_2</item>\n    </string-array>\n    <string-array name=\"lwpPageCountVerticalValues\" translatable=\"false\">\n        <item>1</item>\n        <item>3</item>\n    </string-array>\n\n    <string-array name=\"lwpPageCountHorizontalEntries\">\n        <item>@string/lwp_pages_horizontal_1</item>\n        <item>@string/lwp_pages_horizontal_2</item>\n    </string-array>\n    <string-array name=\"lwpPageCountHorizontalValues\" translatable=\"false\">\n        <item>1</item>\n        <item>3</item>\n    </string-array>\n\n    <string-array name=\"contactActionEntries\">\n        <item>Call</item>\n        <item>Message</item>\n        <item>Send</item>\n    </string-array>\n    <string-array name=\"contactActionValues\" translatable=\"false\">\n        <item>phone</item>\n        <item>message</item>\n        <item>open</item>\n    </string-array>\n\n    <array name=\"material_colors\">\n        <item>@array/mm2d_cc_red</item>\n        <item>@array/mm2d_cc_pink</item>\n        <item>@array/mm2d_cc_purple</item>\n        <item>@array/mm2d_cc_deep_purple</item>\n        <item>@array/mm2d_cc_indigo</item>\n        <item>@array/mm2d_cc_blue</item>\n        <item>@array/mm2d_cc_light_blue</item>\n        <item>@array/mm2d_cc_cyan</item>\n        <item>@array/mm2d_cc_teal</item>\n        <item>@array/mm2d_cc_green</item>\n        <item>@array/mm2d_cc_light_green</item>\n        <item>@array/mm2d_cc_lime</item>\n        <item>@array/mm2d_cc_yellow</item>\n        <item>@array/mm2d_cc_amber</item>\n        <item>@array/mm2d_cc_orange</item>\n        <item>@array/mm2d_cc_deep_orange</item>\n        <item>@array/mm2d_cc_brown</item>\n        <item>@array/mm2d_cc_gray</item>\n        <item>@array/mm2d_cc_blue_gray</item>\n    </array>\n    <array name=\"mm2d_cc_red\">\n        <item>#FFEBEE</item>\n        <item>#FFCDD2</item>\n        <item>#EF9A9A</item>\n        <item>#E57373</item>\n        <item>#EF5350</item>\n        <item>#F44336</item>\n        <item>#E53935</item>\n        <item>#D32F2F</item>\n        <item>#C62828</item>\n        <item>#B71C1C</item>\n        <item>#FF8A80</item>\n        <item>#FF5252</item>\n        <item>#FF1744</item>\n        <item>#D50000</item>\n    </array>\n    <array name=\"mm2d_cc_pink\">\n        <item>#FCE4EC</item>\n        <item>#F8BBD0</item>\n        <item>#F48FB1</item>\n        <item>#F06292</item>\n        <item>#EC407A</item>\n        <item>#E91E63</item>\n        <item>#D81B60</item>\n        <item>#C2185B</item>\n        <item>#AD1457</item>\n        <item>#880E4F</item>\n        <item>#FF80AB</item>\n        <item>#FF4081</item>\n        <item>#F50057</item>\n        <item>#C51162</item>\n    </array>\n    <array name=\"mm2d_cc_purple\">\n        <item>#F3E5F5</item>\n        <item>#E1BEE7</item>\n        <item>#CE93D8</item>\n        <item>#BA68C8</item>\n        <item>#AB47BC</item>\n        <item>#9C27B0</item>\n        <item>#8E24AA</item>\n        <item>#7B1FA2</item>\n        <item>#6A1B9A</item>\n        <item>#4A148C</item>\n        <item>#EA80FC</item>\n        <item>#E040FB</item>\n        <item>#D500F9</item>\n        <item>#AA00FF</item>\n    </array>\n    <array name=\"mm2d_cc_deep_purple\">\n        <item>#EDE7F6</item>\n        <item>#D1C4E9</item>\n        <item>#B39DDB</item>\n        <item>#9575CD</item>\n        <item>#7E57C2</item>\n        <item>#673AB7</item>\n        <item>#5E35B1</item>\n        <item>#512DA8</item>\n        <item>#4527A0</item>\n        <item>#311B92</item>\n        <item>#B388FF</item>\n        <item>#7C4DFF</item>\n        <item>#651FFF</item>\n        <item>#6200EA</item>\n    </array>\n    <array name=\"mm2d_cc_indigo\">\n        <item>#E8EAF6</item>\n        <item>#C5CAE9</item>\n        <item>#9FA8DA</item>\n        <item>#7986CB</item>\n        <item>#5C6BC0</item>\n        <item>#3F51B5</item>\n        <item>#3949AB</item>\n        <item>#303F9F</item>\n        <item>#283593</item>\n        <item>#1A237E</item>\n        <item>#8C9EFF</item>\n        <item>#536DFE</item>\n        <item>#3D5AFE</item>\n        <item>#304FFE</item>\n    </array>\n    <array name=\"mm2d_cc_blue\">\n        <item>#E3F2FD</item>\n        <item>#BBDEFB</item>\n        <item>#90CAF9</item>\n        <item>#64B5F6</item>\n        <item>#42A5F5</item>\n        <item>#2196F3</item>\n        <item>#1E88E5</item>\n        <item>#1976D2</item>\n        <item>#1565C0</item>\n        <item>#0D47A1</item>\n        <item>#82B1FF</item>\n        <item>#448AFF</item>\n        <item>#2979FF</item>\n        <item>#2962FF</item>\n    </array>\n    <array name=\"mm2d_cc_light_blue\">\n        <item>#E1F5FE</item>\n        <item>#B3E5FC</item>\n        <item>#81D4FA</item>\n        <item>#4FC3F7</item>\n        <item>#29B6F6</item>\n        <item>#03A9F4</item>\n        <item>#039BE5</item>\n        <item>#0288D1</item>\n        <item>#0277BD</item>\n        <item>#01579B</item>\n        <item>#80D8FF</item>\n        <item>#40C4FF</item>\n        <item>#00B0FF</item>\n        <item>#0091EA</item>\n    </array>\n    <array name=\"mm2d_cc_cyan\">\n        <item>#E0F7FA</item>\n        <item>#B2EBF2</item>\n        <item>#80DEEA</item>\n        <item>#4DD0E1</item>\n        <item>#26C6DA</item>\n        <item>#00BCD4</item>\n        <item>#00ACC1</item>\n        <item>#0097A7</item>\n        <item>#00838F</item>\n        <item>#006064</item>\n        <item>#84FFFF</item>\n        <item>#18FFFF</item>\n        <item>#00E5FF</item>\n        <item>#00B8D4</item>\n    </array>\n    <array name=\"mm2d_cc_teal\">\n        <item>#E0F2F1</item>\n        <item>#B2DFDB</item>\n        <item>#80CBC4</item>\n        <item>#4DB6AC</item>\n        <item>#26A69A</item>\n        <item>#009688</item>\n        <item>#00897B</item>\n        <item>#00796B</item>\n        <item>#00695C</item>\n        <item>#004D40</item>\n        <item>#A7FFEB</item>\n        <item>#64FFDA</item>\n        <item>#1DE9B6</item>\n        <item>#00BFA5</item>\n    </array>\n    <array name=\"mm2d_cc_green\">\n        <item>#E8F5E9</item>\n        <item>#C8E6C9</item>\n        <item>#A5D6A7</item>\n        <item>#81C784</item>\n        <item>#66BB6A</item>\n        <item>#4CAF50</item>\n        <item>#43A047</item>\n        <item>#388E3C</item>\n        <item>#2E7D32</item>\n        <item>#1B5E20</item>\n        <item>#B9F6CA</item>\n        <item>#69F0AE</item>\n        <item>#00E676</item>\n        <item>#00C853</item>\n    </array>\n    <array name=\"mm2d_cc_light_green\">\n        <item>#F1F8E9</item>\n        <item>#DCEDC8</item>\n        <item>#C5E1A5</item>\n        <item>#AED581</item>\n        <item>#9CCC65</item>\n        <item>#8BC34A</item>\n        <item>#7CB342</item>\n        <item>#689F38</item>\n        <item>#558B2F</item>\n        <item>#33691E</item>\n        <item>#CCFF90</item>\n        <item>#B2FF59</item>\n        <item>#76FF03</item>\n        <item>#64DD17</item>\n    </array>\n    <array name=\"mm2d_cc_lime\">\n        <item>#F9FBE7</item>\n        <item>#F0F4C3</item>\n        <item>#E6EE9C</item>\n        <item>#DCE775</item>\n        <item>#D4E157</item>\n        <item>#CDDC39</item>\n        <item>#C0CA33</item>\n        <item>#AFB42B</item>\n        <item>#9E9D24</item>\n        <item>#827717</item>\n        <item>#F4FF81</item>\n        <item>#EEFF41</item>\n        <item>#C6FF00</item>\n        <item>#AEEA00</item>\n    </array>\n    <array name=\"mm2d_cc_yellow\">\n        <item>#FFFDE7</item>\n        <item>#FFF9C4</item>\n        <item>#FFF59D</item>\n        <item>#FFF176</item>\n        <item>#FFEE58</item>\n        <item>#FFEB3B</item>\n        <item>#FDD835</item>\n        <item>#FBC02D</item>\n        <item>#F9A825</item>\n        <item>#F57F17</item>\n        <item>#FFFF8D</item>\n        <item>#FFFF00</item>\n        <item>#FFEA00</item>\n        <item>#FFD600</item>\n    </array>\n    <array name=\"mm2d_cc_amber\">\n        <item>#FFF8E1</item>\n        <item>#FFECB3</item>\n        <item>#FFE082</item>\n        <item>#FFD54F</item>\n        <item>#FFCA28</item>\n        <item>#FFC107</item>\n        <item>#FFB300</item>\n        <item>#FFA000</item>\n        <item>#FF8F00</item>\n        <item>#FF6F00</item>\n        <item>#FFE57F</item>\n        <item>#FFD740</item>\n        <item>#FFC400</item>\n        <item>#FFAB00</item>\n    </array>\n    <array name=\"mm2d_cc_orange\">\n        <item>#FFF3E0</item>\n        <item>#FFE0B2</item>\n        <item>#FFCC80</item>\n        <item>#FFB74D</item>\n        <item>#FFA726</item>\n        <item>#FF9800</item>\n        <item>#FB8C00</item>\n        <item>#F57C00</item>\n        <item>#EF6C00</item>\n        <item>#E65100</item>\n        <item>#FFD180</item>\n        <item>#FFAB40</item>\n        <item>#FF9100</item>\n        <item>#FF6D00</item>\n    </array>\n    <array name=\"mm2d_cc_deep_orange\">\n        <item>#FBE9E7</item>\n        <item>#FFCCBC</item>\n        <item>#FFAB91</item>\n        <item>#FF8A65</item>\n        <item>#FF7043</item>\n        <item>#FF5722</item>\n        <item>#F4511E</item>\n        <item>#E64A19</item>\n        <item>#D84315</item>\n        <item>#BF360C</item>\n        <item>#FF9E80</item>\n        <item>#FF6E40</item>\n        <item>#FF3D00</item>\n        <item>#DD2C00</item>\n    </array>\n    <array name=\"mm2d_cc_brown\">\n        <item>#EFEBE9</item>\n        <item>#D7CCC8</item>\n        <item>#BCAAA4</item>\n        <item>#A1887F</item>\n        <item>#8D6E63</item>\n        <item>#795548</item>\n        <item>#6D4C41</item>\n        <item>#5D4037</item>\n        <item>#4E342E</item>\n        <item>#3E2723</item>\n        <item>#FFFFFF</item>\n        <item>#000000</item>\n    </array>\n    <array name=\"mm2d_cc_gray\">\n        <item>#FAFAFA</item>\n        <item>#F5F5F5</item>\n        <item>#EEEEEE</item>\n        <item>#E0E0E0</item>\n        <item>#BDBDBD</item>\n        <item>#9E9E9E</item>\n        <item>#757575</item>\n        <item>#616161</item>\n        <item>#424242</item>\n        <item>#212121</item>\n    </array>\n    <array name=\"mm2d_cc_blue_gray\">\n        <item>#ECEFF1</item>\n        <item>#CFD8DC</item>\n        <item>#B0BEC5</item>\n        <item>#90A4AE</item>\n        <item>#78909C</item>\n        <item>#607D8B</item>\n        <item>#546E7A</item>\n        <item>#455A64</item>\n        <item>#37474F</item>\n        <item>#263238</item>\n    </array>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/attrs.xml",
    "content": "<resources>\n\n    <!-- Declare custom theme attributes that allow changing which styles are\n         used for button bars depending on the API level.\n         ?android:attr/buttonBarStyle is new as of API 11 so this is\n         necessary to support previous API levels. -->\n    <declare-styleable name=\"ButtonBarContainerTheme\">\n        <attr name=\"metaButtonBarStyle\" format=\"reference\" />\n        <attr name=\"metaButtonBarButtonStyle\" format=\"reference\" />\n    </declare-styleable>\n\n    <attr name=\"listBackgroundColor\" format=\"reference|color\" />\n    <attr name=\"resultColor\" format=\"reference|color\" />\n    <attr name=\"searchColor\" format=\"reference|color\" />\n    <attr name=\"searchBackgroundColor\" format=\"reference|color\" />\n    <attr name=\"searchBackgroundOutline\" format=\"reference|color\" />\n    <attr name=\"dividerDrawable\" format=\"reference\" />\n    <attr name=\"appSelectableItemBackground\" format=\"reference\" />\n\n    <attr name=\"tabLayoutStyle\" format=\"reference\"/>\n\n    <declare-styleable name=\"MaxHeightViewPager\">\n        <attr name=\"maxHeight\" format=\"dimension\"/>\n    </declare-styleable>\n\n    <declare-styleable name=\"ColorSliderView\">\n        <attr name=\"maxColor\" format=\"color\"/>\n        <attr name=\"baseColor\" format=\"color\"/>\n        <attr name=\"alphaMode\" format=\"boolean\"/>\n    </declare-styleable>\n\n    <declare-styleable name=\"ViewStubPreview\">\n        <attr name=\"inflatedId\" format=\"reference\"/>\n        <attr name=\"layout\" format=\"reference\"/>\n    </declare-styleable>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#FFB000</color>\n    <color name=\"colorPrimaryDark\">#007f00</color>\n    <color name=\"colorAccent\">#33ff00</color>\n\n    <color name=\"undefined\">#DC143C</color>\n    <color name=\"black_overlay\">#66000000</color>\n    <color name=\"white_overlay\">#DDffffff</color>\n    <color name=\"darkBackground\">#242424</color>\n\n    <color name=\"kiss_divider_light\">#e0e0e0</color>\n    <color name=\"kiss_divider_dark\">#434343</color>\n\n    <color name=\"mm2d_cc_sample_frame\">#FFFFFF</color>\n    <color name=\"mm2d_cc_sample_shadow\">#1a000000</color>\n    <color name=\"mm2d_cc_checker_light\">#FFFFFF</color>\n    <color name=\"mm2d_cc_checker_dark\">#808080</color>\n\n    <color name=\"DeepBlues_1\">#0C0032</color>\n    <color name=\"DeepBlues_2\">#190061</color>\n    <color name=\"DeepBlues_3\">#240090</color>\n    <color name=\"DeepBlues_4\">#3500D3</color>\n    <color name=\"DeepBlues_5\">#28283f</color>\n    <color name=\"DeepBlues_gradient_start\">#FF0c0032</color>\n    <color name=\"DeepBlues_gradient_center\">#A80c0032</color>\n    <color name=\"DeepBlues_gradient_finish\">#00282828</color>\n    <color name=\"DeepBlues_window\">#DD282828</color>\n    <color name=\"DeepBlues_selector_primary_disable\">#7Fffffff</color>\n    <color name=\"DeepBlues_selector_primary\">#FFffffff</color>\n    <color name=\"DeepBlues_selector_secondary_disable\">#2Fffffff</color>\n    <color name=\"DeepBlues_selector_secondary\">#6Fffffff</color>\n\n    <color name=\"Black_selector_primary_disable\">#7Fdddddd</color>\n    <color name=\"Black_selector_primary\">#FFdddddd</color>\n    <color name=\"Black_selector_secondary_disable\">#2Fdddddd</color>\n    <color name=\"Black_selector_secondary\">#6Fdddddd</color>\n    <color name=\"Black_accent\">#FFffffff</color>\n\n    <color name=\"Default_dark\">#1e1e28</color>\n    <color name=\"Default_background\">#2d3a5d</color>\n    <color name=\"Default_text\">#32c4c0</color>\n    <color name=\"Default_light\">#8de9d5</color>\n\n    <color name=\"Default_selector_primary_disable\">#3F32c4c0</color>\n    <color name=\"Default_selector_primary\">#FF32c4c0</color>\n    <color name=\"Default_selector_secondary_disable\">#3F8de9d5</color>\n    <color name=\"Default_selector_secondary\">#FF8de9d5</color>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/default_tags.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\" tools:ignore=\"MissingTranslation\">\n    <string name=\"alias_phone\">dial,call,phone</string>\n    <string name=\"alias_contacts\">contacts,people,relations</string>\n    <string name=\"alias_web\">Internet,web,browser</string>\n    <string name=\"alias_mail\">mail,email,e-mail</string>\n    <string name=\"alias_market\">market,store,app center</string>\n    <string name=\"alias_clock\">clock,alarm,timer,stopwatch</string>\n    <string name=\"alias_messaging\">text message,compose,SMS,MMS,messaging</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <dimen name=\"icon_size\">32dp</dimen>\n    <dimen name=\"icon_preview_size\">24dp</dimen>\n    <dimen name=\"color_preview_size\">16dp</dimen>\n    <dimen name=\"color_preview_radius\">6dp</dimen>\n\n    <dimen name=\"icon_margin_left\">0dp</dimen>\n    <dimen name=\"icon_margin_right\">10dp</dimen>\n    <dimen name=\"icon_margin_vertical\">5dp</dimen>\n\n    <dimen name=\"result_margin_horizontal\">5dp</dimen>\n    <dimen name=\"result_margin_vertical\">2dp</dimen>\n    <dimen name=\"result_corner_radius\">12dp</dimen>\n\n    <dimen name=\"quick_list_margin_horizontal\">2dp</dimen>\n    <dimen name=\"quick_list_margin_vertical\">2dp</dimen>\n\n    <dimen name=\"result_title_size\">14sp</dimen>\n    <dimen name=\"result_small_size\">12sp</dimen>\n\n    <dimen name=\"bar_height\">13dp</dimen>\n    <dimen name=\"large_bar_height\">22dp</dimen>\n    <!-- at most half of `large_bar_height` -->\n\n    <dimen name=\"launcher_button_width\">45dp</dimen>\n    <dimen name=\"launcher_button_padding\">10dp</dimen>\n\n    <dimen name=\"popup_title\">12sp</dimen>\n    <dimen name=\"popup_padding_horizontal\">22dp</dimen>\n    <dimen name=\"popup_corner_radius\">11dp</dimen>\n\n    <dimen name=\"custom_icon_column_width\">68dp</dimen> <!-- icon_width + 2 * result_margin_horizontal -->\n    <dimen name=\"app_grid_column_width\">80dp</dimen>\n    <dimen name=\"app_grid_vertical_spacing\">2dp</dimen>\n    <dimen name=\"app_grid_vertical_padding\">5dp</dimen>\n    <dimen name=\"app_grid_horizontal_padding\">2dp</dimen>\n\n    <dimen name=\"edit_tag_padding\">6sp</dimen>\n    <dimen name=\"separator_thickness\">3dp</dimen>\n\n    <dimen name=\"widget_preview_width\">128dp</dimen>\n    <dimen name=\"widget_preview_height\">64dp</dimen>\n    <dimen name=\"widget_preview_padding_vertical\">5dp</dimen>\n\n    <dimen name=\"mm2d_cc_sample_radius\">5dp</dimen>\n    <dimen name=\"mm2d_cc_sample_frame\">2dp</dimen>\n    <dimen name=\"mm2d_cc_sample_shadow\">1dp</dimen>\n    <dimen name=\"mm2d_cc_sample_horizontal_margin\">16dp</dimen>\n    <dimen name=\"mm2d_cc_preview_width\">48dp</dimen>\n    <dimen name=\"mm2d_cc_preview_height\">36dp</dimen>\n    <dimen name=\"mm2d_cc_hsv_size\">256dp</dimen>\n    <dimen name=\"mm2d_cc_hue_width\">24dp</dimen>\n    <dimen name=\"mm2d_cc_slider_height\">32dp</dimen>\n    <dimen name=\"mm2d_cc_slider_width\">256dp</dimen>\n    <dimen name=\"mm2d_cc_panel_margin\">4dp</dimen>\n    <dimen name=\"mm2d_cc_panel_height\">200dp</dimen>\n    <dimen name=\"mm2d_cc_rgb_size\">24dp</dimen>\n    <dimen name=\"mm2d_cc_color_text\">12dp</dimen>\n    <dimen name=\"mm2d_cc_palette_padding\">24dp</dimen>\n    <dimen name=\"mm2d_cc_palette_cell_height\">32dp</dimen>\n    <dimen name=\"mm2d_cc_checker_size\">6dp</dimen>\n    <dimen name=\"mm2d_cc_page_margin\">16dp</dimen>\n    <dimen name=\"mm2d_cc_hsv_margin\">4dp</dimen>\n\n    <dimen name=\"dialog_margin_horizontal\">5dp</dimen>\n    <dimen name=\"dialog_margin_vertical\">4dp</dimen>\n    <dimen name=\"dialog_padding_horizontal\">10dp</dimen>\n    <dimen name=\"dialog_padding_vertical\">10dp</dimen>\n\n    <integer name=\"shadow_preview_grid\">6</integer>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/ids.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <item name=\"tag_filterText\" type=\"id\" />\n    <item name=\"tag_actionId\" type=\"id\" />\n    <item name=\"tag_cacheId\" type=\"id\" />\n    <item name=\"tag_iconTask\" type=\"id\" />\n    <item name=\"iconPreview\" type=\"id\" />\n</resources>"
  },
  {
    "path": "app/src/main/res/values/pref_default.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <integer name=\"default_search_bar_height\" translatable=\"false\">30</integer>         <!-- 30dp -->\n    <integer name=\"default_dock_height\" translatable=\"false\">40</integer>               <!-- 40dp -->\n    <integer name=\"default_background_argb\" translatable=\"false\">0xC11e1e28</integer>   <!-- 75% opacity with default_color -->\n    <integer name=\"default_icon_background\" translatable=\"false\">0x802d3a5d</integer>\n    <integer name=\"default_icon_hue\" translatable=\"false\">0</integer>\n    <integer name=\"default_icon_contrast\" translatable=\"false\">0</integer>\n    <integer name=\"default_icon_brightness\" translatable=\"false\">0</integer>\n    <integer name=\"default_icon_saturation\" translatable=\"false\">0</integer>\n    <integer name=\"default_icon_scale\" translatable=\"false\">0</integer>\n    <integer name=\"default_popup_background\" translatable=\"false\">0x8f1e1e28</integer>\n    <integer name=\"default_color\" translatable=\"false\">0x1e1e28</integer>\n    <integer name=\"default_color_highlight\" translatable=\"false\">0x2d3a5d</integer>\n    <integer name=\"default_color_text\" translatable=\"false\">0x32c4c0</integer>\n    <integer name=\"default_color_text2\" translatable=\"false\">0x8de9d5</integer>\n    <integer name=\"default_size_text\" translatable=\"false\">14</integer>                 <!-- sp -->\n    <integer name=\"default_size_text2\" translatable=\"false\">12</integer>                <!-- sp -->\n    <integer name=\"default_size_icon\" translatable=\"false\">36</integer>                 <!-- dp -->\n    <integer name=\"min_size_icon\" translatable=\"false\">4</integer>                      <!-- dp -->\n    <integer name=\"max_size_icon\" translatable=\"false\">100</integer>                    <!-- dp -->\n    <integer name=\"default_height_row\" translatable=\"false\">58</integer>                <!-- dp -->\n    <integer name=\"default_result_history_size\" translatable=\"false\">32</integer>       <!-- count -->\n    <integer name=\"default_result_history_adaptive\" translatable=\"false\">36</integer>   <!-- adaptive hours -->\n    <integer name=\"default_result_searcher_cap\" translatable=\"false\">0</integer>        <!-- 0->infinite results -->\n    <item name=\"default_result_shadow_radius\" type=\"dimen\" format=\"float\" translatable=\"false\">2.9</item> <!-- px -->\n    <item name=\"default_result_shadow_dx\" type=\"dimen\" format=\"float\" translatable=\"false\">1</item>       <!-- px -->\n    <item name=\"default_result_shadow_dy\" type=\"dimen\" format=\"float\" translatable=\"false\">2</item>       <!-- px -->\n    <integer name=\"default_dock_columns\" translatable=\"false\">6</integer>\n    <integer name=\"default_dock_rows\" translatable=\"false\">1</integer>\n    <!-- Below Android Lollipop we can't crop content with corner radius,\n    so let's make rounded corners small to hide this -->\n    <integer name=\"default_corner_radius\" translatable=\"false\">5</integer>              <!-- 12dp -->\n    <integer name=\"default_result_margin\" translatable=\"false\">10</integer>             <!-- 10dp -->\n    <item name=\"default_result_margin_offset\" type=\"dimen\" format=\"float\" translatable=\"false\">0</item> <!-- dp -->\n</resources>"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\" tools:ignore=\"MissingTranslation\">\n    <string name=\"app_name\">TinyBit launcher</string>\n    <string name=\"app_name_debug\">TinyBit debug launcher</string>\n    <string name=\"hint_ui_search\">Search in apps, contacts, …</string>\n    <string name=\"ui_item_search\">Search in %1$s for \\\"%2$s\\\"?</string>\n    <string name=\"ui_item_visit\">Visit \\\"%1$s\\\"</string>\n    <string name=\"copy_confirmation\">Copied \\\"%s\\\" to clipboard</string>\n\n    <string name=\"removed_item\">Removed %s</string>\n    <string name=\"toast_hibernate_completed\">%s hibernated, relaunch to awake</string>\n    <string name=\"toast_hibernate_error\">[ERROR] %s not hibernated</string>\n\n    <string name=\"unable_to_initialize_shortcuts\">Could not access shortcuts. Is TinyBit your default launcher?</string>\n    <string name=\"cant_pin_shortcut\">Could not add shortcut. Is TinyBit your default launcher?</string>\n    <string name=\"menu_quick_list_add\">Add to dock</string>\n    <string name=\"menu_quick_list_remove\">Remove from dock</string>\n    <string name=\"menu_remove_history\">Remove from history</string>\n    <string name=\"stub_application\">App name</string>\n    <string name=\"stub_app_tag\">app tag list</string>\n    <string name=\"permission_denied\">Grant this permission first</string>\n    <string name=\"menu_exclude\">Exclude…</string>\n    <string name=\"menu_hide\">Hide</string>\n    <string name=\"menu_show\">Reveal</string>\n    <string name=\"menu_remove_shortcut\">Unpin</string>\n    <string name=\"menu_tags_add\">Add tags</string>\n    <string name=\"menu_tags_edit\">Edit tags</string>\n    <string name=\"menu_app_details\">App info</string>\n    <string name=\"menu_app_store\">View in store</string>\n    <string name=\"menu_app_uninstall\">Uninstall</string>\n    <string name=\"menu_app_hibernate\">Hibernate</string>\n    <string name=\"menu_exclude_history\">from history</string>\n    <string name=\"menu_exclude_kiss\">from TinyBit</string>\n    <string name=\"application_not_found\">Unable to launch \\\"%s\\\" app</string>\n    <string name=\"entry_not_found\">Could not find \\\"%s\\\"</string>\n    <string name=\"change_wallpaper\">Change wallpaper</string>\n    <string name=\"menu_widget_title\">Widget menu</string>\n    <string name=\"menu_widget_add\">Add Widget…</string>\n    <string name=\"menu_widget_remove\">Remove Widget…</string>\n    <string name=\"menu_widget_configure\">Customize Widget…</string>\n    <string name=\"menu_widget_setup\">Configure Widget…</string>\n    <string name=\"menu_popup_title\">Main menu</string>\n    <string name=\"menu_popup_title_settings\">Settings</string>\n    <string name=\"menu_popup_android_settings\">Device settings</string>\n    <string name=\"menu_popup_launcher_settings\">TinyBit settings</string>\n    <string name=\"menu_popup_tags_manager\">Tag manager</string>\n    <string name=\"menu_popup_tags_menu\">Tags menu</string>\n    <string name=\"rate_the_app\">Rate the app</string>\n    <string name=\"privacy_policy\">Privacy Policy</string>\n    <string name=\"app_version\">Version %s</string>\n    <string name=\"app_version_summary\">%1$s - %2$s</string>\n    <string name=\"user_interface_summary\">Themes, transparency and colors</string>\n    <string name=\"title_ui\">User interface</string>\n    <string name=\"notification_bar_section\">Notification bar</string>\n    <string name=\"color_and_opacity\">Color and opacity</string>\n    <string name=\"gradient\">Gradient</string>\n    <string name=\"black_notification_icons\">Dark icons</string>\n\n    <!-- Content Description -->\n    <string name=\"cd_main_menu\">Menu</string>\n    <string name=\"cd_main_clear\">Clear search bar</string>\n    <string name=\"cd_show_all_apps\">Show all apps</string>\n    <string name=\"cd_item_contact_message\">Message</string>\n    <string name=\"cd_item_contact_call\">Call</string>\n    <string name=\"cd_item_contact_open\">Open</string>\n    <string name=\"cd_add_tag\">Add tag</string>\n    <string name=\"cd_undo_remove_tag\">Undo remove tag</string>\n    <string name=\"cd_remove_tag\">Remove tag</string>\n    <string name=\"cd_rename_tag\">Rename tag</string>\n    <string name=\"cd_icon_tag\">Set tag icon</string>\n\n    <string name=\"title_select_alpha\">Drag the slider to select the desired opacity</string>\n    <string name=\"title_select_size\">Drag the slider to select the desired size</string>\n    <string name=\"search_bar_section\">Search bar</string>\n    <string name=\"search_bar_text_size\">Text size</string>\n    <string name=\"search_bar_height\">Bar height</string>\n    <string name=\"result_list_section\">Result list</string>\n    <string name=\"icons_pack\">Choose pack</string>\n    <string name=\"icons_pack_default_name\">System icons</string>\n    <string name=\"value\">value %d</string>\n    <string name=\"value_float\">value %.2f</string>\n    <string name=\"value_float_xy\">horizontal %1$.2f | vertical %2$.2f</string>\n    <string name=\"size\">&lt;%d&gt;</string>\n    <string name=\"size_float\">&lt;%.1f&gt;</string>\n    <string name=\"shadow_preview\">&lt;%3$.1f&gt;\\n&lt;%1$.1f&#215;%2$.1f&gt;</string>\n    <string name=\"offset_preview\">&lt;%1$.1f&#215;%2$.1f&gt;</string>\n\n    <string name=\"menu_app_rename\">Rename app</string>\n    <string name=\"menu_action_rename\">Rename</string>\n    <string name=\"menu_action_delete\">Delete</string>\n    <string name=\"menu_shortcut_rename\">Rename shortcut</string>\n    <string name=\"menu_custom_icon\">Change icon</string>\n    <string name=\"title_app_rename\">Choose a custom name for this app</string>\n    <string name=\"title_rename_tag\">Replace everywhere this tag appears</string>\n    <string name=\"hint_rename_tag\">Choose a different name for this tag</string>\n    <string name=\"title_rename_search_engine\">Choose a different name for this search engine</string>\n    <string name=\"title_rename_search_hint\">Choose a different name for this search hint</string>\n    <string name=\"title_edit_url_search_engine\">Choose a different URL for this search engine</string>\n    <string name=\"title_shortcut_rename\">Choose a custom name for this shortcut</string>\n    <string name=\"app_rename_confirmation\">The app name is now %s</string>\n    <string name=\"shortcut_rename_confirmation\">The shortcut name is now \\\"%s\\\"</string>\n    <string name=\"entry_rename_confirmation\">The name is now \\\"%s\\\"</string>\n    <string name=\"hint_custom_icon\">Filter by name</string>\n    <string name=\"default_icon\">default icon</string>\n    <string name=\"default_static_icon\">%s default icon</string>\n    <string name=\"custom_icon_application\">app</string>\n    <string name=\"custom_icon_activity\">activity</string>\n    <string name=\"custom_icon_activity_adaptive_no_background\">activity custom background</string>\n    <string name=\"custom_icon_badged\">badged</string>\n    <string name=\"default_icon_preview_label\">Default\\nicon</string>\n    <string name=\"current_icon_preview_label\">Current\\nicon</string>\n    <string name=\"custom_icon_preview_label\">Selected\\nicon</string>\n    <string name=\"custom_name_set_default\">Default name</string>\n    <string name=\"hint_new_tag\">add tag</string>\n    <string name=\"title_features\">Layout</string>\n    <string name=\"features_summary\">Toggle and customize UI elements</string>\n    <string name=\"quick_list_section\">Dock</string>\n    <string name=\"quick_list_enabled\">Use dock</string>\n    <string name=\"quick_list_icon_size\">Max icon size</string>\n    <string name=\"quick_list_height\">Height</string>\n    <string name=\"tags_section\">Tags</string>\n    <string name=\"fuzzy_search_tags\">Fuzzy-search tags</string>\n    <string name=\"fuzzy_search_tags_summary\">When searching include tags as possible match candidates</string>\n    <string name=\"tags_enabled\">Display tags for apps</string>\n    <string name=\"tags_enabled_summary\">Show tags for each app in the result list</string>\n    <string name=\"icons_visible\">Show Icons</string>\n    <string name=\"popup_title_shortcut_dynamic\">Shortcuts</string>\n    <string name=\"popup_title_hist_fav\">Preferences</string>\n    <string name=\"popup_title_customize\">Customize</string>\n    <string name=\"popup_title_link\">Links</string>\n    <string name=\"popup_title_debug\">Debug info</string>\n    <string name=\"shortcuts_no_host_permission\">Make this your main launcher to access shortcuts.</string>\n    <string name=\"pin_shortcut_message\">Do you want to add this shortcut?</string>\n    <string name=\"pin_shortcut_label\">Choose shortcut label</string>\n    <string name=\"pin_shortcut_icon\">Shortcut icon</string>\n    <string name=\"quick_list_only_for_results\">Hide dock when no results</string>\n    <string name=\"quick_list_only_for_results_summary\">Make dock visible when the result list has entries</string>\n    <string name=\"exit_the_app\">Close launcher</string>\n    <string name=\"exit_the_app_confirm\">Close the launcher?</string>\n    <string name=\"exit_the_app_description\">Your default launcher will be opened, or you will be asked which one you want to use.</string>\n    <string name=\"reset_default_launcher_confirm\">Change the default launcher?</string>\n    <string name=\"reset_default_launcher_description\">Asks for app to open after pressing the homescreen.</string>\n    <string name=\"reset_default_launcher\">Change default launcher</string>\n    <string name=\"filter_apps\">App filter</string>\n    <string name=\"filter_contacts\">Contact filter</string>\n    <string name=\"filter_shortcuts\">Shortcuts filter</string>\n    <string name=\"action_reload\">Reload providers</string>\n    <string name=\"action_toggle_grid\">Toggle grid</string>\n    <string name=\"action_show_apps\">Apps A→Z</string>\n    <string name=\"action_show_apps_grid4\">App grid A→Z</string>\n    <string name=\"action_show_apps_reversed\">Apps Z→A</string>\n    <string name=\"action_show_apps_grid4_reversed\">App grid Z→A</string>\n    <string name=\"action_show_contacts\">Contacts A→Z</string>\n    <string name=\"action_show_contacts_reversed\">Contacts Z→A</string>\n    <string name=\"action_show_shortcuts\">Shortcuts A→Z</string>\n    <string name=\"action_show_shortcuts_reversed\">Shortcuts Z→A</string>\n    <string name=\"action_show_favorites\">Favorites</string>\n    <string name=\"quick_list_content\">Customize content</string>\n    <string name=\"quick_list_content_summary\">Change order and add entries</string>\n    <string name=\"edit_quick_list_preview\">Preview <em>(long touch to start drag and drop, touch to remove)</em></string>\n    <string name=\"edit_quick_list_tab_filters\">Filters</string>\n    <string name=\"edit_quick_list_tab_actions\">Actions</string>\n    <string name=\"edit_quick_list_tab_favorites\">Favorites</string>\n    <string name=\"edit_quick_list_tab_tags\">Tags</string>\n    <string name=\"quick_list_icons_visible\">Show icons</string>\n    <string name=\"quick_list_icons_visible_summary\">Display dock item icons</string>\n    <string name=\"quick_list_text_visible\">Show names</string>\n    <string name=\"quick_list_text_visible_summary\">Display dock item names</string>\n    <string name=\"cache_drawable\">Cache icons</string>\n    <string name=\"screen_off_cache_clear\">Release cache often</string>\n    <string name=\"screen_off_cache_clear_summary\">Empty cache when screen turns off</string>\n    <string name=\"memory_section\">Memory</string>\n    <string name=\"cache_half_apps\">Small cache size</string>\n    <string name=\"cache_half_apps_summary\">Make LRU cache size half the number of apps installed</string>\n    <string name=\"icon_pack_section\">Icon pack</string>\n    <string name=\"icon_pack_content_list\">Icons from\\n%s</string>\n\n    <string name=\"title_wallpaper\">Wallpaper interactions</string>\n    <string name=\"lwp_touch\">Send touch events to wallpaper</string>\n    <string name=\"lwp_drag\">Emulate drag event for wallpaper</string>\n    <string name=\"lwp_drag_desc\">Send multiple touch events</string>\n    <string name=\"lock_portrait\">Lock portrait</string>\n    <string name=\"sensor_orientation\">Sensor Orientation</string>\n    <string name=\"wp_drag_animate\">Scroll wallpaper</string>\n    <string name=\"wp_drag_animate_desc\">Drag <b>←</b> <b>→</b> to scroll wallpaper</string>\n    <string name=\"wp_animate_center\">Center wallpaper</string>\n    <string name=\"wp_animate_center_desc\">The wallpaper will return to center after scrolling</string>\n    <string name=\"wp_animate_sides\">Side stick wallpaper</string>\n    <string name=\"wp_animate_sides_desc\">The wallpaper will stick to the <b>←</b> or <b>→</b> after scrolling</string>\n    <string name=\"cfg_widget_move\">Move</string>\n    <string name=\"cfg_widget_move_switch\">Switch move</string>\n    <string name=\"cfg_widget_move_exit\">Exit move</string>\n    <string name=\"cfg_widget_resize\">Resize</string>\n    <string name=\"cfg_widget_resize_switch\">Switch resize</string>\n    <string name=\"cfg_widget_resize_exit\">Exit \\\"Resize\\\"</string>\n    <string name=\"cfg_widget_move_resize\">Move and resize</string>\n    <string name=\"cfg_widget_move_resize_exit\">Exit \\\"Move and resize\\\"</string>\n    <string name=\"cfg_widget_remove\">Remove</string>\n    <string name=\"cfg_widget_screen_left\">Move to <b>←</b> screen</string>\n    <string name=\"cfg_widget_screen_up\">Move to <b>↑</b> screen</string>\n    <string name=\"cfg_widget_screen_middle\">Move to <b>middle</b> screen</string>\n    <string name=\"cfg_widget_screen_right\">Move to <b>→</b> screen</string>\n    <string name=\"cfg_widget_screen_down\">Move to <b>↓</b> screen</string>\n    <string name=\"cfg_widget_back\">Move <b>behind</b> all</string>\n    <string name=\"cfg_widget_front\">Move <b>above</b> all</string>\n\n    <string name=\"shortcut_with_appName\">%1$s: %2$s</string>\n\n    <string name=\"debug_section\">Debug</string>\n    <string name=\"debug_widget_add_info\">Add extra widget info</string>\n    <string name=\"debug_widget_info\">Long press for extra widget info</string>\n    <string name=\"debug_item_relevance\">Search relevance</string>\n    <string name=\"debug_item_icon_info\">Icon info</string>\n    <string name=\"shortcut_section\">Shortcut</string>\n    <string name=\"shortcut_pin_auto_confirm\">Auto-confirm shortcut</string>\n    <string name=\"shortcut_show_badge\">Show app badge</string>\n    <string name=\"shortcut_show_badge_summary\">Display app icon that created the shortcut</string>\n    <string name=\"quick_list_show_badge\">Show shortcut badge in dock</string>\n    <string name=\"result_list_argb\">Background color and opacity</string>\n    <string name=\"result_highlight_color\">Highlight color</string>\n    <string name=\"quick_list_color\">Background color</string>\n    <string name=\"quick_list_toggle_color\">Toggle background color</string>\n    <string name=\"contact_action_color\">Action icon color</string>\n    <string name=\"result_icon_size\">Icon size</string>\n    <string name=\"result_text_color\">Text color</string>\n    <string name=\"result_text2_color\">Secondary text color</string>\n    <string name=\"result_shadow_color\">Text shadow color</string>\n    <string name=\"result_shadow_radius_dx_dy\">Shadow size and offset</string>\n    <string name=\"search_bar_text_color\">Text color</string>\n    <string name=\"search_bar_icon_color\">Icons color</string>\n    <string name=\"menu_popup_quick_list_customize\">Edit dock</string>\n    <string name=\"result_text_size\">Text size</string>\n    <string name=\"result_text2_size\">Secondary text size</string>\n    <string name=\"invalid_rename_tag\">There is already a \\\"%s\\\" tag</string>\n    <string name=\"invalid_rename_search_engine\">There is already a \\\"%s\\\" search engine</string>\n    <string name=\"provider_section\">Providers</string>\n    <string name=\"enable_search\">Search engine</string>\n    <string name=\"enable_url\">Web URL</string>\n    <string name=\"enable_calculator\">Calculator</string>\n    <string name=\"enable_dial\">Dial search</string>\n    <string name=\"enable_contacts\">Phone contacts</string>\n\n    <string name=\"selected_contact_mime_types\">Select contacts to be shown</string>\n\n    <string name=\"edit_search_engines\">Edit search engines</string>\n    <string name=\"edit_search_engines_summary\">Toggle and edit search providers</string>\n    <string name=\"reset_search_engines\">Reset search engines</string>\n    <string name=\"reset_search_engines_summary\">Restore default search engines</string>\n    <string name=\"add_search_engine\">Add search engine</string>\n\n    <string name=\"edit_search_hint\">Edit search hint</string>\n    <string name=\"edit_search_hint_summary\">Toggle and edit search hints</string>\n    <string name=\"reset_search_hint\">Reset search hint</string>\n    <string name=\"reset_search_hint_summary\">Restore all search hints</string>\n    <string name=\"add_search_hint\">Add search hint</string>\n\n    <string name=\"search_engine_edit_url\">Edit URL</string>\n    <string name=\"confirm_edit_url_search_engine\">Save</string>\n    <string name=\"search_engine_set_default\">Make default</string>\n    <string name=\"hint_add_search_engine_url\">https://search.engine/query=%s</string>\n    <string name=\"label_search_engine_name\">Search provider name</string>\n    <string name=\"label_search_engine_url\">URL to open</string>\n    <string name=\"search_engine_url_help\">%s will be replaced by your query</string>\n    <string name=\"selected_pack\">%s\\n[current pack]</string>\n    <string name=\"tab_app_icons\">%s\\n[app icons]</string>\n    <string name=\"tab_static_icons\">Default icon</string>\n    <string name=\"tab_search_icon\">Search icon</string>\n    <string name=\"tab_button_icon\">Button icon</string>\n    <string name=\"icon_pack_loading\">Loading from icon packs…</string>\n    <string name=\"gesture_section\">Gestures</string>\n    <string name=\"gesture_fling_down_left\">Fling from |<b>←</b>|, <b>↓</b></string>\n    <string name=\"gesture_fling_down_left_summary\">Start from the <b>←</b> side of the screen and fling <b>↓</b></string>\n    <string name=\"gesture_fling_down_right\">Fling from |<b>→</b>|, <b>↓</b></string>\n    <string name=\"gesture_fling_down_right_summary\">Start from the <b>→</b> side of the screen and fling <b>↓</b></string>\n    <string name=\"gesture_fling_up\">Fling ↑</string>\n    <string name=\"gesture_fling_left\">Fling ←</string>\n    <string name=\"gesture_fling_right\">Fling →</string>\n    <string name=\"gesture_click\">Tap empty screen</string>\n    <string name=\"static_icon_letters_label\">Letters to use for generating icons</string>\n    <string name=\"backup_summary\">Save, backup, export and import settings</string>\n    <string name=\"title_backup\">Backup</string>\n    <string name=\"export_settings_section\">Export settings</string>\n    <string name=\"export_chooser\">Export %s</string>\n    <string name=\"export_chooser_xml\">Export \\\"%s\\\" as XML</string>\n    <string name=\"export_xml\">Export as XML</string>\n    <string name=\"export_tags\">Export tags</string>\n    <string name=\"export_tags_summary\">Export tag associations, no icons</string>\n    <string name=\"export_modifications\">Export customizations</string>\n    <string name=\"export_modifications_summary\">Dock, custom icons and names</string>\n    <string name=\"export_apps\">Export apps</string>\n    <string name=\"export_apps_summary\">New names for apps, hidden and custom icons</string>\n    <string name=\"import_settings_section\">Import settings</string>\n    <string name=\"import_settings_set\">Set imported settings</string>\n    <string name=\"import_settings_set_summary\">Clear relevant settings before importing</string>\n    <string name=\"import_settings_overwrite\">Overwrite imported settings</string>\n    <string name=\"import_settings_overwrite_summary\">Imported settings overwrite current settings</string>\n    <string name=\"import_settings_append\">Append imported settings</string>\n    <string name=\"import_settings_append_summary\">Only new settings get imported</string>\n    <string name=\"please_wait\">Please wait.</string>\n    <string name=\"import_dialog_description\">Loading settings…</string>\n    <string name=\"import_chooser\">Select a file to import</string>\n    <string name=\"export_description\">After clicking \\\"OK\\\" you can select a file manager to save your settings as an XML file.</string>\n    <string name=\"error\">Error %s</string>\n\n    <string name=\"export_preferences\">Export settings</string>\n    <string name=\"export_preferences_summary\">All settings, features and behaviours, including interface</string>\n    <string name=\"export_interface\">Export interface</string>\n    <string name=\"export_interface_summary\">All interface colors, sizes, toggles and icon shapes</string>\n    <string name=\"export_backup\">Export backup</string>\n    <string name=\"export_backup_summary\">All custom icons, tags and preferences</string>\n    <string name=\"export_widgets\">Export widgets</string>\n    <string name=\"export_widgets_summary\">Import may not work or require each widget to be added manually</string>\n    <string name=\"export_history\">Export history</string>\n    <string name=\"export_history_summary\">History of all launched apps</string>\n\n    <string name=\"error_fail_import\">Could not import file. Check logcat.</string>\n    <string name=\"widget_placeholder\">Click to restore %s</string>\n    <string name=\"add_widget_failed\">Could not add widget</string>\n    <string name=\"bind_widget_failed\">Missing permission to create widgets</string>\n    <string name=\"widget_name\"><![CDATA[<b>%1$s</b><br/>%2$d×%3$d]]></string>\n    <string name=\"widget_name_and_desc\"><![CDATA[<b>%1$s</b><br/>%3$d×%4$d<br/>%2$s]]></string>\n    <string name=\"widget_placeholder_remove\">Remove the placeholder?</string>\n    <string name=\"choose_file_activity_not_found\">Please install a file manager.</string>\n    <string name=\"result_history_size\">History size</string>\n    <string name=\"result_history_size_summary\">Maximum number of items when showing history</string>\n    <string name=\"action_show_history_recency\">Recent history</string>\n    <string name=\"action_show_history_frequency\">Most accessed</string>\n    <string name=\"action_show_history_frecency\">Recent and frequent</string>\n    <string name=\"action_show_history_adaptive\">Adaptive history</string>\n    <string name=\"show_tags_menu\">Open tags menu</string>\n    <string name=\"show_tags_list\">Tags menu as results</string>\n    <string name=\"show_tags_list_reversed\">Reversed tags menu as results</string>\n    <string name=\"result_history_adaptive\">Adaptive duration</string>\n    <string name=\"result_history_adaptive_summary\">Number of hours for adaptive history</string>\n\n    <string name=\"adaptive_shape_name\">Icon shape</string>\n    <string name=\"force_adaptive\">Set icon background</string>\n    <string name=\"force_adaptive_summary\">Reduce icon scale and place on a shaped background</string>\n    <string name=\"force_shape\">Force shape</string>\n    <string name=\"force_shape_summary\">Use custom shaped background for all</string>\n    <string name=\"contact_pack_mask\">Contacts</string>\n    <string name=\"contact_pack_mask_summary\">Use icon pack shape for contacts</string>\n    <string name=\"contacts_shape_name\">Contact-icon shape</string>\n    <string name=\"contacts_shape_summary\">Shape of contact icon</string>\n    <string name=\"shortcut_pack_mask\">Shortcut icon</string>\n    <string name=\"shortcut_pack_mask_summary\">Use icon pack mask for shortcut icon</string>\n    <string name=\"shortcut_shape_name\">Shortcut icon shape</string>\n    <string name=\"shortcut_shape_summary\">Shape of shortcut icon</string>\n    <string name=\"shortcut_pack_badge_mask\">Shortcut badge</string>\n    <string name=\"shortcut_pack_badge_mask_summary\">Use icon pack mask for shortcut badge</string>\n    <string name=\"choose_letter_color\">Letter color</string>\n    <string name=\"choose_background_color\">Background color</string>\n    <string name=\"choose_icon_scale\">Scale icon</string>\n    <string name=\"choose_icon_shape\">Choose shape</string>\n    <string name=\"choose_icon\">Choose icon</string>\n    <string name=\"choose_icon_menu_add\">Add as alternative</string>\n    <string name=\"choose_icon_menu_add2\">Add alternative and show</string>\n    <string name=\"icon_background\">Icon background</string>\n    <string name=\"label_search_hint\">Search hint</string>\n    <string name=\"result_search_cap\">Maximum search results</string>\n    <string name=\"unlimited_search_cap\">Unlimited search results</string>\n    <string name=\"unlimited_search_cap_summary\">Set highest possible limit</string>\n    <string name=\"result_ripple_color\">Touch color</string>\n    <string name=\"quick_list_ripple_color\">Touch color</string>\n    <string name=\"reset_preferences\">Reset preferences</string>\n    <string name=\"reset_preferences_confirm\">Discard preferences?</string>\n    <string name=\"reset_preferences_description\">All settings will be reset</string>\n    <string name=\"reset_cached_app_icons_confirm\">Reset icon pack cache?</string>\n    <string name=\"reset_cached_app_icons_description\">Resets cached icon pack name and version.</string>\n    <string name=\"generate_theme_confirm\">Set generated colors?</string>\n    <string name=\"generate_theme_description\">Resets all colors and apply generated colors based on the primary and secondary colors</string>\n    <string name=\"reset_matrix_confirm\">Set default values?</string>\n    <string name=\"reset_matrix_description\">Resets all icon settings on this screen</string>\n    <string name=\"popup_section\">Popup</string>\n    <string name=\"popup_ripple_color\">Touch color</string>\n    <string name=\"popup_background_argb\">Background color</string>\n    <string name=\"popup_border_argb\">Border color</string>\n    <string name=\"popup_text_color\">Text color</string>\n    <string name=\"popup_title_color\">Title color</string>\n    <string name=\"popup_shadow_color\">Text shadow color</string>\n    <string name=\"popup_shadow_radius_dx_dy\">Shadow size and offset</string>\n    <string name=\"settings_theme\">Settings theme</string>\n    <string name=\"settings_default\">Default</string>\n    <string name=\"settings_8bit\">8bit</string>\n    <string name=\"settings_white\">White</string>\n    <string name=\"settings_black\">Black</string>\n    <string name=\"settings_dark\">Dark</string>\n    <string name=\"settings_deep_blues\">DeepBlues</string>\n    <string name=\"title_static_rename\">Choose a custom name</string>\n    <string name=\"letters_toggle\">Letter icons</string>\n    <string name=\"button_launcher\">Launcher button</string>\n    <string name=\"button_home\">Home button</string>\n    <string name=\"icon_contrast\">Contrast</string>\n    <string name=\"icon_brightness\">Brightness</string>\n    <string name=\"icon_hue\">Hue</string>\n    <string name=\"icon_saturation\">Saturation</string>\n    <string name=\"matrix_summary\">Brightness, contrast, hue, saturation</string>\n    <string name=\"title_matrix\">Modulate icon color</string>\n    <string name=\"icon_scale_red\">Scale red</string>\n    <string name=\"icon_scale_green\">Scale green</string>\n    <string name=\"icon_scale_blue\">Scale blue</string>\n    <string name=\"icon_scale_alpha\">Scale transparency</string>\n    <string name=\"reset_matrix\">Set default values</string>\n    <string name=\"corner_radius\">Corner radius</string>\n    <string name=\"shadow_offset\">Select pixel amount for shadow offset</string>\n    <string name=\"shadow_offset_preview\">Touch or drag inside.</string>\n    <string name=\"shadow_radius\">Shadow size <em>(zero size will disable shadow)</em></string>\n    <string name=\"behaviour_section\">Behaviour</string>\n    <string name=\"behaviour_link_keyboard_search_bar\">Link keyboard to search bar visibility</string>\n    <string name=\"behaviour_widget_after_launch\">Widget mode after launch</string>\n    <string name=\"behaviour_clear_search_after_launch\">Clear search bar after launch</string>\n    <string name=\"dm_empty_quick_list\">Show dock</string>\n    <string name=\"dm_search_quick_list\">Show dock</string>\n    <string name=\"dm_widget_quick_list\">Show dock</string>\n    <string name=\"dm_empty_fullscreen\">Fullscreen</string>\n    <string name=\"dm_search_fullscreen\">Fullscreen</string>\n    <string name=\"dm_search_fullscreen_summary\">Only when keyboard is closed</string>\n    <string name=\"dm_widget_fullscreen\">Fullscreen</string>\n    <string name=\"title_desktop_last\">Last Desktop mode</string>\n    <string name=\"title_desktop_mode_empty\">Desktop mode &#171;Empty&#187;</string>\n    <string name=\"title_desktop_mode_search\">Desktop mode &#171;Search&#187;</string>\n    <string name=\"title_desktop_mode_widget\">Desktop mode &#171;Widget&#187;</string>\n    <string name=\"search_bar_at_bottom\">Position at bottom</string>\n    <string name=\"search_bar_ripple_color\">Touch color</string>\n    <string name=\"search_bar_cursor_argb\">Cursor color</string>\n    <string name=\"search_bar_shadow_color\">Text shadow color</string>\n    <string name=\"search_bar_shadow_radius_dx_dy\">Shadow size and offset</string>\n    <string name=\"back_device_key\">Back button</string>\n    <string name=\"action_show_untagged\">Untagged</string>\n    <string name=\"dm_search_open_result\">Initial result list</string>\n    <string name=\"action_app_to_run\">App to run</string>\n    <string name=\"action_shortcut_to_run\">Shortcut to run</string>\n    <string name=\"action_entry_to_show\">Tags to show</string>\n    <string name=\"no_tags\">No tags found</string>\n    <string name=\"quick_list_animation\">Animate show/hide</string>\n    <string name=\"initial_desktop\">Initial desktop</string>\n    <string name=\"behaviour_summary\">Gestures and what-to-do-when</string>\n    <string name=\"title_behaviour\">Behaviour</string>\n    <string name=\"root_mode\">Root mode</string>\n    <string name=\"root_mode_summary\">Used to hibernate apps</string>\n    <string name=\"root_mode_error\">Could not gain root access</string>\n    <string name=\"behaviour_link_close_keyboard_back_button\">\\\"Back\\\" action when closing keyboard</string>\n    <string name=\"keyboard_suggestions\">Autocompletion/suggestions</string>\n    <string name=\"device_admin_explanation\">Used to lock the screen</string>\n    <string name=\"device_admin\">Device admin</string>\n    <string name=\"device_admin_disable\">Remove this permission?</string>\n    <string name=\"device_admin_required\">Device-admin access needed</string>\n    <string name=\"gesture_double_click\">Double tap screen</string>\n    <string name=\"loading_icon\">Loading icon</string>\n    <string name=\"keyboard_section\">Keyboard</string>\n    <string name=\"debug_ksh_touch\">Keyboard-scroll hider</string>\n    <string name=\"debug_ksh_touch_summary\">Show state as background color</string>\n    <string name=\"lwp_scroll_pages\">Pages</string>\n\n    <string name=\"lwp_page_count_vertical\">↕ pages</string>\n    <string name=\"lwp_page_count_horizontal\">⟷ pages</string>\n    <string name=\"lwp_pages_vertical_1\">center page</string>\n    <string name=\"lwp_pages_vertical_2\">2 pages (↑ and ↓)</string>\n    <string name=\"lwp_pages_horizontal_1\">center page</string>\n    <string name=\"lwp_pages_horizontal_2\">2 pages (← and →)</string>\n    <string name=\"reset_cached_app_icons\">Reset icon pack</string>\n    <string name=\"matrix_contacts\">Modulate contacts</string>\n    <string name=\"matrix_contacts_summary\">Modulate colors of contact icons</string>\n\n    <string name=\"tags_menu_section\">Tags Menu</string>\n    <string name=\"tags_menu_icons\">Show icons</string>\n    <string name=\"tags_menu_icon_size\">Icon size</string>\n    <string name=\"tags_menu_list\">Tags to show</string>\n    <string name=\"tags_menu_order\">Tags order</string>\n    <string name=\"tags_menu_untagged\">Show untagged</string>\n    <string name=\"result_popup_order\">Long touch category order</string>\n    <string name=\"result_popup_order_summary\">Category order for long touch popup items</string>\n    <string name=\"debug_favorites\">Favorites</string>\n    <string name=\"debug_favorites_summary\">Show entries from DB table `favorites`</string>\n\n    <string name=\"title_presets\">Color preset</string>\n    <string name=\"summary_presets\">Preset and generation of colors</string>\n    <string name=\"primary_color\">Primary color</string>\n    <string name=\"secondary_color\">Secondary color</string>\n    <string name=\"generate_theme_simple\">Simple theme</string>\n    <string name=\"generate_theme_simple_summary\">Use primary for background and secondary for text</string>\n    <string name=\"generate_theme_highlight\">Highlight theme</string>\n    <string name=\"generate_theme_highlight_summary\">Use primary for background and secondary for highlight</string>\n    <string name=\"result_first_at_bottom\">First at bottom</string>\n    <string name=\"result_right_to_left\">Right to left</string>\n    <string name=\"result_right_to_left_summary\">Layout grid row from right to left</string>\n    <string name=\"debug_provider_status\">Provider status</string>\n\n    <string name=\"exit_tags_manager_confirm\">Close Tags manager?</string>\n    <string name=\"exit_tags_manager_description\">You will lose all changes if you close the Tags manager.\\nAre you sure you want to continue?</string>\n    <string name=\"result_fading_edge\">Gradient</string>\n    <string name=\"result_fading_edge_summary\">Top and bottom edges fade out</string>\n    <string name=\"margin_vertical\">Vertical margin</string>\n    <string name=\"margin_horizontal\">Horizontal margin</string>\n    <string name=\"margin_offset\">Margin offset</string>\n    <string name=\"browse_add_icon\">Browse local images</string>\n    <string name=\"result_list_row_height\">List row height</string>\n    <string name=\"result_list_row_height_manual\">Custom list row height</string>\n    <string name=\"shortcut_dynamic_in_results\">Show dynamic in results</string>\n    <string name=\"shortcut_dynamic_in_results_summary\">Dynamic shortcuts will be considered as possible results when searching</string>\n    <string name=\"search_bar_layout\">Layout</string>\n    <string name=\"quick_list_position\">Position</string>\n    <string name=\"quick_list_columns\">Columns</string>\n    <string name=\"quick_list_columns_summary\">Number of items per row</string>\n    <string name=\"quick_list_rows\">Rows</string>\n    <string name=\"quick_list_rows_summary\">Maximum height will change accordingly</string>\n    <string name=\"quick_list_rtl\">Right to left</string>\n    <string name=\"quick_list_rtl_summary\">Layout items from right to left</string>\n    <string name=\"navigation_bar_section\">Navigation bar</string>\n    <string name=\"icons_section\">Icons</string>\n    <string name=\"options_section\">Options</string>\n    <string name=\"search_bar_animation\">Animate expand/collapse</string>\n    <string name=\"contact_button_set_default\">Make default action</string>\n    <string name=\"contact_button_reset_default\">Reset default action</string>\n    <string name=\"done_key_contact_action\">Done key contact action</string>\n\n    <string name=\"loading\">Loading…</string>\n\n    <!-- ACRA crash reporting -->\n    <string name=\"crash_title\">Crash</string>\n    <string name=\"crash_text\">The application crashed.\\nPlease send an EMail to report the crash to the developers.\\nThe EMail will contain information about your phone (brand, model, size, etc) as well as the preferences from the launcher.\\nYou can look over the data before you send and cancel if you so desire.</string>\n    <string name=\"crash_send_email\">Send EMail</string>\n\n    <plurals name=\"tag_entry_count\">\n        <item quantity=\"one\">one entry</item>\n        <item quantity=\"other\">%d entries</item>\n    </plurals>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <style name=\"PopupAnimationTop\" parent=\"android:Animation\">\n        <item name=\"android:windowEnterAnimation\">@anim/popup_in_top</item>\n        <item name=\"android:windowExitAnimation\">@anim/popup_out</item>\n    </style>\n\n    <style name=\"PopupAnimationBottom\" parent=\"android:Animation\">\n        <item name=\"android:windowEnterAnimation\">@anim/popup_in_bottom</item>\n        <item name=\"android:windowExitAnimation\">@anim/popup_out</item>\n    </style>\n\n    <style name=\"SeparatorHorizontal\">\n        <item name=\"android:layout_width\">match_parent</item>\n        <item name=\"android:layout_height\">@dimen/separator_thickness</item>\n        <item name=\"android:minHeight\">1dp</item>\n        <item name=\"android:layout_marginLeft\">5dp</item>\n        <item name=\"android:layout_marginRight\">5dp</item>\n        <item name=\"android:gravity\">center</item>\n        <item name=\"android:layout_gravity\">center_horizontal</item>\n        <item name=\"android:background\">?android:attr/listDivider</item>\n    </style>\n\n    <style name=\"palette_cell\">\n        <item name=\"android:layout_width\">0dp</item>\n        <item name=\"android:layout_height\">@dimen/mm2d_cc_palette_cell_height</item>\n        <item name=\"android:layout_weight\">1</item>\n    </style>\n\n    <style name=\"ItemGrid\">\n        <item name=\"android:background\">@color/colorPrimaryDark</item>\n        <item name=\"android:clipToPadding\">false</item>\n        <item name=\"android:drawSelectorOnTop\">false</item>\n        <item name=\"android:columnWidth\">@dimen/custom_icon_column_width</item>\n        <item name=\"android:gravity\">center</item>\n        <item name=\"android:horizontalSpacing\">1dp</item>\n        <item name=\"android:verticalSpacing\">@dimen/app_grid_vertical_spacing</item>\n        <item name=\"android:numColumns\">auto_fit</item>\n        <item name=\"android:paddingTop\">@dimen/app_grid_vertical_padding</item>\n        <item name=\"android:paddingBottom\">@dimen/app_grid_vertical_padding</item>\n        <item name=\"android:paddingLeft\">@dimen/app_grid_horizontal_padding</item>\n        <item name=\"android:paddingRight\">@dimen/app_grid_horizontal_padding</item>\n        <item name=\"android:stretchMode\">spacingWidth</item>\n    </style>\n\n    <style name=\"DialogPreferenceStyle\" parent=\"Preference.DialogPreference.Material\">\n        <item name=\"iconSpaceReserved\">false</item>\n    </style>\n\n    <style name=\"SwitchPreferenceStyle\" parent=\"Preference.SwitchPreference.Material\">\n        <item name=\"iconSpaceReserved\">false</item>\n        <item name=\"android:widgetLayout\">@layout/preference_switch</item>\n    </style>\n\n    <style name=\"ScreenPreferenceStyle\" parent=\"Preference.PreferenceScreen.Material\">\n        <item name=\"iconSpaceReserved\">false</item>\n    </style>\n\n    <style name=\"PreferenceStyle\" parent=\"Preference.Material\">\n        <item name=\"iconSpaceReserved\">false</item>\n    </style>\n\n    <style name=\"CategoryTitleTextAppearance\" parent=\"TextAppearance.AppCompat.Body2\">\n        <item name=\"android:textSize\">@dimen/abc_text_size_title_material</item>\n    </style>\n\n    <style name=\"CategoryPreferenceStyle\" parent=\"Preference.Category.Material\">\n        <item name=\"iconSpaceReserved\">true</item>\n    </style>\n\n    <style name=\"FragmentPreferenceStyle\" parent=\"PreferenceFragment.Material\">\n        <item name=\"android:divider\">?android:attr/listDivider</item>\n    </style>\n\n    <style name=\"TextInputLayoutStyle\" parent=\"Widget.MaterialComponents.TextInputLayout.OutlinedBox\">\n        <item name=\"materialThemeOverlay\">@style/TextInputLayoutOverlay</item>\n    </style>\n\n    <style name=\"TextInputLayoutOverlay\" parent=\"\">\n        <item name=\"colorPrimary\">?android:attr/textColorPrimary</item>      <!-- Activated color-->\n        <item name=\"colorOnSurface\">?android:attr/textColorSecondary</item>  <!-- Normal color-->\n        <item name=\"colorError\">?android:attr/textColorTertiary</item>       <!-- Error color-->\n\n        <item name=\"editTextStyle\">@style/Widget.MaterialComponents.TextInputEditText.OutlinedBox\n        </item>\n    </style>\n\n    <!-- This is the copy/cut/paste bar\n    see: https://stackoverflow.com/a/30997664/6761564 -->\n    <style name=\"ActionModeStyle\">\n        <item name=\"background\">@android:color/transparent</item>\n    </style>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/themes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <style name=\"BaseThemeLight\" parent=\"Theme.MaterialComponents.Light.NoActionBar\">\n        <item name=\"alertDialogTheme\">@style/NoTitleDialogTheme</item>\n        <item name=\"appSelectableItemBackground\">?android:attr/selectableItemBackground</item>\n        <item name=\"android:actionMenuTextColor\">@color/undefined</item>\n        <item name=\"android:windowTranslucentStatus\">true</item>\n        <item name=\"android:windowTranslucentNavigation\">true</item>\n        <item name=\"colorPrimary\">@color/colorPrimary</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n        <item name=\"colorAccent\">@color/colorAccent</item>\n        <item name=\"android:windowBackground\">@null</item>\n        <item name=\"actionModeStyle\">@style/ActionModeStyle</item>\n    </style>\n\n    <style name=\"AppThemeTransparent\" parent=\"BaseThemeLight\">\n        <item name=\"resultColor\">@android:color/white</item>\n        <item name=\"searchColor\">@android:color/white</item>\n        <item name=\"searchBackgroundColor\">@null</item>\n        <item name=\"listBackgroundColor\">@color/black_overlay</item>\n        <item name=\"dividerDrawable\">@null</item>\n\n        <item name=\"colorControlHighlight\">@color/colorAccent</item>\n        <item name=\"tabLayoutStyle\">@style/TabLayout</item>\n\n        <item name=\"android:listPreferredItemHeight\">40sp</item> <!-- 64dp -->\n        <item name=\"android:listPreferredItemHeightSmall\">20sp</item> <!-- 48dp -->\n        <item name=\"android:listPreferredItemHeightLarge\">64sp</item> <!-- 80dp -->\n\n        <item name=\"android:windowShowWallpaper\">true</item>\n        <item name=\"android:windowBackground\">@android:color/transparent</item>\n        <item name=\"android:textColor\">@color/primary_text_selector_darkbg</item>\n        <item name=\"android:textColorPrimary\">@color/primary_text_selector_darkbg</item>\n        <item name=\"android:textColorSecondary\">@color/secondary_text_selector_darkbg</item>\n        <item name=\"android:textColorTertiary\">@color/undefined</item>\n\n        <item name=\"searchBackgroundOutline\">@android:color/transparent</item>\n    </style>\n\n    <style name=\"GradientWindowTitle\">\n        <item name=\"android:scrollHorizontally\">true</item>\n        <item name=\"android:layout_marginTop\">0dp</item>\n        <item name=\"android:layout_marginStart\">0dp</item>\n        <item name=\"android:layout_marginLeft\">0dp</item>\n        <item name=\"android:layout_marginEnd\">0dp</item>\n        <item name=\"android:layout_marginRight\">0dp</item>\n        <item name=\"android:paddingStart\">?attr/dialogPreferredPadding</item>\n        <item name=\"android:paddingEnd\">?attr/dialogPreferredPadding</item>\n        <item name=\"android:paddingTop\">@dimen/abc_dialog_padding_top_material</item>\n        <item name=\"android:background\">@drawable/window_title_background</item>\n        <item name=\"android:textAppearance\">?android:attr/textAppearanceMedium</item>\n        <item name=\"android:textColor\">@color/colorAccent</item>\n        <item name=\"android:textStyle\">bold</item>\n    </style>\n\n    <!-- KeyboardDialogBuilder.setButtonBarBackground is only looking for a resource background -->\n    <style name=\"GradientButtonBar\" parent=\"@android:style/ButtonBar\">\n        <item name=\"android:background\">@drawable/button_bar_background</item>\n    </style>\n\n    <style name=\"GradientButtonBar.White\">\n        <item name=\"android:background\">@drawable/button_bar_background_light</item>\n    </style>\n\n    <style name=\"GradientButtonBar.DeepBlues\">\n        <item name=\"android:background\">@drawable/button_bar_background_deep_blues</item>\n    </style>\n\n    <style name=\"GradientButtonBar.Black\">\n        <item name=\"android:background\">@android:color/transparent</item>\n    </style>\n\n    <style name=\"GradientButtonBar.Default\">\n        <item name=\"android:background\">@drawable/button_bar_background_default</item>\n    </style>\n\n    <style name=\"GradientBorderlessButton\">\n        <item name=\"android:background\">?attr/selectableItemBackgroundBorderless</item>\n        <item name=\"android:textColor\">@color/accent_text_selector</item>\n        <item name=\"android:textAllCaps\">false</item>\n    </style>\n\n    <style name=\"GradientBorderlessButton.White\">\n        <item name=\"colorControlHighlight\">@android:color/black</item>\n        <item name=\"android:textColor\">@color/accent_text_selector_white</item>\n    </style>\n\n    <style name=\"GradientBorderlessButton.DeepBlues\">\n        <item name=\"colorControlHighlight\">@color/DeepBlues_4</item>\n        <item name=\"android:textColor\">@color/accent_text_selector_deep_blues</item>\n    </style>\n\n    <style name=\"GradientBorderlessButton.Black\">\n        <item name=\"colorControlHighlight\">@color/Black_accent</item>\n        <item name=\"android:textColor\">@color/accent_text_selector_black</item>\n    </style>\n\n    <style name=\"GradientBorderlessButton.Default\">\n        <item name=\"colorControlHighlight\">@color/Default_dark</item>\n        <item name=\"android:textColor\">@color/settings_primary_selector_default</item>\n    </style>\n\n    <style name=\"TitleDialogTheme\" parent=\"Theme.MaterialComponents.Dialog.Alert\">\n        <item name=\"android:windowNoTitle\">false</item>\n        <item name=\"android:colorBackground\">@android:color/transparent</item>\n        <item name=\"android:windowFrame\">@null</item>\n        <item name=\"android:windowTitleStyle\">@style/GradientWindowTitle</item>\n        <item name=\"android:windowBackground\">@drawable/dialog_background</item>\n        <item name=\"android:windowIsFloating\">true</item>\n        <item name=\"android:windowContentOverlay\">@null</item>\n        <item name=\"android:windowSoftInputMode\">stateUnspecified|adjustPan</item>\n        <item name=\"windowActionBar\">false</item>\n        <item name=\"windowActionModeOverlay\">true</item>\n\n        <item name=\"android:textColor\">@color/primary_text_selector_darkbg</item>\n        <item name=\"android:textColorPrimary\">@color/primary_text_selector_darkbg</item>\n        <item name=\"android:textColorSecondary\">@color/secondary_text_selector_darkbg</item>\n        <item name=\"android:textColorHint\">?android:attr/textColorSecondary</item>\n        <item name=\"android:textColorTertiary\">@color/undefined</item>\n        <item name=\"android:listDivider\">@drawable/list_separator_light</item>\n        <item name=\"textInputStyle\">@style/TextInputLayoutStyle</item>\n\n        <item name=\"resultColor\">@android:color/white</item>\n        <item name=\"searchColor\">@android:color/white</item>\n        <item name=\"searchBackgroundColor\">@null</item>\n        <item name=\"listBackgroundColor\">@color/black_overlay</item>\n        <item name=\"dividerDrawable\">@null</item>\n\n        <item name=\"colorControlNormal\">?android:attr/textColorSecondary</item>\n        <item name=\"colorControlHighlight\">@color/colorAccent</item>\n        <item name=\"colorControlActivated\">@color/colorAccent</item>\n        <item name=\"android:textColorPrimaryDisableOnly\">@color/undefined</item>\n\n        <item name=\"android:buttonBarStyle\">@style/GradientButtonBar</item>\n        <item name=\"buttonBarStyle\">@style/GradientButtonBar</item>\n        <item name=\"android:buttonBarButtonStyle\">@style/GradientBorderlessButton</item>\n        <item name=\"android:borderlessButtonStyle\">@style/GradientBorderlessButton</item>\n        <item name=\"buttonBarNeutralButtonStyle\">@style/GradientBorderlessButton</item>\n        <item name=\"buttonBarNegativeButtonStyle\">@style/GradientBorderlessButton</item>\n        <item name=\"buttonBarPositiveButtonStyle\">@style/GradientBorderlessButton</item>\n\n        <item name=\"autoCompleteTextViewStyle\">@style/AutoCompleteTextViewStyle</item>\n        <item name=\"tabLayoutStyle\">@style/TabLayout.Dialog</item>\n    </style>\n\n    <style name=\"AutoCompleteTextViewStyle\" parent=\"Widget.AppCompat.AutoCompleteTextView\">\n        <item name=\"android:popupBackground\">@drawable/dialog_background</item>\n    </style>\n\n    <style name=\"AutoCompleteTextViewStyle.White\">\n        <item name=\"android:popupBackground\">@drawable/dialog_background_light</item>\n    </style>\n\n    <style name=\"AutoCompleteTextViewStyle.Black\">\n        <item name=\"android:popupBackground\">@drawable/dialog_background_black</item>\n    </style>\n\n    <style name=\"AutoCompleteTextViewStyle.DeepBlues\">\n        <item name=\"android:popupBackground\">@drawable/tab_background_deep_blues</item>\n    </style>\n\n    <style name=\"AutoCompleteTextViewStyle.DarkBg\">\n        <item name=\"android:popupBackground\">@color/darkBackground</item>\n    </style>\n\n    <style name=\"AutoCompleteTextViewStyle.Default\">\n        <item name=\"android:popupBackground\">@color/Default_background</item>\n    </style>\n\n    <style name=\"NoTitleDialogTheme\" parent=\"TitleDialogTheme\">\n        <item name=\"android:windowNoTitle\">true</item>\n        <item name=\"windowNoTitle\">true</item>\n    </style>\n\n    <style name=\"ListPopupTitle\">\n        <item name=\"android:textSize\">@dimen/popup_title</item>\n        <item name=\"android:textColor\">@color/primary_text_selector_darkbg</item>\n    </style>\n\n    <style name=\"ListPopupItem\">\n        <item name=\"android:textSize\">@dimen/result_small_size</item>\n        <item name=\"android:textColor\">@color/secondary_text_selector_darkbg</item>\n        <item name=\"android:paddingStart\">?android:attr/listPreferredItemPaddingStart</item>\n        <item name=\"android:paddingEnd\">?android:attr/listPreferredItemPaddingEnd</item>\n        <item name=\"android:paddingLeft\">?android:attr/listPreferredItemPaddingLeft</item>\n        <item name=\"android:paddingRight\">?android:attr/listPreferredItemPaddingRight</item>\n        <item name=\"android:gravity\">center_vertical</item>\n        <item name=\"android:layout_width\">match_parent</item>\n        <item name=\"android:layout_height\">?android:attr/listPreferredItemHeight</item>\n    </style>\n\n    <style name=\"ListPopupTheme\" parent=\"Widget.MaterialComponents.PopupMenu.ListPopupWindow\">\n        <item name=\"android:colorBackground\">@android:color/transparent</item>\n        <item name=\"android:listDivider\">@drawable/list_separator_light</item>\n        <item name=\"android:listPreferredItemPaddingLeft\">@dimen/popup_padding_horizontal</item>\n        <item name=\"android:listPreferredItemPaddingRight\">@dimen/popup_padding_horizontal</item>\n    </style>\n\n    <style name=\"SettingsDialogTitle\" parent=\"GradientWindowTitle\">\n        <item name=\"android:textAppearance\">?android:attr/textAppearanceLarge</item>\n        <item name=\"android:textColor\">@color/colorAccent</item>\n    </style>\n\n    <style name=\"SettingsDialogTitle.White\">\n        <item name=\"android:textColor\">@android:color/black</item>\n        <item name=\"android:background\">@drawable/window_title_background_light</item>\n    </style>\n\n    <style name=\"SettingsDialogTitle.DeepBlues\">\n        <item name=\"android:textColor\">@color/DeepBlues_4</item>\n        <item name=\"android:background\">@drawable/window_title_background_deep_blues</item>\n    </style>\n\n    <style name=\"SettingsDialogTitle.Black\">\n        <item name=\"android:background\">@null</item>\n        <item name=\"android:textColor\">@color/Black_accent</item>\n    </style>\n\n    <style name=\"SettingsDialogTitle.Default\">\n        <item name=\"android:background\">@drawable/window_title_background_default</item>\n        <item name=\"android:textColor\">@color/settings_primary_selector_default</item>\n    </style>\n\n    <style name=\"SettingsDialogTheme\" parent=\"NoTitleDialogTheme\">\n        <item name=\"android:background\">@null</item>\n        <item name=\"background\">@null</item>\n        <item name=\"android:textColor\">@color/primary_text_selector_darkbg</item>\n        <item name=\"android:textColorPrimary\">@color/primary_text_selector_darkbg</item>\n        <item name=\"android:textColorSecondary\">@color/secondary_text_selector_darkbg</item>\n        <item name=\"android:listDivider\">@drawable/list_separator_light</item>\n        <item name=\"android:windowTitleStyle\">@style/SettingsDialogTitle</item>\n\n        <item name=\"tabIndicatorColor\">@color/colorAccent</item>\n        <item name=\"tabBackground\">@color/black_overlay</item>\n        <item name=\"tabSelectedTextColor\">@color/colorAccent</item>\n\n        <item name=\"android:textColorHighlight\">?android:attr/textColorSecondary</item>\n        <item name=\"autoCompleteTextViewStyle\">@style/AutoCompleteTextViewStyle</item>\n    </style>\n\n    <style name=\"SettingsDialogTheme.White\">\n        <item name=\"android:windowBackground\">@drawable/dialog_background_light</item>\n        <item name=\"android:textColor\">@color/settings_primary_selector_lightbg</item>\n        <item name=\"android:textColorPrimary\">@color/settings_primary_selector_lightbg</item>\n        <item name=\"android:textColorSecondary\">@color/settings_secondary_selector_lightbg</item>\n        <item name=\"android:listDivider\">@drawable/list_separator_dark</item>\n        <item name=\"android:windowTitleStyle\">@style/SettingsDialogTitle.White</item>\n\n        <item name=\"textColorAlertDialogListItem\">@color/primary_text_selector_lightbg</item>\n\n        <item name=\"colorPrimary\">@color/primary_text_selector_lightbg</item>\n        <item name=\"colorPrimaryDark\">@android:color/black</item>\n        <item name=\"colorControlNormal\">@android:color/black</item>\n        <item name=\"colorControlHighlight\">@android:color/black</item>\n        <item name=\"colorControlActivated\">@color/settings_secondary_selector_lightbg</item>\n\n        <item name=\"android:buttonBarStyle\">@style/GradientButtonBar.White</item>\n        <item name=\"android:buttonBarButtonStyle\">@style/GradientBorderlessButton.White</item>\n        <item name=\"android:borderlessButtonStyle\">@style/GradientBorderlessButton.White</item>\n        <item name=\"buttonBarNeutralButtonStyle\">@style/GradientBorderlessButton.White</item>\n        <item name=\"buttonBarNegativeButtonStyle\">@style/GradientBorderlessButton.White</item>\n        <item name=\"buttonBarPositiveButtonStyle\">@style/GradientBorderlessButton.White</item>\n\n        <item name=\"autoCompleteTextViewStyle\">@style/AutoCompleteTextViewStyle.White</item>\n    </style>\n\n    <style name=\"SettingsDialogTheme.Black\">\n        <item name=\"android:windowBackground\">@drawable/dialog_background_black</item>\n        <item name=\"android:textColor\">@color/settings_primary_selector_black</item>\n        <item name=\"android:textColorPrimary\">@color/settings_primary_selector_black</item>\n        <item name=\"android:textColorSecondary\">@color/settings_secondary_selector_black</item>\n        <item name=\"android:listDivider\">@drawable/list_separator_dark</item>\n        <item name=\"android:windowTitleStyle\">@style/SettingsDialogTitle.Black</item>\n\n        <item name=\"textColorAlertDialogListItem\">@color/settings_primary_selector_black</item>\n\n        <item name=\"colorPrimary\">@color/Black_selector_primary_disable</item>\n        <item name=\"colorPrimaryDark\">@color/Black_selector_primary</item>\n        <item name=\"colorControlNormal\">@color/Black_selector_primary_disable</item>\n        <item name=\"colorControlHighlight\">@color/Black_selector_primary</item>\n        <item name=\"colorControlActivated\">@color/Black_accent</item>\n\n        <item name=\"android:buttonBarStyle\">@style/GradientButtonBar.Black</item>\n        <item name=\"android:buttonBarButtonStyle\">@style/GradientBorderlessButton.Black</item>\n        <item name=\"android:borderlessButtonStyle\">@style/GradientBorderlessButton.Black</item>\n        <item name=\"buttonBarNeutralButtonStyle\">@style/GradientBorderlessButton.Black</item>\n        <item name=\"buttonBarNegativeButtonStyle\">@style/GradientBorderlessButton.Black</item>\n        <item name=\"buttonBarPositiveButtonStyle\">@style/GradientBorderlessButton.Black</item>\n\n        <item name=\"autoCompleteTextViewStyle\">@style/AutoCompleteTextViewStyle.Black</item>\n    </style>\n\n    <style name=\"SettingsDialogTheme.DeepBlues\">\n        <item name=\"android:windowBackground\">@drawable/dialog_background_deep_blues</item>\n        <item name=\"android:textColor\">@color/settings_primary_selector_deep_blues</item>\n        <item name=\"android:textColorPrimary\">@color/settings_primary_selector_deep_blues</item>\n        <item name=\"android:textColorSecondary\">@color/settings_secondary_selector_deep_blues</item>\n        <item name=\"android:listDivider\">@drawable/list_separator_deep_blues</item>\n        <item name=\"android:windowTitleStyle\">@style/SettingsDialogTitle.DeepBlues</item>\n\n        <item name=\"textColorAlertDialogListItem\">@color/settings_primary_selector_deep_blues</item>\n\n        <item name=\"colorPrimary\">@color/DeepBlues_selector_primary</item>\n        <item name=\"colorPrimaryDark\">@color/DeepBlues_2</item>\n        <item name=\"colorControlNormal\">@color/DeepBlues_3</item>\n        <item name=\"colorControlHighlight\">@color/DeepBlues_selector_primary</item>\n        <item name=\"colorControlActivated\">@color/DeepBlues_selector_primary</item>\n\n        <item name=\"android:buttonBarStyle\">@style/GradientButtonBar.DeepBlues</item>\n        <item name=\"android:buttonBarButtonStyle\">@style/GradientBorderlessButton.DeepBlues</item>\n        <item name=\"android:borderlessButtonStyle\">@style/GradientBorderlessButton.DeepBlues</item>\n        <item name=\"buttonBarNeutralButtonStyle\">@style/GradientBorderlessButton.DeepBlues</item>\n        <item name=\"buttonBarNegativeButtonStyle\">@style/GradientBorderlessButton.DeepBlues</item>\n        <item name=\"buttonBarPositiveButtonStyle\">@style/GradientBorderlessButton.DeepBlues</item>\n\n        <item name=\"autoCompleteTextViewStyle\">@style/AutoCompleteTextViewStyle.DeepBlues</item>\n    </style>\n\n    <style name=\"SettingsDialogTheme.DarkBg\">\n        <item name=\"autoCompleteTextViewStyle\">@style/AutoCompleteTextViewStyle.DarkBg</item>\n    </style>\n\n    <style name=\"SettingsDialogTheme.Default\">\n        <item name=\"android:windowBackground\">@drawable/dialog_background_default</item>\n        <item name=\"android:textColor\">@color/settings_primary_selector_default</item>\n        <item name=\"android:textColorPrimary\">@color/settings_primary_selector_default</item>\n        <item name=\"android:textColorSecondary\">@color/settings_secondary_selector_default</item>\n        <item name=\"android:listDivider\">@drawable/list_separator_default</item>\n        <item name=\"android:windowTitleStyle\">@style/SettingsDialogTitle.Default</item>\n\n        <item name=\"tabIndicatorColor\">@color/Default_text</item>\n        <item name=\"tabBackground\">@drawable/tab_background_default</item>\n        <item name=\"tabSelectedTextColor\">@color/settings_primary_selector_default</item>\n\n        <item name=\"colorPrimary\">@color/settings_primary_selector_default</item>\n        <item name=\"colorPrimaryDark\">@color/Default_dark</item>\n        <item name=\"colorControlNormal\">@color/Default_text</item>\n        <item name=\"colorControlHighlight\">@color/Default_light</item>\n        <item name=\"colorControlActivated\">@color/settings_secondary_selector_default</item>\n\n        <item name=\"android:buttonBarStyle\">@style/GradientButtonBar.Default</item>\n        <item name=\"android:buttonBarButtonStyle\">@style/GradientBorderlessButton.Default</item>\n        <item name=\"android:borderlessButtonStyle\">@style/GradientBorderlessButton.Default</item>\n        <item name=\"buttonBarNeutralButtonStyle\">@style/GradientBorderlessButton.Default</item>\n        <item name=\"buttonBarNegativeButtonStyle\">@style/GradientBorderlessButton.Default</item>\n        <item name=\"buttonBarPositiveButtonStyle\">@style/GradientBorderlessButton.Default</item>\n\n        <item name=\"autoCompleteTextViewStyle\">@style/AutoCompleteTextViewStyle.Default</item>\n    </style>\n\n    <style name=\"PreferenceTheme\" parent=\"PreferenceThemeOverlay\">\n        <item name=\"dialogPreferenceStyle\">@style/DialogPreferenceStyle</item>\n        <item name=\"preferenceCategoryStyle\">@style/CategoryPreferenceStyle</item>\n        <item name=\"preferenceScreenStyle\">@style/ScreenPreferenceStyle</item>\n        <item name=\"switchPreferenceStyle\">@style/SwitchPreferenceStyle</item>\n        <item name=\"preferenceStyle\">@style/PreferenceStyle</item>\n        <item name=\"preferenceFragmentCompatStyle\">@style/FragmentPreferenceStyle</item>\n        <item name=\"preferenceCategoryTitleTextAppearance\">@style/CategoryTitleTextAppearance</item>\n    </style>\n\n    <style name=\"OptionsMenu\" parent=\"Widget.MaterialComponents.PopupMenu.Overflow\">\n        <item name=\"android:popupBackground\">@drawable/dialog_background</item>\n    </style>\n\n    <style name=\"OptionsMenu.White\">\n        <item name=\"android:popupBackground\">@color/white_overlay</item>\n    </style>\n\n    <style name=\"OptionsMenu.Black\">\n        <item name=\"android:popupBackground\">@drawable/dialog_background_black</item>\n    </style>\n\n    <style name=\"OptionsMenu.Dark\">\n        <item name=\"android:popupBackground\">@drawable/popup_background</item>\n    </style>\n\n    <style name=\"OptionsMenu.Default\">\n        <item name=\"android:popupBackground\">@color/Default_dark</item>\n    </style>\n\n    <style name=\"OptionsMenu.DeepBlues\">\n        <item name=\"android:popupBackground\">@color/DeepBlues_1</item>\n    </style>\n\n    <style name=\"OptionsMenuTextAppearance.DeepBlues\" parent=\"TextAppearance.MaterialComponents.Body1\">\n        <item name=\"android:textColor\">@color/DeepBlues_4</item>\n    </style>\n\n    <style name=\"TabLayout\" parent=\"Widget.Design.TabLayout\">\n        <item name=\"tabIndicatorColor\">?attr/colorAccent</item>\n        <item name=\"tabIndicatorGravity\">bottom</item>\n        <item name=\"tabPaddingStart\">6sp</item>\n        <item name=\"tabPaddingEnd\">6sp</item>\n        <item name=\"tabTextAppearance\">@android:style/TextAppearance.Widget.TabWidget</item>\n        <item name=\"tabTextColor\">@null</item>\n        <item name=\"tabRippleColor\">?attr/colorControlHighlight</item>\n        <item name=\"tabUnboundedRipple\">false</item>\n        <item name=\"tabGravity\">fill</item>\n        <item name=\"tabMode\">fixed</item>\n    </style>\n\n    <!-- This is the tab layout of the icon chooser and dock editor -->\n    <style name=\"TabLayout.Dialog\">\n        <item name=\"tabGravity\">center</item>\n        <item name=\"tabMode\">scrollable</item>\n        <item name=\"tabSelectedTextColor\">?attr/colorControlHighlight</item>\n        <item name=\"tabTextColor\">?android:attr/textColorPrimary</item>\n        <item name=\"tabIndicatorColor\">?attr/colorControlHighlight</item>\n        <item name=\"tabBackground\">?attr/tabBackground</item>\n        <item name=\"tabTextAppearance\">@android:style/TextAppearance.Widget.TabWidget</item>\n    </style>\n\n    <!--\n        Settings\n    -->\n    <style name=\"SettingsTheme\" parent=\"AppThemeTransparent\">\n        <item name=\"android:background\">@null</item>\n        <item name=\"background\">@null</item>\n        <item name=\"windowActionBar\">true</item>\n        <item name=\"windowNoTitle\">false</item>\n        <item name=\"android:windowBackground\">@android:color/black</item>\n        <item name=\"alertDialogTheme\">@style/SettingsDialogTheme</item>\n        <item name=\"preferenceTheme\">@style/PreferenceTheme</item>\n        <item name=\"android:textColor\">@color/settings_primary_selector_darkbg</item>\n        <item name=\"android:textColorHint\">?android:attr/textColorSecondary</item>\n\n        <item name=\"tabIndicatorColor\">@color/colorAccent</item>\n        <item name=\"tabBackground\">@color/black_overlay</item>\n        <item name=\"tabSelectedTextColor\">@color/colorAccent</item>\n        <item name=\"tabLayoutStyle\">@style/TabLayout.Dialog</item>\n\n        <item name=\"actionOverflowMenuStyle\">@style/OptionsMenu</item>\n        <item name=\"android:itemBackground\">@drawable/dialog_background</item>\n\n        <item name=\"textInputStyle\">@style/TextInputLayoutStyle</item>\n        <item name=\"editTextStyle\">@style/TextInputLayoutStyle</item>\n\n        <item name=\"android:listPreferredItemHeight\">64dp</item>\n        <item name=\"android:listPreferredItemHeightSmall\">48dp</item>\n        <item name=\"android:listPreferredItemHeightLarge\">80dp</item>\n    </style>\n\n    <!--\n        Settings - White monochrome\n    -->\n    <style name=\"SettingsTheme.White\">\n        <item name=\"android:windowBackground\">@android:color/white</item>\n        <item name=\"alertDialogTheme\">@style/SettingsDialogTheme.White</item>\n        <item name=\"android:textColor\">@color/settings_primary_selector_lightbg</item>\n        <item name=\"android:textColorPrimary\">@color/settings_primary_selector_lightbg</item>\n        <item name=\"android:textColorSecondary\">@color/settings_secondary_selector_lightbg</item>\n\n        <item name=\"tabIndicatorColor\">@android:color/black</item>\n        <item name=\"tabBackground\">@drawable/tab_background_light</item>\n        <item name=\"tabSelectedTextColor\">@color/settings_primary_selector_lightbg</item>\n\n        <item name=\"colorPrimary\">@android:color/black</item>\n        <item name=\"colorControlHighlight\">@android:color/black</item>\n        <item name=\"colorControlNormal\">@android:color/black</item>\n        <item name=\"colorAccent\">@android:color/black</item>\n        <item name=\"actionOverflowMenuStyle\">@style/OptionsMenu.White</item>\n        <item name=\"android:itemBackground\">@drawable/tab_background_light</item>\n    </style>\n\n    <!--\n        Settings - Black monochrome\n    -->\n    <style name=\"SettingsTheme.Black\">\n        <item name=\"android:windowBackground\">@android:color/black</item>\n        <item name=\"alertDialogTheme\">@style/SettingsDialogTheme.Black</item>\n        <item name=\"android:textColor\">@color/settings_primary_selector_black</item>\n        <item name=\"android:textColorPrimary\">@color/settings_primary_selector_black</item>\n        <item name=\"android:textColorSecondary\">@color/settings_secondary_selector_black</item>\n\n        <item name=\"tabIndicatorColor\">@color/Black_selector_primary_disable</item>\n        <item name=\"tabBackground\">@drawable/tab_background_black</item>\n        <item name=\"tabSelectedTextColor\">@color/settings_primary_selector_black</item>\n\n        <item name=\"colorPrimary\">@color/Black_selector_primary</item>\n        <item name=\"colorControlHighlight\">@color/Black_accent</item>\n        <item name=\"colorControlNormal\">@color/Black_selector_primary</item>\n        <item name=\"colorAccent\">@color/Black_accent</item>\n        <item name=\"actionOverflowMenuStyle\">@style/OptionsMenu.Black</item>\n        <item name=\"android:itemBackground\">@drawable/dialog_background_black</item>\n    </style>\n\n    <!--\n        Settings - DeepBlues\n    -->\n    <style name=\"SettingsTheme.DeepBlues\">\n        <item name=\"android:windowBackground\">@color/DeepBlues_1</item>\n        <item name=\"alertDialogTheme\">@style/SettingsDialogTheme.DeepBlues</item>\n        <item name=\"android:textColor\">@color/settings_primary_selector_deep_blues</item>\n        <item name=\"android:textColorPrimary\">@color/settings_primary_selector_deep_blues</item>\n        <item name=\"android:textColorSecondary\">@color/settings_secondary_selector_deep_blues</item>\n\n        <item name=\"tabIndicatorColor\">@color/DeepBlues_4</item>\n        <item name=\"tabBackground\">@drawable/tab_background_deep_blues</item>\n        <item name=\"tabSelectedTextColor\">@color/settings_primary_selector_deep_blues</item>\n\n        <item name=\"colorPrimary\">@color/DeepBlues_selector_primary</item>\n        <item name=\"colorControlHighlight\">@color/DeepBlues_3</item>\n        <item name=\"colorControlNormal\">@color/DeepBlues_selector_primary</item>\n        <item name=\"colorAccent\">@color/DeepBlues_4</item>\n        <item name=\"actionOverflowMenuStyle\">@style/OptionsMenu.DeepBlues</item>\n        <item name=\"android:itemBackground\">@drawable/tab_background_deep_blues</item>\n        <item name=\"android:itemTextAppearance\">@style/OptionsMenuTextAppearance.DeepBlues</item>\n    </style>\n\n    <!--\n        Settings - Dark\n    -->\n    <style name=\"SettingsTheme.DarkBg\">\n        <item name=\"android:windowBackground\">@color/darkBackground</item>\n        <item name=\"alertDialogTheme\">@style/SettingsDialogTheme.DarkBg</item>\n        <item name=\"actionOverflowMenuStyle\">@style/OptionsMenu.Dark</item>\n        <item name=\"android:itemBackground\">@null</item>\n    </style>\n\n    <!--\n        Settings - Default\n    -->\n    <style name=\"SettingsTheme.Default\">\n        <item name=\"android:windowBackground\">@color/Default_background</item>\n        <item name=\"alertDialogTheme\">@style/SettingsDialogTheme.Default</item>\n        <item name=\"actionOverflowMenuStyle\">@style/OptionsMenu.Default</item>\n        <item name=\"android:itemBackground\">@drawable/tab_background_default</item>\n        <item name=\"android:listDivider\">@drawable/list_separator_default</item>\n\n        <item name=\"android:textColor\">@color/settings_primary_selector_default</item>\n        <item name=\"android:textColorPrimary\">@color/settings_primary_selector_default</item>\n        <item name=\"android:textColorSecondary\">@color/settings_secondary_selector_default</item>\n\n        <item name=\"tabIndicatorColor\">@color/Default_text</item>\n        <item name=\"tabBackground\">@drawable/tab_background_default</item>\n        <item name=\"tabSelectedTextColor\">@color/settings_primary_selector_default</item>\n\n        <item name=\"colorPrimary\">@color/Default_selector_secondary</item>\n        <item name=\"colorControlHighlight\">@color/Default_light</item>\n        <item name=\"colorControlNormal\">@color/Default_selector_primary</item>\n        <item name=\"colorAccent\">@color/Default_light</item>\n    </style>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-de/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"app_name\">TinyBit Launcher</string>\n    <string name=\"app_name_debug\">TinyBit debug Launcher</string>\n    <string name=\"hint_ui_search\">Suche in Apps, Kontakten, …</string>\n    <string name=\"menu_exclude\">Ausschließen…</string>\n    <string name=\"unable_to_initialize_shortcuts\">Zugriff auf Verknüpfungen nicht möglich. Ist TinyBit ihr Standard-Launcher\\?</string>\n    <string name=\"stub_app_tag\">App-Tag-Liste</string>\n    <string name=\"stub_application\">App Name</string>\n    <string name=\"permission_denied\">Erteilen Sie zuerst diese Berechtigung</string>\n    <string name=\"user_interface_summary\">Themen, Transparenz und Farben</string>\n    <string name=\"privacy_policy\">Datenschutz-Bestimmungen</string>\n    <string name=\"menu_popup_title_settings\">Einstellungen</string>\n    <string name=\"cd_item_contact_call\">Anruf</string>\n    <string name=\"title_select_size\">Ziehen sie den Schieberegler, um die gewünschte Größe auszuwählen</string>\n    <string name=\"menu_shortcut_rename\">Verknüpfung umbenennen</string>\n    <string name=\"hint_rename_tag\">Wähle einen anderen Namen für diesen Tag</string>\n    <string name=\"ui_item_search\">In %1$s nach \\\"%2$s\\\" suchen\\?</string>\n    <string name=\"ui_item_visit\">\\\"%1$s\\\" besuchen</string>\n    <string name=\"copy_confirmation\">\\\"%s\\\" in die Zwischenablage kopiert</string>\n    <string name=\"removed_item\">%s entfernt</string>\n    <string name=\"toast_hibernate_completed\">%s in den Ruhezustand versetzt, zum Aufwachen neu starten</string>\n    <string name=\"toast_hibernate_error\">[FEHLER] %s nicht im Ruhezustand</string>\n    <string name=\"cant_pin_shortcut\">Verknüpfung konnte nicht hinzugefügt werden. Ist TinyBit ihr Standard-Launcher\\?</string>\n    <string name=\"menu_hide\">Ausblenden</string>\n    <string name=\"menu_quick_list_add\">Zum Dock hinzufügen</string>\n    <string name=\"menu_quick_list_remove\">Vom Dock entfernen</string>\n    <string name=\"menu_remove_history\">Aus der History löschen</string>\n    <string name=\"menu_show\">Anzeigen</string>\n    <string name=\"menu_remove_shortcut\">Lösen</string>\n    <string name=\"menu_tags_add\">Tags hinzufügen</string>\n    <string name=\"menu_tags_edit\">Stichworte bearbeiten</string>\n    <string name=\"menu_app_details\">App-Information</string>\n    <string name=\"menu_app_store\">Im Store ansehen</string>\n    <string name=\"menu_app_uninstall\">Deinstallieren</string>\n    <string name=\"menu_app_hibernate\">Ruhezustand</string>\n    <string name=\"menu_exclude_history\">aus der History</string>\n    <string name=\"menu_exclude_kiss\">von TinyBit</string>\n    <string name=\"application_not_found\">App „%s“ kann nicht gestartet werden</string>\n    <string name=\"entry_not_found\">\\\"%s\\\" konnte nicht gefunden werden</string>\n    <string name=\"change_wallpaper\">Hintergrund ändern</string>\n    <string name=\"menu_widget_title\">Widget-Menü</string>\n    <string name=\"menu_widget_add\">Widget hinzufügen…</string>\n    <string name=\"menu_widget_remove\">Widget entfernen…</string>\n    <string name=\"menu_widget_configure\">Widget anpassen…</string>\n    <string name=\"menu_popup_title\">Hauptmenü</string>\n    <string name=\"menu_popup_android_settings\">Geräteeinstellungen</string>\n    <string name=\"menu_popup_launcher_settings\">TinyBit-Einstellungen</string>\n    <string name=\"menu_popup_tags_manager\">Tag-Manager</string>\n    <string name=\"menu_popup_tags_menu\">Tags-Menü</string>\n    <string name=\"rate_the_app\">Bewerte die App</string>\n    <string name=\"app_version\">Version %s</string>\n    <string name=\"app_version_summary\">%1$s - %2$s</string>\n    <string name=\"title_ui\">Benutzeroberfläche</string>\n    <string name=\"notification_bar_section\">Benachrichtigungsleiste</string>\n    <string name=\"color_and_opacity\">Farbe und Deckkraft</string>\n    <string name=\"gradient\">Gradient</string>\n    <string name=\"black_notification_icons\">Dunkle Symbole</string>\n    <string name=\"cd_main_menu\">Menü</string>\n    <string name=\"cd_main_clear\">Suchleiste leeren</string>\n    <string name=\"cd_show_all_apps\">Alle Anwendungen anzeigen</string>\n    <string name=\"cd_item_contact_message\">Nachricht</string>\n    <string name=\"cd_item_contact_open\">Offen</string>\n    <string name=\"cd_add_tag\">Tag hinzufügen</string>\n    <string name=\"cd_undo_remove_tag\">Tag entfernen rückgängig machen</string>\n    <string name=\"cd_remove_tag\">Tag entfernen</string>\n    <string name=\"cd_rename_tag\">Tag umbenennen</string>\n    <string name=\"cd_icon_tag\">Tag-Symbol festlegen</string>\n    <string name=\"title_select_alpha\">Ziehen sie den Schieberegler, um die gewünschte Deckkraft auszuwählen</string>\n    <string name=\"search_bar_section\">Suchleiste</string>\n    <string name=\"search_bar_text_size\">Textgröße</string>\n    <string name=\"search_bar_height\">Säulehöhe</string>\n    <string name=\"result_list_section\">Ergebnisliste</string>\n    <string name=\"icons_pack\">Packung wählen</string>\n    <string name=\"icons_pack_default_name\">Systemsymbole</string>\n    <string name=\"value\">Wert %d</string>\n    <string name=\"value_float\">Wert %.2f</string>\n    <string name=\"value_float_xy\">horizontal %1$.2f | vertikal %2$.2f</string>\n    <string name=\"size\">&lt;%d&gt;</string>\n    <string name=\"size_float\">&lt;%.1f&gt;</string>\n    <string name=\"shadow_preview\">&lt;%3$.1f&gt;\n\\n&lt;%1$.1f×%2$.1f&gt;</string>\n    <string name=\"menu_app_rename\">App umbenennen</string>\n    <string name=\"menu_action_rename\">Umbenennen</string>\n    <string name=\"menu_action_delete\">Löschen</string>\n    <string name=\"menu_custom_icon\">Icon ändern</string>\n    <string name=\"title_app_rename\">Wähle einen benutzerdefinierten Namen für diese App</string>\n    <string name=\"title_rename_tag\">Ersetze überall wo dieser Tag erscheint</string>\n    <string name=\"title_rename_search_engine\">Wähle einen anderen Namen für die Suchmaschine</string>\n    <string name=\"title_rename_search_hint\">Wähle einen anderen Namen für den Suchhinweis</string>\n    <string name=\"title_edit_url_search_engine\">Wähle eine andere URL für die Suchmaschine</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-de-v26/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"adaptive_shape_name\">Adaptive Symbolform</string>\n    <string name=\"force_adaptive\">Adaptive Symbole erzwingen</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-fr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"app_name_debug\">Lanceur de débogage TinyBit</string>\n    <string name=\"app_name\">Lanceur TinyBit</string>\n    <string name=\"hint_ui_search\">Recherche dans les applications, les contacts…</string>\n    <string name=\"ui_item_visit\">Visitez \\\"%1$s\\\"</string>\n    <string name=\"copy_confirmation\">Copié \\\"%s\\\" ; dans le presse-papiers</string>\n    <string name=\"removed_item\">Supprimé %s</string>\n    <string name=\"toast_hibernate_completed\">%s hiberné, relancer pour réveiller</string>\n    <string name=\"toast_hibernate_error\">[ERREUR] %s non hiberné</string>\n    <string name=\"cant_pin_shortcut\">Impossible d\\'ajouter un raccourci. TinyBit est-il votre lanceur par défaut \\?</string>\n    <string name=\"menu_quick_list_add\">Ajouter au dock</string>\n    <string name=\"menu_quick_list_remove\">Retirer du dock</string>\n    <string name=\"menu_remove_history\">Retirer de l\\'histoire</string>\n    <string name=\"stub_application\">Nom de l\\'application</string>\n    <string name=\"stub_app_tag\">liste de tags d\\'applications</string>\n    <string name=\"permission_denied\">Accordez d\\'abord cette permission</string>\n    <string name=\"menu_exclude\">Exclure…</string>\n    <string name=\"menu_show\">Montrer</string>\n    <string name=\"menu_remove_shortcut\">Déplacer</string>\n    <string name=\"menu_tags_edit\">Modifier les tags</string>\n    <string name=\"unable_to_initialize_shortcuts\">Impossible d\\'accéder aux raccourcis. TinyBit est-il votre lanceur par défaut \\?</string>\n    <string name=\"menu_hide\">Masquer</string>\n    <string name=\"ui_item_search\">Rechercher dans %1$s pour \\\"%2$s\\\" \\?</string>\n    <string name=\"menu_tags_add\">Ajouter des tags</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-fr-v26/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources></resources>"
  },
  {
    "path": "app/src/main/res/values-h400dp/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <dimen name=\"icon_size\">48dp</dimen>\n    <dimen name=\"icon_preview_size\">32dp</dimen>\n    <dimen name=\"color_preview_size\">24dp</dimen>\n    <dimen name=\"color_preview_radius\">8dp</dimen>\n\n    <dimen name=\"result_margin_horizontal\">10dp</dimen>\n    <dimen name=\"result_margin_vertical\">5dp</dimen>\n    <dimen name=\"result_corner_radius\">24dp</dimen>\n\n    <dimen name=\"result_title_size\">18sp</dimen>\n    <dimen name=\"result_small_size\">14sp</dimen>\n\n    <dimen name=\"bar_height\">25dp</dimen>\n    <dimen name=\"large_bar_height\">40dp</dimen>\n\n    <dimen name=\"mm2d_cc_panel_height\">360dp</dimen>\n    <dimen name=\"mm2d_cc_palette_cell_height\">48dp</dimen>\n    <dimen name=\"mm2d_cc_panel_margin\">8dp</dimen>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-in/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"app_name\">Peluncur TinyBit</string>\n    <string name=\"app_name_debug\">Peluncur debug TinyBit</string>\n    <string name=\"hint_ui_search\">Telusuri aplikasi, kontak, …</string>\n    <string name=\"removed_item\">%s dihapus</string>\n    <string name=\"unable_to_initialize_shortcuts\">Tidak dapat mengakses pintasan. Apa TinyBit peluncur utama Anda\\?</string>\n    <string name=\"cant_pin_shortcut\">Tidak dapat menambahkan pintasan. Apa TinyBit peluncur utama Anda\\?</string>\n    <string name=\"menu_quick_list_remove\">Keluarkan dari dok</string>\n    <string name=\"menu_remove_history\">Hapus dari riwayat</string>\n    <string name=\"menu_app_uninstall\">Copot pemasangan</string>\n    <string name=\"application_not_found\">Tidak dapat meluncurkan aplikasi \\\"%s\\\"</string>\n    <string name=\"menu_widget_add\">Tambahkan Widget…</string>\n    <string name=\"menu_widget_remove\">Hapus Widget…</string>\n    <string name=\"menu_widget_configure\">Kustomisasi Widget…</string>\n    <string name=\"menu_popup_title\">Menu utama</string>\n    <string name=\"menu_popup_title_settings\">Pengaturan</string>\n    <string name=\"menu_popup_android_settings\">Pengaturan perangkat</string>\n    <string name=\"menu_popup_launcher_settings\">Pengaturan TinyBit</string>\n    <string name=\"rate_the_app\">Nilai aplikasi</string>\n    <string name=\"gradient\">Gradien</string>\n    <string name=\"black_notification_icons\">Ikon gelap</string>\n    <string name=\"cd_main_menu\">Menu</string>\n    <string name=\"cd_item_contact_message\">Kirim pesan</string>\n    <string name=\"cd_item_contact_call\">Telepon</string>\n    <string name=\"result_list_section\">Daftar hasil</string>\n    <string name=\"icons_pack\">Pilih paket</string>\n    <string name=\"search_bar_section\">Bilah penelusuran</string>\n    <string name=\"menu_shortcut_rename\">Ubah nama pintasan</string>\n    <string name=\"title_app_rename\">Pilih nama khusus untuk aplikasi ini</string>\n    <string name=\"title_shortcut_rename\">Pilih nama khusus untuk pintasan ini</string>\n    <string name=\"shortcut_rename_confirmation\">Nama pintasan sekarang adalah \\\"%s\\\"</string>\n    <string name=\"entry_rename_confirmation\">Namanya sekarang adalah \\\"%s\\\"</string>\n    <string name=\"hint_custom_icon\">Saring menurut nama</string>\n    <string name=\"default_icon\">ikon bawaan</string>\n    <string name=\"title_features\">Tata Letak</string>\n    <string name=\"features_summary\">Sesuaikan elemen-elemen UI</string>\n    <string name=\"quick_list_section\">Dok</string>\n    <string name=\"quick_list_enabled\">Gunakan dok</string>\n    <string name=\"quick_list_icon_size\">Ukuran ikon maksimum</string>\n    <string name=\"quick_list_height\">Tinggi</string>\n    <string name=\"permission_denied\">Berikan izin ini dahulu</string>\n    <string name=\"menu_hide\">Sembunyikan</string>\n    <string name=\"menu_tags_add\">Tambahkan tag</string>\n    <string name=\"menu_app_details\">Informasi aplikasi</string>\n    <string name=\"ui_item_search\">Telusuri \\\"%2$s\\\" di %1$s\\?</string>\n    <string name=\"ui_item_visit\">Kunjungi \\\"%1$s\\\"</string>\n    <string name=\"copy_confirmation\">\\\"%s\\\" disalin ke papan klip</string>\n    <string name=\"menu_quick_list_add\">Tambahkan ke dok</string>\n    <string name=\"stub_application\">Nama aplikasi</string>\n    <string name=\"menu_app_store\">Lihat di toko</string>\n    <string name=\"menu_exclude_kiss\">dari TinyBit</string>\n    <string name=\"title_rename_search_engine\">Pilih nama yang berbeda untuk mesin penelusuran ini</string>\n    <string name=\"custom_icon_application\">aplikasi</string>\n    <string name=\"menu_exclude_history\">dari riwayat</string>\n    <string name=\"entry_not_found\">Tidak dapat menemukan \\\"%s\\\"</string>\n    <string name=\"change_wallpaper\">Ubah latar belakang</string>\n    <string name=\"menu_widget_title\">Menu widget</string>\n    <string name=\"title_ui\">Antarmuka pengguna</string>\n    <string name=\"notification_bar_section\">Bilah notifikasi</string>\n    <string name=\"user_interface_summary\">Tema, transparansi, dan warna</string>\n    <string name=\"cd_show_all_apps\">Tampilkan semua aplikasi</string>\n    <string name=\"cd_item_contact_open\">Buka</string>\n    <string name=\"icons_pack_default_name\">Ikon sistem</string>\n    <string name=\"menu_app_rename\">Ubah nama aplikasi</string>\n    <string name=\"menu_action_rename\">Ubah nama</string>\n    <string name=\"search_bar_text_size\">Ukuran teks</string>\n    <string name=\"search_bar_height\">Tinggi bilah</string>\n    <string name=\"title_edit_url_search_engine\">Pilih URL yang berbeda untuk mesin penelusuran ini</string>\n    <string name=\"menu_action_delete\">Hapus</string>\n    <string name=\"menu_custom_icon\">Ubah ikon</string>\n    <string name=\"app_rename_confirmation\">Nama aplikasi sekarang %s</string>\n    <string name=\"custom_icon_activity\">aktivitas</string>\n    <string name=\"toast_hibernate_completed\">%s hibernasi, luncurkan kembali untuk bangun</string>\n    <string name=\"toast_hibernate_error\">[ERROR] %s tidak hibernasi</string>\n    <string name=\"menu_show\">Buka</string>\n    <string name=\"stub_app_tag\">daftar tag aplikasi</string>\n    <string name=\"menu_exclude\">Kecualikan…</string>\n    <string name=\"menu_remove_shortcut\">Lepaskan pin</string>\n    <string name=\"menu_tags_edit\">Edit tag</string>\n    <string name=\"menu_app_hibernate\">Hibernasi</string>\n    <string name=\"menu_popup_tags_manager\">Manajer tag</string>\n    <string name=\"menu_popup_tags_menu\">Menu tag</string>\n    <string name=\"color_and_opacity\">Warna dan keburaman</string>\n    <string name=\"cd_add_tag\">Tambah tag</string>\n    <string name=\"cd_undo_remove_tag\">Batalkan penghapusan tag</string>\n    <string name=\"cd_remove_tag\">Hapus tag</string>\n    <string name=\"value\">nilai %d</string>\n    <string name=\"value_float\">nilai %.2f</string>\n    <string name=\"title_rename_tag\">Ganti di mana pun tag ini muncul</string>\n    <string name=\"hint_rename_tag\">Pilih nama berbeda untuk tag ini</string>\n    <string name=\"title_rename_search_hint\">Pilih nama lain untuk petunjuk pencarian ini</string>\n    <string name=\"default_static_icon\">%s ikon default</string>\n    <string name=\"privacy_policy\">Kebijakan Privasi</string>\n    <string name=\"app_version\">Versi %s</string>\n    <string name=\"app_version_summary\">%1$s - %2$s</string>\n    <string name=\"cd_icon_tag\">Atur ikon tag</string>\n    <string name=\"cd_main_clear\">bersihkan bilah pencarian</string>\n    <string name=\"cd_rename_tag\">Ganti nama tag</string>\n    <string name=\"title_select_alpha\">Seret penggeser untuk memilih keburaman yang diinginkan</string>\n    <string name=\"title_select_size\">Seret penggeser untuk memilih ukuran yang diinginkan</string>\n    <string name=\"value_float_xy\">horizontal %1$.2f | vertikal %2$.2f</string>\n    <string name=\"size\">&lt;%d&gt;</string>\n    <string name=\"size_float\">&lt;%.1f&gt;</string>\n    <string name=\"shadow_preview\">&lt;%3$.1f&gt;\n\\n&lt;%1$.1f×%2$.1f&gt;</string>\n    <string name=\"custom_icon_activity_adaptive_no_background\">latar belakang kustom aktivitas</string>\n    <string name=\"current_icon_preview_label\">Saat ini\n\\nikon</string>\n    <string name=\"custom_icon_preview_label\">Dipilih\n\\nikon</string>\n    <string name=\"custom_name_set_default\">Nama default</string>\n    <string name=\"hint_new_tag\">tambah tag</string>\n    <string name=\"icons_visible\">Tampilkan Ikon</string>\n    <string name=\"custom_icon_badged\">berlencana</string>\n    <string name=\"default_icon_preview_label\">Default\n\\nikon</string>\n    <string name=\"tags_section\">Tag</string>\n    <string name=\"fuzzy_search_tags\">Tag pencarian fuzzy</string>\n    <string name=\"fuzzy_search_tags_summary\">Saat mencari, sertakan tag sebagai kandidat yang mungkin cocok</string>\n    <string name=\"tags_enabled\">Tampilkan tag untuk aplikasi</string>\n    <string name=\"tags_enabled_summary\">Tampilkan tag untuk setiap aplikasi dalam daftar hasil</string>\n    <string name=\"popup_title_shortcut_dynamic\">Pintasan</string>\n    <string name=\"popup_title_hist_fav\">Preferensi</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-it/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources></resources>"
  },
  {
    "path": "app/src/main/res/values-it-v26/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources></resources>"
  },
  {
    "path": "app/src/main/res/values-ja/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"menu_remove_history\">履歴から削除</string>\n    <string name=\"app_name_debug\">TinyBit debug launcher</string>\n    <string name=\"menu_quick_list_add\">ドックに追加</string>\n    <string name=\"stub_application\">アプリ名</string>\n    <string name=\"menu_tags_add\">タグを追加</string>\n    <string name=\"menu_tags_edit\">タグを編集</string>\n    <string name=\"menu_app_uninstall\">アンインストール</string>\n    <string name=\"change_wallpaper\">壁紙を変更</string>\n    <string name=\"menu_widget_title\">ウィジェットメニュー</string>\n    <string name=\"menu_widget_add\">ウィジェットを追加…</string>\n    <string name=\"menu_widget_remove\">ウィジェットを削除…</string>\n    <string name=\"menu_popup_title\">メインメニュー</string>\n    <string name=\"menu_popup_android_settings\">デバイス設定</string>\n    <string name=\"menu_popup_launcher_settings\">TinyBit 設定</string>\n    <string name=\"menu_popup_tags_menu\">タグメニュー</string>\n    <string name=\"rate_the_app\">アプリを評価する</string>\n    <string name=\"title_ui\">ユーザーインターフェース</string>\n    <string name=\"gradient\">グラデーション</string>\n    <string name=\"cd_show_all_apps\">すべてのアプリを表示する</string>\n    <string name=\"cd_item_contact_open\">開く</string>\n    <string name=\"cd_add_tag\">タグを追加</string>\n    <string name=\"cd_remove_tag\">タグを削除</string>\n    <string name=\"cd_undo_remove_tag\">タグの削除の取り消し</string>\n    <string name=\"search_bar_section\">検索バー</string>\n    <string name=\"search_bar_height\">バーの高さ</string>\n    <string name=\"result_list_section\">結果一覧</string>\n    <string name=\"menu_action_delete\">削除</string>\n    <string name=\"menu_custom_icon\">アイコンを変更</string>\n    <string name=\"hint_new_tag\">タグを追加</string>\n    <string name=\"title_features\">レイアウト</string>\n    <string name=\"quick_list_enabled\">ドックを使用する</string>\n    <string name=\"popup_title_customize\">カスタマイズ</string>\n    <string name=\"exit_the_app\">ランチャーを閉じる</string>\n    <string name=\"exit_the_app_confirm\">ランチャーを閉じますか\\?</string>\n    <string name=\"reset_default_launcher_confirm\">デフォルトのランチャーを変更しますか？</string>\n    <string name=\"reset_default_launcher\">デフォルトのランチャーを変更</string>\n    <string name=\"action_show_favorites\">お気に入り</string>\n    <string name=\"edit_quick_list_tab_favorites\">お気に入り</string>\n    <string name=\"edit_quick_list_tab_tags\">タグ</string>\n    <string name=\"quick_list_text_visible\">名前を表示</string>\n    <string name=\"app_name\">TinyBit launcher</string>\n    <string name=\"quick_list_section\">ドック</string>\n    <string name=\"menu_quick_list_remove\">ドックから削除</string>\n    <string name=\"menu_popup_title_settings\">設定</string>\n    <string name=\"menu_widget_configure\">ウィジェットをカスタマイズ…</string>\n    <string name=\"black_notification_icons\">ダークアイコン</string>\n    <string name=\"user_interface_summary\">テーマ、透明度、カラー</string>\n    <string name=\"notification_bar_section\">通知バー</string>\n    <string name=\"color_and_opacity\">色と不透明度</string>\n    <string name=\"cd_main_menu\">メニュー</string>\n    <string name=\"default_icon\">デフォルトアイコン</string>\n    <string name=\"icons_pack\">パックを選択</string>\n    <string name=\"cd_item_contact_message\">メッセージ</string>\n    <string name=\"tags_section\">タグ</string>\n    <string name=\"search_bar_text_size\">テキストサイズ</string>\n    <string name=\"icons_pack_default_name\">システムアイコン</string>\n    <string name=\"quick_list_only_for_results_summary\">結果リストにエントリーがある場合、ドックを表示する</string>\n    <string name=\"hint_custom_icon\">名前で絞り込む</string>\n    <string name=\"quick_list_only_for_results\">結果がない場合、ドックを隠す</string>\n    <string name=\"quick_list_icons_visible\">ドックアイコンを表示</string>\n    <string name=\"cache_drawable\">キャッシュアイコン</string>\n    <string name=\"screen_off_cache_clear\">キャッシュを頻繁に解放する</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-nb-rNO/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"menu_hide\">Skjul</string>\n    <string name=\"stub_application\">Programnavn</string>\n    <string name=\"menu_remove_history\">Fjern fra historikk</string>\n    <string name=\"title_wallpaper\">Interaksjon med bakgrunner</string>\n    <string name=\"shortcuts_no_host_permission\">Dette er ikke din hovedoppstarter lenger, og har ikke tilgang til snarveiene dine.</string>\n    <string name=\"black_notification_icons\">Mørke ikoner</string>\n    <string name=\"menu_widget_remove\">Fjern miniprogram …</string>\n    <string name=\"menu_widget_add\">Legg til miniprogram …</string>\n    <string name=\"menu_exclude_kiss\">fra TinyBit</string>\n    <string name=\"menu_tags_edit\">Rediger etiketter</string>\n    <string name=\"cant_pin_shortcut\">Kunne ikke legge til snarvei. Er TinyBit din forvalgte oppstarter\\?</string>\n    <string name=\"unable_to_initialize_shortcuts\">Kunne ikke starte snarveier. Er TinyBit din forvalgte oppstarter\\?</string>\n    <string name=\"app_name\">TinyBit-oppstarter</string>\n    <string name=\"ui_item_search\">Søk på %1$s etter «%2$s»</string>\n    <string name=\"ui_item_visit\">Besøk «%1$s»</string>\n    <string name=\"copy_confirmation\">Kopierte «%s» utklippstavlen</string>\n    <string name=\"wp_animate_sides_desc\">Bakgrunnen vil feste seg til <b>←</b> eller <b>→</b> etter rulling</string>\n    <string name=\"wp_animate_sides\">Fest bakgrunn til siden</string>\n    <string name=\"wp_animate_center_desc\">Bakgrunnen vil returnere til senter etter rulling</string>\n    <string name=\"wp_animate_center\">Sentrer bakgrunn</string>\n    <string name=\"wp_drag_animate_desc\">Dra <b>←</b> <b>→</b> for å rulle bakgrunnsbilde</string>\n    <string name=\"wp_drag_animate\">Rull bakgrunnsbilde</string>\n    <string name=\"lwp_drag_desc\">Send flerfoldige pekehendelser</string>\n    <string name=\"lwp_drag\">Emuler dragingshendelse for bakgrunnsbilde</string>\n    <string name=\"lwp_touch\">Send pekehendelser til bakgrunnsbilde</string>\n    <string name=\"icons_pack_default_name\">Systemikon</string>\n    <string name=\"title_ui\">Brukergrensesnitt</string>\n    <string name=\"user_interface_summary\">Drakter, gjennomsiktighet og farger</string>\n    <string name=\"rate_the_app\">Vurder programmet</string>\n    <string name=\"application_not_found\">Oida … kunne ikke starte \\\"%s\\\" programmet</string>\n    <string name=\"menu_exclude_history\">fra historikk</string>\n    <string name=\"menu_app_uninstall\">Avinstaller</string>\n    <string name=\"menu_app_store\">Vis i butikk</string>\n    <string name=\"menu_app_details\">Programinfo</string>\n    <string name=\"menu_exclude\">Ekskluder …</string>\n    <string name=\"permission_denied\">Tilgang nektet</string>\n    <string name=\"removed_item\">Fjernet %s</string>\n    <string name=\"title_static_rename\">Velg egendefinert navn</string>\n    <string name=\"settings_dark\">Mørk</string>\n    <string name=\"settings_black\">Svart</string>\n    <string name=\"settings_white\">Hvit</string>\n    <string name=\"tab_app_icons\">%s\n\\n[programikoner]</string>\n    <string name=\"debug_section\">Avlus</string>\n    <string name=\"cfg_widget_front\">Flytt <b>ovenfor</b> alt</string>\n    <string name=\"cfg_widget_back\">Flytt <b>under</b> alt</string>\n    <string name=\"cfg_widget_screen_right\">Flytt til <br/>\n\\n<b>→</b> skjerm</string>\n    <string name=\"cfg_widget_screen_middle\">Flytt til <br/>\n\\n<b>midtre</b> skjerm</string>\n    <string name=\"cfg_widget_screen_left\">Flytt til <br/>\n\\n<b>←</b> skjerm</string>\n    <plurals name=\"tag_entry_count\">\n        <item quantity=\"one\">én oppføring</item>\n        <item quantity=\"other\">%d oppføringer</item>\n    </plurals>\n    <string name=\"unlimited_search_cap_summary\">Sett høyeste mulige grense</string>\n    <string name=\"unlimited_search_cap\">Ubegrensede søkeresultater</string>\n    <string name=\"result_search_cap\">Maksimalt antall søkeresultater</string>\n    <string name=\"label_search_hint\">Søkehint</string>\n    <string name=\"icon_background\">Ikonbakgrunn</string>\n    <string name=\"choose_icon_menu_add2\">Legg til som alternativ og vis</string>\n    <string name=\"choose_icon_menu_add\">Legg til som alternativ</string>\n    <string name=\"choose_icon\">Velg ikon</string>\n    <string name=\"choose_icon_shape\">Velg form</string>\n    <string name=\"contact_pack_mask_summary\">Bruk ikonpakkeform for kontakter</string>\n    <string name=\"contact_pack_mask\">Kontakter</string>\n    <string name=\"force_adaptive\">Sett ikonbakgrunn</string>\n    <string name=\"adaptive_shape_name\">Ikonform</string>\n    <string name=\"result_history_adaptive_summary\">Antall timer for tilpasset historikk</string>\n    <string name=\"result_history_adaptive\">Tilpasningsvarighet</string>\n    <string name=\"action_show_history_adaptive\">Tilpasningsdyktig historikk</string>\n    <string name=\"action_show_history_frecency\">Nylig og ofte brukt</string>\n    <string name=\"action_show_history_frequency\">Oftest brukt</string>\n    <string name=\"action_show_history_recency\">Nylig historikk</string>\n    <string name=\"result_history_size\">Historikkstørrelse</string>\n    <string name=\"choose_file_activity_not_found\">Installer en filbehandler.</string>\n    <string name=\"widget_placeholder_remove\">Vil du virkelig fjerne plassholderen</string>\n    <string name=\"add_widget_failed\">Kunne ikke legge til miniprogram</string>\n    <string name=\"widget_placeholder\">Klikk for å gjenopprette %s</string>\n    <string name=\"error_fail_import\">Kunne ikke importere fil. Sjekk logcat.</string>\n    <string name=\"export_history_summary\">Historikk for alle kjørte programmer</string>\n    <string name=\"export_history\">Eksporter historikk</string>\n    <string name=\"export_widgets\">Eksporter miniprogrammer</string>\n    <string name=\"export_backup\">Eksporter sikkerhetskopi</string>\n    <string name=\"export_interface\">Eksporter grensesnitt</string>\n    <string name=\"error\">Feil %s</string>\n    <string name=\"import_chooser\">Velg en fil å importere</string>\n    <string name=\"import_dialog_description\">Laster inn innstillinger …</string>\n    <string name=\"please_wait\">Vent.</string>\n    <string name=\"import_settings_overwrite\">Overskriv importerte innstillinger</string>\n    <string name=\"import_settings_set\">Sett importerte innstillinger</string>\n    <string name=\"import_settings_section\">Importer innstillinger</string>\n    <string name=\"export_apps\">Eksporter programmer</string>\n    <string name=\"export_tags\">Eksporter etiketter</string>\n    <string name=\"export_xml\">Eksporter som XML</string>\n    <string name=\"gesture_click\">Trykk</string>\n    <string name=\"gesture_section\">Håndvendinger</string>\n    <string name=\"icon_pack_loading\">Laster fra ikonpakker …</string>\n    <string name=\"tab_static_icons\">Forvalgt ikon</string>\n    <string name=\"label_search_engine_url\">Nettadresse å åpne</string>\n    <string name=\"search_engine_set_default\">Gjør til forvalg</string>\n    <string name=\"confirm_edit_url_search_engine\">Lagre</string>\n    <string name=\"search_engine_edit_url\">Rediger nettadresse</string>\n    <string name=\"add_search_hint\">Legg til søkehint</string>\n    <string name=\"reset_search_hint\">Tilbakestill søkehint</string>\n    <string name=\"edit_search_hint\">Rediger søkehint</string>\n    <string name=\"add_search_engine\">Legg til søkemotor</string>\n    <string name=\"reset_search_engines\">Tilbakestill søkemotorer</string>\n    <string name=\"edit_search_engines\">Rediger søkemotorer</string>\n    <string name=\"enable_calculator\">Kalkulator</string>\n    <string name=\"enable_url\">Nettsidenettadresse</string>\n    <string name=\"enable_search\">Søkemotor</string>\n    <string name=\"result_text2_size\">Sekundær tekststørrelse</string>\n    <string name=\"result_text_size\">Tekststørrelse</string>\n    <string name=\"search_bar_text_color\">Tekstfarge</string>\n    <string name=\"result_text2_color\">Sekundær tekstfarge</string>\n    <string name=\"result_text_color\">Tekstfarge</string>\n    <string name=\"result_icon_size\">Ikonstørrelse</string>\n    <string name=\"quick_list_color\">Bakgrunnsfarge</string>\n    <string name=\"shortcut_section\">Snarvei</string>\n    <string name=\"shortcut_with_appName\">%1$s: %2$s</string>\n    <string name=\"cfg_widget_remove\">Fjern</string>\n    <string name=\"cfg_widget_move\">Flytt</string>\n    <string name=\"icon_pack_content_list\">Ikoner fra\n\\n%s</string>\n    <string name=\"icon_pack_section\">Ikonpakke</string>\n    <string name=\"memory_section\">Minne</string>\n    <string name=\"quick_list_text_visible\">Vis navn</string>\n    <string name=\"quick_list_icons_visible\">Vis hurtiglisteikoner</string>\n    <string name=\"edit_quick_list_tab_tags\">Etiketter</string>\n    <string name=\"edit_quick_list_tab_favorites\">Favoritter</string>\n    <string name=\"edit_quick_list_tab_actions\">Handlinger</string>\n    <string name=\"edit_quick_list_tab_filters\">Filter</string>\n    <string name=\"quick_list_content_summary\">Endre rekkefølge og legg til oppføringer</string>\n    <string name=\"quick_list_content\">Tilpass innhold</string>\n    <string name=\"action_show_favorites\">Favoritter</string>\n    <string name=\"action_show_shortcuts_reversed\">Snarveier Z→A</string>\n    <string name=\"action_show_shortcuts\">Snarveier A→Z</string>\n    <string name=\"action_show_contacts_reversed\">Kontakter Z→A</string>\n    <string name=\"action_show_contacts\">Kontakter A→Z</string>\n    <string name=\"action_show_apps_reversed\">Programmer Z→A</string>\n    <string name=\"filter_shortcuts\">Snarveisfilter</string>\n    <string name=\"filter_contacts\">Kontaktfilter</string>\n    <string name=\"filter_apps\">Programfilter</string>\n    <string name=\"reset_default_launcher\">Endre forvalgt oppstarter</string>\n    <string name=\"exit_the_app_confirm\">Vil du virkelig lukke oppstarteren\\?</string>\n    <string name=\"exit_the_app\">Lukk oppstarter</string>\n    <string name=\"pin_shortcut_icon\">Snarveisikon</string>\n    <string name=\"pin_shortcut_label\">Velg snarveisetikett</string>\n    <string name=\"pin_shortcut_message\">Ønsker du å legge til denne snarveien\\?</string>\n    <string name=\"popup_title_link\">Lenker</string>\n    <string name=\"popup_title_customize\">Tilpass</string>\n    <string name=\"popup_title_hist_fav\">Innstillinger</string>\n    <string name=\"icons_visible\">Vis ikoner</string>\n    <string name=\"tags_enabled\">Vis etiketter for programmer</string>\n    <string name=\"tags_section\">Etiketter</string>\n    <string name=\"quick_list_height\">Felthøyde</string>\n    <string name=\"quick_list_enabled\">Bruk hurtigliste</string>\n    <string name=\"quick_list_section\">Hurtigliste</string>\n    <string name=\"features_summary\">Veksle og tilpass programfunksjoner</string>\n    <string name=\"title_features\">FUnksjoner</string>\n    <string name=\"hint_new_tag\">Legg til etikett</string>\n    <string name=\"custom_name_set_default\">Forvalgt navn</string>\n    <string name=\"custom_icon_preview_label\">Valgt\n\\nikon</string>\n    <string name=\"current_icon_preview_label\">Nåværende\n\\nikon</string>\n    <string name=\"default_icon_preview_label\">Forvalgt\n\\nikon</string>\n    <string name=\"custom_icon_activity\">aktivitet</string>\n    <string name=\"custom_icon_application\">program</string>\n    <string name=\"default_static_icon\">%s forvalgt ikon</string>\n    <string name=\"default_icon\">forvalgt ikon</string>\n    <string name=\"hint_custom_icon\">Filtrer etter navn</string>\n    <string name=\"shortcut_rename_confirmation\">Snarveisnavnet er nå «%s»</string>\n    <string name=\"app_rename_confirmation\">Programnavnet er nå «%s»</string>\n    <string name=\"title_shortcut_rename\">Velg et tilpasset navn for denne snarveien</string>\n    <string name=\"title_edit_url_search_engine\">Velg en annen nettadresse for denne søkemotoren</string>\n    <string name=\"title_rename_search_hint\">Velg et annet navn for dette søkehintet</string>\n    <string name=\"title_rename_search_engine\">Velg et annet navn for denne søkemotoren</string>\n    <string name=\"title_rename_tag\">Velg et annet navn for denne etiketten</string>\n    <string name=\"title_app_rename\">Tilpass egendefinert navn for dette programmet</string>\n    <string name=\"menu_custom_icon\">Endre ikon</string>\n    <string name=\"menu_shortcut_rename\">Gi snarvei nytt navn</string>\n    <string name=\"menu_action_delete\">Slett</string>\n    <string name=\"menu_action_rename\">Gi nytt navn</string>\n    <string name=\"menu_app_rename\">Gi programmet nytt navn</string>\n    <string name=\"size\">&lt;%d&gt;</string>\n    <string name=\"value\">verdi %d</string>\n    <string name=\"icons_pack\">Velg pakke</string>\n    <string name=\"result_list_section\">Resultatliste</string>\n    <string name=\"search_bar_section\">Søkefelt</string>\n    <string name=\"title_select_size\">Dra glidebryteren for å velge ønsket størrelse</string>\n    <string name=\"title_select_alpha\">Dra glidebryteren for å velge ønsket dekkevne</string>\n    <string name=\"cd_icon_tag\">Sett etikettikon</string>\n    <string name=\"cd_rename_tag\">Gi etikett nytt navn</string>\n    <string name=\"cd_remove_tag\">Fjern etikett</string>\n    <string name=\"cd_undo_remove_tag\">Angre fjerning av etikett</string>\n    <string name=\"cd_add_tag\">Legg til etikett</string>\n    <string name=\"cd_item_contact_message\">Melding</string>\n    <string name=\"cd_show_all_apps\">Vis alle programmer</string>\n    <string name=\"cd_main_clear\">Tøm søkefelt</string>\n    <string name=\"cd_main_menu\">Meny</string>\n    <string name=\"notification_bar_section\">Merknadsfelt</string>\n    <string name=\"menu_popup_tags_manager\">Etiketthåndtering</string>\n    <string name=\"menu_popup_launcher_settings\">TinyBit-innstillinger</string>\n    <string name=\"menu_popup_android_settings\">Enhetsinnstillinger</string>\n    <string name=\"menu_popup_title_settings\">Innstillinger</string>\n    <string name=\"menu_popup_title\">Hovedmeny</string>\n    <string name=\"menu_widget_configure\">Tilpass miniprogram …</string>\n    <string name=\"menu_widget_title\">Miniprogramsmeny</string>\n    <string name=\"change_wallpaper\">Endre bakgrunnsbilde</string>\n    <string name=\"entry_not_found\">Fantt ikke «%s»</string>\n    <string name=\"menu_tags_add\">Legg til etiketter</string>\n    <string name=\"menu_remove_shortcut\">Opphev festing</string>\n    <string name=\"menu_show\">Avslør</string>\n    <string name=\"hint_ui_search\">Søk i programmer, kontakter, …</string>\n    <string name=\"behaviour_clear_search_after_launch\">Tøm søkefelt etter oppstart</string>\n    <string name=\"behaviour_widget_after_launch\">Miniprogramsmodus etter oppstart</string>\n    <string name=\"title_matrix\">Juster ikonfarge</string>\n    <string name=\"icon_saturation\">Metning</string>\n    <string name=\"settings_deep_blues\">BlåttDyp</string>\n    <string name=\"settings_8bit\">8-bit</string>\n    <string name=\"popup_ripple_color\">Trykkefarge</string>\n    <string name=\"reset_matrix_description\">Alle ikoninnstillinger på denne skjermen vil bli tilbakestilt til sine forvalgte, nøytrale verdier</string>\n    <string name=\"reset_preferences_description\">Alle innstillinger vil bli tilbakestilt til forvalgt verdi</string>\n    <string name=\"reset_preferences_confirm\">Vil du virkelig forkaste innstillinene\\?</string>\n    <string name=\"reset_preferences\">Tilbakestill innstillinger</string>\n    <string name=\"quick_list_ripple_color\">Trykkefarge</string>\n    <string name=\"result_ripple_color\">Trykkefarge</string>\n    <string name=\"cfg_widget_resize_switch\">Veksle endring av størrelse</string>\n    <string name=\"cfg_widget_move_exit\">Avslutt flytting</string>\n    <string name=\"action_show_untagged\">Uten etikett</string>\n    <string name=\"search_bar_ripple_color\">Trykkefarge</string>\n    <string name=\"search_bar_at_bottom\">Posisjoner på bunnen</string>\n    <string name=\"title_desktop_mode_widget\">Skrivebordsmodus «Miniprogram»</string>\n    <string name=\"title_desktop_mode_search\">Skrivebordsmodus «Søk»</string>\n    <string name=\"title_desktop_mode_empty\">Skribebordsmodus «Tomt»</string>\n    <string name=\"dm_widget_quick_list\">Vis hurtigliste</string>\n    <string name=\"dm_search_quick_list\">Vis hurtigliste</string>\n    <string name=\"dm_empty_quick_list\">Vis hurtigliste</string>\n    <string name=\"corner_radius\">Hjørneradius</string>\n    <string name=\"reset_matrix\">Sett forvalgte verdier</string>\n    <string name=\"icon_scale_alpha\">Juster gjennomsiktighet</string>\n    <string name=\"icon_scale_blue\">Juster blå</string>\n    <string name=\"icon_scale_green\">Juster grønn</string>\n    <string name=\"icon_scale_red\">Juster rød</string>\n    <string name=\"button_launcher\">Oppstarter-knapp</string>\n    <string name=\"letters_toggle\">Bokstavikoner</string>\n    <string name=\"popup_background_argb\">Bakgrunnsfarge</string>\n    <string name=\"popup_section\">Oppsprett</string>\n    <string name=\"reset_matrix_confirm\">Sett forvalgte verdier\\?</string>\n    <string name=\"choose_background_color\">Bakgrunnsfarge</string>\n    <string name=\"shortcut_pack_badge_mask\">Snarveisskilt</string>\n    <string name=\"shortcut_pack_mask\">Snarveisikon</string>\n    <string name=\"import_settings_overwrite_summary\">Importerte innstillinger overskriver nåværende</string>\n    <string name=\"export_chooser_xml\">Eksporter «%s» som XML</string>\n    <string name=\"selected_pack\">%s\n\\n[nåværende pakke]</string>\n    <string name=\"search_engine_url_help\">%s vil bli erstattet av din spørring</string>\n    <string name=\"label_search_engine_name\">Søketilbydernavn</string>\n    <string name=\"reset_search_hint_summary\">Gjenopprett alle søkehint</string>\n    <string name=\"edit_search_hint_summary\">Veksle og rediger søkehint</string>\n    <string name=\"reset_search_engines_summary\">Gjenopprett forvalgte søkemotorer</string>\n    <string name=\"edit_search_engines_summary\">Veksle og rediger søketilbydere</string>\n    <string name=\"provider_section\">Tilbydere</string>\n    <string name=\"invalid_rename_search_engine\">Det finnes allerede en «%s»-søkemotor</string>\n    <string name=\"invalid_rename_tag\">Det finnes allerede en «%s»-etikett</string>\n    <string name=\"menu_popup_quick_list_customize\">Lag din hurtigliste</string>\n    <string name=\"search_bar_icon_color\">Ikonfarge</string>\n    <string name=\"contact_action_color\">Handlingsikonfarge</string>\n    <string name=\"quick_list_toggle_color\">Veksle bakgrunnsfarge</string>\n    <string name=\"result_highlight_color\">Framhevelsesfarge</string>\n    <string name=\"quick_list_show_badge\">Vis «Hurtigliste»-snarveisskilt</string>\n    <string name=\"shortcut_show_badge_summary\">Vis programikon som opprettet snarveien</string>\n    <string name=\"shortcut_show_badge\">Vis programskilt</string>\n    <string name=\"shortcut_pin_auto_confirm\">Auto-bekreft snarvei</string>\n    <string name=\"debug_item_relevance\">Søkerelevans</string>\n    <string name=\"debug_widget_info\">Lang-trykk for ekstra miniprogramsinfo</string>\n    <string name=\"debug_widget_add_info\">Legg til ekstra miniprogramsinfo</string>\n    <string name=\"cfg_widget_move_resize_exit\">Avslutt «flytt og endre størrelse»</string>\n    <string name=\"cfg_widget_move_resize\">Flytt og endre størrelse</string>\n    <string name=\"cfg_widget_resize_exit\">Avslutt «endre størrelse»</string>\n    <string name=\"cfg_widget_resize\">Endre størrelse</string>\n    <string name=\"sensor_orientation\">Sensororientering</string>\n    <string name=\"lock_portrait\">Lås i portrettmodus</string>\n    <string name=\"cache_half_apps\">Liten hurtiglagerstørrelse</string>\n    <string name=\"screen_off_cache_clear_summary\">Tøm hurtiglager når skjermen slås av</string>\n    <string name=\"screen_off_cache_clear\">Tøm hurtiglager ofte</string>\n    <string name=\"cache_drawable\">Hurtiglagre ikoner</string>\n    <string name=\"action_show_apps\">Programmer A→Z</string>\n    <string name=\"reset_default_launcher_confirm\">Vil du virkelig endre forvalgt oppstarter\\?</string>\n    <string name=\"exit_the_app_description\">Din forvalgte oppstarter vil åpnes, eller du vil bli spurt om hvilken du ønsker å bruke.</string>\n    <string name=\"quick_list_only_for_results_summary\">Synlig når resultatlisten har oppføringen</string>\n    <string name=\"quick_list_only_for_results\">Synlig for resultater</string>\n    <string name=\"tags_enabled_summary\">Vis etiketter for hvert program i resultatlisten</string>\n    <string name=\"fuzzy_search_tags\">Inkluderende søk</string>\n    <string name=\"fuzzy_search_tags_summary\">Søking inkluderer etiketter som mulige treff</string>\n    <string name=\"custom_icon_badged\">skiltet</string>\n    <string name=\"custom_icon_activity_adaptive_no_background\">tilpasset aktivitetsbakgrunn</string>\n    <string name=\"cd_item_contact_call\">Samtale</string>\n    <string name=\"gradient\">Gradient</string>\n    <string name=\"stub_app_tag\">programetikettliste</string>\n    <string name=\"action_app_to_run\">Program å kjøre</string>\n    <string name=\"quick_list_animation\">Animer skjul/vis</string>\n    <string name=\"no_tags\">Ingen tagger funnet</string>\n    <string name=\"action_entry_to_show\">Favoritt å vise</string>\n    <string name=\"back_device_key\">Tilbake-knapp</string>\n    <string name=\"search_bar_cursor_argb\">Pekerfarge</string>\n    <string name=\"dm_widget_fullscreen\">Fullskjermsvisning</string>\n    <string name=\"dm_search_fullscreen\">Fullskjermsvisning</string>\n    <string name=\"dm_empty_fullscreen\">Fullskjermsvisning</string>\n    <string name=\"behaviour_section\">Oppførsel</string>\n    <string name=\"icon_brightness\">Lysstyrke</string>\n    <string name=\"icon_contrast\">Kontrast</string>\n    <string name=\"button_home\">Hjem-knapp</string>\n    <string name=\"settings_theme\">Innstillingsdrakt</string>\n    <string name=\"popup_title_color\">Tittelfarge</string>\n    <string name=\"popup_text_color\">Tekstfarge</string>\n    <string name=\"result_history_size_summary\">Maksimalt antall elementer i historikkvisning</string>\n    <string name=\"export_widgets_summary\">Import kan feile, eller kreve at hvert miniprogram legges til manuelt</string>\n    <string name=\"cache_half_apps_summary\">Gjør LRU-huriglagerstørrelse halvparten av installerte programmer</string>\n    <string name=\"gesture_double_click\">Dobbelttrykk på skjermen</string>\n    <string name=\"behaviour_summary\">Håndvendinger og oppførsel</string>\n    <string name=\"dm_search_fullscreen_summary\">Kun når tastaturet er lukket</string>\n    <string name=\"behaviour_link_keyboard_search_bar\">Lenk tastatur til søkefeltssynlighet</string>\n    <string name=\"gesture_fling_down_left\">Flikk fra |<b>←</b>|, <b>↓</b></string>\n    <string name=\"gesture_fling_down_left_summary\">Start fra <b>←</b> side av skjermen og flikk <b>↓</b></string>\n    <string name=\"gesture_fling_down_right\">Flikk fra |<b>→</b>|, <b>↓</b></string>\n    <string name=\"gesture_fling_down_right_summary\">Start fra <b>→</b> side av skjermen og flikk <b>↓</b></string>\n    <string name=\"gesture_fling_left\">Flikk ←</string>\n    <string name=\"gesture_fling_right\">Flikk →</string>\n    <string name=\"gesture_fling_up\">Flikk ↑</string>\n    <string name=\"choose_letter_color\">Bokstavfarge</string>\n    <string name=\"device_admin_disable\">Fjern tilgangen\\?</string>\n    <string name=\"device_admin\">Enhetsadministrator</string>\n    <string name=\"root_mode\">Rot-modus</string>\n    <string name=\"title_behaviour\">Oppførsel</string>\n    <string name=\"export_chooser\">Eksporter %s</string>\n    <string name=\"title_backup\">Sikkerhetskopi</string>\n    <string name=\"menu_app_hibernate\">Send i dvalemodus</string>\n    <string name=\"toast_hibernate_error\">[Feil] %s er ikke i dvalemodus</string>\n    <string name=\"toast_hibernate_completed\">%s i dvalemodus, kjør igjen for å vekke</string>\n    <string name=\"export_settings_section\">Eksporter innstillinger</string>\n    <string name=\"device_admin_explanation\">Brukt til å låse skjermen</string>\n    <string name=\"popup_border_argb\">Kantfarge</string>\n    <string name=\"icon_hue\">Kulør</string>\n    <string name=\"keyboard_section\">Tastatur</string>\n    <string name=\"device_admin_required\">Enhets-administrasjonstilgang kreves</string>\n    <string name=\"shortcut_shape_summary\">Omriss for snarveisikon</string>\n    <string name=\"menu_quick_list_add\">Legg til i dokk</string>\n    <string name=\"export_modifications\">Eksporter tilpasninger</string>\n    <string name=\"export_modifications_summary\">Dokk, egendefinerte ikoner og navn</string>\n    <string name=\"export_backup_summary\">Alle egendefinerte ikoner, etiketter og innstillinger</string>\n    <string name=\"menu_quick_list_remove\">Fjern fra dokk</string>\n    <string name=\"entry_rename_confirmation\">Navnet er nå «%s»</string>\n    <string name=\"generate_theme_confirm\">Sett genererte farger\\?</string>\n    <string name=\"root_mode_error\">Fikk ikke rot-tilgang</string>\n    <string name=\"export_apps_summary\">Nye navn på programmer, skjulte og egendefinerte ikoner</string>\n    <string name=\"reset_cached_app_icons_description\">Tilbakestiller mellomlagret ikonpakkenavn og versjon.</string>\n    <string name=\"secondary_color\">Sekundærfarge</string>\n    <string name=\"force_shape\">Påtving omriss</string>\n    <string name=\"root_mode_summary\">Brukt for dvalgang av programmer</string>\n    <string name=\"tags_menu_order\">Etikett-rekkefølge</string>\n    <string name=\"contacts_shape_summary\">Omriss for kontaktikon</string>\n    <string name=\"choose_icon_scale\">Skaler ikon</string>\n    <string name=\"reset_cached_app_icons_confirm\">Tilbakestill ikonpakke-mellomlager\\?</string>\n    <string name=\"tags_menu_icon_size\">Ikonstørrelse</string>\n    <string name=\"generate_theme_simple\">Enkel drakt</string>\n    <string name=\"generate_theme_description\">Tilbakestiller aller farger og bruker genererte farger basert på primær- og skundærfarger</string>\n    <string name=\"tags_menu_icons\">Vis ikoner</string>\n    <string name=\"tags_menu_list\">Etiketter å vise</string>\n    <string name=\"primary_color\">Primærfarge</string>\n    <string name=\"generate_theme_highlight_summary\">Buk primærfarge for bakgrunn og sekundær for framheving</string>\n    <string name=\"cd_item_contact_open\">Åpne</string>\n    <string name=\"hint_rename_tag\">Velg et annet navn for denne etiketten</string>\n    <string name=\"popup_title_shortcut_dynamic\">Snarveier</string>\n    <string name=\"enable_contacts\">Telefonkontakter</string>\n    <string name=\"selected_contact_mime_types\">Velg kontakter å vise</string>\n    <string name=\"static_icon_letters_label\">Bokstaver å bruke for generering av ikoner</string>\n    <string name=\"backup_summary\">Lagre, sikkerhetskopier, eksporter og importer innstillinger</string>\n    <string name=\"export_tags_summary\">Eksporter etikett-tilknytninger, ingen ikoner</string>\n    <string name=\"show_tags_menu\">Åpne etikett-menyen</string>\n    <string name=\"show_tags_list\">Etikettmenyer som resultat</string>\n    <string name=\"show_tags_list_reversed\">Reverserte etikettmenyer som resultat</string>\n    <string name=\"force_adaptive_summary\">Rediser ikonskala for å plassere dem på en sjablong-bakgrunn</string>\n    <string name=\"matrix_summary\">Lysstyrke, kontrast, kulør, metning</string>\n    <string name=\"debug_favorites_summary\">Vis oppføringer fra databasetabellen «favoritter»</string>\n    <string name=\"title_presets\">Fargeforhåndsinnstilling</string>\n    <string name=\"summary_presets\">Forhåndsinnstilling og generering av farger</string>\n    <string name=\"generate_theme_simple_summary\">Bruk primærfarge for bakgrunn og sekundær for tekst</string>\n    <string name=\"generate_theme_highlight\">Framhev drakt</string>\n    <string name=\"menu_popup_tags_menu\">Etikett-meny</string>\n    <string name=\"cfg_widget_screen_down\">Flytt til <b>↓</b>-skjermen</string>\n    <string name=\"cfg_widget_screen_up\">Flytt til <b>↑</b>-skjermen</string>\n    <string name=\"import_settings_set_summary\">Tøm relevante innstillinger før import</string>\n    <string name=\"import_settings_append_summary\">Kun nye innstillinger blir importert</string>\n    <string name=\"export_description\">Etter at du har klikket «OK» kan du velge en filbehandler for å lagre innstillingene dine som en XML-fil.</string>\n    <string name=\"export_interface_summary\">Alle grensesnittsfarger, størrelser, brytere og ikonomriss</string>\n    <string name=\"shortcut_pack_mask_summary\">Bruk ikonpakkemaske for snarveisinkon</string>\n    <string name=\"shortcut_pack_badge_mask_summary\">Bruk ikonpakkemaske for snarveisskilt</string>\n    <string name=\"matrix_contacts_summary\">Moduler farger for kontaktikoner</string>\n    <string name=\"tags_menu_section\">Etikettmeny</string>\n    <string name=\"tags_menu_untagged\">Vis dem uten etiketter</string>\n    <string name=\"result_popup_order\">Lang-trykk for kategori-rekkefølge</string>\n    <string name=\"result_popup_order_summary\">Kategorirekkefølge for lang-trykking av oppsprettselementer</string>\n    <string name=\"debug_favorites\">Favoritter</string>\n    <string name=\"lwp_scroll_pages\">Sider</string>\n    <string name=\"lwp_page_count_vertical\">↕ sider</string>\n    <string name=\"lwp_page_count_horizontal\">⟷ sider</string>\n    <string name=\"reset_cached_app_icons\">Tilbakestill ikonpakke</string>\n    <string name=\"matrix_contacts\">Moduler kontakter</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-nb-rNO-v26/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"force_shape_summary\">Bruk adaptiv maske på ikoner som ikke er det</string>\n    <string name=\"force_shape\">Påtving adaptiv form</string>\n    <string name=\"force_adaptive_summary\">For ikke-adaptive ikoner, reduser skalaen og plasser den på en formet bakgrunn</string>\n    <string name=\"force_adaptive\">Påtving adaptive ikoner</string>\n    <string name=\"adaptive_shape_name\">Adaptiv ikonform</string>\n    <string name=\"removed_item\">Fjernet %s</string>\n    <string name=\"ui_item_visit\">Besøk \\\"%1$s\\\"</string>\n    <string name=\"ui_item_search\">Søk i %1$s etter \\\"%2$s\\\"\\?</string>\n    <string name=\"hint_ui_search\">Søk i apper, kontakter, …</string>\n    <string name=\"app_name\">TinyBit launcher</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-pl/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"copy_confirmation\">Skopiowano do schowka: \\\"%s\\\"</string>\n    <string name=\"toast_hibernate_error\">[BŁĄD] Nie zahibernowano %s</string>\n    <string name=\"ui_item_visit\">Odwiedź \\\"%1$s\\\"</string>\n    <string name=\"toast_hibernate_completed\">Zahibernowano %s. Uruchom aplikację ponownie w celu wybudzenia.</string>\n    <string name=\"removed_item\">Usunięto %s</string>\n    <string name=\"hint_ui_search\">Szukaj w aplikacjach, kontaktach, …</string>\n    <string name=\"ui_item_search\">Szukaj \\\"%2$s\\\" w %1$s?</string>\n    <string name=\"unable_to_initialize_shortcuts\">Brak dostępu do skrótów. Upewnij się, że TiniBit jest domyślnym programem uruchamiającym</string>\n    <string name=\"app_name\">TinyBit launcher</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-pl-v26/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"adaptive_shape_name\">Adaptacyjny kształt ikon</string>\n    <string name=\"force_adaptive\">Wymuś ikony adaptacyjne</string>\n    <string name=\"force_adaptive_summary\">W przypadku ikon nieadaptacyjnych, zmniejsz skalę i umieść na ukształtowanym tle</string>\n    <string name=\"force_shape\">Wymuś kształt adaptacyjny</string>\n    <string name=\"force_shape_summary\">Użyj maski adaptacyjnej na ikonach nieadaptacyjnych</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-pt/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"gesture_section\">Gestos</string>\n    <string name=\"import_settings_overwrite_summary\">Configurações importadas sobrescrevem as configurações actuais</string>\n    <string name=\"import_settings_append\">Anexar definições importadas</string>\n    <string name=\"import_settings_append_summary\">Apenas novas configurações são importadas</string>\n    <string name=\"please_wait\">Aguarde, por favor.</string>\n    <string name=\"import_dialog_description\">Configurações de carregamento…</string>\n    <string name=\"import_chooser\">Seleccione um ficheiro a importar</string>\n    <string name=\"export_description\">Após clicar em \\\"OK\\\" pode seleccionar um gestor de ficheiros para guardar as suas definições como um ficheiro XML.</string>\n    <string name=\"error\">Erro %s</string>\n    <string name=\"export_preferences\">Definições de exportação</string>\n    <string name=\"export_preferences_summary\">Todas as configurações, características e comportamentos, incluindo interface</string>\n    <string name=\"export_interface\">Interface de exportação</string>\n    <string name=\"export_interface_summary\">Todas as cores, tamanhos, comutadores e formas de ícones da interface</string>\n    <string name=\"export_backup\">Exportar cópia de segurança</string>\n    <string name=\"export_backup_summary\">Todos os ícones, etiquetas e preferências personalizadas</string>\n    <string name=\"export_widgets\">Widgets de exportação</string>\n    <string name=\"export_history_summary\">História de todas as aplicações lançadas</string>\n    <string name=\"error_fail_import\">Não foi possível importar ficheiro. Verificar logcat.</string>\n    <string name=\"widget_placeholder\">Clique para restaurar %s</string>\n    <string name=\"add_widget_failed\">Não foi possível acrescentar widget</string>\n    <string name=\"widget_placeholder_remove\">Retirar o local\\?</string>\n    <string name=\"choose_file_activity_not_found\">Por favor, instale um gestor de ficheiros.</string>\n    <string name=\"result_history_size\">Tamanho da história</string>\n    <string name=\"result_history_size_summary\">Quanitade máxima de itens ao mostrar a história</string>\n    <string name=\"action_show_history_recency\">História recente</string>\n    <string name=\"action_show_history_frequency\">Mais acessados</string>\n    <string name=\"action_show_history_adaptive\">História adaptativa</string>\n    <string name=\"show_tags_menu\">Abrir menu de etiquetas</string>\n    <string name=\"show_tags_list\">Menu de etiquetas como resultados</string>\n    <string name=\"show_tags_list_reversed\">Menu de etiquetas invertido como resultados</string>\n    <string name=\"result_history_adaptive\">Duração adaptativa</string>\n    <string name=\"result_history_adaptive_summary\">Quantidade de horas para história adaptativa</string>\n    <string name=\"contact_pack_mask\">Contactos</string>\n    <string name=\"shortcut_pack_mask\">Ícone de atalho</string>\n    <string name=\"shortcut_shape_name\">Forma de ícone de atalho</string>\n    <string name=\"shortcut_shape_summary\">Forma do ícone de atalho</string>\n    <string name=\"choose_icon_scale\">Ícone da escala</string>\n    <string name=\"choose_icon\">Escolher ícone</string>\n    <string name=\"choose_icon_menu_add2\">Adicionar alternativa e mostrar</string>\n    <string name=\"choose_icon_menu_add\">Adicionar como alternativa</string>\n    <string name=\"icon_background\">Ícones de fundo</string>\n    <string name=\"label_search_hint\">Dica de pesquisa</string>\n    <string name=\"result_search_cap\">Máximo de resultados de pesquisa</string>\n    <string name=\"unlimited_search_cap\">Resultados de pesquisa ilimitada</string>\n    <string name=\"unlimited_search_cap_summary\">Estabelecer o limite máximo possível</string>\n    <string name=\"result_ripple_color\">Cor de toque</string>\n    <string name=\"quick_list_ripple_color\">Cor de toque</string>\n    <string name=\"reset_preferences\">Repor preferências</string>\n    <string name=\"reset_preferences_confirm\">Preferências de descarte\\?</string>\n    <string name=\"reset_preferences_description\">Todas as definições serão reiniciadas</string>\n    <string name=\"reset_cached_app_icons_confirm\">Reiniciar a cache do pacote de ícones\\?</string>\n    <string name=\"generate_theme_description\">Repõe todas as cores e aplica as cores geradas com base nas cores primárias e secundárias</string>\n    <string name=\"reset_matrix_confirm\">Definir valores por defeito\\?</string>\n    <string name=\"reset_matrix_description\">Reinicia todas as definições de ícones neste ecrã</string>\n    <string name=\"popup_ripple_color\">Cor de toque</string>\n    <string name=\"popup_background_argb\">Cor de fundo</string>\n    <string name=\"popup_border_argb\">Cor da fronteira</string>\n    <string name=\"popup_text_color\">Cor do texto</string>\n    <string name=\"popup_title_color\">Cor do título</string>\n    <string name=\"popup_shadow_color\">Cor da sombra do texto</string>\n    <string name=\"popup_shadow_radius_dx_dy\">Tamanho da sombra e offset</string>\n    <string name=\"settings_theme\">Tema das definições</string>\n    <string name=\"settings_default\">Predefinição</string>\n    <string name=\"settings_8bit\">8bit</string>\n    <string name=\"settings_white\">Branco</string>\n    <string name=\"settings_black\">Preto</string>\n    <string name=\"settings_dark\">Escuro</string>\n    <string name=\"title_static_rename\">Escolha um nome personalizado</string>\n    <string name=\"letters_toggle\">Ícones de cartas</string>\n    <string name=\"button_launcher\">Botão lançador</string>\n    <string name=\"icon_scale_green\">Verde escama</string>\n    <string name=\"icon_scale_blue\">Escala azul</string>\n    <string name=\"icon_scale_alpha\">Transparência de escalas</string>\n    <string name=\"corner_radius\">Raio do canto</string>\n    <string name=\"reset_matrix\">Definir valores por defeito</string>\n    <string name=\"shadow_offset\">Seleccionar quantidade de píxeis para compensação de sombras</string>\n    <string name=\"shadow_offset_preview\">Tocar ou arrastar para dentro.</string>\n    <string name=\"shadow_radius\">Tamanho da sombra <em>(tamanho zero irá desactivar a sombra)</em></string>\n    <string name=\"behaviour_clear_search_after_launch\">Limpar barra de pesquisa após o lançamento</string>\n    <string name=\"dm_empty_quick_list\">Mostrar doca</string>\n    <string name=\"dm_search_quick_list\">Mostrar doca</string>\n    <string name=\"dm_widget_quick_list\">Mostrar doca</string>\n    <string name=\"dm_empty_fullscreen\">Ecrã completo</string>\n    <string name=\"dm_search_fullscreen\">Ecrã completo</string>\n    <string name=\"dm_widget_fullscreen\">Ecrã completo</string>\n    <string name=\"title_desktop_mode_empty\">Modo de secretária \\\"Vazio</string>\n    <string name=\"title_desktop_mode_search\">Modo Desktop \\\"Pesquisa</string>\n    <string name=\"root_mode\">Modo Raiz</string>\n    <string name=\"gesture_double_click\">Ecrã de toque duplo</string>\n    <string name=\"tags_menu_icons\">Mostrar ícones</string>\n    <string name=\"contact_button_reset_default\">Acção de reinicialização por defeito</string>\n    <string name=\"removed_item\">Removido %s</string>\n    <string name=\"toast_hibernate_completed\">%s hibernado, relançar para despertar</string>\n    <string name=\"menu_exclude_history\">da história</string>\n    <string name=\"menu_exclude_kiss\">de TinyBit</string>\n    <string name=\"application_not_found\">Incapaz de lançar \\\"%s\\\" app</string>\n    <string name=\"menu_app_uninstall\">Desinstalar</string>\n    <string name=\"entry_not_found\">Não foi possível encontrar \\\"%s\\\"</string>\n    <string name=\"menu_popup_android_settings\">Configurações do aparelho</string>\n    <string name=\"menu_popup_launcher_settings\">Configurações TinyBit</string>\n    <string name=\"menu_popup_tags_manager\">Gestor de etiquetas</string>\n    <string name=\"menu_popup_tags_menu\">Menu de etiquetas</string>\n    <string name=\"user_interface_summary\">Temas, transparência e cores</string>\n    <string name=\"title_ui\">Interface do utilizador</string>\n    <string name=\"cd_item_contact_call\">Chamada</string>\n    <string name=\"cd_item_contact_open\">Aberto</string>\n    <string name=\"cd_add_tag\">Adicionar etiqueta</string>\n    <string name=\"cd_item_contact_message\">Mensagem</string>\n    <string name=\"cd_undo_remove_tag\">Desfazer remover a etiqueta</string>\n    <string name=\"cd_remove_tag\">Retirar a etiqueta</string>\n    <string name=\"cd_rename_tag\">Renomear etiqueta</string>\n    <string name=\"cd_icon_tag\">Ícone da etiqueta</string>\n    <string name=\"title_select_alpha\">Arraste o selector para seleccionar a opacidade desejada</string>\n    <string name=\"search_bar_text_size\">Tamanho do texto</string>\n    <string name=\"menu_app_rename\">Renomear aplicação</string>\n    <string name=\"hint_custom_icon\">Filtrar pelo nome</string>\n    <string name=\"custom_icon_activity_adaptive_no_background\">antecedentes personalizados da actividade</string>\n    <string name=\"tags_section\">Etiquetas</string>\n    <string name=\"fuzzy_search_tags\">Etiquetas de pesquisa de pelúcia</string>\n    <string name=\"popup_title_hist_fav\">Preferências</string>\n    <string name=\"reset_default_launcher_description\">Pede para a aplicação abrir depois de pressionar ao ecrã inicial.</string>\n    <string name=\"action_show_shortcuts\">Atalhos A→Z</string>\n    <string name=\"action_show_apps_grid4_reversed\">Grelha de aplicação Z→A</string>\n    <string name=\"action_show_contacts\">Contactos A→Z</string>\n    <string name=\"action_show_contacts_reversed\">Contactos Z→A</string>\n    <string name=\"action_show_shortcuts_reversed\">Atalhos Z→A</string>\n    <string name=\"action_show_favorites\">Favoritos</string>\n    <string name=\"quick_list_content\">Personalizar o conteúdo</string>\n    <string name=\"quick_list_content_summary\">Alterar a ordem e acrescentar entradas</string>\n    <string name=\"edit_quick_list_tab_filters\">Filtros</string>\n    <string name=\"cache_half_apps\">Pequeno tamanho de cache</string>\n    <string name=\"cache_half_apps_summary\">Fazer a cache LRU com metade da quanitade de apps instaladas</string>\n    <string name=\"lwp_touch\">Enviar eventos de toque para papel de parede</string>\n    <string name=\"cfg_widget_move\">Mudança</string>\n    <string name=\"cfg_widget_resize_switch\">Mudar de tamanho</string>\n    <string name=\"cfg_widget_remove\">Remover</string>\n    <string name=\"result_list_argb\">Cor de fundo e opacidade</string>\n    <string name=\"quick_list_show_badge\">Mostrar crachá de atalho na doca</string>\n    <string name=\"result_highlight_color\">Cor em destaque</string>\n    <string name=\"quick_list_color\">Cor de fundo</string>\n    <string name=\"quick_list_toggle_color\">Alternar a cor de fundo</string>\n    <string name=\"contact_action_color\">Cor do ícone de acção</string>\n    <string name=\"search_bar_text_color\">Cor do texto</string>\n    <string name=\"enable_url\">URL da Web</string>\n    <string name=\"reset_search_engines\">Reiniciar os motores de busca</string>\n    <string name=\"permission_denied\">Conceder esta permissão primeiro</string>\n    <string name=\"menu_exclude\">Excluir…</string>\n    <string name=\"menu_hide\">Esconder</string>\n    <string name=\"menu_show\">Revelar</string>\n    <string name=\"app_name\">TinyBit Launcher</string>\n    <string name=\"app_name_debug\">Lançador de depuração TinyBit</string>\n    <string name=\"hint_ui_search\">Pesquisar em aplicações, contactos, …</string>\n    <string name=\"ui_item_search\">Pesquisar em %1$s por \\\"%2$s\\\"\\?</string>\n    <string name=\"ui_item_visit\">Visite \\\"%1$s\\\"</string>\n    <string name=\"copy_confirmation\">Copiado \\\"%s\\\" para prancheta</string>\n    <string name=\"toast_hibernate_error\">[ERRO] %s não hibernado</string>\n    <string name=\"unable_to_initialize_shortcuts\">Não foi possível aceder a atalhos. TinyBit é o seu lançador por defeito\\?</string>\n    <string name=\"cant_pin_shortcut\">Não foi possível acrescentar atalho. TinyBit é o seu lançador por defeito\\?</string>\n    <string name=\"menu_quick_list_add\">Adicionar à doca</string>\n    <string name=\"menu_quick_list_remove\">Retirar da doca</string>\n    <string name=\"menu_remove_history\">Remover da história</string>\n    <string name=\"stub_application\">Nome da app</string>\n    <string name=\"stub_app_tag\">lista de etiquetas da app</string>\n    <string name=\"menu_tags_add\">Adicionar etiquetas</string>\n    <string name=\"menu_tags_edit\">Editar etiquetas</string>\n    <string name=\"menu_app_details\">Informação da aplicação</string>\n    <string name=\"menu_app_store\">Ver na loja</string>\n    <string name=\"change_wallpaper\">Mudar o papel de parede</string>\n    <string name=\"menu_widget_title\">Menu Widget</string>\n    <string name=\"menu_widget_add\">Adicionar Widget…</string>\n    <string name=\"menu_widget_remove\">Retirar o Widget…</string>\n    <string name=\"menu_widget_configure\">Personalizar o Widget…</string>\n    <string name=\"menu_popup_title\">Menu principal</string>\n    <string name=\"menu_popup_title_settings\">Definições</string>\n    <string name=\"rate_the_app\">Avalie a aplicação</string>\n    <string name=\"privacy_policy\">Política de Privacidade</string>\n    <string name=\"app_version\">Versão %s</string>\n    <string name=\"app_version_summary\">%1$s - %2$s</string>\n    <string name=\"default_icon\">ícone predefinido</string>\n    <string name=\"notification_bar_section\">Barra de Notificação</string>\n    <string name=\"color_and_opacity\">Cor e opacidade</string>\n    <string name=\"gradient\">Gradiente</string>\n    <string name=\"black_notification_icons\">Ícones escuros</string>\n    <string name=\"cd_main_menu\">Menu</string>\n    <string name=\"cd_main_clear\">Limpar barra de pesquisa</string>\n    <string name=\"cd_show_all_apps\">Mostrar todas as aplicações</string>\n    <string name=\"title_select_size\">Arraste o selector para seleccionar o tamanho desejado</string>\n    <string name=\"search_bar_section\">Barra de pesquisa</string>\n    <string name=\"search_bar_height\">Altura da barra</string>\n    <string name=\"result_list_section\">Lista de resultados</string>\n    <string name=\"icons_pack\">Escolher pacote</string>\n    <string name=\"icons_pack_default_name\">Ícones do sistema</string>\n    <string name=\"value\">valor %d</string>\n    <string name=\"value_float_xy\">horizontal %1$.2f | vertical %2$.2f</string>\n    <string name=\"size\">&lt;%d&gt;</string>\n    <string name=\"shadow_preview\">&lt;%3$.1f&gt;\n\\n&lt;%1$.1f×%2$.1f&gt;</string>\n    <string name=\"menu_action_rename\">Renomear</string>\n    <string name=\"menu_action_delete\">Eliminar</string>\n    <string name=\"menu_shortcut_rename\">Renomear atalho</string>\n    <string name=\"menu_custom_icon\">Alterar ícone</string>\n    <string name=\"title_app_rename\">Escolha um nome personalizado para esta aplicação</string>\n    <string name=\"title_rename_tag\">Substituir em todo o lado esta etiqueta</string>\n    <string name=\"hint_rename_tag\">Escolha um nome diferente para esta etiqueta</string>\n    <string name=\"title_rename_search_engine\">Escolha um nome diferente para este motor de busca</string>\n    <string name=\"title_rename_search_hint\">Escolha um nome diferente para esta dica de pesquisa</string>\n    <string name=\"title_edit_url_search_engine\">Escolha um URL diferente para este motor de busca</string>\n    <string name=\"title_shortcut_rename\">Escolha um nome personalizado para este atalho</string>\n    <string name=\"app_rename_confirmation\">O nome da app é agora %s</string>\n    <string name=\"default_static_icon\">%s ícone predefinido</string>\n    <string name=\"custom_icon_activity\">actividade</string>\n    <string name=\"custom_icon_badged\">insígnia</string>\n    <string name=\"default_icon_preview_label\">Predefinição\n\\nícone</string>\n    <string name=\"current_icon_preview_label\">Actual\n\\nícone</string>\n    <string name=\"custom_icon_preview_label\">Seleccionado\n\\nícone</string>\n    <string name=\"custom_name_set_default\">Nome por defeito</string>\n    <string name=\"hint_new_tag\">adicionar etiqueta</string>\n    <string name=\"title_features\">Layout</string>\n    <string name=\"edit_quick_list_tab_actions\">Acções</string>\n    <string name=\"edit_quick_list_tab_favorites\">Favoritos</string>\n    <string name=\"edit_quick_list_tab_tags\">Etiquetas</string>\n    <string name=\"features_summary\">Alternar e personalizar elementos UI</string>\n    <string name=\"quick_list_section\">Doca</string>\n    <string name=\"quick_list_enabled\">Use a doca</string>\n    <string name=\"quick_list_icon_size\">Tamanho máximo do ícone</string>\n    <string name=\"quick_list_height\">Altura</string>\n    <string name=\"fuzzy_search_tags_summary\">Ao pesquisar incluir etiquetas como possíveis candidatos à correspondência</string>\n    <string name=\"tags_enabled\">Mostrar etiquetas para aplicações</string>\n    <string name=\"tags_enabled_summary\">Mostrar etiquetas para cada aplicação na lista de resultados</string>\n    <string name=\"icons_visible\">Mostrar Ícones</string>\n    <string name=\"popup_title_shortcut_dynamic\">Atalhos</string>\n    <string name=\"popup_title_customize\">Personalizar</string>\n    <string name=\"popup_title_link\">Ligações</string>\n    <string name=\"popup_title_debug\">Informação de depuração</string>\n    <string name=\"shortcuts_no_host_permission\">Faça deste o seu lançador principal para aceder a atalhos.</string>\n    <string name=\"pin_shortcut_message\">Quer adicionar este atalho\\?</string>\n    <string name=\"pin_shortcut_label\">Escolher etiqueta de atalho</string>\n    <string name=\"pin_shortcut_icon\">Ícone de atalho</string>\n    <string name=\"quick_list_only_for_results\">Ocultar a doca quando não há resultados</string>\n    <string name=\"quick_list_only_for_results_summary\">Tornar a doca visível quando a lista de resultados tiver entradas</string>\n    <string name=\"exit_the_app\">Lançador próximo</string>\n    <string name=\"exit_the_app_confirm\">Fechar o lançador\\?</string>\n    <string name=\"exit_the_app_description\">O seu lançador por defeito será aberto, ou ser-lhe-á perguntado qual deles quer utilizar.</string>\n    <string name=\"reset_default_launcher_confirm\">Mudar o lançador padrão\\?</string>\n    <string name=\"reset_default_launcher\">Alterar lançador padrão</string>\n    <string name=\"filter_apps\">Filtro de aplicação</string>\n    <string name=\"filter_contacts\">Filtro de contacto</string>\n    <string name=\"filter_shortcuts\">Filtro de atalhos</string>\n    <string name=\"action_reload\">Recarregar fornecedores</string>\n    <string name=\"action_toggle_grid\">Grelha de alternância</string>\n    <string name=\"action_show_apps_grid4\">Grelha de aplicação A→Z</string>\n    <string name=\"edit_quick_list_preview\">Pré-visualização <em>(toque longo para começar a arrastar e largar, toque para remover)</em></string>\n    <string name=\"quick_list_icons_visible\">Mostrar ícones</string>\n    <string name=\"quick_list_icons_visible_summary\">Ícones de itens da doca de exposição</string>\n    <string name=\"quick_list_text_visible\">Mostrar nomes</string>\n    <string name=\"quick_list_text_visible_summary\">Mostrar nomes dos artigos da doca</string>\n    <string name=\"lwp_drag_desc\">Enviar múltiplos eventos de toque</string>\n    <string name=\"lock_portrait\">Retrato de eclusa</string>\n    <string name=\"sensor_orientation\">Orientação de sensores</string>\n    <string name=\"cache_drawable\">Cache ícones</string>\n    <string name=\"screen_off_cache_clear\">Cache de libertação frequentemente</string>\n    <string name=\"screen_off_cache_clear_summary\">Cache vazia quando o ecrã é desligado</string>\n    <string name=\"memory_section\">Memória</string>\n    <string name=\"icon_pack_section\">Pacote de ícones</string>\n    <string name=\"icon_pack_content_list\">Ícones de\n\\n%s</string>\n    <string name=\"title_wallpaper\">Interacções de papel de parede</string>\n    <string name=\"lwp_drag\">Emular evento de arrastamento para papel de parede</string>\n    <string name=\"wp_drag_animate\">Papel de parede de pergaminho</string>\n    <string name=\"wp_animate_center\">Papel de parede central</string>\n    <string name=\"wp_animate_center_desc\">O papel de parede voltará ao centro após a rolagem</string>\n    <string name=\"wp_animate_sides\">Papel de parede de pau lateral</string>\n    <string name=\"reset_search_engines_summary\">Restaurar os motores de busca por defeito</string>\n    <string name=\"cfg_widget_move_switch\">Mudança de movimento</string>\n    <string name=\"cfg_widget_move_exit\">Sair em movimento</string>\n    <string name=\"cfg_widget_resize\">Redimensionar</string>\n    <string name=\"cfg_widget_resize_exit\">Saída \\\"Redimensionar\\\"</string>\n    <string name=\"cfg_widget_move_resize\">Mudar e redimensionar</string>\n    <string name=\"cfg_widget_move_resize_exit\">Saída \\\"Mudar e redimensionar\\\"</string>\n    <string name=\"cfg_widget_screen_left\">Mudar para <b>←</b> ecrã</string>\n    <string name=\"cfg_widget_screen_up\">Mudar para <b>↑</b> ecrã</string>\n    <string name=\"cfg_widget_screen_middle\">Ir para <b>meio</b> ecrã</string>\n    <string name=\"cfg_widget_screen_right\">Ir para <b>→</b> ecrã</string>\n    <string name=\"cfg_widget_screen_down\">Mudar para <b>↓</b> ecrã</string>\n    <string name=\"cfg_widget_back\">Mover <b>para trás</b> todos</string>\n    <string name=\"cfg_widget_front\">Mover <b>acima</b> todos</string>\n    <string name=\"shortcut_with_appName\">%1$s: %2$s</string>\n    <string name=\"debug_section\">Debug</string>\n    <string name=\"debug_widget_add_info\">Acrescentar informação extra de widget</string>\n    <string name=\"debug_widget_info\">Imprensa longa para informações extra widget</string>\n    <string name=\"debug_item_relevance\">Relevância da pesquisa</string>\n    <string name=\"debug_item_icon_info\">Informação de ícones</string>\n    <string name=\"shortcut_section\">Atalho</string>\n    <string name=\"shortcut_pin_auto_confirm\">Atalho de auto-confirmação</string>\n    <string name=\"shortcut_show_badge\">Mostrar crachá da app</string>\n    <string name=\"shortcut_show_badge_summary\">Mostrar o ícone da app que criou o atalho</string>\n    <string name=\"result_icon_size\">Tamanho do ícone</string>\n    <string name=\"result_text_color\">Cor do texto</string>\n    <string name=\"result_text2_color\">Cor do texto secundário</string>\n    <string name=\"result_shadow_color\">Cor da sombra do texto</string>\n    <string name=\"result_shadow_radius_dx_dy\">Tamanho da sombra e offset</string>\n    <string name=\"search_bar_icon_color\">Cor dos ícones</string>\n    <string name=\"menu_popup_quick_list_customize\">Editar a doca</string>\n    <string name=\"result_text_size\">Tamanho do texto</string>\n    <string name=\"result_text2_size\">Tamanho do texto secundário</string>\n    <string name=\"provider_section\">Fornecedores</string>\n    <string name=\"enable_search\">Motor de busca</string>\n    <string name=\"enable_calculator\">Calculadora</string>\n    <string name=\"enable_dial\">Pesquisa por marcação</string>\n    <string name=\"enable_contacts\">Contactos telefónicos</string>\n    <string name=\"selected_contact_mime_types\">Seleccione os contactos a serem mostrados</string>\n    <string name=\"edit_search_engines\">Editar motores de busca</string>\n    <string name=\"edit_search_engines_summary\">Alternar e editar fornecedores de pesquisa</string>\n    <string name=\"add_search_engine\">Adicionar motor de busca</string>\n    <string name=\"edit_search_hint\">Editar dica de pesquisa</string>\n    <string name=\"edit_search_hint_summary\">Alternar e editar dicas de pesquisa</string>\n    <string name=\"reset_search_hint\">Reiniciar a dica de pesquisa</string>\n    <string name=\"reset_search_hint_summary\">Restaurar todas as dicas de pesquisa</string>\n    <string name=\"add_search_hint\">Adicionar dica de pesquisa</string>\n    <string name=\"search_engine_edit_url\">Editar URL</string>\n    <string name=\"confirm_edit_url_search_engine\">Guardar</string>\n    <string name=\"search_engine_set_default\">Faça o padrão</string>\n    <string name=\"hint_add_search_engine_url\">https://search.engine/query=%s</string>\n    <string name=\"label_search_engine_name\">Nome do fornecedor de pesquisa</string>\n    <string name=\"label_search_engine_url\">URL para abrir</string>\n    <string name=\"search_engine_url_help\">%s será substituído pela sua consulta</string>\n    <string name=\"selected_pack\">%s\n\\n[pacote actual]</string>\n    <string name=\"tab_app_icons\">%s\n\\n[ícones de aplicação]</string>\n    <string name=\"tab_static_icons\">Ícone predefinido</string>\n    <string name=\"tab_search_icon\">Ícone de pesquisa</string>\n    <string name=\"tab_button_icon\">Ícone do botão</string>\n    <string name=\"icon_pack_loading\">Carregamento a partir de pacotes de ícones…</string>\n    <string name=\"gesture_fling_down_left\">Fling de |<b> <b> </b> |, ← ↓</b></string>\n    <string name=\"gesture_fling_down_right\">Fling de |<b>→</b>|, <b>↓</b></string>\n    <string name=\"gesture_fling_down_right_summary\">Comece a partir do <b>→</b> lado do ecrã e atire <b>↓</b></string>\n    <string name=\"gesture_click\">Toque em ecrã vazio</string>\n    <string name=\"static_icon_letters_label\">Cartas a utilizar para gerar ícones</string>\n    <string name=\"backup_summary\">Configurações de salvaguarda, cópia de segurança, exportação e importação</string>\n    <string name=\"title_backup\">Cópia de segurança</string>\n    <string name=\"export_settings_section\">Definições de exportação</string>\n    <string name=\"export_chooser\">Exportação %s</string>\n    <string name=\"export_chooser_xml\">Exportar \\\"%s\\\" como XML</string>\n    <string name=\"export_xml\">Exportar como XML</string>\n    <string name=\"export_tags\">Etiquetas de exportação</string>\n    <string name=\"export_tags_summary\">Associações de etiquetas de exportação, sem ícones</string>\n    <string name=\"export_modifications\">Personalizações de exportação</string>\n    <string name=\"export_modifications_summary\">Dock, ícones e nomes personalizados</string>\n    <string name=\"export_apps\">Aplicações de exportação</string>\n    <string name=\"export_apps_summary\">Novos nomes para aplicações, ícones escondidos e personalizados</string>\n    <string name=\"import_settings_section\">Configurações de importação</string>\n    <string name=\"import_settings_set\">Definir configurações importadas</string>\n    <string name=\"import_settings_set_summary\">Limpar definições relevantes antes de importar</string>\n    <string name=\"import_settings_overwrite\">Sobregravar configurações importadas</string>\n    <string name=\"export_widgets_summary\">A importação pode não funcionar ou exigir que cada widget seja adicionado manualmente</string>\n    <string name=\"export_history\">Histórico de exportação</string>\n    <string name=\"action_show_history_frecency\">Recentes e frequentes</string>\n    <string name=\"contact_pack_mask_summary\">Usar a forma de pacote de ícones para contactos</string>\n    <string name=\"no_tags\">Não foram encontradas etiquetas</string>\n    <string name=\"quick_list_animation\">Animar espectáculo/ocultar</string>\n    <string name=\"initial_desktop\">Ambiente de trabalho inicial</string>\n    <string name=\"contacts_shape_name\">Forma de contacto-icónico</string>\n    <string name=\"contacts_shape_summary\">Forma do ícone de contacto</string>\n    <string name=\"shortcut_pack_mask_summary\">Usar máscara de pacote de ícones para ícone de atalho</string>\n    <string name=\"shortcut_pack_badge_mask\">Crachá de atalho</string>\n    <string name=\"shortcut_pack_badge_mask_summary\">Usar máscara de pacote de ícones para crachá de atalho</string>\n    <string name=\"choose_letter_color\">Cor da letra</string>\n    <string name=\"choose_background_color\">Cor de fundo</string>\n    <string name=\"choose_icon_shape\">Escolha a forma</string>\n    <string name=\"reset_cached_app_icons_description\">Reinicia o nome e a versão do pacote de ícones em cache.</string>\n    <string name=\"generate_theme_confirm\">Cores geradas pelo conjunto\\?</string>\n    <string name=\"button_home\">Botão Home</string>\n    <string name=\"icon_contrast\">Contraste</string>\n    <string name=\"icon_brightness\">Luminosidade</string>\n    <string name=\"icon_hue\">Matiz</string>\n    <string name=\"icon_saturation\">Saturação</string>\n    <string name=\"matrix_summary\">Luminosidade, contraste, tonalidade, saturação</string>\n    <string name=\"title_matrix\">Modular cor do ícone</string>\n    <string name=\"icon_scale_red\">Escala vermelha</string>\n    <string name=\"behaviour_section\">Comportamento</string>\n    <string name=\"behaviour_link_keyboard_search_bar\">Ligar teclado à visibilidade da barra de pesquisa</string>\n    <string name=\"behaviour_widget_after_launch\">Modo Widget após o lançamento</string>\n    <string name=\"dm_search_fullscreen_summary\">Apenas quando o teclado está fechado</string>\n    <string name=\"title_desktop_mode_widget\">Modo de secretária \\\"Widget</string>\n    <string name=\"search_bar_at_bottom\">Posição no fundo</string>\n    <string name=\"search_bar_ripple_color\">Cor de toque</string>\n    <string name=\"search_bar_cursor_argb\">Cor do Cursor</string>\n    <string name=\"search_bar_shadow_color\">Cor da sombra do texto</string>\n    <string name=\"search_bar_shadow_radius_dx_dy\">Tamanho da sombra e offset</string>\n    <string name=\"back_device_key\">Botão Voltar</string>\n    <string name=\"action_show_untagged\">Desmarcado</string>\n    <string name=\"dm_search_open_result\">Lista inicial de resultados</string>\n    <string name=\"action_app_to_run\">Aplicação a executar</string>\n    <string name=\"action_shortcut_to_run\">Atalho para correr</string>\n    <string name=\"action_entry_to_show\">Etiquetas para mostrar</string>\n    <string name=\"behaviour_summary\">Gestos e o que fazer - quando</string>\n    <string name=\"title_behaviour\">Comportamento</string>\n    <string name=\"root_mode_summary\">Utilizado para hibernar aplicações</string>\n    <string name=\"root_mode_error\">Não foi possível obter acesso à raiz</string>\n    <string name=\"tags_menu_icon_size\">Tamanho do ícone</string>\n    <string name=\"tags_menu_list\">Etiquetas para mostrar</string>\n    <string name=\"tags_menu_order\">Encomenda de etiquetas</string>\n    <string name=\"tags_menu_untagged\">Mostrar sem marcação</string>\n    <string name=\"result_popup_order\">Ordem de categoria de toque longo</string>\n    <string name=\"result_popup_order_summary\">Encomenda por categoria para artigos de toque longo popup</string>\n    <string name=\"debug_favorites\">Favoritos</string>\n    <string name=\"title_presets\">Pré-selecção da cor</string>\n    <string name=\"summary_presets\">Predefinição e geração de cores</string>\n    <string name=\"behaviour_link_close_keyboard_back_button\">Acção \\\"Voltar\\\" ao fechar o teclado</string>\n    <string name=\"keyboard_suggestions\">Auto-completamento/sugestões</string>\n    <string name=\"device_admin_explanation\">Usado para bloquear o ecrã</string>\n    <string name=\"device_admin\">Administração do aparelho</string>\n    <string name=\"device_admin_disable\">Retirar esta permissão\\?</string>\n    <string name=\"device_admin_required\">Acesso aos aparelhos necessário</string>\n    <string name=\"loading_icon\">Ícone de carregamento</string>\n    <string name=\"keyboard_section\">Teclado</string>\n    <string name=\"debug_ksh_touch\">Teclado-rolo de rolagem</string>\n    <string name=\"debug_ksh_touch_summary\">Mostrar o estado como cor de fundo</string>\n    <string name=\"lwp_scroll_pages\">Páginas</string>\n    <string name=\"lwp_page_count_vertical\">páginas ↕</string>\n    <string name=\"lwp_page_count_horizontal\">⟷ páginas</string>\n    <string name=\"lwp_pages_vertical_1\">página central</string>\n    <string name=\"lwp_pages_vertical_2\">2 páginas (↑ e ↓)</string>\n    <string name=\"lwp_pages_horizontal_1\">página central</string>\n    <string name=\"lwp_pages_horizontal_2\">2 páginas (← e →)</string>\n    <string name=\"reset_cached_app_icons\">Pacote de ícones de reinicialização</string>\n    <string name=\"matrix_contacts\">Modular contactos</string>\n    <string name=\"matrix_contacts_summary\">Modular as cores dos ícones de contacto</string>\n    <string name=\"tags_menu_section\">Menu Etiquetas</string>\n    <string name=\"primary_color\">Cor primária</string>\n    <string name=\"secondary_color\">Cor secundária</string>\n    <string name=\"generate_theme_simple\">Tema simples</string>\n    <string name=\"generate_theme_simple_summary\">Usar primário para fundo e secundário para texto</string>\n    <string name=\"generate_theme_highlight\">Tema em destaque</string>\n    <string name=\"generate_theme_highlight_summary\">Usar primário para fundo e secundário para destaque</string>\n    <string name=\"result_first_at_bottom\">Primeiro no fundo</string>\n    <string name=\"result_right_to_left\">Da direita à esquerda</string>\n    <string name=\"result_right_to_left_summary\">Linha da grelha de disposição da direita à esquerda</string>\n    <string name=\"debug_provider_status\">Estatuto de fornecedor</string>\n    <string name=\"exit_tags_manager_confirm\">Fechar o gestor de etiquetas\\?</string>\n    <string name=\"exit_tags_manager_description\">Perderá todas as alterações se fechar o gestor de Etiquetas.\n\\nTem a certeza de querer continuar\\?</string>\n    <string name=\"result_fading_edge\">Gradiente</string>\n    <string name=\"result_fading_edge_summary\">As extremidades superior e inferior desvanecem-se</string>\n    <string name=\"margin_vertical\">Margem vertical</string>\n    <string name=\"margin_horizontal\">Margem horizontal</string>\n    <string name=\"browse_add_icon\">Pesquisar imagens locais</string>\n    <string name=\"result_list_row_height\">Altura da fila da lista</string>\n    <string name=\"result_list_row_height_manual\">Altura da fila da lista personalizada</string>\n    <string name=\"shortcut_dynamic_in_results\">Mostrar dinâmica nos resultados</string>\n    <string name=\"shortcut_dynamic_in_results_summary\">Os atalhos dinâmicos serão considerados como resultados possíveis na pesquisa</string>\n    <string name=\"search_bar_layout\">Layout</string>\n    <string name=\"quick_list_position\">Posição</string>\n    <string name=\"quick_list_columns\">Colunas</string>\n    <string name=\"quick_list_columns_summary\">Quantidade de artigos por fila</string>\n    <string name=\"quick_list_rows\">Linhas</string>\n    <string name=\"quick_list_rows_summary\">A altura máxima mudará em conformidade</string>\n    <string name=\"quick_list_rtl\">Da direita à esquerda</string>\n    <string name=\"quick_list_rtl_summary\">Layout itens da direita à esquerda</string>\n    <string name=\"navigation_bar_section\">Barra de navegação</string>\n    <string name=\"icons_section\">Ícones</string>\n    <string name=\"options_section\">Opções</string>\n    <string name=\"search_bar_animation\">Animar expansão/colapso</string>\n    <string name=\"contact_button_set_default\">Fazer acções por defeito</string>\n    <string name=\"done_key_contact_action\">Feito acção chave de contacto</string>\n    <string name=\"force_adaptive\">Definir plano de fundo do ícone</string>\n    <string name=\"menu_remove_shortcut\">Desafixar</string>\n    <string name=\"menu_app_hibernate\">Hibernar</string>\n    <string name=\"value_float\">valor %.2f</string>\n    <string name=\"size_float\">&lt;%.1f&gt;</string>\n    <string name=\"action_show_apps\">Apps A→Z</string>\n    <string name=\"bind_widget_failed\">Permissões insuficientes para criar widgets</string>\n    <string name=\"adaptive_shape_name\">Forma de ícone</string>\n    <string name=\"force_adaptive_summary\">Reduzir a escala de ícones e colocar sobre um fundo moldado</string>\n    <string name=\"shortcut_rename_confirmation\">O nome do atalho é agora \\\"%s\\\"</string>\n    <string name=\"entry_rename_confirmation\">O nome é agora \\\"%s\\\"</string>\n    <string name=\"custom_icon_application\">app</string>\n    <string name=\"action_show_apps_reversed\">Apps Z→A</string>\n    <string name=\"wp_drag_animate_desc\">Arrastar <b>←</b> <b>→</b> para percorrer o papel de parede</string>\n    <string name=\"wp_animate_sides_desc\">O papel de parede ficará no <b>←</b> ou <b>→</b> após a rolagem</string>\n    <string name=\"invalid_rename_tag\">Já existe uma etiqueta \\\"%s\\\"</string>\n    <string name=\"invalid_rename_search_engine\">Já existe um motor de busca \\\"%s\\\"</string>\n    <string name=\"settings_deep_blues\">DeepBlues</string>\n    <string name=\"gesture_fling_down_left_summary\">Iniciar a partir do <b>←</b> lado do ecrã e atirar <b>↓</b></string>\n    <string name=\"gesture_fling_up\">Fling ↑</string>\n    <string name=\"gesture_fling_left\">Fling ←</string>\n    <string name=\"gesture_fling_right\">Fling →</string>\n    <string name=\"debug_favorites_summary\">Mostrar entradas da tabela DB `favorites\\'</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-pt-rBR/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"user_interface_summary\">Temas, transparência e cores</string>\n    <string name=\"title_ui\">Interface de usuário</string>\n    <string name=\"notification_bar_section\">Barra de notificações</string>\n    <string name=\"title_select_size\">Arraste o controle deslizante para selecionar o tamanho desejado</string>\n    <string name=\"search_bar_section\">Barra de busca</string>\n    <string name=\"search_bar_text_size\">Tamanho do texto</string>\n    <string name=\"search_bar_height\">Altura da barra</string>\n    <string name=\"result_list_section\">Lista de resultados</string>\n    <string name=\"icons_pack\">Escolher pacote</string>\n    <string name=\"icons_pack_default_name\">Ícones do sistema</string>\n    <string name=\"value\">valor %d</string>\n    <string name=\"cd_add_tag\">Adicionar tag</string>\n    <string name=\"cd_remove_tag\">Remover tag</string>\n    <string name=\"cd_rename_tag\">Renomear tag</string>\n    <string name=\"title_select_alpha\">Arraste o controle deslizante para selecionar a opacidade desejada</string>\n    <string name=\"size\">&lt;%d&gt;</string>\n    <string name=\"menu_app_rename\">Renomear o app</string>\n    <string name=\"cd_undo_remove_tag\">Desfazer remover tag</string>\n    <string name=\"cd_icon_tag\">Definir ícone de tag</string>\n    <string name=\"quick_list_height\">Altura do encaixe</string>\n    <string name=\"quick_list_icon_size\">Tamanho máximo do ícone</string>\n    <string name=\"quick_list_enabled\">Usar encaixe</string>\n    <string name=\"app_name\">Iniciador TinyBit</string>\n    <string name=\"app_name_debug\">Iniciador de depuração TinyBit</string>\n    <string name=\"ui_item_search\">Buscar em %1$s por \\\"%2$s\\\"\\?</string>\n    <string name=\"removed_item\">Removido %s</string>\n    <string name=\"toast_hibernate_completed\">%s hibernou, reinicie para acordar</string>\n    <string name=\"toast_hibernate_error\">[ERRO] %s não hibernou</string>\n    <string name=\"unable_to_initialize_shortcuts\">Não foi possível acessar os atalhos. O TinyBit é seu iniciador padrão\\?</string>\n    <string name=\"menu_remove_history\">Remover do histórico</string>\n    <string name=\"stub_application\">Nome do app</string>\n    <string name=\"menu_show\">Revelar</string>\n    <string name=\"menu_remove_shortcut\">Desafixar</string>\n    <string name=\"menu_tags_add\">Adicionar tags</string>\n    <string name=\"menu_tags_edit\">Editar tags</string>\n    <string name=\"menu_app_details\">Detalhes do app</string>\n    <string name=\"menu_app_store\">Ver na loja</string>\n    <string name=\"menu_app_uninstall\">Desinstalar</string>\n    <string name=\"menu_app_hibernate\">Hibernar</string>\n    <string name=\"menu_exclude_history\">do histórico</string>\n    <string name=\"menu_exclude_kiss\">do TinyBit</string>\n    <string name=\"application_not_found\">Não foi possível iniciar o app \\\"%s\\\"</string>\n    <string name=\"entry_not_found\">Não foi possível encontrar \\\"%s\\\"</string>\n    <string name=\"change_wallpaper\">Mudar papel de parede</string>\n    <string name=\"menu_widget_title\">Menu de widgets</string>\n    <string name=\"menu_widget_add\">Adicionar widget…</string>\n    <string name=\"menu_popup_title\">Menu principal</string>\n    <string name=\"menu_popup_title_settings\">Configurações</string>\n    <string name=\"menu_popup_tags_manager\">Gerenciador de tags</string>\n    <string name=\"menu_popup_tags_menu\">Menu de tags</string>\n    <string name=\"menu_quick_list_add\">Adicionar ao encaixe</string>\n    <string name=\"menu_quick_list_remove\">Remover do encaixe</string>\n    <string name=\"fuzzy_search_tags\">Tags de pesquisa difusa</string>\n    <string name=\"fuzzy_search_tags_summary\">Ao pesquisar, inclua tags como possíveis candidatos de correspondência</string>\n    <string name=\"tags_enabled\">Mostrar tags para apps</string>\n    <string name=\"tags_enabled_summary\">Mostrar tags para cada app na lista de resultados</string>\n    <string name=\"icons_visible\">Mostrar ícones</string>\n    <string name=\"popup_title_shortcut_dynamic\">Atalhos</string>\n    <string name=\"quick_list_only_for_results\">Esconder encaixe quando não houver resultados</string>\n    <string name=\"quick_list_only_for_results_summary\">Tornar o encaixe visível quando a lista de resultados tiver entradas</string>\n    <string name=\"pin_shortcut_label\">Escolher categoria de atalho</string>\n    <string name=\"pin_shortcut_icon\">Ícone de atalho</string>\n    <string name=\"exit_the_app\">Fechar iniciador</string>\n    <string name=\"reset_default_launcher_confirm\">Alterar o iniciador padrão\\?</string>\n    <string name=\"filter_apps\">Filtrar apps</string>\n    <string name=\"reset_default_launcher\">Alterar iniciador padrão</string>\n    <string name=\"filter_contacts\">Filtrar contatos</string>\n    <string name=\"reset_default_launcher_description\">Solicita que o app abra após toque na tela inicial.</string>\n    <string name=\"action_show_apps_reversed\">Apps Z→A</string>\n    <string name=\"action_show_apps\">Apps A→Z</string>\n    <string name=\"action_show_apps_grid4\">Grade dos apps A→Z</string>\n    <string name=\"action_show_apps_grid4_reversed\">Grade dos apps Z→A</string>\n    <string name=\"action_show_contacts\">Contatos A→Z</string>\n    <string name=\"action_show_contacts_reversed\">Contatos Z→A</string>\n    <string name=\"action_show_shortcuts\">Atalhos A→Z</string>\n    <string name=\"action_show_shortcuts_reversed\">Atalhos Z→A</string>\n    <string name=\"action_show_favorites\">Favoritos</string>\n    <string name=\"quick_list_content\">Personalizar conteúdo</string>\n    <string name=\"quick_list_content_summary\">Alterar ordem e adicionar entradas</string>\n    <string name=\"edit_quick_list_tab_filters\">Filtros</string>\n    <string name=\"icon_pack_section\">Pacote de ícones</string>\n    <string name=\"icon_pack_content_list\">Ícones de\n\\n%s</string>\n    <string name=\"title_wallpaper\">Interações de papel de parede</string>\n    <string name=\"wp_animate_center_desc\">O papel de parede retornará ao centro após a rolagem</string>\n    <string name=\"wp_drag_animate_desc\">Arraste <b>←</b> <b>→</b> para rolar o papel de parede</string>\n    <string name=\"wp_animate_center\">Papel de parede central</string>\n    <string name=\"wp_animate_sides\">papel de parede lateral</string>\n    <string name=\"wp_animate_sides_desc\">O papel de parede ficará no <b>←</b> ou <b>→</b> após a rolagem</string>\n    <string name=\"cfg_widget_screen_left\">Mover para a tela <b>←</b></string>\n    <string name=\"cfg_widget_screen_up\">Mover para a tela <b>↑</b></string>\n    <string name=\"cfg_widget_screen_middle\">Mover para a tela do <b>meio</b></string>\n    <string name=\"cfg_widget_screen_right\">Mover para a tela <b>→</b></string>\n    <string name=\"cfg_widget_screen_down\">Mover para a tela <b>↓</b></string>\n    <string name=\"debug_item_icon_info\">Detalhes do ícone</string>\n    <string name=\"shortcut_section\">Atalho</string>\n    <string name=\"shortcut_pin_auto_confirm\">Confirmação automática de atalhos</string>\n    <string name=\"shortcut_show_badge\">Mostrar selo do app</string>\n    <string name=\"shortcut_show_badge_summary\">Mostrar ícone do app que criou o atalho</string>\n    <string name=\"result_icon_size\">Tamanho do ícone</string>\n    <string name=\"result_text_color\">Cor do texto</string>\n    <string name=\"result_text2_color\">Cor do texto secundário</string>\n    <string name=\"search_bar_text_color\">Cor do texto</string>\n    <string name=\"search_bar_icon_color\">Cor dos ícones</string>\n    <string name=\"result_text_size\">Tamanho do texto</string>\n    <string name=\"result_text2_size\">Tamanho do texto secundário</string>\n    <string name=\"invalid_rename_tag\">Já existe uma tag \\\"%s\\\"</string>\n    <string name=\"enable_contacts\">Contatos de telefone</string>\n    <string name=\"enable_search\">Motor de busca</string>\n    <string name=\"enable_url\">URL da Web</string>\n    <string name=\"enable_calculator\">Calculadora</string>\n    <string name=\"enable_dial\">Pesquisa de discagem</string>\n    <string name=\"edit_search_engines_summary\">Alternar e editar provedores de pesquisa</string>\n    <string name=\"reset_search_engines\">Redefinir mecanismos de pesquisa</string>\n    <string name=\"edit_search_hint_summary\">Alternar e editar dicas de pesquisa</string>\n    <string name=\"reset_search_hint\">Redefinir dica de pesquisa</string>\n    <string name=\"reset_search_hint_summary\">Restaurar todas as dicas de pesquisa</string>\n    <string name=\"add_search_hint\">Adicionar dica de pesquisa</string>\n    <string name=\"confirm_edit_url_search_engine\">Salvar</string>\n    <string name=\"search_engine_set_default\">Tornar padrão</string>\n    <string name=\"hint_add_search_engine_url\">https://search.engine/query=%s</string>\n    <string name=\"selected_pack\">%s\n\\n[pacote atual]</string>\n    <string name=\"tab_app_icons\">%s\n\\n[ícones dos apps]</string>\n    <string name=\"tab_static_icons\">Ícone padrão</string>\n    <string name=\"tab_search_icon\">Ícone de pesquisa</string>\n    <string name=\"gesture_fling_down_right_summary\">Comece do lado <b>→</b> da tela e lance <b>↓</b></string>\n    <string name=\"gesture_fling_up\">Lançar ↑</string>\n    <string name=\"gesture_fling_left\">Lançar ←</string>\n    <string name=\"gesture_fling_right\">Lançar →</string>\n    <string name=\"gesture_click\">Toque na tela vazia</string>\n    <string name=\"static_icon_letters_label\">Letras a usar para gerar ícones</string>\n    <string name=\"export_chooser_xml\">Exportar \\\"%s\\\" como XML</string>\n    <string name=\"export_xml\">Exportar como XML</string>\n    <string name=\"export_tags\">Exportar tags</string>\n    <string name=\"export_modifications_summary\">Encaixe, ícones e nomes personalizados</string>\n    <string name=\"export_apps\">Exportar apps</string>\n    <string name=\"export_apps_summary\">Novos nomes para apps, ícones ocultos e personalizados</string>\n    <string name=\"import_settings_section\">Configurações de importação</string>\n    <string name=\"import_settings_append\">Anexar configurações importadas</string>\n    <string name=\"import_settings_append_summary\">Somente novas configurações são importadas</string>\n    <string name=\"please_wait\">Por favor, aguarde.</string>\n    <string name=\"import_dialog_description\">Carregando configurações…</string>\n    <string name=\"export_backup\">Exportar backup</string>\n    <string name=\"import_chooser\">Selecione um arquivo para importar</string>\n    <string name=\"export_description\">Depois de clicar em \\\"OK\\\", você pode selecionar um gerenciador de arquivos para salvar suas configurações como um arquivo XML.</string>\n    <string name=\"error\">Erro %s</string>\n    <string name=\"export_preferences\">Exportar configurações</string>\n    <string name=\"export_backup_summary\">Todos os ícones, tags e preferências personalizados</string>\n    <string name=\"export_widgets_summary\">A importação pode não funcionar ou exigir que cada widget seja adicionado manualmente</string>\n    <string name=\"action_show_history_recency\">Histórico recente</string>\n    <string name=\"result_history_size_summary\">Número máximo de itens mostrado no histórico</string>\n    <string name=\"action_show_history_frequency\">Mais acessados</string>\n    <string name=\"action_show_history_frecency\">Recente e frequente</string>\n    <string name=\"action_show_history_adaptive\">Histórico adaptável</string>\n    <string name=\"show_tags_menu\">Abrir menu de tags</string>\n    <string name=\"show_tags_list\">Menu de tags como resultados</string>\n    <string name=\"adaptive_shape_name\">Forma do ícone</string>\n    <string name=\"choose_letter_color\">Cor da letra</string>\n    <string name=\"shortcut_shape_name\">Forma do ícone de atalho</string>\n    <string name=\"shortcut_shape_summary\">Forma do ícone de atalho</string>\n    <string name=\"shortcut_pack_badge_mask\">Selo de atalho</string>\n    <string name=\"choose_icon\">Escolha o ícone</string>\n    <string name=\"choose_icon_menu_add\">Adicionar como alternativa</string>\n    <string name=\"choose_icon_menu_add2\">Adicionar alternativa e mostrar</string>\n    <string name=\"popup_section\">Popup</string>\n    <string name=\"unlimited_search_cap_summary\">Definir o limite mais alto possível</string>\n    <string name=\"matrix_summary\">Brilho, contraste, matiz, saturação</string>\n    <string name=\"title_matrix\">Modular a cor do ícone</string>\n    <string name=\"icon_scale_red\">Escala vermelha</string>\n    <string name=\"icon_scale_green\">Escala verde</string>\n    <string name=\"reset_matrix\">Definir valores padrão</string>\n    <string name=\"corner_radius\">Raio de canto</string>\n    <string name=\"behaviour_section\">Comportamento</string>\n    <string name=\"behaviour_widget_after_launch\">Modo widget após iniciar</string>\n    <string name=\"action_show_untagged\">Não tem tag</string>\n    <string name=\"dm_search_open_result\">Lista de resultados iniciais</string>\n    <string name=\"action_app_to_run\">App para executar</string>\n    <string name=\"action_shortcut_to_run\">Atalho para executar</string>\n    <string name=\"action_entry_to_show\">Tags para mostrar</string>\n    <string name=\"no_tags\">Nenhum tag encontrado</string>\n    <string name=\"quick_list_animation\">Animação mostrar/ocultar</string>\n    <string name=\"initial_desktop\">Desktop inicial</string>\n    <string name=\"behaviour_summary\">Gestos e o que fazer quando</string>\n    <string name=\"title_behaviour\">Comportamento</string>\n    <string name=\"root_mode\">Modo root</string>\n    <string name=\"root_mode_summary\">Usado para hibernar apps</string>\n    <string name=\"root_mode_error\">Não foi possível obter acesso root</string>\n    <string name=\"behaviour_link_close_keyboard_back_button\">Ação \\\"Voltar\\\" ao fechar o teclado</string>\n    <string name=\"keyboard_suggestions\">Preenchimento automático/sugestões</string>\n    <string name=\"device_admin_explanation\">Usado para bloquear a tela</string>\n    <string name=\"device_admin\">Administrador do dispositivo</string>\n    <string name=\"gesture_double_click\">Toque duplo na tela</string>\n    <string name=\"loading_icon\">Carregando o ícone</string>\n    <string name=\"keyboard_section\">Teclado</string>\n    <string name=\"lwp_scroll_pages\">Páginas</string>\n    <string name=\"debug_ksh_touch\">Esconder rolagem do teclado</string>\n    <string name=\"generate_theme_simple\">Tema simples</string>\n    <string name=\"generate_theme_simple_summary\">Use primário para plano de fundo e secundário para texto</string>\n    <string name=\"generate_theme_highlight\">Destaque de tema</string>\n    <string name=\"generate_theme_highlight_summary\">Use primário para plano de fundo e secundário para destaque</string>\n    <string name=\"title_rename_search_engine\">Escolha um nome diferente para este mecanismo de pesquisa</string>\n    <string name=\"title_rename_tag\">Substituir em todos os lugares em quais esta tag aparecer</string>\n    <string name=\"hint_rename_tag\">Escolha um nome diferente para esta tag</string>\n    <string name=\"menu_custom_icon\">Alterar ícone</string>\n    <string name=\"title_app_rename\">Escolha um nome personalizado para este app</string>\n    <string name=\"title_rename_search_hint\">Escolha um nome diferente para esta dica de pesquisa</string>\n    <string name=\"title_edit_url_search_engine\">Escolha uma URL diferente para este mecanismo de pesquisa</string>\n    <string name=\"pin_shortcut_message\">Deseja adicionar este atalho\\?</string>\n    <string name=\"exit_the_app_confirm\">Fechar o iniciador\\?</string>\n    <string name=\"exit_the_app_description\">Seu iniciador padrão será aberto, ou você será perguntado qual você quer usar.</string>\n    <string name=\"screen_off_cache_clear_summary\">Limpar o cache quando a tela desliga</string>\n    <string name=\"cache_half_apps\">Tamanho pequeno do cache</string>\n    <string name=\"quick_list_text_visible_summary\">Mostrar nomes de itens do encaixe</string>\n    <string name=\"cache_half_apps_summary\">Faça com que o tamanho do cache LRU seja metade do número de apps instalados</string>\n    <string name=\"cache_drawable\">Ícones de cache</string>\n    <string name=\"screen_off_cache_clear\">Libere o cache com frequência</string>\n    <string name=\"memory_section\">Memória</string>\n    <string name=\"wp_drag_animate\">Rolar papel de parede</string>\n    <string name=\"cfg_widget_move\">Mover</string>\n    <string name=\"cfg_widget_move_switch\">Mudar de movimento</string>\n    <string name=\"cfg_widget_move_exit\">Sair do movimento</string>\n    <string name=\"cfg_widget_resize\">Redimensionar</string>\n    <string name=\"cfg_widget_resize_switch\">Mudar redimensionamento</string>\n    <string name=\"shortcut_with_appName\">%1$s: %2$s</string>\n    <string name=\"debug_section\">Depurar</string>\n    <string name=\"quick_list_toggle_color\">Alternar cor de fundo</string>\n    <string name=\"contact_action_color\">Cor do ícone de ação</string>\n    <string name=\"result_shadow_color\">Cor da sombra do texto</string>\n    <string name=\"result_shadow_radius_dx_dy\">Tamanho e deslocamento da sombra</string>\n    <string name=\"menu_popup_quick_list_customize\">Editar encaixe</string>\n    <string name=\"selected_contact_mime_types\">Selecione os contatos a serem mostrados</string>\n    <string name=\"edit_search_engines\">Editar mecanismos de pesquisa</string>\n    <string name=\"reset_search_engines_summary\">Restaurar mecanismos de pesquisa padrão</string>\n    <string name=\"gesture_fling_down_left\">Lance de |<b>←</b>|, <b>↓</b></string>\n    <string name=\"gesture_fling_down_right\">Lance de |<b>→</b>|, <b>↓</b></string>\n    <string name=\"gesture_fling_down_left_summary\">Comece do lado <b>←</b> da tela e lance <b>↓</b></string>\n    <string name=\"backup_summary\">Salvar, fazer backup, exportar e importar configurações</string>\n    <string name=\"title_backup\">Cópia de segurança</string>\n    <string name=\"export_chooser\">Exportar %s</string>\n    <string name=\"export_interface\">Interface de exportação</string>\n    <string name=\"export_widgets\">Exportar widgets</string>\n    <string name=\"export_preferences_summary\">Todas as configurações, funcionalidades e comportamentos, incluindo interface</string>\n    <string name=\"export_interface_summary\">Todas as cores, tamanhos, alternâncias e formas de ícones da interface</string>\n    <string name=\"export_history_summary\">Histórico de todos os apps iniciados</string>\n    <string name=\"choose_file_activity_not_found\">Instale um gerenciador de arquivos.</string>\n    <string name=\"result_history_size\">Tamanho do histórico</string>\n    <string name=\"show_tags_list_reversed\">Menu de tags invertidas como resultados</string>\n    <string name=\"result_history_adaptive\">Duração adaptável</string>\n    <string name=\"choose_icon_shape\">Escolha a forma</string>\n    <string name=\"icon_background\">Plano de fundo do ícone</string>\n    <string name=\"choose_background_color\">Cor de fundo</string>\n    <string name=\"choose_icon_scale\">Escalar o ícone</string>\n    <string name=\"settings_black\">Preto</string>\n    <string name=\"settings_dark\">Escuro</string>\n    <string name=\"dm_search_fullscreen_summary\">Somente quando o teclado está fechado</string>\n    <string name=\"dm_widget_fullscreen\">Tela cheia</string>\n    <string name=\"title_desktop_mode_empty\">Modo Desktop «Vazio»</string>\n    <string name=\"title_desktop_mode_search\">Modo Desktop «Buscar»</string>\n    <string name=\"title_desktop_mode_widget\">Modo Desktop «Widget»</string>\n    <string name=\"lwp_pages_vertical_2\">2 páginas (↑ e ↓)</string>\n    <string name=\"lwp_pages_horizontal_1\">página central</string>\n    <string name=\"lwp_pages_horizontal_2\">2 páginas (← e →)</string>\n    <string name=\"reset_cached_app_icons\">Resetar pacote de ícones</string>\n    <string name=\"matrix_contacts\">Modular contatos</string>\n    <string name=\"matrix_contacts_summary\">Modular as cores dos ícones de contato</string>\n    <string name=\"tags_menu_untagged\">Mostrar os sem tag</string>\n    <string name=\"tags_menu_order\">Ordem das tags</string>\n    <string name=\"result_right_to_left_summary\">Linha de grade de layout da direita para a esquerda</string>\n    <string name=\"debug_provider_status\">Status do provedor</string>\n    <string name=\"exit_tags_manager_confirm\">Fechar o gerenciador de tags\\?</string>\n    <string name=\"exit_tags_manager_description\">Você perderá todas as alterações se fechar o gerenciador de tags.\n\\nVocê tem certeza que quer continuar\\?</string>\n    <string name=\"result_fading_edge\">Gradiente</string>\n    <string name=\"result_fading_edge_summary\">As bordas superior e inferior desaparecem</string>\n    <string name=\"margin_vertical\">Margem vertical</string>\n    <string name=\"shortcut_dynamic_in_results\">Mostrar dinâmica nos resultados</string>\n    <string name=\"shortcut_dynamic_in_results_summary\">Atalhos dinâmicos serão considerados como possíveis resultados ao pesquisar</string>\n    <string name=\"search_bar_layout\">Layout</string>\n    <string name=\"quick_list_position\">Posição</string>\n    <string name=\"quick_list_columns\">Colunas</string>\n    <string name=\"quick_list_columns_summary\">Número de itens por linha</string>\n    <string name=\"navigation_bar_section\">Barra de navegação</string>\n    <string name=\"quick_list_rows_summary\">A altura máxima mudará de acordo</string>\n    <string name=\"quick_list_rtl\">Direita para esquerda</string>\n    <string name=\"quick_list_rtl_summary\">Itens de layout da direita para a esquerda</string>\n    <plurals name=\"tag_entry_count\">\n        <item quantity=\"one\">%d uma entrada</item>\n        <item quantity=\"other\">%d entradas</item>\n    </plurals>\n    <string name=\"search_bar_animation\">Animação expandir/recolher</string>\n    <string name=\"contact_button_set_default\">Tornar ação padrão</string>\n    <string name=\"contact_button_reset_default\">Redefinir ação padrão</string>\n    <string name=\"done_key_contact_action\">Ação de contato chave concluída</string>\n    <string name=\"app_version\">Versão %s</string>\n    <string name=\"app_version_summary\">%1$s - %2$s</string>\n    <string name=\"color_and_opacity\">Cor e opacidade</string>\n    <string name=\"gradient\">Gradiente</string>\n    <string name=\"black_notification_icons\">Ícones escuros</string>\n    <string name=\"cd_main_menu\">Menu</string>\n    <string name=\"cd_main_clear\">Limpar a barra de busca</string>\n    <string name=\"cd_show_all_apps\">Mostrar todos os apps</string>\n    <string name=\"cd_item_contact_message\">Mensagem</string>\n    <string name=\"value_float\">valor %.2f</string>\n    <string name=\"value_float_xy\">horizontal %1$.2f | vertical %2$.2f</string>\n    <string name=\"shadow_preview\">&lt;%3$.1f&gt;\n\\n&lt;%1$.1f×%2$.1f&gt;</string>\n    <string name=\"menu_action_rename\">Renomear</string>\n    <string name=\"menu_action_delete\">Excluir</string>\n    <string name=\"title_shortcut_rename\">Escolha um nome personalizado para este atalho</string>\n    <string name=\"app_rename_confirmation\">O nome do app agora é %s</string>\n    <string name=\"custom_icon_activity_adaptive_no_background\">fundo personalizado da atividade</string>\n    <string name=\"custom_icon_badged\">selo</string>\n    <string name=\"default_icon_preview_label\">Padrão\n\\nícone</string>\n    <string name=\"hint_new_tag\">adicionar tag</string>\n    <string name=\"title_features\">Layout</string>\n    <string name=\"features_summary\">Alterne e personalize elementos da interface do usuário</string>\n    <string name=\"tags_section\">Tags</string>\n    <string name=\"popup_title_hist_fav\">Preferências</string>\n    <string name=\"popup_title_customize\">Personalizar</string>\n    <string name=\"popup_title_link\">Links</string>\n    <string name=\"quick_list_section\">Encaixe</string>\n    <string name=\"filter_shortcuts\">Filtrar atalhos</string>\n    <string name=\"edit_quick_list_preview\">Visualizar <em>(toque longo para começar a arrastar e soltar, toque para remover)</em></string>\n    <string name=\"quick_list_icons_visible_summary\">Mostrar ícones de itens do encaixe</string>\n    <string name=\"quick_list_text_visible\">Mostrar nomes</string>\n    <string name=\"lwp_touch\">Enviar eventos de toque para papel de parede</string>\n    <string name=\"lwp_drag\">Emular evento de arrastar para papel de parede</string>\n    <string name=\"lwp_drag_desc\">Envie vários eventos de toque</string>\n    <string name=\"lock_portrait\">Fixar retrato</string>\n    <string name=\"sensor_orientation\">Orientação do sensor</string>\n    <string name=\"cfg_widget_resize_exit\">Sair de \\\"Redimensionar\\\"</string>\n    <string name=\"cfg_widget_move_resize\">Mover e redimensionar</string>\n    <string name=\"cfg_widget_move_resize_exit\">Saia de \\\"Mover e redimensionar\\\"</string>\n    <string name=\"cfg_widget_remove\">Remover</string>\n    <string name=\"cfg_widget_back\">Mover <b>para trás</b> todos</string>\n    <string name=\"debug_widget_add_info\">Adicionar informações extras do widget</string>\n    <string name=\"debug_item_relevance\">Relevância da pesquisa</string>\n    <string name=\"quick_list_show_badge\">Mostrar selo do atalho no encaixe</string>\n    <string name=\"invalid_rename_search_engine\">Já existe um mecanismo de pesquisa \\\"%s\\\"</string>\n    <string name=\"provider_section\">Provedores</string>\n    <string name=\"add_search_engine\">Adicionar mecanismo de pesquisa</string>\n    <string name=\"edit_search_hint\">Editar dica de pesquisa</string>\n    <string name=\"search_engine_edit_url\">Editar URL</string>\n    <string name=\"label_search_engine_url\">URL para abrir</string>\n    <string name=\"search_engine_url_help\">%s será substituído pela sua consulta</string>\n    <string name=\"tab_button_icon\">Ícone de botão</string>\n    <string name=\"icon_pack_loading\">Carregando de pacotes de ícones…</string>\n    <string name=\"gesture_section\">Gestos</string>\n    <string name=\"export_settings_section\">Exportar configurações</string>\n    <string name=\"export_tags_summary\">Exportar associações de tags, sem ícones</string>\n    <string name=\"import_settings_set\">Definir configurações importadas</string>\n    <string name=\"import_settings_set_summary\">Limpe as configurações relevantes antes de importar</string>\n    <string name=\"import_settings_overwrite\">Substituir configurações importadas</string>\n    <string name=\"widget_placeholder\">Clique para restaurar %s</string>\n    <string name=\"result_history_adaptive_summary\">Número de horas para o histórico adaptável</string>\n    <string name=\"force_adaptive\">Definir plano de fundo do ícone</string>\n    <string name=\"force_adaptive_summary\">Reduza a escala do ícone e coloque em um plano de fundo em forma</string>\n    <string name=\"contact_pack_mask_summary\">Use a forma do pacote de ícones para contatos</string>\n    <string name=\"contacts_shape_name\">Forma do ícone de contato</string>\n    <string name=\"contacts_shape_summary\">Forma do ícone de contato</string>\n    <string name=\"shortcut_pack_mask\">Ícone de atalho</string>\n    <string name=\"shortcut_pack_mask_summary\">Use a máscara do pacote de ícones para o ícone de atalho</string>\n    <string name=\"shortcut_pack_badge_mask_summary\">Use a máscara do pacote de ícones para o selo de atalho</string>\n    <string name=\"label_search_hint\">Dica de pesquisa</string>\n    <string name=\"result_search_cap\">Máximo de resultados de pesquisa</string>\n    <string name=\"unlimited_search_cap\">Resultados de pesquisa ilimitados</string>\n    <string name=\"quick_list_ripple_color\">Cor do toque</string>\n    <string name=\"reset_cached_app_icons_confirm\">Resetar o cache do pacote de ícones\\?</string>\n    <string name=\"reset_cached_app_icons_description\">Redefine o nome e a versão do pacote de ícones em cache.</string>\n    <string name=\"generate_theme_confirm\">Definir cores geradas\\?</string>\n    <string name=\"generate_theme_description\">Redefine todas as cores e aplica as cores geradas com base nas cores primárias e secundárias</string>\n    <string name=\"reset_matrix_confirm\">Definir valores padrão\\?</string>\n    <string name=\"popup_ripple_color\">Cor do toque</string>\n    <string name=\"popup_background_argb\">Cor de fundo</string>\n    <string name=\"popup_border_argb\">Cor da borda</string>\n    <string name=\"popup_text_color\">Cor do texto</string>\n    <string name=\"settings_8bit\">8 bits</string>\n    <string name=\"settings_deep_blues\">Deep Blues</string>\n    <string name=\"title_static_rename\">Escolha um nome personalizado</string>\n    <string name=\"icon_hue\">Matiz</string>\n    <string name=\"icon_scale_blue\">Escala azul</string>\n    <string name=\"dm_empty_fullscreen\">Tela cheia</string>\n    <string name=\"search_bar_at_bottom\">Posição na parte inferior</string>\n    <string name=\"device_admin_disable\">Remover esta permissão\\?</string>\n    <string name=\"lwp_page_count_vertical\">↕ páginas</string>\n    <string name=\"tags_menu_section\">Menu de tags</string>\n    <string name=\"tags_menu_icons\">Mostrar ícones</string>\n    <string name=\"result_popup_order\">Ordem de categoria de toque longo</string>\n    <string name=\"result_first_at_bottom\">Primeiro em baixo</string>\n    <string name=\"result_right_to_left\">Direita para esquerda</string>\n    <string name=\"quick_list_rows\">Linhas</string>\n    <string name=\"icons_section\">Ícones</string>\n    <string name=\"options_section\">Opções</string>\n    <string name=\"shortcut_rename_confirmation\">O nome do atalho agora é \\\"%s\\\"</string>\n    <string name=\"menu_shortcut_rename\">Renomear atalho</string>\n    <string name=\"cd_item_contact_call\">Ligar</string>\n    <string name=\"default_static_icon\">%s ícone padrão</string>\n    <string name=\"default_icon\">ícone padrão</string>\n    <string name=\"custom_icon_preview_label\">Selecionado\n\\nícone</string>\n    <string name=\"hint_custom_icon\">Filtrar por nome</string>\n    <string name=\"custom_name_set_default\">Nome padrão</string>\n    <string name=\"popup_title_debug\">Detalhes de depuração</string>\n    <string name=\"shortcuts_no_host_permission\">Faça deste seu iniciador principal para acessar atalhos.</string>\n    <string name=\"action_toggle_grid\">Alternar grade</string>\n    <string name=\"action_reload\">Recarregar provedores</string>\n    <string name=\"edit_quick_list_tab_tags\">Tags</string>\n    <string name=\"edit_quick_list_tab_favorites\">Favoritos</string>\n    <string name=\"edit_quick_list_tab_actions\">Ações</string>\n    <string name=\"quick_list_icons_visible\">Mostrar ícones</string>\n    <string name=\"result_highlight_color\">Cor de destaque</string>\n    <string name=\"result_list_argb\">Cor de fundo e opacidade</string>\n    <string name=\"cfg_widget_front\">Mover <b>acima de</b> todos</string>\n    <string name=\"debug_widget_info\">Toque longo para obter detalhes extras do widget</string>\n    <string name=\"quick_list_color\">Cor de fundo</string>\n    <string name=\"export_modifications\">Exportar personalizações</string>\n    <string name=\"label_search_engine_name\">Nome do provedor de pesquisa</string>\n    <string name=\"import_settings_overwrite_summary\">As configurações importadas substituem as configurações atuais</string>\n    <string name=\"force_shape\">Forçar forma</string>\n    <string name=\"contact_pack_mask\">Contatos</string>\n    <string name=\"export_history\">Exportar histórico</string>\n    <string name=\"add_widget_failed\">Não foi possível adicionar o widget</string>\n    <string name=\"force_shape_summary\">Use um plano de fundo personalizado para todos</string>\n    <string name=\"error_fail_import\">Não foi possível importar o arquivo. Verifique o logcat.</string>\n    <string name=\"widget_placeholder_remove\">Remover o marcador de posição\\?</string>\n    <string name=\"settings_white\">Branco</string>\n    <string name=\"result_ripple_color\">Cor do toque</string>\n    <string name=\"reset_preferences_description\">Todas as configurações serão redefinidas</string>\n    <string name=\"reset_preferences\">Resetar preferências</string>\n    <string name=\"reset_preferences_confirm\">Descartar preferências\\?</string>\n    <string name=\"reset_matrix_description\">Redefine todas as configurações de ícone nesta tela</string>\n    <string name=\"popup_title_color\">Cor do título</string>\n    <string name=\"popup_shadow_radius_dx_dy\">Tamanho e deslocamento da sombra</string>\n    <string name=\"settings_default\">Padrão</string>\n    <string name=\"button_home\">Botão de início</string>\n    <string name=\"button_launcher\">Botão do iniciador</string>\n    <string name=\"popup_shadow_color\">Cor da sombra do texto</string>\n    <string name=\"settings_theme\">Configurações de tema</string>\n    <string name=\"letters_toggle\">Ícones de letras</string>\n    <string name=\"shadow_offset_preview\">Toque ou arraste para dentro.</string>\n    <string name=\"shadow_radius\">Tamanho da sombra <em>(tamanho zero desativará a sombra)</em></string>\n    <string name=\"search_bar_ripple_color\">Cor do toque</string>\n    <string name=\"search_bar_cursor_argb\">Cor do cursor</string>\n    <string name=\"search_bar_shadow_color\">Cor da sombra do texto</string>\n    <string name=\"search_bar_shadow_radius_dx_dy\">Tamanho e deslocamento da sombra</string>\n    <string name=\"back_device_key\">Botão Voltar</string>\n    <string name=\"tags_menu_list\">Tags para mostrar</string>\n    <string name=\"icon_contrast\">Contraste</string>\n    <string name=\"icon_brightness\">Brilho</string>\n    <string name=\"icon_saturation\">Saturação</string>\n    <string name=\"shadow_offset\">Selecione a quantidade de pixels para deslocamento de sombra</string>\n    <string name=\"behaviour_link_keyboard_search_bar\">Vincular teclado à visibilidade da barra de pesquisa</string>\n    <string name=\"icon_scale_alpha\">Transparência da escala</string>\n    <string name=\"dm_search_fullscreen\">Tela cheia</string>\n    <string name=\"secondary_color\">Cor secundária</string>\n    <string name=\"device_admin_required\">Necessário acesso de administrador do dispositivo</string>\n    <string name=\"debug_ksh_touch_summary\">Mostrar estado como cor de fundo</string>\n    <string name=\"lwp_page_count_horizontal\">⟷ páginas</string>\n    <string name=\"lwp_pages_vertical_1\">página central</string>\n    <string name=\"tags_menu_icon_size\">Tamanho do ícone</string>\n    <string name=\"result_popup_order_summary\">Ordem de categoria para itens pop-up de toque longo</string>\n    <string name=\"debug_favorites\">Favoritos</string>\n    <string name=\"debug_favorites_summary\">Mostrar entradas da tabela de banco de dados `favoritos`</string>\n    <string name=\"summary_presets\">Predefinição e geração de cores</string>\n    <string name=\"browse_add_icon\">Buscar imagens locais</string>\n    <string name=\"primary_color\">Cor primária</string>\n    <string name=\"title_presets\">Predefinição de cores</string>\n    <string name=\"margin_horizontal\">Margem horizontal</string>\n    <string name=\"result_list_row_height\">Listar altura da linha</string>\n    <string name=\"result_list_row_height_manual\">Altura da linha da lista personalizada</string>\n    <string name=\"menu_hide\">Esconder</string>\n    <string name=\"hint_ui_search\">Busque em apps, contatos, …</string>\n    <string name=\"copy_confirmation\">Copiado \\\"%s\\\" ao clipboard</string>\n    <string name=\"ui_item_visit\">Visite \\\"%1$s\\\"</string>\n    <string name=\"cant_pin_shortcut\">Não foi possível adicionar o atalho. O TinyBit é seu iniciador padrão\\?</string>\n    <string name=\"menu_exclude\">Excluir…</string>\n    <string name=\"menu_widget_configure\">Personalizar widget…</string>\n    <string name=\"stub_app_tag\">lista de tags dos apps</string>\n    <string name=\"permission_denied\">Conceda esta permissão primeiro</string>\n    <string name=\"size_float\">&lt;%.1f&gt;</string>\n    <string name=\"menu_popup_launcher_settings\">Configurações do TinyBit</string>\n    <string name=\"menu_widget_remove\">Remover widget…</string>\n    <string name=\"menu_popup_android_settings\">Configurações do dispositivo</string>\n    <string name=\"rate_the_app\">Avalie o app</string>\n    <string name=\"privacy_policy\">Política de privacidade</string>\n    <string name=\"cd_item_contact_open\">Abrir</string>\n    <string name=\"entry_rename_confirmation\">O nome agora é \\\"%s\\\"</string>\n    <string name=\"custom_icon_application\">app</string>\n    <string name=\"custom_icon_activity\">atividade</string>\n    <string name=\"current_icon_preview_label\">Atual\n\\nícone</string>\n    <string name=\"behaviour_clear_search_after_launch\">Limpar barra de pesquisa após iniciar</string>\n    <string name=\"dm_empty_quick_list\">Mostrar encaixe</string>\n    <string name=\"dm_search_quick_list\">Mostrar encaixe</string>\n    <string name=\"dm_widget_quick_list\">Mostrar encaixe</string>\n    <string name=\"bind_widget_failed\">Permissões insuficientes para criar widgets</string>\n    <string name=\"loading\">Carregando…</string>\n    <string name=\"widget_name\"><b>%1$s</b><br/>%2$d×%3$d</string>\n    <string name=\"widget_name_and_desc\"><b>%1$s</b><br/>%3$d×%4$d<br/>%2$s</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-pt-rBR-v26/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"adaptive_shape_name\">Forma adaptativa do ícone</string>\n    <string name=\"force_adaptive\">Forçar ícones adaptativos</string>\n    <string name=\"force_adaptive_summary\">Para ícones não-adaptativos, reduza a escala e coloque sobre um fundo estruturado</string>\n    <string name=\"force_shape\">Forçar forma adaptativa</string>\n    <string name=\"force_shape_summary\">Usar máscara adaptativa em ícones não-adaptativos</string>\n    <string name=\"app_name\">Iniciador do TinyBit</string>\n    <string name=\"app_name_debug\">Iniciador de depuração do TinyBit</string>\n    <string name=\"hint_ui_search\">Pesquise em aplicativos, contatos, …</string>\n    <string name=\"ui_item_search\">Pesquisar em %1$s por \\\"%2$s\\\"\\?</string>\n    <string name=\"cant_pin_shortcut\">Não foi possível adicionar o atalho. O TinyBit é seu iniciador padrão\\?</string>\n    <string name=\"unable_to_initialize_shortcuts\">Não foi possível acessar os atalhos. O Tiny Bit é seu iniciador padrão\\?</string>\n    <string name=\"menu_quick_list_add\">Adicionar ao encaixe</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-pt-rPT/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"removed_item\">Removido %s</string>\n    <string name=\"value\">valor %d</string>\n    <string name=\"title_features\">Layout</string>\n    <string name=\"action_show_favorites\">Favoritos</string>\n    <string name=\"wp_animate_center\">Papel de parede central</string>\n    <string name=\"wp_animate_center_desc\">O papel de parede voltará ao centro após a rolagem</string>\n    <string name=\"cfg_widget_move\">Mudança</string>\n    <string name=\"cfg_widget_resize_exit\">Saída \\\"Redimensionar\\\"</string>\n    <string name=\"cfg_widget_move_resize\">Mudar e redimensionar</string>\n    <string name=\"cfg_widget_move_resize_exit\">Saída \\\"Mudar e redimensionar\\\"</string>\n    <string name=\"cfg_widget_remove\">Remover</string>\n    <string name=\"cfg_widget_screen_left\">Mudar para <b>←</b> ecrã</string>\n    <string name=\"cfg_widget_screen_up\">Mudar para <b>↑</b> ecrã</string>\n    <string name=\"cfg_widget_screen_right\">Ir para <b>→</b> ecrã</string>\n    <string name=\"cfg_widget_screen_down\">Mudar para <b>↓</b> ecrã</string>\n    <string name=\"cfg_widget_back\">Mover <b>para trás</b> todos</string>\n    <string name=\"cfg_widget_front\">Mover <b>acima</b> todos</string>\n    <string name=\"shortcut_with_appName\">%1$s: %2$s</string>\n    <string name=\"debug_section\">Debug</string>\n    <string name=\"invalid_rename_search_engine\">Já existe um motor de busca \\\"%s\\\"</string>\n    <string name=\"provider_section\">Fornecedores</string>\n    <string name=\"enable_search\">Motor de busca</string>\n    <string name=\"enable_url\">URL da Web</string>\n    <string name=\"enable_calculator\">Calculadora</string>\n    <string name=\"export_tags\">Etiquetas de exportação</string>\n    <string name=\"export_tags_summary\">Associações de etiquetas de exportação, sem ícones</string>\n    <string name=\"export_modifications\">Personalizações de exportação</string>\n    <string name=\"import_settings_append_summary\">Apenas novas configurações são importadas</string>\n    <string name=\"please_wait\">Aguarde, por favor.</string>\n    <string name=\"show_tags_menu\">Abrir menu de etiquetas</string>\n    <string name=\"contact_pack_mask\">Contactos</string>\n    <string name=\"shortcut_pack_badge_mask\">Crachá de atalho</string>\n    <string name=\"shortcut_pack_badge_mask_summary\">Usar máscara de pacote de ícones para crachá de atalho</string>\n    <string name=\"choose_icon_menu_add\">Adicionar como alternativa</string>\n    <string name=\"choose_icon_menu_add2\">Adicionar alternativa e mostrar</string>\n    <string name=\"result_ripple_color\">Cor de toque</string>\n    <string name=\"quick_list_ripple_color\">Cor de toque</string>\n    <string name=\"reset_preferences\">Repor preferências</string>\n    <string name=\"reset_preferences_confirm\">Preferências de descarte\\?</string>\n    <string name=\"reset_preferences_description\">Todas as definições serão reiniciadas</string>\n    <string name=\"reset_cached_app_icons_confirm\">Reiniciar a cache do pacote de ícones\\?</string>\n    <string name=\"reset_cached_app_icons_description\">Reinicia o nome e a versão do pacote de ícones em cache.</string>\n    <string name=\"popup_text_color\">Cor do texto</string>\n    <string name=\"button_launcher\">Botão lançador</string>\n    <string name=\"debug_favorites\">Favoritos</string>\n    <string name=\"quick_list_rows\">Linhas</string>\n    <string name=\"quick_list_rows_summary\">A altura máxima mudará em conformidade</string>\n    <string name=\"app_name\">TinyBit Launcher</string>\n    <string name=\"app_name_debug\">Lançador de depuração TinyBit</string>\n    <string name=\"hint_ui_search\">Pesquisar em aplicações, contactos, …</string>\n    <string name=\"ui_item_search\">Pesquisar em %1$s por \\\"%2$s\\\"\\?</string>\n    <string name=\"ui_item_visit\">Visite \\\"%1$s\\\"</string>\n    <string name=\"copy_confirmation\">Copiado \\\"%s\\\" para prancheta</string>\n    <string name=\"toast_hibernate_completed\">%s hibernado, relançar para despertar</string>\n    <string name=\"toast_hibernate_error\">[ERRO] %s não hibernado</string>\n    <string name=\"unable_to_initialize_shortcuts\">Não foi possível aceder a atalhos. TinyBit é o seu lançador por defeito\\?</string>\n    <string name=\"cant_pin_shortcut\">Não foi possível acrescentar atalho. TinyBit é o seu lançador por defeito\\?</string>\n    <string name=\"menu_quick_list_add\">Adicionar à doca</string>\n    <string name=\"menu_quick_list_remove\">Retirar da doca</string>\n    <string name=\"menu_remove_history\">Remover da história</string>\n    <string name=\"stub_application\">Nome da app</string>\n    <string name=\"stub_app_tag\">lista de etiquetas da app</string>\n    <string name=\"permission_denied\">Conceder esta permissão primeiro</string>\n    <string name=\"menu_exclude\">Excluir…</string>\n    <string name=\"menu_hide\">Esconder</string>\n    <string name=\"menu_show\">Revelar</string>\n    <string name=\"menu_remove_shortcut\">Unpin</string>\n    <string name=\"menu_tags_add\">Adicionar etiquetas</string>\n    <string name=\"menu_tags_edit\">Editar etiquetas</string>\n    <string name=\"menu_app_details\">Informação da aplicação</string>\n    <string name=\"menu_app_store\">Ver na loja</string>\n    <string name=\"menu_app_uninstall\">Desinstalar</string>\n    <string name=\"menu_app_hibernate\">Hibernate</string>\n    <string name=\"menu_exclude_history\">da história</string>\n    <string name=\"menu_exclude_kiss\">de TinyBit</string>\n    <string name=\"application_not_found\">Incapaz de lançar \\\"%s\\\" app</string>\n    <string name=\"entry_not_found\">Não foi possível encontrar \\\"%s\\\"</string>\n    <string name=\"change_wallpaper\">Mudar o papel de parede</string>\n    <string name=\"menu_widget_title\">Menu Widget</string>\n    <string name=\"menu_widget_add\">Adicionar Widget…</string>\n    <string name=\"menu_widget_remove\">Retirar o Widget…</string>\n    <string name=\"menu_widget_configure\">Personalizar o Widget…</string>\n    <string name=\"menu_popup_title\">Menu principal</string>\n    <string name=\"menu_popup_title_settings\">Definições</string>\n    <string name=\"menu_popup_android_settings\">Configurações do aparelho</string>\n    <string name=\"menu_popup_launcher_settings\">Configurações TinyBit</string>\n    <string name=\"menu_popup_tags_manager\">Gestor de etiquetas</string>\n    <string name=\"menu_popup_tags_menu\">Menu de etiquetas</string>\n    <string name=\"rate_the_app\">Avalie a aplicação</string>\n    <string name=\"privacy_policy\">Política de Privacidade</string>\n    <string name=\"app_version\">Versão %s</string>\n    <string name=\"app_version_summary\">%1$s - %2$s</string>\n    <string name=\"user_interface_summary\">Temas, transparência e cores</string>\n    <string name=\"title_ui\">Interface do utilizador</string>\n    <string name=\"notification_bar_section\">Barra de Notificação</string>\n    <string name=\"color_and_opacity\">Cor e opacidade</string>\n    <string name=\"gradient\">Gradiente</string>\n    <string name=\"black_notification_icons\">Ícones escuros</string>\n    <string name=\"cd_main_menu\">Menu</string>\n    <string name=\"cd_main_clear\">Limpar barra de pesquisa</string>\n    <string name=\"cd_show_all_apps\">Mostrar todas as aplicações</string>\n    <string name=\"cd_item_contact_message\">Mensagem</string>\n    <string name=\"cd_item_contact_call\">Chamada</string>\n    <string name=\"cd_item_contact_open\">Aberto</string>\n    <string name=\"cd_add_tag\">Adicionar etiqueta</string>\n    <string name=\"cd_undo_remove_tag\">Desfazer remover a etiqueta</string>\n    <string name=\"cd_remove_tag\">Retirar a etiqueta</string>\n    <string name=\"cd_rename_tag\">Renomear etiqueta</string>\n    <string name=\"cd_icon_tag\">Ícone da etiqueta</string>\n    <string name=\"title_select_alpha\">Arraste o selector para seleccionar a opacidade desejada</string>\n    <string name=\"title_select_size\">Arraste o selector para seleccionar o tamanho desejado</string>\n    <string name=\"search_bar_section\">Barra de pesquisa</string>\n    <string name=\"search_bar_text_size\">Tamanho do texto</string>\n    <string name=\"search_bar_height\">Altura da barra</string>\n    <string name=\"result_list_section\">Lista de resultados</string>\n    <string name=\"icons_pack\">Escolher pacote</string>\n    <string name=\"icons_pack_default_name\">Ícones do sistema</string>\n    <string name=\"value_float\">valor %.2f</string>\n    <string name=\"value_float_xy\">horizontal %1$.2f | vertical %2$.2f</string>\n    <string name=\"size\">&lt;%d&gt;</string>\n    <string name=\"size_float\">&lt;%.1f&gt;</string>\n    <string name=\"shadow_preview\">&lt;%3$.1f&gt;\n\\n&lt;%1$.1f×%2$.1f&gt;</string>\n    <string name=\"menu_app_rename\">Renomear aplicação</string>\n    <string name=\"menu_action_rename\">Renomear</string>\n    <string name=\"menu_action_delete\">Eliminar</string>\n    <string name=\"menu_shortcut_rename\">Renomear atalho</string>\n    <string name=\"menu_custom_icon\">Alterar ícone</string>\n    <string name=\"title_app_rename\">Escolha um nome personalizado para esta aplicação</string>\n    <string name=\"title_rename_tag\">Substituir em todo o lado esta etiqueta</string>\n    <string name=\"hint_rename_tag\">Escolha um nome diferente para esta etiqueta</string>\n    <string name=\"title_rename_search_engine\">Escolha um nome diferente para este motor de busca</string>\n    <string name=\"title_rename_search_hint\">Escolha um nome diferente para esta dica de pesquisa</string>\n    <string name=\"title_edit_url_search_engine\">Escolha um URL diferente para este motor de busca</string>\n    <string name=\"title_shortcut_rename\">Escolha um nome personalizado para este atalho</string>\n    <string name=\"app_rename_confirmation\">O nome da app é agora %s</string>\n    <string name=\"shortcut_rename_confirmation\">O nome do atalho é agora \\\"%s\\\"</string>\n    <string name=\"entry_rename_confirmation\">O nome é agora \\\"%s\\\"</string>\n    <string name=\"hint_custom_icon\">Filtrar pelo nome</string>\n    <string name=\"default_icon\">ícone predefinido</string>\n    <string name=\"default_static_icon\">%s ícone predefinido</string>\n    <string name=\"custom_icon_application\">app</string>\n    <string name=\"custom_icon_activity\">actividade</string>\n    <string name=\"custom_icon_activity_adaptive_no_background\">antecedentes personalizados da actividade</string>\n    <string name=\"custom_icon_badged\">insígnia</string>\n    <string name=\"default_icon_preview_label\">Predefinição\n\\nícone</string>\n    <string name=\"current_icon_preview_label\">Actual\n\\nícone</string>\n    <string name=\"custom_icon_preview_label\">Seleccionado\n\\nícone</string>\n    <string name=\"custom_name_set_default\">Nome por defeito</string>\n    <string name=\"hint_new_tag\">adicionar etiqueta</string>\n    <string name=\"features_summary\">Alternar e personalizar elementos UI</string>\n    <string name=\"quick_list_section\">Doca</string>\n    <string name=\"quick_list_enabled\">Use a doca</string>\n    <string name=\"quick_list_icon_size\">Tamanho máximo do ícone</string>\n    <string name=\"quick_list_height\">Altura</string>\n    <string name=\"tags_section\">Etiquetas</string>\n    <string name=\"fuzzy_search_tags\">Etiquetas de pesquisa de pelúcia</string>\n    <string name=\"fuzzy_search_tags_summary\">Ao pesquisar incluir etiquetas como possíveis candidatos à correspondência</string>\n    <string name=\"tags_enabled\">Mostrar etiquetas para aplicações</string>\n    <string name=\"tags_enabled_summary\">Mostrar etiquetas para cada aplicação na lista de resultados</string>\n    <string name=\"icons_visible\">Mostrar Ícones</string>\n    <string name=\"popup_title_shortcut_dynamic\">Atalhos</string>\n    <string name=\"popup_title_hist_fav\">Preferências</string>\n    <string name=\"popup_title_customize\">Personalizar</string>\n    <string name=\"popup_title_link\">Ligações</string>\n    <string name=\"popup_title_debug\">Informação de depuração</string>\n    <string name=\"shortcuts_no_host_permission\">Faça deste o seu lançador principal para aceder a atalhos.</string>\n    <string name=\"pin_shortcut_message\">Quer adicionar este atalho\\?</string>\n    <string name=\"pin_shortcut_label\">Escolher etiqueta de atalho</string>\n    <string name=\"pin_shortcut_icon\">Ícone de atalho</string>\n    <string name=\"quick_list_only_for_results\">Ocultar a doca quando não há resultados</string>\n    <string name=\"quick_list_only_for_results_summary\">Tornar a doca visível quando a lista de resultados tiver entradas</string>\n    <string name=\"exit_the_app\">Lançador próximo</string>\n    <string name=\"exit_the_app_confirm\">Fechar o lançador\\?</string>\n    <string name=\"exit_the_app_description\">O seu lançador por defeito será aberto, ou ser-lhe-á perguntado qual deles quer utilizar.</string>\n    <string name=\"reset_default_launcher_confirm\">Mudar o lançador padrão\\?</string>\n    <string name=\"reset_default_launcher_description\">Pede para a aplicação abrir depois de pressionar ao ecrã inicial.</string>\n    <string name=\"reset_default_launcher\">Alterar lançador padrão</string>\n    <string name=\"filter_apps\">Filtro de aplicação</string>\n    <string name=\"filter_contacts\">Filtro de contacto</string>\n    <string name=\"filter_shortcuts\">Filtro de atalhos</string>\n    <string name=\"action_reload\">Recarregar fornecedores</string>\n    <string name=\"action_toggle_grid\">Grelha de alternância</string>\n    <string name=\"action_show_apps\">Apps A→Z</string>\n    <string name=\"action_show_apps_grid4\">Grelha de aplicação A→Z</string>\n    <string name=\"action_show_apps_reversed\">Apps Z→A</string>\n    <string name=\"action_show_apps_grid4_reversed\">Grelha de aplicação Z→A</string>\n    <string name=\"action_show_contacts\">Contactos A→Z</string>\n    <string name=\"action_show_contacts_reversed\">Contactos Z→A</string>\n    <string name=\"action_show_shortcuts\">Atalhos A→Z</string>\n    <string name=\"action_show_shortcuts_reversed\">Atalhos Z→A</string>\n    <string name=\"quick_list_content\">Personalizar o conteúdo</string>\n    <string name=\"quick_list_content_summary\">Alterar a ordem e acrescentar entradas</string>\n    <string name=\"edit_quick_list_preview\">Pré-visualização <em>(toque longo para começar a arrastar e largar, toque para remover)</em></string>\n    <string name=\"edit_quick_list_tab_filters\">Filtros</string>\n    <string name=\"edit_quick_list_tab_actions\">Acções</string>\n    <string name=\"edit_quick_list_tab_favorites\">Favoritos</string>\n    <string name=\"edit_quick_list_tab_tags\">Etiquetas</string>\n    <string name=\"quick_list_icons_visible\">Mostrar ícones</string>\n    <string name=\"quick_list_icons_visible_summary\">Ícones de itens da doca de exposição</string>\n    <string name=\"quick_list_text_visible\">Mostrar nomes</string>\n    <string name=\"quick_list_text_visible_summary\">Mostrar nomes dos artigos da doca</string>\n    <string name=\"cache_drawable\">Cache ícones</string>\n    <string name=\"screen_off_cache_clear\">Cache de libertação frequentemente</string>\n    <string name=\"screen_off_cache_clear_summary\">Cache vazia quando o ecrã é desligado</string>\n    <string name=\"memory_section\">Memória</string>\n    <string name=\"cache_half_apps\">Pequeno tamanho de cache</string>\n    <string name=\"cache_half_apps_summary\">Fazer a cache LRU com metade da quanitade de apps instaladas</string>\n    <string name=\"icon_pack_section\">Pacote de ícones</string>\n    <string name=\"icon_pack_content_list\">Ícones de\n\\n%s</string>\n    <string name=\"title_wallpaper\">Interacções de papel de parede</string>\n    <string name=\"lwp_touch\">Enviar eventos de toque para papel de parede</string>\n    <string name=\"lwp_drag\">Emular evento de arrastamento para papel de parede</string>\n    <string name=\"lwp_drag_desc\">Enviar múltiplos eventos de toque</string>\n    <string name=\"lock_portrait\">Retrato de eclusa</string>\n    <string name=\"sensor_orientation\">Orientação de sensores</string>\n    <string name=\"wp_drag_animate\">Papel de parede de pergaminho</string>\n    <string name=\"wp_drag_animate_desc\">Arrastar <b>←</b> <b>→</b> para percorrer o papel de parede</string>\n    <string name=\"wp_animate_sides\">Papel de parede de pau lateral</string>\n    <string name=\"wp_animate_sides_desc\">O papel de parede ficará no <b>←</b> ou <b>→</b> após a rolagem</string>\n    <string name=\"cfg_widget_move_switch\">Mudança de movimento</string>\n    <string name=\"cfg_widget_move_exit\">Sair em movimento</string>\n    <string name=\"cfg_widget_resize\">Redimensionar</string>\n    <string name=\"cfg_widget_resize_switch\">Mudar de tamanho</string>\n    <string name=\"cfg_widget_screen_middle\">Ir para <b>meio</b> ecrã</string>\n    <string name=\"debug_widget_add_info\">Acrescentar informação extra de widget</string>\n    <string name=\"debug_widget_info\">Imprensa longa para informações extra widget</string>\n    <string name=\"debug_item_relevance\">Relevância da pesquisa</string>\n    <string name=\"debug_item_icon_info\">Informação de ícones</string>\n    <string name=\"shortcut_section\">Atalho</string>\n    <string name=\"shortcut_pin_auto_confirm\">Atalho de auto-confirmação</string>\n    <string name=\"shortcut_show_badge\">Mostrar crachá da app</string>\n    <string name=\"shortcut_show_badge_summary\">Mostrar o ícone da app que criou o atalho</string>\n    <string name=\"quick_list_show_badge\">Mostrar crachá de atalho na doca</string>\n    <string name=\"result_list_argb\">Cor de fundo e opacidade</string>\n    <string name=\"result_highlight_color\">Cor em destaque</string>\n    <string name=\"quick_list_color\">Cor de fundo</string>\n    <string name=\"quick_list_toggle_color\">Alternar a cor de fundo</string>\n    <string name=\"contact_action_color\">Cor do ícone de acção</string>\n    <string name=\"result_icon_size\">Tamanho do ícone</string>\n    <string name=\"result_text_color\">Cor do texto</string>\n    <string name=\"result_text2_color\">Cor do texto secundário</string>\n    <string name=\"result_shadow_color\">Cor da sombra do texto</string>\n    <string name=\"result_shadow_radius_dx_dy\">Tamanho da sombra e offset</string>\n    <string name=\"search_bar_text_color\">Cor do texto</string>\n    <string name=\"search_bar_icon_color\">Cor dos ícones</string>\n    <string name=\"menu_popup_quick_list_customize\">Editar a doca</string>\n    <string name=\"result_text_size\">Tamanho do texto</string>\n    <string name=\"result_text2_size\">Tamanho do texto secundário</string>\n    <string name=\"invalid_rename_tag\">Já existe uma etiqueta \\\"%s\\\"</string>\n    <string name=\"enable_dial\">Pesquisa por marcação</string>\n    <string name=\"enable_contacts\">Contactos telefónicos</string>\n    <string name=\"selected_contact_mime_types\">Seleccione os contactos a serem mostrados</string>\n    <string name=\"edit_search_engines\">Editar motores de busca</string>\n    <string name=\"edit_search_engines_summary\">Alternar e editar fornecedores de pesquisa</string>\n    <string name=\"reset_search_engines\">Reiniciar os motores de busca</string>\n    <string name=\"reset_search_engines_summary\">Restaurar os motores de busca por defeito</string>\n    <string name=\"add_search_engine\">Adicionar motor de busca</string>\n    <string name=\"edit_search_hint\">Editar dica de pesquisa</string>\n    <string name=\"edit_search_hint_summary\">Alternar e editar dicas de pesquisa</string>\n    <string name=\"reset_search_hint\">Reiniciar a dica de pesquisa</string>\n    <string name=\"reset_search_hint_summary\">Restaurar todas as dicas de pesquisa</string>\n    <string name=\"add_search_hint\">Adicionar dica de pesquisa</string>\n    <string name=\"search_engine_edit_url\">Editar URL</string>\n    <string name=\"confirm_edit_url_search_engine\">Guardar</string>\n    <string name=\"search_engine_set_default\">Faça o padrão</string>\n    <string name=\"hint_add_search_engine_url\">https://search.engine/query=%s</string>\n    <string name=\"selected_pack\">%s\n\\n[pacote actual]</string>\n    <string name=\"label_search_engine_name\">Nome do fornecedor de pesquisa</string>\n    <string name=\"tab_app_icons\">%s\n\\n[ícones de aplicação]</string>\n    <string name=\"label_search_engine_url\">URL para abrir</string>\n    <string name=\"tab_static_icons\">Ícone predefinido</string>\n    <string name=\"search_engine_url_help\">%s será substituído pela sua consulta</string>\n    <string name=\"tab_search_icon\">Ícone de pesquisa</string>\n    <string name=\"tab_button_icon\">Ícone do botão</string>\n    <string name=\"icon_pack_loading\">Carregamento a partir de pacotes de ícones…</string>\n    <string name=\"gesture_section\">Gestos</string>\n    <string name=\"gesture_fling_down_left\">Fling de |<b> <b> </b> |, ← ↓</b></string>\n    <string name=\"gesture_fling_down_left_summary\">Iniciar a partir do <b>←</b> lado do ecrã e atirar <b>↓</b></string>\n    <string name=\"gesture_fling_down_right\">Fling de |<b>→</b>|, <b>↓</b></string>\n    <string name=\"gesture_fling_down_right_summary\">Comece a partir do <b>→</b> lado do ecrã e atire <b>↓</b></string>\n    <string name=\"gesture_fling_up\">Fling ↑</string>\n    <string name=\"gesture_fling_left\">Fling ←</string>\n    <string name=\"gesture_fling_right\">Fling →</string>\n    <string name=\"gesture_click\">Toque em ecrã vazio</string>\n    <string name=\"static_icon_letters_label\">Cartas a utilizar para gerar ícones</string>\n    <string name=\"backup_summary\">Configurações de salvaguarda, cópia de segurança, exportação e importação</string>\n    <string name=\"title_backup\">Cópia de segurança</string>\n    <string name=\"export_settings_section\">Definições de exportação</string>\n    <string name=\"export_chooser\">Exportação %s</string>\n    <string name=\"export_chooser_xml\">Exportar \\\"%s\\\" como XML</string>\n    <string name=\"export_xml\">Exportar como XML</string>\n    <string name=\"export_modifications_summary\">Dock, ícones e nomes personalizados</string>\n    <string name=\"export_apps\">Aplicações de exportação</string>\n    <string name=\"export_apps_summary\">Novos nomes para aplicações, ícones escondidos e personalizados</string>\n    <string name=\"import_settings_section\">Configurações de importação</string>\n    <string name=\"import_settings_set\">Definir configurações importadas</string>\n    <string name=\"import_settings_set_summary\">Limpar definições relevantes antes de importar</string>\n    <string name=\"import_settings_overwrite\">Sobregravar configurações importadas</string>\n    <string name=\"import_settings_overwrite_summary\">Configurações importadas sobrescrevem as configurações actuais</string>\n    <string name=\"import_settings_append\">Anexar definições importadas</string>\n    <string name=\"import_dialog_description\">Configurações de carregamento…</string>\n    <string name=\"import_chooser\">Seleccione um ficheiro a importar</string>\n    <string name=\"export_description\">Após clicar em \\\"OK\\\" pode seleccionar um gestor de ficheiros para guardar as suas definições como um ficheiro XML.</string>\n    <string name=\"error\">Erro %s</string>\n    <string name=\"export_preferences\">Definições de exportação</string>\n    <string name=\"export_preferences_summary\">Todas as configurações, características e comportamentos, incluindo interface</string>\n    <string name=\"export_interface\">Interface de exportação</string>\n    <string name=\"export_interface_summary\">Todas as cores, tamanhos, comutadores e formas de ícones da interface</string>\n    <string name=\"export_backup\">Exportar cópia de segurança</string>\n    <string name=\"export_backup_summary\">Todos os ícones, etiquetas e preferências personalizadas</string>\n    <string name=\"export_widgets\">Widgets de exportação</string>\n    <string name=\"export_widgets_summary\">A importação pode não funcionar ou exigir que cada widget seja adicionado manualmente</string>\n    <string name=\"export_history\">Histórico de exportação</string>\n    <string name=\"export_history_summary\">História de todas as aplicações lançadas</string>\n    <string name=\"error_fail_import\">Não foi possível importar ficheiro. Verificar logcat.</string>\n    <string name=\"widget_placeholder\">Clique para restaurar %s</string>\n    <string name=\"add_widget_failed\">Não foi possível acrescentar widget</string>\n    <string name=\"widget_placeholder_remove\">Retirar o local\\?</string>\n    <string name=\"choose_file_activity_not_found\">Por favor, instale um gestor de ficheiros.</string>\n    <string name=\"result_history_size\">Tamanho da história</string>\n    <string name=\"result_history_size_summary\">Quanitade máxima de itens ao mostrar a história</string>\n    <string name=\"action_show_history_recency\">História recente</string>\n    <string name=\"action_show_history_frequency\">Mais acessados</string>\n    <string name=\"action_show_history_frecency\">Recentes e frequentes</string>\n    <string name=\"action_show_history_adaptive\">História adaptativa</string>\n    <string name=\"show_tags_list\">Menu de etiquetas como resultados</string>\n    <string name=\"show_tags_list_reversed\">Menu de etiquetas invertido como resultados</string>\n    <string name=\"result_history_adaptive\">Duração adaptativa</string>\n    <string name=\"result_history_adaptive_summary\">Quantidade de horas para história adaptativa</string>\n    <string name=\"adaptive_shape_name\">Forma de ícone</string>\n    <string name=\"force_adaptive\">Definir fundo de ícone</string>\n    <string name=\"force_adaptive_summary\">Reduzir a escala de ícones e colocar sobre um fundo moldado</string>\n    <string name=\"force_shape\">Forçar forma</string>\n    <string name=\"force_shape_summary\">Usar fundo moldado personalizado para todos</string>\n    <string name=\"contact_pack_mask_summary\">Usar a forma de pacote de ícones para contactos</string>\n    <string name=\"contacts_shape_name\">Forma de contacto-icónico</string>\n    <string name=\"contacts_shape_summary\">Forma do ícone de contacto</string>\n    <string name=\"shortcut_pack_mask\">Ícone de atalho</string>\n    <string name=\"shortcut_pack_mask_summary\">Usar máscara de pacote de ícones para ícone de atalho</string>\n    <string name=\"shortcut_shape_name\">Forma de ícone de atalho</string>\n    <string name=\"shortcut_shape_summary\">Forma do ícone de atalho</string>\n    <string name=\"choose_letter_color\">Cor da letra</string>\n    <string name=\"choose_background_color\">Cor de fundo</string>\n    <string name=\"choose_icon_scale\">Ícone da escala</string>\n    <string name=\"choose_icon_shape\">Escolha a forma</string>\n    <string name=\"choose_icon\">Escolher ícone</string>\n    <string name=\"icon_background\">Ícones de fundo</string>\n    <string name=\"label_search_hint\">Dica de pesquisa</string>\n    <string name=\"result_search_cap\">Máximo de resultados de pesquisa</string>\n    <string name=\"unlimited_search_cap\">Resultados de pesquisa ilimitada</string>\n    <string name=\"unlimited_search_cap_summary\">Estabelecer o limite máximo possível</string>\n    <string name=\"generate_theme_confirm\">Cores geradas pelo conjunto\\?</string>\n    <string name=\"generate_theme_description\">Repõe todas as cores e aplica as cores geradas com base nas cores primárias e secundárias</string>\n    <string name=\"reset_matrix_confirm\">Definir valores por defeito\\?</string>\n    <string name=\"reset_matrix_description\">Reinicia todas as definições de ícones neste ecrã</string>\n    <string name=\"popup_section\">Popup</string>\n    <string name=\"popup_ripple_color\">Cor de toque</string>\n    <string name=\"popup_background_argb\">Cor de fundo</string>\n    <string name=\"popup_border_argb\">Cor da fronteira</string>\n    <string name=\"popup_title_color\">Cor do título</string>\n    <string name=\"popup_shadow_color\">Cor da sombra do texto</string>\n    <string name=\"popup_shadow_radius_dx_dy\">Tamanho da sombra e offset</string>\n    <string name=\"settings_theme\">Tema das definições</string>\n    <string name=\"settings_default\">Predefinição</string>\n    <string name=\"settings_8bit\">8bit</string>\n    <string name=\"settings_white\">Branco</string>\n    <string name=\"settings_black\">Preto</string>\n    <string name=\"settings_dark\">Escuro</string>\n    <string name=\"settings_deep_blues\">DeepBlues</string>\n    <string name=\"title_static_rename\">Escolha um nome personalizado</string>\n    <string name=\"letters_toggle\">Ícones de cartas</string>\n    <string name=\"button_home\">Botão Home</string>\n    <string name=\"icon_contrast\">Contraste</string>\n    <string name=\"icon_brightness\">Luminosidade</string>\n    <string name=\"icon_hue\">Matiz</string>\n    <string name=\"icon_saturation\">Saturação</string>\n    <string name=\"matrix_summary\">Luminosidade, contraste, tonalidade, saturação</string>\n    <string name=\"title_matrix\">Modular cor do ícone</string>\n    <string name=\"icon_scale_red\">Escala vermelha</string>\n    <string name=\"icon_scale_green\">Verde escama</string>\n    <string name=\"icon_scale_blue\">Escala azul</string>\n    <string name=\"icon_scale_alpha\">Transparência de escalas</string>\n    <string name=\"reset_matrix\">Definir valores por defeito</string>\n    <string name=\"corner_radius\">Raio do canto</string>\n    <string name=\"shadow_offset\">Seleccionar quantidade de píxeis para compensação de sombras</string>\n    <string name=\"shadow_offset_preview\">Tocar ou arrastar para dentro.</string>\n    <string name=\"shadow_radius\">Tamanho da sombra <em>(tamanho zero irá desactivar a sombra)</em></string>\n    <string name=\"behaviour_section\">Comportamento</string>\n    <string name=\"behaviour_link_keyboard_search_bar\">Ligar teclado à visibilidade da barra de pesquisa</string>\n    <string name=\"behaviour_widget_after_launch\">Modo Widget após o lançamento</string>\n    <string name=\"behaviour_clear_search_after_launch\">Limpar barra de pesquisa após o lançamento</string>\n    <string name=\"dm_empty_quick_list\">Mostrar doca</string>\n    <string name=\"dm_search_quick_list\">Mostrar doca</string>\n    <string name=\"dm_widget_quick_list\">Mostrar doca</string>\n    <string name=\"dm_empty_fullscreen\">Ecrã completo</string>\n    <string name=\"dm_search_fullscreen\">Ecrã completo</string>\n    <string name=\"dm_search_fullscreen_summary\">Apenas quando o teclado está fechado</string>\n    <string name=\"dm_widget_fullscreen\">Ecrã completo</string>\n    <string name=\"title_desktop_mode_empty\">Modo de secretária \\\"Vazio</string>\n    <string name=\"title_desktop_mode_search\">Modo Desktop \\\"Pesquisa</string>\n    <string name=\"title_desktop_mode_widget\">Modo de secretária \\\"Widget</string>\n    <string name=\"search_bar_at_bottom\">Posição no fundo</string>\n    <string name=\"search_bar_ripple_color\">Cor de toque</string>\n    <string name=\"search_bar_cursor_argb\">Cor do Cursor</string>\n    <string name=\"search_bar_shadow_color\">Cor da sombra do texto</string>\n    <string name=\"search_bar_shadow_radius_dx_dy\">Tamanho da sombra e offset</string>\n    <string name=\"back_device_key\">Botão Voltar</string>\n    <string name=\"action_show_untagged\">Desmarcado</string>\n    <string name=\"dm_search_open_result\">Lista inicial de resultados</string>\n    <string name=\"action_app_to_run\">Aplicação a executar</string>\n    <string name=\"action_shortcut_to_run\">Atalho para correr</string>\n    <string name=\"action_entry_to_show\">Etiquetas para mostrar</string>\n    <string name=\"no_tags\">Não foram encontradas etiquetas</string>\n    <string name=\"quick_list_animation\">Animar espectáculo/ocultar</string>\n    <string name=\"initial_desktop\">Ambiente de trabalho inicial</string>\n    <string name=\"behaviour_summary\">Gestos e o que fazer - quando</string>\n    <string name=\"title_behaviour\">Comportamento</string>\n    <string name=\"root_mode\">Modo Raiz</string>\n    <string name=\"root_mode_summary\">Utilizado para hibernar aplicações</string>\n    <string name=\"root_mode_error\">Não foi possível obter acesso à raiz</string>\n    <string name=\"behaviour_link_close_keyboard_back_button\">Acção \\\"Voltar\\\" ao fechar o teclado</string>\n    <string name=\"keyboard_suggestions\">Auto-completamento/sugestões</string>\n    <string name=\"device_admin_explanation\">Usado para bloquear o ecrã</string>\n    <string name=\"device_admin\">Administração do aparelho</string>\n    <string name=\"device_admin_disable\">Retirar esta permissão\\?</string>\n    <string name=\"device_admin_required\">Acesso aos aparelhos necessário</string>\n    <string name=\"gesture_double_click\">Ecrã de toque duplo</string>\n    <string name=\"loading_icon\">Ícone de carregamento</string>\n    <string name=\"keyboard_section\">Teclado</string>\n    <string name=\"debug_ksh_touch\">Teclado-rolo de rolagem</string>\n    <string name=\"debug_ksh_touch_summary\">Mostrar o estado como cor de fundo</string>\n    <string name=\"lwp_scroll_pages\">Páginas</string>\n    <string name=\"lwp_page_count_vertical\">páginas ↕</string>\n    <string name=\"lwp_page_count_horizontal\">⟷ páginas</string>\n    <string name=\"lwp_pages_vertical_1\">página central</string>\n    <string name=\"lwp_pages_vertical_2\">2 páginas (↑ e ↓)</string>\n    <string name=\"lwp_pages_horizontal_1\">página central</string>\n    <string name=\"lwp_pages_horizontal_2\">2 páginas (← e →)</string>\n    <string name=\"reset_cached_app_icons\">Pacote de ícones de reinicialização</string>\n    <string name=\"matrix_contacts\">Modular contactos</string>\n    <string name=\"matrix_contacts_summary\">Modular as cores dos ícones de contacto</string>\n    <string name=\"tags_menu_section\">Menu Etiquetas</string>\n    <string name=\"tags_menu_icons\">Mostrar ícones</string>\n    <string name=\"tags_menu_icon_size\">Tamanho do ícone</string>\n    <string name=\"tags_menu_list\">Etiquetas para mostrar</string>\n    <string name=\"tags_menu_order\">Encomenda de etiquetas</string>\n    <string name=\"tags_menu_untagged\">Mostrar sem marcação</string>\n    <string name=\"result_popup_order\">Ordem de categoria de toque longo</string>\n    <string name=\"result_popup_order_summary\">Encomenda por categoria para artigos de toque longo popup</string>\n    <string name=\"debug_favorites_summary\">Mostrar entradas da tabela DB `favorites\\'</string>\n    <string name=\"title_presets\">Pré-selecção da cor</string>\n    <string name=\"summary_presets\">Predefinição e geração de cores</string>\n    <string name=\"primary_color\">Cor primária</string>\n    <string name=\"secondary_color\">Cor secundária</string>\n    <string name=\"generate_theme_simple\">Tema simples</string>\n    <string name=\"generate_theme_simple_summary\">Usar primário para fundo e secundário para texto</string>\n    <string name=\"generate_theme_highlight\">Tema em destaque</string>\n    <string name=\"generate_theme_highlight_summary\">Usar primário para fundo e secundário para destaque</string>\n    <string name=\"result_first_at_bottom\">Primeiro no fundo</string>\n    <string name=\"result_right_to_left\">Da direita à esquerda</string>\n    <string name=\"result_right_to_left_summary\">Linha da grelha de disposição da direita à esquerda</string>\n    <string name=\"debug_provider_status\">Estatuto de fornecedor</string>\n    <string name=\"exit_tags_manager_confirm\">Fechar o gestor de etiquetas\\?</string>\n    <string name=\"exit_tags_manager_description\">Perderá todas as alterações se fechar o gestor de Etiquetas.\n\\nTem a certeza de querer continuar\\?</string>\n    <string name=\"result_fading_edge\">Gradiente</string>\n    <string name=\"result_fading_edge_summary\">As extremidades superior e inferior desvanecem-se</string>\n    <string name=\"margin_vertical\">Margem vertical</string>\n    <string name=\"margin_horizontal\">Margem horizontal</string>\n    <string name=\"browse_add_icon\">Pesquisar imagens locais</string>\n    <string name=\"result_list_row_height\">Altura da fila da lista</string>\n    <string name=\"result_list_row_height_manual\">Altura da fila da lista personalizada</string>\n    <string name=\"shortcut_dynamic_in_results\">Mostrar dinâmica nos resultados</string>\n    <string name=\"shortcut_dynamic_in_results_summary\">Os atalhos dinâmicos serão considerados como resultados possíveis na pesquisa</string>\n    <string name=\"search_bar_layout\">Layout</string>\n    <string name=\"quick_list_position\">Posição</string>\n    <string name=\"quick_list_columns\">Colunas</string>\n    <string name=\"quick_list_columns_summary\">Quantidade de artigos por fila</string>\n    <string name=\"quick_list_rtl\">Da direita à esquerda</string>\n    <string name=\"quick_list_rtl_summary\">Layout itens da direita à esquerda</string>\n    <string name=\"navigation_bar_section\">Barra de navegação</string>\n    <string name=\"icons_section\">Ícones</string>\n    <string name=\"options_section\">Opções</string>\n    <string name=\"search_bar_animation\">Animar expansão/colapso</string>\n    <string name=\"contact_button_set_default\">Fazer acções por defeito</string>\n    <string name=\"contact_button_reset_default\">Acção de reinicialização por defeito</string>\n    <string name=\"done_key_contact_action\">Feito acção chave de contacto</string>\n    <plurals name=\"tag_entry_count\">\n        <item quantity=\"one\">uma entrada</item>\n        <item quantity=\"many\"/>\n        <item quantity=\"other\"/>\n    </plurals>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-pt-rPT-v26/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"adaptive_shape_name\">Forma adaptativa do ícone</string>\n    <string name=\"force_adaptive\">Forçar ícones adaptativos</string>\n    <string name=\"force_adaptive_summary\">Para ícones não adaptativos, reduzir a escala e colocar sobre um fundo moldado</string>\n    <string name=\"force_shape\">Forçar forma adaptativa</string>\n    <string name=\"force_shape_summary\">Usar máscara adaptativa em ícones não adaptativos</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-ro/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"application_not_found\">Nu am putut lansa aplicația \\\"%s\\\"</string>\n    <string name=\"stub_application\">Numele aplicației</string>\n    <plurals name=\"tag_entry_count\">\n        <item quantity=\"one\">o intrare</item>\n        <item quantity=\"few\"/>\n        <item quantity=\"other\">%d intrări</item>\n    </plurals>\n    <string name=\"reset_preferences_description\">Toate setările vor fi resetate la valoarea implicită</string>\n    <string name=\"reset_preferences_confirm\">Renunțați la preferințe\\?</string>\n    <string name=\"reset_preferences\">Reseteaza preferintele</string>\n    <string name=\"quick_list_ripple_color\">Atingeți culoarea</string>\n    <string name=\"result_ripple_color\">Atingeți culoarea</string>\n    <string name=\"unlimited_search_cap_summary\">Setați cea mai mare limită posibilă</string>\n    <string name=\"unlimited_search_cap\">Rezultate de căutare nelimitate</string>\n    <string name=\"result_search_cap\">Rezultatele maxime ale căutării</string>\n    <string name=\"label_search_hint\">Indiciu de căutare</string>\n    <string name=\"icon_background\">Fundalul pictogramei</string>\n    <string name=\"choose_icon_menu_add2\">Adaugă alternativă și arată</string>\n    <string name=\"choose_icon_menu_add\">Adăugați ca alternativă</string>\n    <string name=\"choose_icon\">Alege pictograma</string>\n    <string name=\"choose_icon_shape\">Alege forma</string>\n    <string name=\"choose_icon_scale\">Pictogramă scalare</string>\n    <string name=\"choose_background_color\">Culoare de fundal</string>\n    <string name=\"choose_letter_color\">Culoare scrisoare</string>\n    <string name=\"shortcut_pack_badge_mask_summary\">Utilizați masca de pachete de pictograme pentru insigna de comenzi rapide</string>\n    <string name=\"shortcut_pack_badge_mask\">Insignă de comandă rapidă</string>\n    <string name=\"shortcut_shape_summary\">Forma pictogramei de comenzi rapide</string>\n    <string name=\"shortcut_shape_name\">Formă pictogramă comandă rapidă</string>\n    <string name=\"shortcut_pack_mask_summary\">Utilizați masca de pachet de pictograme pentru pictograma de comandă rapidă</string>\n    <string name=\"shortcut_pack_mask\">Pictogramă de comandă rapidă</string>\n    <string name=\"contacts_shape_summary\">Forma pictogramei contactelor</string>\n    <string name=\"contacts_shape_name\">Forma contactelor</string>\n    <string name=\"contact_pack_mask_summary\">Utilizați forma pachetului de pictograme pentru contacte</string>\n    <string name=\"contact_pack_mask\">Contacte</string>\n    <string name=\"force_shape_summary\">Utilizați fundal personalizat peste tot</string>\n    <string name=\"force_shape\">Forțează forma</string>\n    <string name=\"force_adaptive_summary\">Reduceți scara pictogramei și plasați-o pe un fundal cu formă</string>\n    <string name=\"force_adaptive\">Setează fundalul pictogramei</string>\n    <string name=\"adaptive_shape_name\">Forma pictogramei</string>\n    <string name=\"result_history_adaptive_summary\">Numărul de ore pentru istoricul adaptiv</string>\n    <string name=\"result_history_adaptive\">Durata adaptativă</string>\n    <string name=\"action_show_history_adaptive\">Istorie adaptativă</string>\n    <string name=\"action_show_history_frecency\">Recent și frecvent</string>\n    <string name=\"action_show_history_frequency\">Cele mai accesate</string>\n    <string name=\"action_show_history_recency\">Istoria recentă</string>\n    <string name=\"result_history_size_summary\">Numărul maxim de articole la afișarea istoricului</string>\n    <string name=\"result_history_size\">Dimensiunea istoriei</string>\n    <string name=\"choose_file_activity_not_found\">Vă rugăm să instalați un manager de fișiere.</string>\n    <string name=\"widget_placeholder_remove\">Ștergeți substituentul\\?</string>\n    <string name=\"add_widget_failed\">Nu s-a putut adăuga un widget</string>\n    <string name=\"widget_placeholder\">Faceți clic pentru a restabili %s</string>\n    <string name=\"error_fail_import\">Nu s-a putut importa fișierul. Verificați logcat.</string>\n    <string name=\"export_history_summary\">Istoricul tuturor aplicațiilor lansate</string>\n    <string name=\"export_history\">Istoric exportări</string>\n    <string name=\"export_widgets_summary\">Este posibil ca importul să nu funcționeze sau să necesite adăugarea manuală a fiecărui widget</string>\n    <string name=\"export_widgets\">Exportați widgeturi</string>\n    <string name=\"export_backup_summary\">Toate pictogramele, etichetele și preferințele personalizate</string>\n    <string name=\"export_backup\">Exportate copie de rezervă</string>\n    <string name=\"export_interface_summary\">Toate culorile, dimensiunile, comutatoarele și formele de interfață ale interfeței</string>\n    <string name=\"export_interface\">Interfață de export</string>\n    <string name=\"error\">Eroare %s</string>\n    <string name=\"export_description\">După ce faceți clic pe „OK” puteți selecta un manager de fișiere pentru a vă salva setările ca fișier XML.</string>\n    <string name=\"import_chooser\">Selectați un fișier de importat</string>\n    <string name=\"import_dialog_description\">Se încarcă setările …</string>\n    <string name=\"please_wait\">Vă rugăm așteptați…</string>\n    <string name=\"import_settings_append_summary\">Se importă doar setările noi</string>\n    <string name=\"import_settings_append\">Adăugați setările importate</string>\n    <string name=\"import_settings_overwrite_summary\">Setările importate suprascriu setările actuale</string>\n    <string name=\"import_settings_overwrite\">Suprascrieți setările importate</string>\n    <string name=\"import_settings_set_summary\">Ștergeți setările relevante înainte de import</string>\n    <string name=\"import_settings_set\">Setați setările importate</string>\n    <string name=\"import_settings_section\">Importă setări</string>\n    <string name=\"export_apps_summary\">Nume noi pentru aplicații, pictograme ascunse și personalizate</string>\n    <string name=\"export_apps\">Exportați aplicații</string>\n    <string name=\"export_modifications_summary\">Lista rapidă, pictograme și etichete personalizate</string>\n    <string name=\"export_modifications\">Exportați personalizări</string>\n    <string name=\"export_tags_summary\">Exportați asociații de etichete, fără pictograme</string>\n    <string name=\"export_tags\">Exportați etichete</string>\n    <string name=\"export_xml\">Exportați ca XML</string>\n    <string name=\"export_chooser_xml\">Exportați „%s” ca XML</string>\n    <string name=\"export_chooser\">Export %s</string>\n    <string name=\"export_settings_section\">Exporta setarile</string>\n    <string name=\"title_backup\">Copie de rezervă</string>\n    <string name=\"backup_summary\">Setări de salvare, backup, export și import</string>\n    <string name=\"static_icon_letters_label\">Scrisori de utilizat pentru generarea de icoane</string>\n    <string name=\"gesture_click\">Atingeți ecranul gol</string>\n    <string name=\"gesture_fling_right\">Fling →</string>\n    <string name=\"gesture_fling_left\">Fling ←</string>\n    <string name=\"gesture_fling_up\">Fling ↑</string>\n    <string name=\"gesture_fling_down_right_summary\">Începeți din partea <b> → </b> a ecranului și aruncați <b> ↓ </b></string>\n    <string name=\"gesture_fling_down_right\">Zboară din | <b> → </b> |, <b> ↓ </b></string>\n    <string name=\"gesture_fling_down_left_summary\">Începeți din partea <b> ← </b> a ecranului și aruncați <b> ↓ </b></string>\n    <string name=\"gesture_fling_down_left\">Zboară din | <b> ← </b> |, <b> ↓ </b></string>\n    <string name=\"gesture_section\">Gesturi</string>\n    <string name=\"icon_pack_loading\">Se încarcă din pachetele de pictograme …</string>\n    <string name=\"tab_static_icons\">pictogramă implicită</string>\n    <string name=\"tab_app_icons\">%s\n\\n[pictogramele aplicației]</string>\n    <string name=\"selected_pack\">%s\n\\n[pachet curent]</string>\n    <string name=\"search_engine_url_help\">%s va fi înlocuit de interogarea dvs.</string>\n    <string name=\"label_search_engine_url\">URL pentru a deschide</string>\n    <string name=\"label_search_engine_name\">Căutați numele furnizorului</string>\n    <string name=\"hint_add_search_engine_url\">https: //search.engine/query= %s</string>\n    <string name=\"search_engine_set_default\">Face implicit</string>\n    <string name=\"confirm_edit_url_search_engine\">Salvează</string>\n    <string name=\"search_engine_edit_url\">Editați adresa URL</string>\n    <string name=\"add_search_hint\">Adăugați indiciu de căutare</string>\n    <string name=\"reset_search_hint_summary\">Restabiliți toate indicii de căutare</string>\n    <string name=\"reset_search_hint\">Resetați indiciu de căutare</string>\n    <string name=\"edit_search_hint_summary\">Comutați și editați indicii de căutare</string>\n    <string name=\"edit_search_hint\">Editați indiciu de căutare</string>\n    <string name=\"add_search_engine\">Adăugați motor de căutare</string>\n    <string name=\"reset_search_engines_summary\">Restabiliți motoarele de căutare implicite</string>\n    <string name=\"reset_search_engines\">Resetați motoarele de căutare</string>\n    <string name=\"edit_search_engines_summary\">Comutați și editați furnizorii de căutare</string>\n    <string name=\"edit_search_engines\">Editați motoarele de căutare</string>\n    <string name=\"enable_calculator\">Calculator</string>\n    <string name=\"enable_url\">URL-ul web</string>\n    <string name=\"enable_search\">Motor de căutare</string>\n    <string name=\"provider_section\">Furnizori</string>\n    <string name=\"invalid_rename_search_engine\">Există deja un motor de căutare „%s”</string>\n    <string name=\"invalid_rename_tag\">Există deja o etichetă „%s”</string>\n    <string name=\"result_text2_size\">Dimensiunea textului secundar</string>\n    <string name=\"result_text_size\">Mărimea textului</string>\n    <string name=\"menu_popup_quick_list_customize\">Modificați lista rapidă</string>\n    <string name=\"search_bar_icon_color\">Culoarea icoanelor</string>\n    <string name=\"search_bar_text_color\">Culoarea textului</string>\n    <string name=\"result_text2_color\">Culoarea textului secundar</string>\n    <string name=\"result_text_color\">Culoarea textului</string>\n    <string name=\"result_icon_size\">Dimensiunea pictogramei</string>\n    <string name=\"contact_action_color\">Culoare pictogramă acțiune</string>\n    <string name=\"quick_list_toggle_color\">Comutați culoarea de fundal</string>\n    <string name=\"quick_list_color\">Culoare de fundal</string>\n    <string name=\"result_highlight_color\">Evidențiați culoarea</string>\n    <string name=\"quick_list_show_badge\">Afișați insignă la scurtături în „Lista rapidă”</string>\n    <string name=\"shortcut_show_badge_summary\">Afișați pictograma aplicației care a creat comanda rapidă</string>\n    <string name=\"shortcut_show_badge\">Afișați insigna aplicației</string>\n    <string name=\"shortcut_pin_auto_confirm\">Comandă rapidă de confirmare automată</string>\n    <string name=\"shortcut_section\">Comandă rapidă</string>\n    <string name=\"debug_item_relevance\">Căutați relevanță</string>\n    <string name=\"debug_widget_info\">Apăsați lung pentru informații suplimentare despre widget</string>\n    <string name=\"debug_widget_add_info\">Adăugați informații suplimentare despre widget</string>\n    <string name=\"debug_section\">Depanare</string>\n    <string name=\"shortcut_with_appName\">%1$s: %2$s</string>\n    <string name=\"cfg_widget_front\">Mutați <b> deasupra </b> tuturor</string>\n    <string name=\"cfg_widget_back\">Mutați <b>sub</b> toate</string>\n    <string name=\"cfg_widget_screen_right\">Mută în ecranul <b> → </b></string>\n    <string name=\"cfg_widget_screen_middle\">Mută în ecranul <b>central</b></string>\n    <string name=\"cfg_widget_screen_left\">Mută în <b> ecranul ← </b></string>\n    <string name=\"cfg_widget_remove\">Elimină</string>\n    <string name=\"cfg_widget_move_resize_exit\">Ieșiți din „Mutați și redimensionați”</string>\n    <string name=\"cfg_widget_move_resize\">Mutați și redimensionați</string>\n    <string name=\"cfg_widget_resize_exit\">Ieșiți din „Redimensionare”</string>\n    <string name=\"cfg_widget_resize_switch\">Schimbați redimensionarea</string>\n    <string name=\"cfg_widget_resize\">Redimensionează</string>\n    <string name=\"cfg_widget_move_exit\">Ieșiți din mișcare</string>\n    <string name=\"cfg_widget_move_switch\">Comutați mutarea</string>\n    <string name=\"cfg_widget_move\">Mută</string>\n    <string name=\"wp_animate_sides_desc\">Imaginea de fundal se va lipi de <b> ← </b> sau <b> → </b> după derulare</string>\n    <string name=\"wp_animate_sides\">Fundal lipit de margine</string>\n    <string name=\"wp_animate_center_desc\">După derulare fundalul se va centra automat</string>\n    <string name=\"wp_animate_center\">Centrează fundal</string>\n    <string name=\"wp_drag_animate_desc\">Trageți <b> ← </b> <b> → </b> pentru a derula imaginea de fundal</string>\n    <string name=\"wp_drag_animate\">Derulează fundalul</string>\n    <string name=\"sensor_orientation\">Orientarea senzorului</string>\n    <string name=\"lock_portrait\">Portret de blocare</string>\n    <string name=\"lwp_drag_desc\">Trimite mai multe atingeri</string>\n    <string name=\"lwp_drag\">Emulează glisarea fundalul</string>\n    <string name=\"lwp_touch\">Trimite atingerile la fundal</string>\n    <string name=\"title_wallpaper\">Interacțiuni de tapet</string>\n    <string name=\"icon_pack_content_list\">Icoane din\n\\n%s</string>\n    <string name=\"icon_pack_section\">Pachet de pictograme</string>\n    <string name=\"cache_half_apps_summary\">Faceți dimensiunea cache LRU la jumătate din numărul de aplicații instalate</string>\n    <string name=\"cache_half_apps\">Dimensiune mică a memoriei cache</string>\n    <string name=\"memory_section\">Memorie</string>\n    <string name=\"screen_off_cache_clear_summary\">Goliți memoria cache când ecranul se oprește</string>\n    <string name=\"screen_off_cache_clear\">Eliberați des memoria cache</string>\n    <string name=\"cache_drawable\">Pictograme cache</string>\n    <string name=\"quick_list_text_visible\">Afișați nume</string>\n    <string name=\"quick_list_icons_visible\">Afișați pictograme</string>\n    <string name=\"edit_quick_list_tab_tags\">Tag-uri</string>\n    <string name=\"edit_quick_list_tab_favorites\">Favorite</string>\n    <string name=\"edit_quick_list_tab_actions\">Acțiuni</string>\n    <string name=\"edit_quick_list_tab_filters\">Filtre</string>\n    <string name=\"edit_quick_list_preview\">Previzualizare <em> (atingere lungă pentru a începe tragerea și fixarea, atingere pentru eliminare) </em></string>\n    <string name=\"quick_list_content_summary\">Schimbați ordinea și adăugați intrări</string>\n    <string name=\"quick_list_content\">Personalizați conținutul</string>\n    <string name=\"action_show_favorites\">Favorite</string>\n    <string name=\"action_show_shortcuts_reversed\">Comenzi rapide Z → A</string>\n    <string name=\"action_show_shortcuts\">Comenzi rapide A → Z</string>\n    <string name=\"action_show_contacts_reversed\">Contacte Z → A</string>\n    <string name=\"action_show_contacts\">Contacte A → Z</string>\n    <string name=\"action_show_apps_reversed\">Aplicații Z→A</string>\n    <string name=\"action_show_apps\">Aplicații A→Z</string>\n    <string name=\"filter_shortcuts\">Filtru de comenzi rapide</string>\n    <string name=\"filter_contacts\">Filtru contacte</string>\n    <string name=\"filter_apps\">Filtru aplicații</string>\n    <string name=\"reset_default_launcher\">Schimbă lansatorul de aplicații implicit</string>\n    <string name=\"reset_default_launcher_description\">După ce apăsați acasă, veți cere deschiderea unei aplicații. Selectarea întotdeauna face ca selecția dvs. să fie implicită.</string>\n    <string name=\"reset_default_launcher_confirm\">Doriti să schimbați lansatorul implicit\\?</string>\n    <string name=\"exit_the_app_description\">Lansatorul dvs. implicit va fi deschis sau vi se va întreba pe care doriți să îl utilizați.</string>\n    <string name=\"exit_the_app_confirm\">Chiar închideți lansatorul\\?</string>\n    <string name=\"exit_the_app\">Închideți lansatorul</string>\n    <string name=\"pin_shortcut_icon\">Pictogramă de comandă rapidă</string>\n    <string name=\"pin_shortcut_label\">Alegeți eticheta de comandă rapidă</string>\n    <string name=\"pin_shortcut_message\">Doriți să adăugați această comandă rapidă\\?</string>\n    <string name=\"shortcuts_no_host_permission\">Deoarece acesta nu este lansatorul dvs. principal, acesta nu vă poate accesa comenzile rapide.</string>\n    <string name=\"popup_title_link\">Legături</string>\n    <string name=\"popup_title_customize\">Personalizați</string>\n    <string name=\"popup_title_hist_fav\">Preferințe</string>\n    <string name=\"icons_visible\">Afișați pictogramele</string>\n    <string name=\"tags_enabled_summary\">Afișați etichete pentru fiecare aplicație în lista de rezultate</string>\n    <string name=\"tags_enabled\">Afișați etichete pentru aplicații</string>\n    <string name=\"fuzzy_search_tags_summary\">Când căutați, includeți etichete ca posibili candidați</string>\n    <string name=\"fuzzy_search_tags\">Etichete de căutare fuzzy</string>\n    <string name=\"tags_section\">Tag-uri</string>\n    <string name=\"quick_list_height\">Înălțime</string>\n    <string name=\"quick_list_enabled\">Utilizați lista rapidă</string>\n    <string name=\"quick_list_section\">Lista rapidă</string>\n    <string name=\"features_summary\">Comutați și personalizați aspectul aplicației</string>\n    <string name=\"title_features\">Aspect</string>\n    <string name=\"hint_new_tag\">adaugă etichetă</string>\n    <string name=\"custom_name_set_default\">Nume implicit</string>\n    <string name=\"custom_icon_preview_label\">Selectat\n\\npictogramă</string>\n    <string name=\"current_icon_preview_label\">Actual\n\\npictogramă</string>\n    <string name=\"default_icon_preview_label\">Mod implicit\n\\npictogramă</string>\n    <string name=\"custom_icon_badged\">insignă</string>\n    <string name=\"custom_icon_activity_adaptive_no_background\">fundal personalizat de activitate</string>\n    <string name=\"custom_icon_activity\">activitate</string>\n    <string name=\"custom_icon_application\">aplicație</string>\n    <string name=\"default_static_icon\">%s pictogramă implicită</string>\n    <string name=\"default_icon\">pictogramă implicită</string>\n    <string name=\"hint_custom_icon\">Filtrează după nume</string>\n    <string name=\"shortcut_rename_confirmation\">Numele comenzii rapide este acum „%s”</string>\n    <string name=\"app_rename_confirmation\">Numele aplicației este acum %s</string>\n    <string name=\"title_shortcut_rename\">Alegeți un nume personalizat pentru această comandă rapidă</string>\n    <string name=\"title_edit_url_search_engine\">Alegeți o adresă URL diferită pentru acest motor de căutare</string>\n    <string name=\"title_rename_search_hint\">Alegeți un alt nume pentru acest indiciu de căutare</string>\n    <string name=\"title_rename_search_engine\">Alegeți un alt nume pentru acest motor de căutare</string>\n    <string name=\"title_rename_tag\">Redenumeşte această etichetă peste tot</string>\n    <string name=\"title_app_rename\">Alegeți un nume personalizat pentru această aplicație</string>\n    <string name=\"menu_custom_icon\">Schimbă iconița</string>\n    <string name=\"menu_shortcut_rename\">Redenumiți comanda rapidă</string>\n    <string name=\"menu_action_delete\">Șterge</string>\n    <string name=\"menu_action_rename\">Redenumire</string>\n    <string name=\"menu_app_rename\">Redenumiți aplicația</string>\n    <string name=\"size\">&lt;%d&gt;</string>\n    <string name=\"value\">valoare %d</string>\n    <string name=\"icons_pack_default_name\">Iconițe de sistem</string>\n    <string name=\"icons_pack\">Alegeți pachetul</string>\n    <string name=\"result_list_section\">Lista de rezultate</string>\n    <string name=\"search_bar_section\">Bara de căutare</string>\n    <string name=\"title_select_size\">Trageți glisorul pentru a selecta dimensiunea dorită</string>\n    <string name=\"title_select_alpha\">Trageți glisorul pentru a selecta opacitatea dorită</string>\n    <string name=\"cd_icon_tag\">Setează pictograma etichetei</string>\n    <string name=\"cd_rename_tag\">Redenumiți eticheta</string>\n    <string name=\"cd_remove_tag\">_Elimină etichetă</string>\n    <string name=\"cd_undo_remove_tag\">Anulați eliminarea etichetei</string>\n    <string name=\"cd_add_tag\">Adaugă etichetă</string>\n    <string name=\"cd_item_contact_call\">Sună</string>\n    <string name=\"cd_item_contact_message\">Mesaj</string>\n    <string name=\"cd_show_all_apps\">Afișați toate aplicațiile</string>\n    <string name=\"cd_main_clear\">Curăță bara de căutare</string>\n    <string name=\"cd_main_menu\">Meniu</string>\n    <string name=\"black_notification_icons\">Icoane întunecate</string>\n    <string name=\"gradient\">Gradient</string>\n    <string name=\"notification_bar_section\">Bara de notificari</string>\n    <string name=\"title_ui\">Interfață</string>\n    <string name=\"user_interface_summary\">Teme, transparență și culori</string>\n    <string name=\"rate_the_app\">Evaluați aplicația</string>\n    <string name=\"menu_popup_tags_manager\">Manager de etichete</string>\n    <string name=\"menu_popup_launcher_settings\">Setări TinyBit</string>\n    <string name=\"menu_popup_android_settings\">Setări dispozitiv</string>\n    <string name=\"menu_popup_title_settings\">Setări</string>\n    <string name=\"menu_popup_title\">Meniul principal</string>\n    <string name=\"menu_widget_configure\">Personalizați widgetul …</string>\n    <string name=\"menu_widget_remove\">Eliminați widgetul …</string>\n    <string name=\"menu_widget_add\">Adăugați un widget …</string>\n    <string name=\"menu_widget_title\">Meniu widget</string>\n    <string name=\"change_wallpaper\">Schimbă imaginea de fundal</string>\n    <string name=\"entry_not_found\">Nu s-a putut găsi „%s”</string>\n    <string name=\"menu_exclude_kiss\">de la TinyBit</string>\n    <string name=\"menu_exclude_history\">din istoric</string>\n    <string name=\"menu_app_uninstall\">Dezinstalare</string>\n    <string name=\"menu_app_store\">Arată în magazin</string>\n    <string name=\"menu_app_details\">Info aplicație</string>\n    <string name=\"menu_tags_edit\">Etichete de editare</string>\n    <string name=\"menu_tags_add\">Adaugă etichete</string>\n    <string name=\"menu_remove_shortcut\">Anulați fixarea</string>\n    <string name=\"menu_show\">Dezvăluie</string>\n    <string name=\"menu_hide\">Ascunde</string>\n    <string name=\"menu_exclude\">Exclude…</string>\n    <string name=\"permission_denied\">Acordă mai întâi această permisiune</string>\n    <string name=\"stub_app_tag\">lista de etichete a aplicației</string>\n    <string name=\"menu_remove_history\">Elimină din istoric</string>\n    <string name=\"cant_pin_shortcut\">Nu s-a putut adăuga comanda rapidă. TinyBit este lansatorul dvs. implicit\\?</string>\n    <string name=\"unable_to_initialize_shortcuts\">Nu se pot accesa comenzile rapide. Este TinyBit lansatorul dvs. implicit\\?</string>\n    <string name=\"removed_item\">Eliminat %s</string>\n    <string name=\"copy_confirmation\">Copiat „%s” în clipboard</string>\n    <string name=\"ui_item_visit\">Accesați „%1$s”</string>\n    <string name=\"ui_item_search\">Căutați în %1$s „%2$s”\\?</string>\n    <string name=\"hint_ui_search\">Căutați în aplicații, contacte, …</string>\n    <string name=\"app_name\">Lansator TinyBit</string>\n    <string name=\"debug_ksh_touch\">Ascunsător de defilare pentru tastatură</string>\n    <string name=\"lwp_pages_horizontal_1\">pagina centrală</string>\n    <string name=\"popup_section\">Pop-up</string>\n    <string name=\"debug_ksh_touch_summary\">Afișați starea ca culoare de fundal</string>\n    <string name=\"toast_hibernate_completed\">%s a hibernat, relansați pentru a se trezi</string>\n    <string name=\"menu_app_hibernate\">Hibernează</string>\n    <string name=\"toast_hibernate_error\">[EROARE] %s nu a hibernat</string>\n    <string name=\"cd_item_contact_open\">Deschis</string>\n    <string name=\"popup_title_shortcut_dynamic\">Comenzi rapide</string>\n    <string name=\"reset_cached_app_icons_confirm\">Resetați memoria cache a pachetului de pictograme\\?</string>\n    <string name=\"lwp_pages_horizontal_2\">2 pagini (← și →)</string>\n    <string name=\"reset_cached_app_icons\">Resetați pachetul de pictograme</string>\n    <string name=\"cfg_widget_screen_down\">Treceți la ecranul <b>↓</b></string>\n    <string name=\"hint_rename_tag\">Alegeți un alt nume pentru această etichetă</string>\n    <string name=\"selected_contact_mime_types\">Selectați contactele care vor fi afișate</string>\n    <string name=\"reset_cached_app_icons_description\">Resetează numele și versiunea pachetului de pictograme din cache.</string>\n    <string name=\"generate_theme_confirm\">Setați culorile generate\\?</string>\n    <string name=\"generate_theme_description\">Resetează toate culorile și aplică culorile generate pe baza culorilor primare și secundare</string>\n    <string name=\"reset_matrix_confirm\">Setați valori implicite\\?</string>\n    <string name=\"reset_matrix_description\">Resetează toate setările pictogramelor de pe acest ecran</string>\n    <string name=\"popup_ripple_color\">Atingeți culoarea</string>\n    <string name=\"popup_background_argb\">Culoare de fundal</string>\n    <string name=\"popup_border_argb\">Culoarea chenarului</string>\n    <string name=\"popup_text_color\">Culoarea textului</string>\n    <string name=\"popup_title_color\">Culoarea titlului</string>\n    <string name=\"settings_theme\">Tema setărilor</string>\n    <string name=\"settings_8bit\">8 biți</string>\n    <string name=\"settings_white\">alb</string>\n    <string name=\"settings_black\">Negru</string>\n    <string name=\"settings_dark\">Întuneric</string>\n    <string name=\"behaviour_clear_search_after_launch\">Ștergeți bara de căutare după lansare</string>\n    <string name=\"dm_empty_quick_list\">Arată docul</string>\n    <string name=\"dm_search_quick_list\">Arată docul</string>\n    <string name=\"dm_widget_quick_list\">Arată docul</string>\n    <string name=\"dm_empty_fullscreen\">Ecran complet</string>\n    <string name=\"dm_search_fullscreen\">Ecran complet</string>\n    <string name=\"dm_search_fullscreen_summary\">Doar când tastatura este închisă</string>\n    <string name=\"dm_widget_fullscreen\">Ecran complet</string>\n    <string name=\"title_desktop_mode_empty\">Modul desktop „Gol”</string>\n    <string name=\"title_desktop_mode_search\">Modul desktop „Căutare”</string>\n    <string name=\"title_desktop_mode_widget\">Modul desktop „Widget”</string>\n    <string name=\"search_bar_at_bottom\">Poziție în jos</string>\n    <string name=\"search_bar_ripple_color\">Atingeți culoarea</string>\n    <string name=\"search_bar_cursor_argb\">Culoarea cursorului</string>\n    <string name=\"back_device_key\">Butonul Înapoi</string>\n    <string name=\"action_show_untagged\">Neetichetat</string>\n    <string name=\"dm_search_open_result\">Lista inițială de rezultate</string>\n    <string name=\"action_app_to_run\">Aplicație de rulat</string>\n    <string name=\"lwp_pages_vertical_2\">2 pagini (↑ și ↓)</string>\n    <string name=\"matrix_contacts\">Modulează contacte</string>\n    <string name=\"matrix_contacts_summary\">Modulați culorile pictogramelor de contact</string>\n    <string name=\"tags_menu_section\">Meniul Etichete</string>\n    <string name=\"tags_menu_icons\">Afișați pictograme</string>\n    <string name=\"tags_menu_icon_size\">Dimensiunea pictogramei</string>\n    <string name=\"tags_menu_list\">Etichete de afișat</string>\n    <string name=\"tags_menu_order\">Comanda etichetelor</string>\n    <string name=\"tags_menu_untagged\">Afișați neetichetat</string>\n    <string name=\"result_popup_order\">Comanda de categorie cu atingere lungă</string>\n    <string name=\"result_popup_order_summary\">Ordine pe categorii pentru elementele pop-up cu atingere lungă</string>\n    <string name=\"debug_favorites\">Favorite</string>\n    <string name=\"debug_favorites_summary\">Afișați intrările din tabelul DB „favorite”.</string>\n    <string name=\"title_presets\">Culoarea prestabilită</string>\n    <string name=\"summary_presets\">Presetare și generare de culori</string>\n    <string name=\"primary_color\">Culoare primară</string>\n    <string name=\"secondary_color\">Culoare secundară</string>\n    <string name=\"generate_theme_simple\">Tema simplă</string>\n    <string name=\"generate_theme_simple_summary\">Utilizați primar pentru fundal și secundar pentru text</string>\n    <string name=\"generate_theme_highlight\">Evidențiați tema</string>\n    <string name=\"generate_theme_highlight_summary\">Utilizați primar pentru fundal și secundar pentru evidențiere</string>\n    <string name=\"menu_popup_tags_menu\">Meniul Etichete</string>\n    <string name=\"menu_quick_list_add\">Adăugați la andocare</string>\n    <string name=\"menu_quick_list_remove\">Scoateți de pe andocare</string>\n    <string name=\"entry_rename_confirmation\">Numele este acum „%s”</string>\n    <string name=\"quick_list_only_for_results\">Ascunde lista rapidă când nu sunt rezultate</string>\n    <string name=\"quick_list_only_for_results_summary\">Lista rapidă vizibilă atunci când nu sunt rezultate</string>\n    <string name=\"cfg_widget_screen_up\">Treceți la ecranul <b>↑</b></string>\n    <string name=\"enable_contacts\">Contacte telefonice</string>\n    <string name=\"show_tags_menu\">Deschideți meniul de etichete</string>\n    <string name=\"show_tags_list\">Meniul Etichete ca rezultate</string>\n    <string name=\"show_tags_list_reversed\">Meniul de etichete inversat ca rezultate</string>\n    <string name=\"title_matrix\">Modulați culoarea pictogramei</string>\n    <string name=\"icon_scale_red\">Scara rosie</string>\n    <string name=\"icon_scale_green\">Scara verde</string>\n    <string name=\"icon_scale_blue\">Scara albastră</string>\n    <string name=\"icon_scale_alpha\">Transparența la scară</string>\n    <string name=\"reset_matrix\">Setați valorile implicite</string>\n    <string name=\"corner_radius\">Raza colțului</string>\n    <string name=\"behaviour_section\">Comportament</string>\n    <string name=\"behaviour_link_keyboard_search_bar\">Conectați tastatura la vizibilitatea barei de căutare</string>\n    <string name=\"behaviour_widget_after_launch\">Modul widget după lansare</string>\n    <string name=\"settings_deep_blues\">DeepBlues</string>\n    <string name=\"title_static_rename\">Alegeți un nume personalizat</string>\n    <string name=\"letters_toggle\">Pictograme cu litere</string>\n    <string name=\"button_launcher\">Butonul Lansatorului</string>\n    <string name=\"button_home\">butonul Acasă</string>\n    <string name=\"icon_contrast\">Contrast</string>\n    <string name=\"icon_brightness\">Luminozitate</string>\n    <string name=\"icon_hue\">Nuanţă</string>\n    <string name=\"icon_saturation\">Saturare</string>\n    <string name=\"matrix_summary\">Luminozitate, contrast, nuanță, saturație</string>\n    <string name=\"initial_desktop\">Desktop inițial</string>\n    <string name=\"behaviour_summary\">Gesturi și comportament</string>\n    <string name=\"title_behaviour\">Comportament</string>\n    <string name=\"root_mode\">Modul rădăcină</string>\n    <string name=\"root_mode_summary\">Folosit pentru a hiberna aplicații</string>\n    <string name=\"root_mode_error\">Nu s-a putut obține acces root</string>\n    <string name=\"behaviour_link_close_keyboard_back_button\">Acțiune „Înapoi” la închiderea tastaturii</string>\n    <string name=\"keyboard_suggestions\">Autocompletare/sugestii</string>\n    <string name=\"device_admin_explanation\">Folosit pentru a bloca ecranul</string>\n    <string name=\"device_admin\">Administratorul dispozitivului</string>\n    <string name=\"device_admin_disable\">Eliminați această permisiune\\?</string>\n    <string name=\"device_admin_required\">Este necesar accesul de administrator al dispozitivului</string>\n    <string name=\"gesture_double_click\">Atingeți de două ori ecranul</string>\n    <string name=\"loading_icon\">Pictograma de încărcare</string>\n    <string name=\"keyboard_section\">Tastatură</string>\n    <string name=\"lwp_scroll_pages\">Pagini</string>\n    <string name=\"lwp_page_count_vertical\">↕ pagini</string>\n    <string name=\"lwp_page_count_horizontal\">⟷ pagini</string>\n    <string name=\"lwp_pages_vertical_1\">pagina centrală</string>\n    <string name=\"action_entry_to_show\">Etichete de afișat</string>\n    <string name=\"no_tags\">Nu au fost găsite etichete</string>\n    <string name=\"quick_list_animation\">Animați spectacol/ascundere</string>\n    <string name=\"enable_dial\">Căutare prin apelare</string>\n    <string name=\"tab_search_icon\">Pictograma de căutare</string>\n    <string name=\"quick_list_icons_visible_summary\">Afișarea pictogramelor elementelor din dock</string>\n    <string name=\"quick_list_text_visible_summary\">Afișarea numelor elementelor de dock</string>\n    <string name=\"debug_item_icon_info\">Informații despre pictograme</string>\n    <string name=\"export_preferences\">Setări de export</string>\n    <string name=\"export_preferences_summary\">Toate setările, caracteristicile și comportamentele, inclusiv interfața</string>\n    <string name=\"margin_horizontal\">Margine orizontală</string>\n    <string name=\"browse_add_icon\">Răsfoiți imagini locale</string>\n    <string name=\"result_list_row_height\">Înălțimea rândului de listă</string>\n    <string name=\"result_list_row_height_manual\">Înălțimea personalizată a rândului de listă</string>\n    <string name=\"shortcut_dynamic_in_results\">Afișați dinamica în rezultate</string>\n    <string name=\"search_bar_layout\">Layout</string>\n    <string name=\"quick_list_position\">Poziția</string>\n    <string name=\"quick_list_columns\">Coloane</string>\n    <string name=\"quick_list_columns_summary\">Numărul de elemente pe rând</string>\n    <string name=\"quick_list_rows\">Rânduri</string>\n    <string name=\"quick_list_rows_summary\">Înălțimea maximă se va modifica în consecință</string>\n    <string name=\"quick_list_rtl\">De la dreapta la stânga</string>\n    <string name=\"quick_list_rtl_summary\">Așezați elementele de la dreapta la stânga</string>\n    <string name=\"navigation_bar_section\">Bara de navigare</string>\n    <string name=\"icons_section\">Icoane</string>\n    <string name=\"options_section\">Opțiuni</string>\n    <string name=\"search_bar_animation\">Animați extinderea/coborârea</string>\n    <string name=\"app_name_debug\">Lansator de depanare TinyBit</string>\n    <string name=\"search_bar_text_size\">Dimensiunea textului</string>\n    <string name=\"search_bar_height\">Înălțimea barului</string>\n    <string name=\"exit_tags_manager_confirm\">Închideți Managerul de etichete\\?</string>\n    <string name=\"exit_tags_manager_description\">Veți pierde toate modificările dacă închideți Managerul de etichete.\n\\nSunteți sigur că doriți să continuați\\?</string>\n    <string name=\"color_and_opacity\">Culoare și opacitate</string>\n    <string name=\"quick_list_icon_size\">Dimensiunea maximă a pictogramei</string>\n    <string name=\"result_list_argb\">Culoarea și opacitatea fundalului</string>\n    <string name=\"settings_default\">Implicit</string>\n    <string name=\"action_shortcut_to_run\">Comenzi rapide pentru a rula</string>\n    <string name=\"result_first_at_bottom\">Primul în partea de jos</string>\n    <string name=\"result_right_to_left\">De la dreapta la stânga</string>\n    <string name=\"result_right_to_left_summary\">Așezare rând grilă de la dreapta la stânga</string>\n    <string name=\"debug_provider_status\">Statutul furnizorului</string>\n    <string name=\"result_fading_edge\">Gradient</string>\n    <string name=\"result_fading_edge_summary\">Marginile de sus și de jos se estompează</string>\n    <string name=\"margin_vertical\">Margine verticală</string>\n    <string name=\"shortcut_dynamic_in_results_summary\">Comenzile rapide dinamice vor fi considerate ca rezultate posibile la căutare</string>\n    <string name=\"popup_title_debug\">Informații de depanare</string>\n    <string name=\"action_reload\">Furnizori de reîncărcare</string>\n    <string name=\"action_toggle_grid\">Comutați grila</string>\n    <string name=\"action_show_apps_grid4\">Aplicație grilă A→Z</string>\n    <string name=\"action_show_apps_grid4_reversed\">Aplicație grilă Z→A</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-ro-v26/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"force_shape\">Forțează forma adaptativă</string>\n    <string name=\"force_shape_summary\">Utilizați masca adaptivă pe pictogramele neadaptative</string>\n    <string name=\"force_adaptive_summary\">Pentru pictogramele non-adaptive, reduceți scalarea și așezați-le pe un fundal alb</string>\n    <string name=\"force_adaptive\">Forțează pictogramele adaptive</string>\n    <string name=\"adaptive_shape_name\">Forma pictogramei adaptive</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-ru/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"copy_confirmation\">Скопиравно \\\"%s\\\" в буфер обмена</string>\n    <string name=\"stub_application\">Имя приложения</string>\n    <string name=\"hint_ui_search\">Поиск в приложениях, контактах, …</string>\n    <string name=\"removed_item\">Удалено %s</string>\n    <string name=\"toast_hibernate_error\">[ОШИБКА] %s не остановлено</string>\n    <string name=\"unable_to_initialize_shortcuts\">Нет доступа к ярлыкам. TinyBit Ваш лаунчер по умолчанию\\?</string>\n    <string name=\"menu_exclude\">Исключить…</string>\n    <string name=\"menu_hide\">Скрыть</string>\n    <string name=\"menu_tags_add\">Добавить теги</string>\n    <string name=\"menu_tags_edit\">Изменить теги</string>\n    <string name=\"menu_app_details\">О приложении</string>\n    <string name=\"menu_app_hibernate\">Остановить</string>\n    <string name=\"menu_app_uninstall\">Деинсталляция</string>\n    <string name=\"menu_app_store\">Найти в маркете</string>\n    <string name=\"menu_exclude_history\">из истории</string>\n    <string name=\"application_not_found\">Не удалось запустить приложение \\\"%s\\\"</string>\n    <string name=\"menu_quick_list_add\">В избранное</string>\n    <string name=\"menu_quick_list_remove\">Убрать из избранного</string>\n    <string name=\"stub_app_tag\">Список тегов</string>\n    <string name=\"menu_show\">Показать</string>\n    <string name=\"menu_widget_add\">Добавить виджет…</string>\n    <string name=\"menu_widget_remove\">Убрать виджет…</string>\n    <string name=\"menu_widget_configure\">Настроить виджет…</string>\n    <string name=\"menu_popup_title\">Главное меню</string>\n    <string name=\"menu_popup_android_settings\">Настройки устройства</string>\n    <string name=\"menu_popup_tags_manager\">Менеджер тегов</string>\n    <string name=\"menu_popup_tags_menu\">Меню тегов</string>\n    <string name=\"rate_the_app\">Оценить приложение</string>\n    <string name=\"user_interface_summary\">Темы, прозрачность и цвета</string>\n    <string name=\"title_ui\">Внешний вид</string>\n    <string name=\"notification_bar_section\">Панель уведомлений</string>\n    <string name=\"color_and_opacity\">Цвет и размытие</string>\n    <string name=\"gradient\">Градиент</string>\n    <string name=\"cd_main_clear\">Очистить панель поиска</string>\n    <string name=\"black_notification_icons\">Тёмные значки</string>\n    <string name=\"cd_show_all_apps\">Показать все приложения</string>\n    <string name=\"cd_item_contact_message\">Сообщение</string>\n    <string name=\"cd_item_contact_call\">Звонок</string>\n    <string name=\"cd_item_contact_open\">Открыть</string>\n    <string name=\"cd_undo_remove_tag\">Отменить удаление тега</string>\n    <string name=\"cd_icon_tag\">Назначить иконку тега</string>\n    <string name=\"title_select_alpha\">Тяните ползунок для выбора нужного размытия</string>\n    <string name=\"title_select_size\">Тяните ползунок для выбора нужного размера</string>\n    <string name=\"search_bar_text_size\">Размер текста</string>\n    <string name=\"search_bar_height\">Высота панели</string>\n    <string name=\"result_list_section\">Список</string>\n    <string name=\"icons_pack\">Выберите пакет</string>\n    <string name=\"value\">значение %d</string>\n    <string name=\"size\">&lt;%d&gt;</string>\n    <string name=\"menu_app_rename\">Переименовать приложение</string>\n    <string name=\"menu_action_delete\">Удалить</string>\n    <string name=\"menu_shortcut_rename\">Переименовать ярлык</string>\n    <string name=\"menu_custom_icon\">Сменить иконку</string>\n    <string name=\"title_app_rename\">Своё название для этого приложения</string>\n    <string name=\"hint_rename_tag\">Новое имя для тега</string>\n    <string name=\"title_edit_url_search_engine\">Введите другой URL для этого поисковика</string>\n    <string name=\"title_shortcut_rename\">Введите новое имя для ярлыка</string>\n    <string name=\"app_rename_confirmation\">Имя приложения теперь %s</string>\n    <string name=\"shortcut_rename_confirmation\">Имя ярлыка теперь \\\"%s\\\"</string>\n    <string name=\"default_icon\">Иконка по умолчанию</string>\n    <string name=\"custom_icon_application\">Приложение</string>\n    <string name=\"default_icon_preview_label\">Иконка\n\\nпо умолчанию</string>\n    <string name=\"current_icon_preview_label\">Актуальная\n\\nиконка</string>\n    <string name=\"custom_name_set_default\">Имя по умолчанию</string>\n    <string name=\"hint_new_tag\">Новый тег</string>\n    <string name=\"quick_list_section\">Избранное</string>\n    <string name=\"quick_list_enabled\">Показать избранное</string>\n    <string name=\"quick_list_icon_size\">Максимальный размер иконки</string>\n    <string name=\"fuzzy_search_tags\">Теги для нечёткого поиска</string>\n    <string name=\"app_name\">TinyBit Launcher</string>\n    <string name=\"menu_widget_title\">Меню виджетов</string>\n    <string name=\"ui_item_search\">Поиск в %1$s для \\\"%2$s\\\"\\?</string>\n    <string name=\"toast_hibernate_completed\">%s остановлено, запустите снова для пробуждения</string>\n    <string name=\"cant_pin_shortcut\">Ярлык не добавлен. TinyBit Ваш лаунчер по умолчанию\\?</string>\n    <string name=\"permission_denied\">Предоставьте сначала это разрешение</string>\n    <string name=\"menu_remove_history\">Убрать из истории</string>\n    <string name=\"menu_exclude_kiss\">из TinyBit</string>\n    <string name=\"change_wallpaper\">Изменить обои</string>\n    <string name=\"entry_not_found\">Не удалось найти \\\"%s\\\"</string>\n    <string name=\"menu_popup_title_settings\">Настройки</string>\n    <string name=\"menu_popup_launcher_settings\">Настройки TinyBit</string>\n    <string name=\"cd_main_menu\">Меню</string>\n    <string name=\"cd_add_tag\">Добавить тег</string>\n    <string name=\"cd_remove_tag\">Удалить тег</string>\n    <string name=\"cd_rename_tag\">Переименовать тег</string>\n    <string name=\"menu_action_rename\">Переименовать</string>\n    <string name=\"search_bar_section\">Панель поиска</string>\n    <string name=\"icons_pack_default_name\">Системные иконки</string>\n    <string name=\"entry_rename_confirmation\">Новое имя теперь \\\"%s\\\"</string>\n    <string name=\"title_rename_search_engine\">Новое имя поисковика</string>\n    <string name=\"quick_list_height\">Высота избранного</string>\n    <string name=\"custom_icon_preview_label\">Выбранная\n\\nиконка</string>\n    <string name=\"tags_section\">Теги</string>\n    <string name=\"fuzzy_search_tags_summary\">При поиске включайте теги в качестве возможных вариантов на совпадение</string>\n    <string name=\"app_name_debug\">Отладка TinyBit launcher</string>\n    <string name=\"ui_item_visit\">Посетить \\\"%1$s\\\"</string>\n    <string name=\"menu_remove_shortcut\">Открепить</string>\n    <string name=\"hint_custom_icon\">Фильтровать по имени</string>\n    <string name=\"default_static_icon\">%s значок по умолчанию</string>\n    <string name=\"export_settings_section\">Экспорт настроек</string>\n    <string name=\"export_chooser\">Экспорт %s</string>\n    <string name=\"export_xml\">Экспортировать как XML</string>\n    <string name=\"export_tags\">Экспорт тегов</string>\n    <string name=\"export_apps\">Экспорт приложений</string>\n    <string name=\"export_widgets\">Экспорт виджетов</string>\n    <string name=\"result_history_adaptive_summary\">Количество часов для адаптивной истории</string>\n    <string name=\"unlimited_search_cap_summary\">Установить максимально возможный предел</string>\n    <string name=\"value_float\">значение %.2f</string>\n    <string name=\"value_float_xy\">горизонтальный %1$.2f | вертикальный %2$.2f</string>\n    <string name=\"size_float\">&lt;%.1f&gt;</string>\n    <string name=\"cfg_widget_front\">Переместить <b>выше</b> всех</string>\n    <string name=\"edit_search_engines\">Изменение поисковой системы</string>\n    <string name=\"enable_contacts\">Телефонные контакты</string>\n    <string name=\"please_wait\">Пожалуйста подождите.</string>\n    <string name=\"error\">Ошибка %s</string>\n    <string name=\"export_backup_summary\">Все пользовательские значки, теги и настройки</string>\n    <string name=\"export_history\">Экспорт истории</string>\n    <string name=\"choose_file_activity_not_found\">Пожалуйста, установите файловый менеджер.</string>\n    <string name=\"label_search_hint\">Подсказка для поиска</string>\n    <string name=\"reset_cached_app_icons_description\">Сбрасывает кэшированное имя и версию пакета значков.</string>\n    <string name=\"title_desktop_mode_search\">Настольный режим «Поиск»</string>\n    <string name=\"device_admin\">Администратор устройства</string>\n    <string name=\"device_admin_explanation\">Используется для блокировки экрана</string>\n    <string name=\"reset_cached_app_icons\">Сброс пакета значков</string>\n    <string name=\"debug_provider_status\">Состояние провайдера</string>\n    <string name=\"shadow_preview\">&lt;%3$.1f&gt;\n\\n&lt;%1$.1f×%2$.1f&gt;</string>\n    <string name=\"action_show_apps\">Приложения А→Я</string>\n    <string name=\"action_show_apps_grid4_reversed\">Сетка приложений Я→А</string>\n    <string name=\"action_show_shortcuts\">Ярлыки А→Я</string>\n    <string name=\"action_show_shortcuts_reversed\">Ярлыки Я→А</string>\n    <string name=\"edit_quick_list_tab_actions\">Действия</string>\n    <string name=\"quick_list_icons_visible\">Показать значки</string>\n    <string name=\"enable_search\">Поисковая система</string>\n    <string name=\"enable_dial\">Поиск по номеру</string>\n    <string name=\"dm_search_quick_list\">Показать док-станцию</string>\n    <string name=\"result_search_cap\">Максимальное количество результатов поиска</string>\n    <string name=\"unlimited_search_cap\">Неограниченное количество результатов поиска</string>\n    <string name=\"dm_search_open_result\">Исходный список результатов</string>\n    <string name=\"invalid_rename_tag\">Тег \\\"%s\\\" уже существует</string>\n    <string name=\"icon_pack_loading\">Загрузка из пакетов значков…</string>\n    <string name=\"tab_button_icon\">Значок кнопки</string>\n    <string name=\"export_chooser_xml\">Экспорт \\\"%s\\\" в формате XML</string>\n    <string name=\"export_description\">После нажатия \\\"OK\\\" вы можете выбрать файловый менеджер для сохранения настроек в виде XML-файла.</string>\n    <string name=\"export_interface_summary\">Все цвета интерфейса, размеры, переключатели и формы значков</string>\n    <string name=\"import_settings_append_summary\">Импортируются только новые настройки</string>\n    <string name=\"result_history_size_summary\">Максимальное количество элементов при отображении истории</string>\n    <string name=\"import_settings_set_summary\">Очистите соответствующие настройки перед импортом</string>\n    <string name=\"import_settings_overwrite\">Перезаписать импортированные настройки</string>\n    <string name=\"import_settings_overwrite_summary\">Импортированные настройки перезаписывают текущие настройки</string>\n    <string name=\"show_tags_list_reversed\">Перевернутое меню тегов как результаты</string>\n    <string name=\"contact_pack_mask_summary\">Использовать форму пакета значков для контактов</string>\n    <string name=\"import_dialog_description\">Загрузка настроек…</string>\n    <string name=\"export_widgets_summary\">Импорт может не работать или требовать добавления каждого виджета вручную.</string>\n    <string name=\"force_adaptive_summary\">Уменьшите масштаб значка и поместите его на фигурный фон</string>\n    <string name=\"widget_placeholder_remove\">Удалить заполнитель\\?</string>\n    <string name=\"result_history_adaptive\">Адаптивная продолжительность</string>\n    <string name=\"shortcut_pack_mask\">Значок ярлыка</string>\n    <string name=\"app_version_summary\">%1$s - %2$s</string>\n    <string name=\"privacy_policy\">Политика конфиденциальности</string>\n    <string name=\"app_version\">Версия %s</string>\n    <string name=\"title_rename_tag\">Заменить везде, где встречается этот тег</string>\n    <string name=\"title_rename_search_hint\">Выберите другое имя для этой поисковой подсказки</string>\n    <string name=\"custom_icon_badged\">под значком</string>\n    <string name=\"features_summary\">Переключение и настройка элементов пользовательского интерфейса</string>\n    <string name=\"tags_enabled\">Отображение тегов для приложений</string>\n    <string name=\"tags_enabled_summary\">Показать теги для каждого приложения в списке результатов</string>\n    <string name=\"icons_visible\">Показать значки</string>\n    <string name=\"popup_title_shortcut_dynamic\">Ярлыки</string>\n    <string name=\"popup_title_hist_fav\">Параметры</string>\n    <string name=\"popup_title_customize\">Персонализировать</string>\n    <string name=\"popup_title_link\">Ссылки</string>\n    <string name=\"shortcuts_no_host_permission\">Сделайте это основным средством запуска для доступа к ярлыкам.</string>\n    <string name=\"pin_shortcut_message\">Вы хотите добавить этот ярлык\\?</string>\n    <string name=\"pin_shortcut_label\">Выбор ярлыка быстрого доступа</string>\n    <string name=\"pin_shortcut_icon\">Значок ярлыка</string>\n    <string name=\"quick_list_only_for_results\">Скрыть док при отсутствии результатов</string>\n    <string name=\"quick_list_only_for_results_summary\">Сделать док-станцию видимой, когда в списке результатов есть записи</string>\n    <string name=\"exit_the_app\">Закрыть средство запуска</string>\n    <string name=\"exit_the_app_confirm\">Закрыть средство запуска\\?</string>\n    <string name=\"exit_the_app_description\">Будет открыто ваше программа средства запуска по умолчанию, или вам будет предложено, какую программу вы хотите использовать.</string>\n    <string name=\"reset_default_launcher_confirm\">Изменить программу средства запуска по умолчанию\\?</string>\n    <string name=\"reset_default_launcher_description\">Запрос на открытие приложения после нажатия на домашний экран.</string>\n    <string name=\"reset_default_launcher\">Изменить программу средства запуска по умолчанию</string>\n    <string name=\"filter_apps\">Фильтр приложений</string>\n    <string name=\"filter_contacts\">Фильтр контактов</string>\n    <string name=\"filter_shortcuts\">Фильтр ярлыков</string>\n    <string name=\"action_reload\">Перезагрузка провайдеров</string>\n    <string name=\"quick_list_icons_visible_summary\">Отображение значков элементов док-станции</string>\n    <string name=\"quick_list_text_visible\">Показать имена</string>\n    <string name=\"quick_list_text_visible_summary\">Отображение названий элементов док-станции</string>\n    <string name=\"icon_pack_content_list\">Иконки из\n\\n%s</string>\n    <string name=\"title_wallpaper\">Взаимодействие с обоями</string>\n    <string name=\"lwp_touch\">Отправка сенсорных событий на обои</string>\n    <string name=\"lwp_drag\">Эмулировать событие перетаскивания для обоев</string>\n    <string name=\"lwp_drag_desc\">Отправка нескольких сенсорных событий</string>\n    <string name=\"cache_drawable\">Кэшировать значки</string>\n    <string name=\"wp_drag_animate\">Прокрутка обоев</string>\n    <string name=\"cfg_widget_move\">Переместить</string>\n    <string name=\"cfg_widget_move_switch\">Переключить ход</string>\n    <string name=\"cfg_widget_resize\">Изменить размер</string>\n    <string name=\"debug_section\">Отладка</string>\n    <string name=\"debug_widget_add_info\">Добавить дополнительную информацию о виджете</string>\n    <string name=\"debug_widget_info\">Длительное нажатие для получения дополнительной информации о виджете</string>\n    <string name=\"debug_item_relevance\">Актуальность поиска</string>\n    <string name=\"debug_item_icon_info\">Информация о значке</string>\n    <string name=\"shortcut_section\">Ярлык</string>\n    <string name=\"provider_section\">Провайдеры</string>\n    <string name=\"search_bar_text_color\">Цвет текста</string>\n    <string name=\"search_bar_icon_color\">Цвет значков</string>\n    <string name=\"invalid_rename_search_engine\">Поисковая система \\\"%s\\\" уже существует</string>\n    <string name=\"result_shadow_color\">Цвет тени текста</string>\n    <string name=\"enable_calculator\">Калькулятор</string>\n    <string name=\"enable_url\">Веб-адрес</string>\n    <string name=\"edit_search_engines_summary\">Переключение и редактирование поисковых систем</string>\n    <string name=\"reset_search_engines\">Сброс поисковых систем</string>\n    <string name=\"reset_search_engines_summary\">Восстановление поисковой системы по умолчанию</string>\n    <string name=\"add_search_engine\">Добавление поисковой системы</string>\n    <string name=\"edit_search_hint\">Изменить подсказку для поиска</string>\n    <string name=\"edit_search_hint_summary\">Переключение и редактирование подсказок для поиска</string>\n    <string name=\"reset_search_hint\">Сброс поисковой подсказки</string>\n    <string name=\"reset_search_hint_summary\">Восстановить все поисковые подсказки</string>\n    <string name=\"add_search_hint\">Добавить подсказку для поиска</string>\n    <string name=\"label_search_engine_name\">Имя поисковой системы</string>\n    <string name=\"search_engine_edit_url\">Изменить URL-адрес</string>\n    <string name=\"confirm_edit_url_search_engine\">Сохранить</string>\n    <string name=\"search_engine_set_default\">Использовать по умолчанию</string>\n    <string name=\"hint_add_search_engine_url\">https://search.engine/query=%s</string>\n    <string name=\"label_search_engine_url\">URL-адрес для открытия</string>\n    <string name=\"search_engine_url_help\">%s будет заменен на ваш запрос</string>\n    <string name=\"tab_search_icon\">Значок поиска</string>\n    <string name=\"tab_static_icons\">Значок по умолчанию</string>\n    <string name=\"gesture_section\">Жесты</string>\n    <string name=\"gesture_click\">Коснитесь пустого экрана</string>\n    <string name=\"export_tags_summary\">Экспорт ассоциаций тегов, без значков</string>\n    <string name=\"export_apps_summary\">Новые имена для приложений, скрытые и пользовательские значки</string>\n    <string name=\"import_settings_section\">Импорт настроек</string>\n    <string name=\"import_settings_set\">Установить импортированные настройки</string>\n    <string name=\"import_settings_append\">Добавить импортированные настройки</string>\n    <string name=\"import_chooser\">Выбрать файл для импорта</string>\n    <string name=\"export_preferences\">Экспорт настроек</string>\n    <string name=\"export_preferences_summary\">Все настройки, функции и поведение, включая интерфейс</string>\n    <string name=\"export_backup\">Экспорт резервной копии</string>\n    <string name=\"export_history_summary\">История всех запущенных приложений</string>\n    <string name=\"error_fail_import\">Не удалось импортировать файл. Проверьте журнал.</string>\n    <string name=\"widget_placeholder\">Нажмите, чтобы восстановить %s</string>\n    <string name=\"add_widget_failed\">Не удалось добавить виджет</string>\n    <string name=\"result_history_size\">Размер истории</string>\n    <string name=\"action_show_history_recency\">Недавняя история</string>\n    <string name=\"action_show_history_frequency\">Самые популярные</string>\n    <string name=\"action_show_history_frecency\">Недавние и частые</string>\n    <string name=\"show_tags_menu\">Открыть меню тегов</string>\n    <string name=\"show_tags_list\">Меню тегов как результаты</string>\n    <string name=\"adaptive_shape_name\">Форма значка</string>\n    <string name=\"force_adaptive\">Установить фон значка</string>\n    <string name=\"force_shape\">Форма усилия</string>\n    <string name=\"force_shape_summary\">Использовать фон нестандартной формы для всех</string>\n    <string name=\"contact_pack_mask\">Контакты</string>\n    <string name=\"contacts_shape_name\">Форма значка контакта</string>\n    <string name=\"contacts_shape_summary\">Форма значка контакта</string>\n    <string name=\"reset_cached_app_icons_confirm\">Вы хотите сбросить кэш пакета значков\\?</string>\n    <string name=\"behaviour_clear_search_after_launch\">Очистить строку поиска после запуска</string>\n    <string name=\"dm_search_fullscreen\">Полноэкранный</string>\n    <string name=\"dm_search_fullscreen_summary\">Только когда клавиатура закрыта</string>\n    <string name=\"search_bar_ripple_color\">Цвет касания</string>\n    <string name=\"search_bar_cursor_argb\">Цвет курсора</string>\n    <string name=\"search_bar_shadow_color\">Цвет тени текста</string>\n    <string name=\"search_bar_shadow_radius_dx_dy\">Размер тени и смещение</string>\n    <string name=\"root_mode\">Режим root-прав</string>\n    <string name=\"root_mode_summary\">Используется для перевода приложений в спящий режим</string>\n    <string name=\"device_admin_disable\">Убрать это разрешение\\?</string>\n    <string name=\"device_admin_required\">Требуется доступ приложению администратора устройства</string>\n    <string name=\"root_mode_error\">Не удалось получить root-доступ</string>\n    <string name=\"search_bar_layout\">Макет</string>\n    <string name=\"search_bar_animation\">Анимация развертывания/свертывания</string>\n    <string name=\"shortcut_dynamic_in_results_summary\">Динамические ярлыки будут рассматриваться как возможные результаты при поиске.</string>\n    <string name=\"popup_title_debug\">Информация об отладке</string>\n    <string name=\"action_toggle_grid\">Переключить сетку</string>\n    <string name=\"action_show_apps_grid4\">Сетка приложений А→Я</string>\n    <string name=\"action_show_apps_reversed\">Приложения Я→А</string>\n    <string name=\"action_show_contacts\">Контакты А→Я</string>\n    <string name=\"action_show_contacts_reversed\">Контакты Я→А</string>\n    <string name=\"action_show_favorites\">Избранное</string>\n    <string name=\"quick_list_content\">Персонализировать контент</string>\n    <string name=\"quick_list_content_summary\">Изменить порядок и добавить записи</string>\n    <string name=\"edit_quick_list_preview\">Предварительный просмотр <em> (долгое нажатие, чтобы начать перетаскивание, нажмите, чтобы удалить) </em></string>\n    <string name=\"edit_quick_list_tab_filters\">Фильтры</string>\n    <string name=\"edit_quick_list_tab_favorites\">Избранное</string>\n    <string name=\"edit_quick_list_tab_tags\">Теги</string>\n    <string name=\"screen_off_cache_clear_summary\">Очистка кэша при выключении экрана</string>\n    <string name=\"cache_half_apps\">Малый размер кэша</string>\n    <string name=\"cache_half_apps_summary\">Сделать размер кэша LRU вдвое меньше количества установленных приложений</string>\n    <string name=\"icon_pack_section\">Пакет значков</string>\n    <string name=\"memory_section\">Память</string>\n    <string name=\"screen_off_cache_clear\">Периодически освобождать кэш</string>\n    <string name=\"backup_summary\">Сохранение, резервное копирование, экспорт и импорт настроек</string>\n    <string name=\"title_backup\">Резервное копирование</string>\n    <string name=\"export_modifications\">Экспорт персонализации</string>\n    <string name=\"export_interface\">Экспорт интерфейсной части</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-tr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"copy_confirmation\">\\\"%s\\\" panoya kopyalandı</string>\n    <string name=\"app_name\">TinyBit başlatıcısı</string>\n    <string name=\"hint_ui_search\">Uygulamalarda, kişilerde ara…</string>\n    <string name=\"ui_item_search\">%1$s içinde \\\"%2$s\\\" aransın mı\\?</string>\n    <string name=\"removed_item\">%s Kaldırıldı</string>\n    <string name=\"app_name_debug\">TinyBit hata ayıklama başlatıcısı</string>\n    <string name=\"ui_item_visit\">\\\"%1$s\\\" sayfasını ziyaret edin</string>\n    <string name=\"quick_list_text_visible\">İsimleri göster</string>\n    <string name=\"quick_list_text_visible_summary\">Dock öğe adlarını görüntüleme</string>\n    <string name=\"cache_drawable\">Önbellek simgeleri</string>\n    <string name=\"screen_off_cache_clear\">Önbelleği sık sık serbest bırakın</string>\n    <string name=\"screen_off_cache_clear_summary\">Ekran kapandığında önbelleği boşaltma</string>\n    <string name=\"import_settings_append_summary\">Yalnızca yeni ayarlar içe aktarılır</string>\n    <string name=\"shortcut_pack_badge_mask_summary\">Kısayol rozeti için simge paketi maskesi kullanın</string>\n    <string name=\"reset_preferences_description\">Tüm ayarlar sıfırlanacaktır</string>\n    <string name=\"popup_background_argb\">Arka plan rengi</string>\n    <string name=\"button_home\">Ana sayfa düğmesi</string>\n    <string name=\"icon_contrast\">Kontrast</string>\n    <string name=\"icon_brightness\">Parlaklık</string>\n    <string name=\"behaviour_clear_search_after_launch\">Başlattıktan sonra arama çubuğunu temizleyin</string>\n    <string name=\"search_bar_ripple_color\">Dokunmatik renk</string>\n    <string name=\"search_bar_cursor_argb\">İmleç rengi</string>\n    <string name=\"back_device_key\">Geri düğmesi</string>\n    <string name=\"result_first_at_bottom\">En alttaki ilk</string>\n    <string name=\"result_right_to_left\">Sağdan sola</string>\n    <string name=\"margin_vertical\">Dikey kenar boşluğu</string>\n    <string name=\"margin_horizontal\">Yatay kenar boşluğu</string>\n    <string name=\"browse_add_icon\">Yerel görsellere göz atın</string>\n    <string name=\"result_list_row_height\">Liste satır yüksekliği</string>\n    <string name=\"quick_list_columns\">Sütunlar</string>\n    <string name=\"quick_list_rows_summary\">Maksimum yükseklik buna göre değişecektir</string>\n    <string name=\"quick_list_rtl\">Sağdan sola</string>\n    <string name=\"quick_list_icons_visible_summary\">Dock öğesi simgelerini görüntüleme</string>\n    <string name=\"memory_section\">Hafıza</string>\n    <string name=\"cache_half_apps\">Küçük önbellek boyutu</string>\n    <string name=\"cache_half_apps_summary\">LRU önbellek boyutunu yüklü uygulama sayısının yarısı kadar yapın</string>\n    <string name=\"icon_pack_section\">Simge paketi</string>\n    <string name=\"icon_pack_content_list\">Simgeler\n\\n%s</string>\n    <string name=\"lwp_drag_desc\">Birden fazla dokunma olayı gönderme</string>\n    <string name=\"sensor_orientation\">Sensör Yönü</string>\n    <string name=\"wp_drag_animate\">Kaydırmalı duvar kağıdı</string>\n    <string name=\"wp_animate_center\">Merkez duvar kağıdı</string>\n    <string name=\"wp_animate_center_desc\">Duvar kağıdı kaydırıldıktan sonra merkeze dönecektir</string>\n    <string name=\"wp_animate_sides_desc\">Duvar kağıdı kaydırıldıktan sonra <b>←</b> veya <b>→</b>\\'e yapışacaktır</string>\n    <string name=\"cfg_widget_move_switch\">Hareket değiştir</string>\n    <string name=\"cfg_widget_back\"><b>behind</b> all öğesini taşı</string>\n    <string name=\"result_icon_size\">Simge boyutu</string>\n    <string name=\"result_text_color\">Metin rengi</string>\n    <string name=\"result_text2_color\">İkincil metin rengi</string>\n    <string name=\"search_bar_text_color\">Metin rengi</string>\n    <string name=\"search_bar_icon_color\">Simgelerin rengi</string>\n    <string name=\"menu_popup_quick_list_customize\">İskeleyi düzenle</string>\n    <string name=\"result_text_size\">Metin boyutu</string>\n    <string name=\"enable_calculator\">Hesap Makinesi</string>\n    <string name=\"enable_dial\">Arama</string>\n    <string name=\"enable_contacts\">Telefon bağlantıları</string>\n    <string name=\"selected_contact_mime_types\">Gösterilecek kişileri seçin</string>\n    <string name=\"reset_search_engines\">Arama motorlarını sıfırlayın</string>\n    <string name=\"reset_search_engines_summary\">Varsayılan arama motorlarını geri yükleme</string>\n    <string name=\"add_search_engine\">Arama motoru ekleyin</string>\n    <string name=\"edit_search_hint\">Arama ipucunu düzenle</string>\n    <string name=\"reset_search_hint_summary\">Tüm arama ipuçlarını geri yükle</string>\n    <string name=\"search_engine_edit_url\">URL\\'yi Düzenle</string>\n    <string name=\"confirm_edit_url_search_engine\">Kaydet</string>\n    <string name=\"search_engine_set_default\">Varsayılan yap</string>\n    <string name=\"search_engine_url_help\">%s sorgunuz ile değiştirilecektir</string>\n    <string name=\"selected_pack\">%s\n\\n[mevcut paket]</string>\n    <string name=\"tab_app_icons\">%s\n\\n[uygulama simgeleri]</string>\n    <string name=\"tab_static_icons\">Varsayılan simge</string>\n    <string name=\"tab_search_icon\">Arama simgesi</string>\n    <string name=\"icon_pack_loading\">Simge paketlerinden yükleniyor…</string>\n    <string name=\"gesture_section\">Jestler</string>\n    <string name=\"gesture_fling_down_left\">Fling from |<b>←</b>|, <b>↓</b></string>\n    <string name=\"gesture_fling_down_left_summary\">Ekranın <b>←</b> tarafından başlayın ve <b>↓</b> fırlatın</string>\n    <string name=\"gesture_fling_down_right_summary\">Ekranın <b>→</b> tarafından başlayın ve <b>↓</b> fırlatın</string>\n    <string name=\"gesture_fling_up\">Fling ↑</string>\n    <string name=\"gesture_fling_left\">Fling ←</string>\n    <string name=\"gesture_fling_right\">Fling →</string>\n    <string name=\"static_icon_letters_label\">Simge oluşturmak için kullanılacak harfler</string>\n    <string name=\"backup_summary\">Ayarları kaydetme, yedekleme, dışa aktarma ve içe aktarma</string>\n    <string name=\"export_settings_section\">Dışa aktarma ayarları</string>\n    <string name=\"export_chooser\">Dışa Aktar %s</string>\n    <string name=\"export_xml\">XML olarak dışa aktar</string>\n    <string name=\"export_tags\">Etiketleri dışa aktar</string>\n    <string name=\"export_tags_summary\">Etiket ilişkilerini dışa aktarın, simge yok</string>\n    <string name=\"export_modifications\">Özelleştirmeleri dışa aktarma</string>\n    <string name=\"export_modifications_summary\">Dock, özel simgeler ve adlar</string>\n    <string name=\"export_apps\">Uygulamaları dışa aktarma</string>\n    <string name=\"export_apps_summary\">Uygulamalar, gizli ve özel simgeler için yeni isimler</string>\n    <string name=\"import_settings_section\">Ayarları içe aktar</string>\n    <string name=\"import_settings_set\">İçe aktarılan ayarları ayarlama</string>\n    <string name=\"import_settings_set_summary\">İçe aktarmadan önce ilgili ayarları temizleyin</string>\n    <string name=\"import_settings_overwrite\">İçe aktarılan ayarların üzerine yaz</string>\n    <string name=\"import_settings_overwrite_summary\">İçe aktarılan ayarlar mevcut ayarların üzerine yazılır</string>\n    <string name=\"please_wait\">Lütfen bekleyin.</string>\n    <string name=\"import_dialog_description\">Ayarlar yükleniyor…</string>\n    <string name=\"import_chooser\">İçe aktarılacak bir dosya seçin</string>\n    <string name=\"export_preferences\">Dışa aktarma ayarları</string>\n    <string name=\"export_preferences_summary\">Arayüz de dahil olmak üzere tüm ayarlar, özellikler ve davranışlar</string>\n    <string name=\"export_widgets_summary\">İçe aktarma çalışmayabilir veya her bir widget\\'ın manuel olarak eklenmesini gerektirebilir</string>\n    <string name=\"export_history\">İhracat geçmişi</string>\n    <string name=\"export_history_summary\">Başlatılan tüm uygulamaların geçmişi</string>\n    <string name=\"result_history_size\">Tarih boyutu</string>\n    <string name=\"show_tags_list\">Sonuç olarak etiketler menüsü</string>\n    <string name=\"force_adaptive\">Simge arka planını ayarlama</string>\n    <string name=\"force_adaptive_summary\">Simge ölçeğini küçültün ve şekilli bir arka plan üzerine yerleştirin</string>\n    <string name=\"force_shape\">Kuvvet şekli</string>\n    <string name=\"force_shape_summary\">Tümü için özel şekilli arka plan kullanın</string>\n    <string name=\"contact_pack_mask\">İletişim</string>\n    <string name=\"contact_pack_mask_summary\">Kişiler için simge paketi şeklini kullanın</string>\n    <string name=\"shortcut_pack_mask_summary\">Kısayol simgesi için simge paketi maskesi kullanın</string>\n    <string name=\"shortcut_shape_name\">Kısayol simgesi şekli</string>\n    <string name=\"shortcut_shape_summary\">Kısayol simgesinin şekli</string>\n    <string name=\"shortcut_pack_badge_mask\">Kısayol rozeti</string>\n    <string name=\"choose_letter_color\">Harf rengi</string>\n    <string name=\"choose_background_color\">Arka plan rengi</string>\n    <string name=\"choose_icon_scale\">Ölçek simgesi</string>\n    <string name=\"choose_icon_shape\">Şekil seçin</string>\n    <string name=\"choose_icon\">Simge seçin</string>\n    <string name=\"quick_list_ripple_color\">Dokunmatik renk</string>\n    <string name=\"reset_preferences\">Tercihleri sıfırla</string>\n    <string name=\"reset_preferences_confirm\">Tercihleri atalım mı\\?</string>\n    <string name=\"popup_border_argb\">Kenarlık rengi</string>\n    <string name=\"popup_text_color\">Metin rengi</string>\n    <string name=\"popup_title_color\">Başlık rengi</string>\n    <string name=\"settings_theme\">Ayarlar teması</string>\n    <string name=\"settings_8bit\">8bit</string>\n    <string name=\"settings_white\">Beyaz</string>\n    <string name=\"settings_black\">Siyah</string>\n    <string name=\"settings_dark\">Karanlık</string>\n    <string name=\"settings_deep_blues\">DeepBlues</string>\n    <string name=\"title_static_rename\">Özel bir ad seçin</string>\n    <string name=\"letters_toggle\">Harf simgeleri</string>\n    <string name=\"button_launcher\">Başlatıcı düğmesi</string>\n    <string name=\"action_show_untagged\">Etiketlenmemiş</string>\n    <string name=\"action_shortcut_to_run\">Çalıştırmak için kısayol</string>\n    <string name=\"action_entry_to_show\">Gösterilecek etiketler</string>\n    <string name=\"root_mode_summary\">Uygulamaları hazırda bekletmek için kullanılır</string>\n    <string name=\"root_mode_error\">Kök erişimi elde edilemedi</string>\n    <string name=\"debug_ksh_touch\">Klavye kaydırma gizleyicisi</string>\n    <string name=\"matrix_contacts\">Kontakları modüle edin</string>\n    <string name=\"matrix_contacts_summary\">Kişi simgelerinin renklerini değiştirme</string>\n    <string name=\"tags_menu_section\">Etiketler Menü</string>\n    <string name=\"summary_presets\">Renklerin önceden ayarlanması ve oluşturulması</string>\n    <string name=\"generate_theme_highlight_summary\">Arka plan için birincil ve vurgu için ikincil kullanın</string>\n    <string name=\"result_list_row_height_manual\">Özel liste satırı yüksekliği</string>\n    <string name=\"shortcut_dynamic_in_results\">Sonuçlarda dinamik göster</string>\n    <string name=\"quick_list_columns_summary\">Satır başına öğe sayısı</string>\n    <string name=\"quick_list_rows\">Sıralar</string>\n    <string name=\"quick_list_rtl_summary\">Öğeleri sağdan sola doğru düzenleyin</string>\n    <string name=\"icons_section\">Simgeler</string>\n    <string name=\"options_section\">Seçenekler</string>\n    <string name=\"search_bar_animation\">Genişletme/daraltma animasyonu</string>\n    <string name=\"unable_to_initialize_shortcuts\">Kısayollara erişilemedi. TinyBit varsayılan başlatıcınız mı\\?</string>\n    <string name=\"cant_pin_shortcut\">Kısayol eklenemedi. TinyBit varsayılan başlatıcınız mı\\?</string>\n    <string name=\"menu_widget_remove\">Widget\\'ı Kaldır…</string>\n    <string name=\"menu_widget_configure\">Widget\\'ı Özelleştir…</string>\n    <string name=\"cd_show_all_apps\">Tüm uygulamaları göster</string>\n    <string name=\"cd_item_contact_message\">Mesaj</string>\n    <string name=\"popup_title_shortcut_dynamic\">Kısayollar</string>\n    <string name=\"popup_title_hist_fav\">Tercihler</string>\n    <string name=\"popup_title_customize\">Özelleştirme</string>\n    <string name=\"popup_title_link\">Bağlantılar</string>\n    <string name=\"export_interface_summary\">Tüm arayüz renkleri, boyutları, geçişler ve simge şekilleri</string>\n    <string name=\"generate_theme_confirm\">Oluşturulan renkleri ayarlayın\\?</string>\n    <string name=\"dm_empty_quick_list\">İskeleyi göster</string>\n    <string name=\"title_desktop_mode_search\">Masaüstü modu \\\"Arama\\\"</string>\n    <plurals name=\"tag_entry_count\">\n        <item quantity=\"one\">bir giriş</item>\n        <item quantity=\"other\"/>\n    </plurals>\n    <string name=\"cd_icon_tag\">Etiket simgesini ayarla</string>\n    <string name=\"menu_shortcut_rename\">Kısayolu yeniden adlandır</string>\n    <string name=\"title_app_rename\">Bu uygulama için özel bir ad seçin</string>\n    <string name=\"title_rename_tag\">Bu etiketin göründüğü her yeri değiştirin</string>\n    <string name=\"hint_rename_tag\">Bu etiket için farklı bir ad seçin</string>\n    <string name=\"title_rename_search_engine\">Bu arama motoru için farklı bir ad seçin</string>\n    <string name=\"features_summary\">Kullanıcı arayüzü öğelerini değiştirme ve özelleştirme</string>\n    <string name=\"custom_icon_badged\">rozetli</string>\n    <string name=\"default_icon_preview_label\">Varsayılan\n\\nsimgesi</string>\n    <string name=\"tags_section\">Etiketler</string>\n    <string name=\"fuzzy_search_tags\">Fuzzy-search etiketleri</string>\n    <string name=\"exit_the_app_description\">Varsayılan başlatıcınız açılacak veya hangisini kullanmak istediğiniz sorulacaktır.</string>\n    <string name=\"reset_default_launcher_description\">Ana ekrana bastıktan sonra uygulamanın açılmasını ister.</string>\n    <string name=\"action_show_shortcuts_reversed\">Kısayollar Z→A</string>\n    <string name=\"action_show_favorites\">Favoriler</string>\n    <string name=\"behaviour_summary\">Jestler ve ne zaman ne yapılacağı</string>\n    <string name=\"edit_quick_list_tab_favorites\">Favoriler</string>\n    <string name=\"edit_quick_list_tab_tags\">Etiketler</string>\n    <string name=\"toast_hibernate_completed\">%s hazırda bekletildi, uyandırmak için yeniden başlatıldı</string>\n    <string name=\"toast_hibernate_error\">[HATA] %s hazırda bekletilmedi</string>\n    <string name=\"menu_quick_list_add\">İskeleye ekle</string>\n    <string name=\"menu_quick_list_remove\">İskeleden çıkarın</string>\n    <string name=\"menu_remove_history\">Geçmişten kaldır</string>\n    <string name=\"stub_application\">Uygulama adı</string>\n    <string name=\"stub_app_tag\">uygulama etiket listesi</string>\n    <string name=\"permission_denied\">Önce bu izni verin</string>\n    <string name=\"menu_exclude\">Hariç tut…</string>\n    <string name=\"menu_hide\">Gizle</string>\n    <string name=\"menu_app_details\">Uygulama bilgisi</string>\n    <string name=\"menu_app_store\">Mağazada görüntüle</string>\n    <string name=\"menu_app_uninstall\">Kaldır</string>\n    <string name=\"menu_app_hibernate\">Uyut</string>\n    <string name=\"menu_exclude_history\">geçmişten</string>\n    <string name=\"menu_exclude_kiss\">TinyBit\\'ten</string>\n    <string name=\"application_not_found\">\\\"%s\\\" uygulaması başlatılamıyor</string>\n    <string name=\"entry_not_found\">\\\"%s\\\" bulunamadı;</string>\n    <string name=\"change_wallpaper\">Duvar kağıdını değiştir</string>\n    <string name=\"menu_widget_title\">Widget menüsü</string>\n    <string name=\"menu_widget_add\">Widget Ekle…</string>\n    <string name=\"menu_popup_title\">Ana menü</string>\n    <string name=\"menu_popup_title_settings\">Ayarlar</string>\n    <string name=\"menu_popup_android_settings\">Cihaz ayarları</string>\n    <string name=\"menu_popup_launcher_settings\">TinyBit ayarları</string>\n    <string name=\"menu_popup_tags_manager\">Etiket yöneticisi</string>\n    <string name=\"menu_popup_tags_menu\">Etiketler menü</string>\n    <string name=\"rate_the_app\">Uygulamayı değerlendirin</string>\n    <string name=\"user_interface_summary\">Temalar, şeffaflık ve renkler</string>\n    <string name=\"title_ui\">Kullanıcı arayüzü</string>\n    <string name=\"notification_bar_section\">Bildirim çubuğu</string>\n    <string name=\"gradient\">Gradyan</string>\n    <string name=\"black_notification_icons\">Karanlık simgeler</string>\n    <string name=\"cd_main_menu\">Menü</string>\n    <string name=\"cd_main_clear\">Arama çubuğunu temizle</string>\n    <string name=\"cd_item_contact_open\">Açık</string>\n    <string name=\"cd_add_tag\">Etiket ekle</string>\n    <string name=\"cd_undo_remove_tag\">Etiketi kaldırmayı geri al</string>\n    <string name=\"cd_remove_tag\">Etiketi kaldır</string>\n    <string name=\"title_select_alpha\">İstediğiniz opaklığı seçmek için kaydırıcıyı sürükleyin</string>\n    <string name=\"title_select_size\">İstediğiniz boyutu seçmek için kaydırıcıyı sürükleyin</string>\n    <string name=\"search_bar_section\">Arama çubuğu</string>\n    <string name=\"search_bar_text_size\">Metin boyutu</string>\n    <string name=\"search_bar_height\">Bar yüksekliği</string>\n    <string name=\"result_list_section\">Sonuç listesi</string>\n    <string name=\"icons_pack\">Paket seçin</string>\n    <string name=\"icons_pack_default_name\">Sistem simgeleri</string>\n    <string name=\"value\">değer %d</string>\n    <string name=\"size\">&lt;%d&gt;</string>\n    <string name=\"menu_app_rename\">Uygulamayı yeniden adlandır</string>\n    <string name=\"menu_action_rename\">Yeniden Adlandır</string>\n    <string name=\"menu_action_delete\">Silme</string>\n    <string name=\"menu_custom_icon\">Simge değiştir</string>\n    <string name=\"title_rename_search_hint\">Bu arama ipucu için farklı bir ad seçin</string>\n    <string name=\"title_edit_url_search_engine\">Bu arama motoru için farklı bir URL seçin</string>\n    <string name=\"title_shortcut_rename\">Bu kısayol için özel bir ad seçin</string>\n    <string name=\"default_icon\">varsayılan simge</string>\n    <string name=\"default_static_icon\">%s varsayılan simge</string>\n    <string name=\"custom_icon_application\">uygulama</string>\n    <string name=\"custom_icon_activity\">aktivite</string>\n    <string name=\"custom_icon_activity_adaptive_no_background\">etkinlik özel arka planı</string>\n    <string name=\"current_icon_preview_label\">Güncel\n\\nsimgesi</string>\n    <string name=\"custom_icon_preview_label\">Seçilmiş\n\\nsimgesi</string>\n    <string name=\"custom_name_set_default\">Varsayılan ad</string>\n    <string name=\"hint_new_tag\">etiket ekle</string>\n    <string name=\"title_features\">Düzen</string>\n    <string name=\"fuzzy_search_tags_summary\">Arama yaparken olası eşleşme adayları olarak etiketleri ekleyin</string>\n    <string name=\"tags_enabled\">Uygulamalar için etiketleri görüntüleme</string>\n    <string name=\"tags_enabled_summary\">Sonuç listesindeki her uygulama için etiketleri gösterin</string>\n    <string name=\"icons_visible\">Simgeleri Göster</string>\n    <string name=\"pin_shortcut_message\">Bu kısayolu eklemek istiyor musunuz\\?</string>\n    <string name=\"quick_list_only_for_results_summary\">Sonuç listesinde girişler olduğunda dock\\'u görünür yapma</string>\n    <string name=\"reset_default_launcher_confirm\">Varsayılan başlatıcıyı değiştirelim mi\\?</string>\n    <string name=\"exit_the_app\">Fırlatıcıyı kapat</string>\n    <string name=\"exit_the_app_confirm\">Başlatıcıyı kapatayım mı\\?</string>\n    <string name=\"reset_default_launcher\">Varsayılan başlatıcıyı değiştirme</string>\n    <string name=\"action_show_apps_reversed\">Uygulamalar Z→A</string>\n    <string name=\"action_show_apps_grid4\">Uygulama ızgarası A→Z</string>\n    <string name=\"action_show_apps_grid4_reversed\">Uygulama ızgarası Z→A</string>\n    <string name=\"action_show_contacts\">İletişim A→Z</string>\n    <string name=\"action_show_contacts_reversed\">İletişim Z→A</string>\n    <string name=\"quick_list_content\">İçeriği özelleştirin</string>\n    <string name=\"quick_list_content_summary\">Sırayı değiştirin ve giriş ekleyin</string>\n    <string name=\"lwp_touch\">Dokunma olaylarını duvar kağıdına gönderme</string>\n    <string name=\"lwp_drag\">Duvar kağıdı için sürükleme olayını taklit etme</string>\n    <string name=\"lock_portrait\">Portreyi kilitle</string>\n    <string name=\"wp_animate_sides\">Yandan yapışkanlı duvar kağıdı</string>\n    <string name=\"cfg_widget_move\">Hareket</string>\n    <string name=\"cfg_widget_resize\">Yeniden Boyutlandır</string>\n    <string name=\"cfg_widget_move_resize_exit\">\\\"Taşı ve yeniden boyutlandır\\\" öğesinden çıkın;</string>\n    <string name=\"cfg_widget_remove\">Kaldırmak</string>\n    <string name=\"cfg_widget_screen_left\"><b>←</b> ekranına taşı</string>\n    <string name=\"cfg_widget_screen_up\"><b>↑</b> ekranına taşı</string>\n    <string name=\"cfg_widget_screen_middle\"><b>orta</b> ekrana geçme</string>\n    <string name=\"cfg_widget_screen_right\"><b>→</b> ekranına geçme</string>\n    <string name=\"cfg_widget_screen_down\"><b>↓</b> ekranına geçme</string>\n    <string name=\"cfg_widget_front\"><b>tümünü</b>üstüne taşı</string>\n    <string name=\"shortcut_pin_auto_confirm\">Otomatik onay kısayolu</string>\n    <string name=\"shortcut_show_badge\">Uygulama rozetini göster</string>\n    <string name=\"result_highlight_color\">Vurgulama rengi</string>\n    <string name=\"quick_list_color\">Arka plan rengi</string>\n    <string name=\"invalid_rename_search_engine\">Zaten bir \\\"%s\\\" arama motoru var</string>\n    <string name=\"provider_section\">Sağlayıcılar</string>\n    <string name=\"enable_search\">Arama motoru</string>\n    <string name=\"edit_search_engines\">Arama motorlarını düzenleme</string>\n    <string name=\"edit_search_engines_summary\">Arama sağlayıcılarını değiştirme ve düzenleme</string>\n    <string name=\"reset_search_hint\">Arama ipucunu sıfırla</string>\n    <string name=\"add_search_hint\">Arama ipucu ekle</string>\n    <string name=\"gesture_fling_down_right\">Fling from |<b>→</b>|, <b>↓</b></string>\n    <string name=\"gesture_click\">Boş ekrana dokunun</string>\n    <string name=\"title_backup\">Yedekleme</string>\n    <string name=\"export_chooser_xml\">\\\"%s\\\" öğesini XML olarak dışa aktarın</string>\n    <string name=\"import_settings_append\">İçe aktarılan ayarları ekle</string>\n    <string name=\"export_description\">Tamam\\\"a tıkladıktan sonra ayarlarınızı XML dosyası olarak kaydetmek için bir dosya yöneticisi seçebilirsiniz.</string>\n    <string name=\"error\">Hata %s</string>\n    <string name=\"export_interface\">Dışa aktarma arayüzü</string>\n    <string name=\"export_backup\">Yedeklemeyi dışa aktar</string>\n    <string name=\"export_backup_summary\">Tüm özel simgeler, etiketler ve tercihler</string>\n    <string name=\"export_widgets\">Widget\\'ları dışa aktarma</string>\n    <string name=\"error_fail_import\">Dosya içe aktarılamadı. Logcat\\'i kontrol edin.</string>\n    <string name=\"widget_placeholder\">Geri yüklemek için tıklayın %s</string>\n    <string name=\"add_widget_failed\">Widget eklenemedi</string>\n    <string name=\"widget_placeholder_remove\">Yer tutucuyu kaldıralım mı\\?</string>\n    <string name=\"choose_file_activity_not_found\">Lütfen bir dosya yöneticisi yükleyin.</string>\n    <string name=\"action_show_history_frequency\">En çok erişilen</string>\n    <string name=\"action_show_history_frecency\">Yakın zamanda ve sık sık</string>\n    <string name=\"action_show_history_adaptive\">Uyarlanabilir geçmiş</string>\n    <string name=\"show_tags_menu\">Etiketler menüsünü aç</string>\n    <string name=\"result_history_adaptive\">Uyarlanabilir süre</string>\n    <string name=\"result_history_adaptive_summary\">Uyarlanabilir geçmiş için saat sayısı</string>\n    <string name=\"adaptive_shape_name\">Simge şekli</string>\n    <string name=\"contacts_shape_name\">İletişim simgesi şekli</string>\n    <string name=\"contacts_shape_summary\">Kişi simgesinin şekli</string>\n    <string name=\"shortcut_pack_mask\">Kısayol simgesi</string>\n    <string name=\"label_search_hint\">Arama ipucu</string>\n    <string name=\"result_search_cap\">Maksimum arama sonuçları</string>\n    <string name=\"unlimited_search_cap\">Sınırsız arama sonuçları</string>\n    <string name=\"result_ripple_color\">Dokunmatik renk</string>\n    <string name=\"reset_cached_app_icons_confirm\">Simge paketi önbelleğini sıfırla\\?</string>\n    <string name=\"reset_cached_app_icons_description\">Önbelleğe alınmış simge paketi adını ve sürümünü sıfırlar.</string>\n    <string name=\"generate_theme_description\">Tüm renkleri sıfırlar ve birincil ve ikincil renklere dayalı olarak oluşturulan renkleri uygular</string>\n    <string name=\"reset_matrix_confirm\">Varsayılan değerleri ayarlayın\\?</string>\n    <string name=\"reset_matrix_description\">Bu ekrandaki tüm simge ayarlarını sıfırlar</string>\n    <string name=\"popup_section\">Popup</string>\n    <string name=\"popup_ripple_color\">Dokunmatik renk</string>\n    <string name=\"icon_scale_green\">Yeşil ölçekli</string>\n    <string name=\"icon_scale_blue\">Ölçek mavi</string>\n    <string name=\"icon_scale_alpha\">Ölçek şeffaflığı</string>\n    <string name=\"reset_matrix\">Varsayılan değerleri ayarlama</string>\n    <string name=\"corner_radius\">Köşe yarıçapı</string>\n    <string name=\"behaviour_section\">Davranış</string>\n    <string name=\"behaviour_link_keyboard_search_bar\">Klavyeyi arama çubuğu görünürlüğüne bağlayın</string>\n    <string name=\"behaviour_widget_after_launch\">Başlatmadan sonra Widget modu</string>\n    <string name=\"dm_search_quick_list\">İskeleyi göster</string>\n    <string name=\"dm_widget_quick_list\">İskeleyi göster</string>\n    <string name=\"dm_empty_fullscreen\">Tam Ekran</string>\n    <string name=\"dm_search_fullscreen\">Tam Ekran</string>\n    <string name=\"dm_search_fullscreen_summary\">Sadece klavye kapalıyken</string>\n    <string name=\"dm_widget_fullscreen\">Tam Ekran</string>\n    <string name=\"title_desktop_mode_empty\">Masaüstü modu \\\"Boş\\\"</string>\n    <string name=\"dm_search_open_result\">İlk sonuç listesi</string>\n    <string name=\"action_app_to_run\">Çalıştırılacak uygulama</string>\n    <string name=\"behaviour_link_close_keyboard_back_button\">Klavye kapatılırken \\\"Geri\\\" eylemi</string>\n    <string name=\"keyboard_suggestions\">Otomatik tamamlama/öneriler</string>\n    <string name=\"device_admin_explanation\">Ekranı kilitlemek için kullanılır</string>\n    <string name=\"device_admin\">Cihaz yöneticisi</string>\n    <string name=\"device_admin_disable\">Bu izni kaldıralım mı\\?</string>\n    <string name=\"device_admin_required\">Cihaz yöneticisi erişimi gerekli</string>\n    <string name=\"gesture_double_click\">Çift dokunma ekranı</string>\n    <string name=\"loading_icon\">Yükleme simgesi</string>\n    <string name=\"keyboard_section\">Klavye</string>\n    <string name=\"lwp_scroll_pages\">Sayfalar</string>\n    <string name=\"lwp_page_count_vertical\">↕ sayfalar</string>\n    <string name=\"lwp_page_count_horizontal\">⟷ sayfalar</string>\n    <string name=\"lwp_pages_vertical_1\">orta sayfa</string>\n    <string name=\"lwp_pages_vertical_2\">2 sayfa (↑ ve ↓)</string>\n    <string name=\"lwp_pages_horizontal_1\">orta sayfa</string>\n    <string name=\"reset_cached_app_icons\">Simge paketini sıfırla</string>\n    <string name=\"debug_favorites\">Favoriler</string>\n    <string name=\"debug_favorites_summary\">DB tablosu `favoriler`den girdileri göster</string>\n    <string name=\"generate_theme_simple_summary\">Arka plan için birincil ve metin için ikincil kullanın</string>\n    <string name=\"generate_theme_highlight\">Temayı vurgulayın</string>\n    <string name=\"result_right_to_left_summary\">Izgara satırını sağdan sola doğru düzenleyin</string>\n    <string name=\"debug_provider_status\">Sağlayıcı durumu</string>\n    <string name=\"exit_tags_manager_confirm\">Etiketler yöneticisini kapat\\?</string>\n    <string name=\"exit_tags_manager_description\">Etiketler yöneticisini kapatırsanız tüm değişiklikleri kaybedersiniz.\n\\nDevam etmek istediğinizden emin misiniz\\?</string>\n    <string name=\"menu_show\">Açığa Çıkar</string>\n    <string name=\"menu_remove_shortcut\">Sabitleme</string>\n    <string name=\"menu_tags_add\">Etiketler ekle</string>\n    <string name=\"menu_tags_edit\">Etiketleri düzenle</string>\n    <string name=\"color_and_opacity\">Renk ve opaklık</string>\n    <string name=\"cd_item_contact_call\">Arayın</string>\n    <string name=\"cd_rename_tag\">Etiketi yeniden adlandır</string>\n    <string name=\"app_rename_confirmation\">Uygulama adı artık %s şeklindedir</string>\n    <string name=\"shortcut_rename_confirmation\">Kısayol adı artık \\\"%s\\\" şeklindedir;</string>\n    <string name=\"entry_rename_confirmation\">İsim artık \\\"%s\\\" şeklindedir;</string>\n    <string name=\"hint_custom_icon\">İsme göre filtrele</string>\n    <string name=\"quick_list_height\">Yükseklik</string>\n    <string name=\"quick_list_section\">Rıhtım</string>\n    <string name=\"quick_list_enabled\">İskeleyi kullanın</string>\n    <string name=\"quick_list_icon_size\">Maksimum simge boyutu</string>\n    <string name=\"title_wallpaper\">Duvar kağıdı etkileşimleri</string>\n    <string name=\"wp_drag_animate_desc\">Duvar kağıdını kaydırmak için <b>←</b> <b>→</b> öğesini sürükleyin</string>\n    <string name=\"cfg_widget_move_resize\">Taşı ve yeniden boyutlandır</string>\n    <string name=\"shortcut_with_appName\">%1$s: %2$s</string>\n    <string name=\"debug_section\">Hata Ayıklama</string>\n    <string name=\"debug_widget_add_info\">Ekstra widget bilgisi ekleyin</string>\n    <string name=\"debug_widget_info\">Ekstra widget bilgisi için uzun basın</string>\n    <string name=\"debug_item_relevance\">Arama alaka düzeyi</string>\n    <string name=\"shortcut_section\">Kısayol</string>\n    <string name=\"shortcut_show_badge_summary\">Kısayolu oluşturan uygulama simgesini görüntüle</string>\n    <string name=\"result_text2_size\">İkincil metin boyutu</string>\n    <string name=\"invalid_rename_tag\">Zaten bir \\\"%s\\\" etiketi var</string>\n    <string name=\"edit_search_hint_summary\">Arama ipuçlarını değiştirme ve düzenleme</string>\n    <string name=\"hint_add_search_engine_url\">https://search.engine/query=%s</string>\n    <string name=\"label_search_engine_name\">Sağlayıcı adını ara</string>\n    <string name=\"label_search_engine_url\">Açılacak URL</string>\n    <string name=\"result_history_size_summary\">Geçmişi gösterirken maksimum öğe sayısı</string>\n    <string name=\"action_show_history_recency\">Yakın tarih</string>\n    <string name=\"show_tags_list_reversed\">Sonuç olarak tersine çevrilmiş etiketler menüsü</string>\n    <string name=\"choose_icon_menu_add\">Alternatif olarak ekle</string>\n    <string name=\"choose_icon_menu_add2\">Alternatif ekleyin ve gösterin</string>\n    <string name=\"icon_background\">Simge arka planı</string>\n    <string name=\"unlimited_search_cap_summary\">Mümkün olan en yüksek limiti ayarlayın</string>\n    <string name=\"settings_default\">Varsayılan</string>\n    <string name=\"icon_hue\">Hue</string>\n    <string name=\"icon_saturation\">Doygunluk</string>\n    <string name=\"matrix_summary\">Parlaklık, kontrast, renk tonu, doygunluk</string>\n    <string name=\"title_matrix\">Simge rengini modüle etme</string>\n    <string name=\"icon_scale_red\">Kırmızı ölçek</string>\n    <string name=\"title_desktop_mode_widget\">Masaüstü modu \\\"Widget\\\"</string>\n    <string name=\"search_bar_at_bottom\">Alttaki konum</string>\n    <string name=\"no_tags\">Etiket bulunamadı</string>\n    <string name=\"quick_list_animation\">Göster/gizle animasyonu</string>\n    <string name=\"initial_desktop\">İlk masaüstü</string>\n    <string name=\"title_behaviour\">Davranış</string>\n    <string name=\"root_mode\">Kök modu</string>\n    <string name=\"debug_ksh_touch_summary\">Durumu arka plan rengi olarak göster</string>\n    <string name=\"lwp_pages_horizontal_2\">2 sayfa (← ve →)</string>\n    <string name=\"tags_menu_icons\">Simgeleri göster</string>\n    <string name=\"tags_menu_icon_size\">Simge boyutu</string>\n    <string name=\"tags_menu_list\">Gösterilecek etiketler</string>\n    <string name=\"tags_menu_order\">Etiketler sipariş</string>\n    <string name=\"tags_menu_untagged\">Etiketsiz göster</string>\n    <string name=\"result_popup_order\">Uzun dokunma kategorisi sırası</string>\n    <string name=\"result_popup_order_summary\">Uzun dokunma açılır öğeleri için kategori sırası</string>\n    <string name=\"title_presets\">Renk ön ayarı</string>\n    <string name=\"primary_color\">Ana renk</string>\n    <string name=\"secondary_color\">İkincil renk</string>\n    <string name=\"generate_theme_simple\">Basit tema</string>\n    <string name=\"result_fading_edge\">Gradyan</string>\n    <string name=\"result_fading_edge_summary\">Üst ve alt kenarlar soluklaşır</string>\n    <string name=\"shortcut_dynamic_in_results_summary\">Dinamik kısayollar arama yapılırken olası sonuçlar olarak dikkate alınacaktır</string>\n    <string name=\"search_bar_layout\">Düzen</string>\n    <string name=\"quick_list_position\">Pozisyon</string>\n    <string name=\"navigation_bar_section\">Gezinti çubuğu</string>\n    <string name=\"popup_title_debug\">Hata ayıklama bilgisi</string>\n    <string name=\"shortcuts_no_host_permission\">Kısayollara erişmek için bunu ana başlatıcınız yapın.</string>\n    <string name=\"pin_shortcut_label\">Kısayol etiketini seçin</string>\n    <string name=\"pin_shortcut_icon\">Kısayol simgesi</string>\n    <string name=\"quick_list_icons_visible\">Simgeleri göster</string>\n    <string name=\"quick_list_only_for_results\">Sonuç olmadığında dock\\'u gizle</string>\n    <string name=\"filter_apps\">Uygulama filtresi</string>\n    <string name=\"filter_contacts\">İletişim filtresi</string>\n    <string name=\"filter_shortcuts\">Kısayollar filtresi</string>\n    <string name=\"action_reload\">Sağlayıcıları yeniden yükle</string>\n    <string name=\"action_toggle_grid\">Izgarayı değiştir</string>\n    <string name=\"action_show_apps\">Uygulamalar A→Z</string>\n    <string name=\"action_show_shortcuts\">Kısayollar A→Z</string>\n    <string name=\"edit_quick_list_preview\">Önizleme <em>(sürükle ve bırak işlemini başlatmak için uzun dokunun, kaldırmak için dokunun)</em></string>\n    <string name=\"edit_quick_list_tab_filters\">Filtreler</string>\n    <string name=\"edit_quick_list_tab_actions\">Eylemler</string>\n    <string name=\"enable_url\">Web URL\\'si</string>\n    <string name=\"cfg_widget_move_exit\">Çıkış hamlesi</string>\n    <string name=\"cfg_widget_resize_switch\">Yeniden boyutlandırmayı değiştir</string>\n    <string name=\"cfg_widget_resize_exit\">Çıkış \\\"Yeniden Boyutlandır\\\"</string>\n    <string name=\"debug_item_icon_info\">Simge bilgisi</string>\n    <string name=\"quick_list_show_badge\">Dock\\'ta kısayol rozetini göster</string>\n    <string name=\"result_list_argb\">Arka plan rengi ve opaklık</string>\n    <string name=\"quick_list_toggle_color\">Arka plan rengini değiştir</string>\n    <string name=\"contact_action_color\">Eylem simgesi rengi</string>\n    <string name=\"size_float\">&lt;%.1f&gt;</string>\n    <string name=\"result_shadow_color\">Metin gölge rengi</string>\n    <string name=\"bind_widget_failed\">Widget oluşturmak için izin eksik</string>\n    <string name=\"widget_name_and_desc\"><b>%1$s</b><br/>%3$d×%4$d<br/>%2$s</string>\n    <string name=\"widget_name\"><b>%1$s</b><br/>%2$d×%3$d</string>\n    <string name=\"shadow_offset\">Gölge ofseti için piksel miktarını seçin</string>\n    <string name=\"shadow_offset_preview\">Dokunun veya içeri sürükleyin.</string>\n    <string name=\"shadow_radius\">Gölge boyutu <em>(sıfır boyut gölgeyi devre dışı bırakır)</em></string>\n    <string name=\"search_bar_shadow_color\">Metin gölge rengi</string>\n    <string name=\"search_bar_shadow_radius_dx_dy\">Gölge boyutu ve ofset</string>\n    <string name=\"contact_button_set_default\">Varsayılan eylem yap</string>\n    <string name=\"loading\">Yükleniyor…</string>\n    <string name=\"tab_button_icon\">Düğme simgesi</string>\n    <string name=\"popup_shadow_radius_dx_dy\">Gölge boyutu ve ofset</string>\n    <string name=\"privacy_policy\">Gizlilik Politikası</string>\n    <string name=\"app_version\">Sürüm %s</string>\n    <string name=\"value_float\">değer %.2f</string>\n    <string name=\"value_float_xy\">yatay %1$.2f | dikey %2$.2f</string>\n    <string name=\"shadow_preview\">&lt;%3$.1f&gt;\n\\n&lt;%1$.1f×%2$.1f&gt;</string>\n    <string name=\"app_version_summary\">%1$s - %2$s</string>\n    <string name=\"result_shadow_radius_dx_dy\">Gölge boyutu ve ofset</string>\n    <string name=\"contact_button_reset_default\">Varsayılan eylemi sıfırla</string>\n    <string name=\"popup_shadow_color\">Metin gölge rengi</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-tr-v26/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"adaptive_shape_name\">Uyarlanabilir simge şekli</string>\n    <string name=\"force_adaptive\">Uyarlanabilir simgeleri zorla</string>\n    <string name=\"force_shape\">Uyarlanabilir şekli zorla</string>\n    <string name=\"force_shape_summary\">Uyarlanabilir olmayan simgelerde uyarlanabilir maske kullanın</string>\n    <string name=\"force_adaptive_summary\">Uyarlanabilir olmayan simgeler için ölçeği küçültün ve şekilli bir arka plan üzerine yerleştirin</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-v21/pref_default.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <integer name=\"default_corner_radius\" translatable=\"false\">12</integer>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-v26/strings.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\" tools:ignore=\"MissingTranslation\">\n    <string name=\"adaptive_shape_name\">Adaptive icon shape</string>\n    <string name=\"force_adaptive\">Force adaptive icons</string>\n    <string name=\"force_adaptive_summary\">For non-adaptive icons, reduce scale and place on a shaped background</string>\n    <string name=\"force_shape\">Force adaptive shape</string>\n    <string name=\"force_shape_summary\">Use adaptive mask on non-adaptive icons</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-zh-rCN/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"custom_icon_activity\">活动</string>\n    <string name=\"custom_icon_application\">应用</string>\n    <string name=\"default_icon\">默认图标</string>\n    <string name=\"hint_custom_icon\">按名称过滤</string>\n    <string name=\"shortcut_rename_confirmation\">快捷方式名称现在为\\\"%s\\\"</string>\n    <string name=\"size\">&lt;%d&gt;</string>\n    <string name=\"menu_exclude_kiss\">来自TinyBit</string>\n    <string name=\"cant_pin_shortcut\">无法添加快捷方式。 TinyBit是您的默认启动器吗？</string>\n    <string name=\"unable_to_initialize_shortcuts\">无法初始化快捷方式。 TinyBit是您的默认启动器吗？</string>\n    <string name=\"removed_item\">移除%s</string>\n    <string name=\"copy_confirmation\">已将\\\"%s\\\"复制到剪贴板</string>\n    <string name=\"ui_item_visit\">访问\\\"%1$s\\\"</string>\n    <string name=\"ui_item_search\">在%1$s中搜索\\\"%2$s\\\"\\?</string>\n    <string name=\"hint_ui_search\">在应用程序，联系人中搜索…</string>\n    <string name=\"app_name\">TinyBit启动器</string>\n    <string name=\"menu_app_rename\">重命名应用</string>\n    <string name=\"app_rename_confirmation\">应用名称是%s</string>\n    <string name=\"title_shortcut_rename\">选择此快捷方式的自定义名称</string>\n    <string name=\"title_edit_url_search_engine\">为此搜索引擎选择其他网址</string>\n    <string name=\"title_rename_search_hint\">为此搜索提示选择其他名称</string>\n    <string name=\"title_rename_search_engine\">给搜索引擎设置不同名称</string>\n    <string name=\"title_rename_tag\">替换出现此标签的所有位置</string>\n    <string name=\"title_app_rename\">设置自定义名称</string>\n    <string name=\"menu_custom_icon\">换图标</string>\n    <string name=\"menu_shortcut_rename\">重命名快捷方式</string>\n    <string name=\"menu_action_delete\">删除</string>\n    <string name=\"menu_action_rename\">重命名</string>\n    <string name=\"icons_pack_default_name\">系统图标</string>\n    <string name=\"icons_pack\">选择图标包</string>\n    <string name=\"result_list_section\">清单</string>\n    <string name=\"search_bar_section\">搜索栏</string>\n    <string name=\"title_select_size\">拖动滑块设置大小</string>\n    <string name=\"title_select_alpha\">拖动滑块设置透明度</string>\n    <string name=\"cd_icon_tag\">设置标签图标</string>\n    <string name=\"cd_rename_tag\">重命名标签</string>\n    <string name=\"cd_remove_tag\">删除标签</string>\n    <string name=\"cd_undo_remove_tag\">撤销删除标签</string>\n    <string name=\"cd_add_tag\">添加标签</string>\n    <string name=\"cd_item_contact_call\">电话</string>\n    <string name=\"cd_item_contact_message\">信息</string>\n    <string name=\"cd_show_all_apps\">显示全部应用</string>\n    <string name=\"cd_main_clear\">清空搜索栏</string>\n    <string name=\"cd_main_menu\">菜单</string>\n    <string name=\"black_notification_icons\">暗色图标</string>\n    <string name=\"gradient\">坡度</string>\n    <string name=\"notification_bar_section\">通知栏</string>\n    <string name=\"title_ui\">用户界面</string>\n    <string name=\"user_interface_summary\">主题,透明度,和颜色</string>\n    <string name=\"rate_the_app\">给这个软件评分</string>\n    <string name=\"menu_popup_tags_manager\">标签管理</string>\n    <string name=\"menu_popup_launcher_settings\">桌面设置</string>\n    <string name=\"menu_popup_android_settings\">系统设置</string>\n    <string name=\"menu_popup_title_settings\">设置</string>\n    <string name=\"menu_popup_title\">主菜单</string>\n    <string name=\"menu_widget_configure\">定制小部件…</string>\n    <string name=\"menu_widget_remove\">删除小部件…</string>\n    <string name=\"menu_widget_add\">添加小部件…</string>\n    <string name=\"menu_widget_title\">小部件菜单</string>\n    <string name=\"change_wallpaper\">设置壁纸</string>\n    <string name=\"entry_not_found\">找不到\\\"%s\\\"</string>\n    <string name=\"application_not_found\">无法启动 \\\"%s\\\"应用</string>\n    <string name=\"menu_exclude_history\">查看历史</string>\n    <string name=\"menu_app_hibernate\">冻结</string>\n    <string name=\"menu_app_uninstall\">卸载</string>\n    <string name=\"menu_app_store\">在应用市场查看</string>\n    <string name=\"menu_app_details\">应用信息</string>\n    <string name=\"menu_tags_edit\">编辑标签</string>\n    <string name=\"menu_tags_add\">添加标签</string>\n    <string name=\"menu_remove_shortcut\">取消固定</string>\n    <string name=\"menu_show\">提示</string>\n    <string name=\"menu_hide\">隐藏</string>\n    <string name=\"menu_exclude\">排除…</string>\n    <string name=\"permission_denied\">先要授予权限</string>\n    <string name=\"stub_app_tag\">标签列表</string>\n    <string name=\"stub_application\">软件名</string>\n    <string name=\"menu_remove_history\">删除历史记录</string>\n    <string name=\"tags_section\">标签</string>\n    <string name=\"debug_section\">调试</string>\n    <string name=\"hint_new_tag\">添加标签</string>\n    <string name=\"cfg_widget_remove\">删除</string>\n    <string name=\"fuzzy_search_tags_summary\">搜索时包括标签作为可能的匹配候选</string>\n    <string name=\"exit_the_app_description\">您的默认启动器将被打开，或者您将被询问要使用哪个启动器。</string>\n    <string name=\"pin_shortcut_icon\">快捷方式图标</string>\n    <string name=\"cfg_widget_move_resize_exit\">退出“移动和调整大小”</string>\n    <string name=\"app_name_debug\">TinyBit 调试启动器</string>\n    <string name=\"toast_hibernate_completed\">%s 休眠，重新启动唤醒</string>\n    <string name=\"toast_hibernate_error\">[ERROR] %s 没有冬眠</string>\n    <string name=\"search_bar_text_size\">文字大小</string>\n    <string name=\"search_bar_height\">条高</string>\n    <string name=\"hint_rename_tag\">为此标签选择其他名称</string>\n    <string name=\"entry_rename_confirmation\">现在的名字是 \\\"%s\\\"</string>\n    <string name=\"quick_list_height\">dock高度</string>\n    <string name=\"shortcuts_no_host_permission\">将此作为您访问快捷方式的主要启动器。</string>\n    <string name=\"action_reload\">重新加载提供程序</string>\n    <string name=\"action_toggle_grid\">切换网格</string>\n    <string name=\"action_show_favorites\">收藏夹</string>\n    <string name=\"quick_list_content\">自定义内容</string>\n    <string name=\"quick_list_content_summary\">更改订单并添加条目</string>\n    <string name=\"edit_quick_list_preview\">（长按开始拖放，长按删除）</string>\n    <string name=\"edit_quick_list_tab_filters\">过滤器</string>\n    <string name=\"edit_quick_list_tab_actions\">动作</string>\n    <string name=\"edit_quick_list_tab_favorites\">收藏夹</string>\n    <string name=\"edit_quick_list_tab_tags\">标签</string>\n    <string name=\"quick_list_icons_visible\">显示dock栏图标</string>\n    <string name=\"quick_list_text_visible\">显示名字</string>\n    <string name=\"cache_drawable\">缓存图标</string>\n    <string name=\"screen_off_cache_clear\">经常释放缓存</string>\n    <string name=\"cfg_widget_resize\">调整大小</string>\n    <string name=\"cfg_widget_resize_switch\">切换调整大小</string>\n    <string name=\"cfg_widget_resize_exit\">退出“调整大小”</string>\n    <string name=\"cfg_widget_move_resize\">移动和调整大小</string>\n    <string name=\"debug_widget_add_info\">添加额外的小部件信息</string>\n    <string name=\"shortcut_section\">快捷方式</string>\n    <string name=\"shortcut_pin_auto_confirm\">自动确认快捷方式</string>\n    <string name=\"shortcut_show_badge\">显示应用徽章</string>\n    <string name=\"shortcut_show_badge_summary\">显示创建快捷方式的应用程序图标</string>\n    <string name=\"quick_list_show_badge\">在 Dock 中显示快捷方式徽章</string>\n    <string name=\"result_highlight_color\">高亮颜色</string>\n    <string name=\"quick_list_color\">背景颜色</string>\n    <string name=\"cd_item_contact_open\">打开</string>\n    <string name=\"debug_widget_info\">长按以获取额外的小部件信息</string>\n    <string name=\"menu_popup_tags_menu\">标签菜单</string>\n    <string name=\"debug_item_relevance\">搜索相关性</string>\n    <string name=\"menu_quick_list_add\">添加到Dock栏</string>\n    <string name=\"menu_quick_list_remove\">从dock栏移除</string>\n    <string name=\"default_static_icon\">%s 默认图标</string>\n    <string name=\"custom_icon_activity_adaptive_no_background\">活动自定义背景</string>\n    <string name=\"custom_icon_badged\">徽章</string>\n    <string name=\"default_icon_preview_label\">默认\n\\n图标</string>\n    <string name=\"current_icon_preview_label\">当前\n\\n图标</string>\n    <string name=\"custom_icon_preview_label\">已选中\n\\n图标</string>\n    <string name=\"custom_name_set_default\">默认名</string>\n    <string name=\"title_features\">特征</string>\n    <string name=\"features_summary\">切换和自定义应用功能</string>\n    <string name=\"quick_list_enabled\">设置dock</string>\n    <string name=\"fuzzy_search_tags\">模糊搜索标签</string>\n    <string name=\"tags_enabled\">显示应用的标签</string>\n    <string name=\"tags_enabled_summary\">在结果列表中显示每个应用的标签</string>\n    <string name=\"icons_visible\">显示图标</string>\n    <string name=\"popup_title_shortcut_dynamic\">快捷方式</string>\n    <string name=\"popup_title_hist_fav\">优先</string>\n    <string name=\"popup_title_customize\">定制</string>\n    <string name=\"popup_title_link\">链接</string>\n    <string name=\"pin_shortcut_message\">要添加此快捷方式吗？</string>\n    <string name=\"pin_shortcut_label\">选择快捷方式标签</string>\n    <string name=\"filter_apps\">应用过滤器</string>\n    <string name=\"filter_contacts\">联系过滤器</string>\n    <string name=\"quick_list_only_for_results\">结果可见</string>\n    <string name=\"quick_list_only_for_results_summary\">当结果列表有条目时可见</string>\n    <string name=\"exit_the_app\">关闭启动器</string>\n    <string name=\"exit_the_app_confirm\">关闭启动器？</string>\n    <string name=\"reset_default_launcher_confirm\">更改默认启动器？</string>\n    <string name=\"reset_default_launcher_description\">按下主屏幕后要求打开应用程序。</string>\n    <string name=\"reset_default_launcher\">更改默认启动器</string>\n    <string name=\"filter_shortcuts\">快捷方式过滤器</string>\n    <string name=\"screen_off_cache_clear_summary\">屏幕关闭时清空缓存</string>\n    <string name=\"memory_section\">记忆</string>\n    <string name=\"cache_half_apps\">小缓存大小</string>\n    <string name=\"cache_half_apps_summary\">使 LRU 缓存大小为已安装应用程序数量的一半</string>\n    <string name=\"icon_pack_section\">图标包</string>\n    <string name=\"icon_pack_content_list\">图标来自\n\\n%s</string>\n    <string name=\"title_wallpaper\">壁纸互动</string>\n    <string name=\"lwp_touch\">将触摸事件发送到壁纸</string>\n    <string name=\"lwp_drag\">模拟壁纸的拖动事件</string>\n    <string name=\"lwp_drag_desc\">发送多个触摸事件</string>\n    <string name=\"lock_portrait\">锁定人像</string>\n    <string name=\"sensor_orientation\">传感器方向</string>\n    <string name=\"wp_drag_animate\">滚动壁纸</string>\n    <string name=\"wp_animate_center\">中心壁纸</string>\n    <string name=\"wp_animate_center_desc\">滚动后壁纸将返回中心</string>\n    <string name=\"wp_animate_sides\">侧贴壁纸</string>\n    <string name=\"cfg_widget_move\">移动</string>\n    <string name=\"cfg_widget_move_switch\">切换移动</string>\n    <string name=\"cfg_widget_move_exit\">退出移动</string>\n    <string name=\"quick_list_toggle_color\">切换背景颜色</string>\n    <string name=\"contact_action_color\">操作图标颜色</string>\n    <string name=\"result_icon_size\">图标大小</string>\n    <string name=\"result_text_color\">文字颜色</string>\n    <string name=\"result_text2_color\">辅助文本颜色</string>\n    <string name=\"search_bar_text_color\">文字颜色</string>\n    <string name=\"search_bar_icon_color\">图标颜色</string>\n    <string name=\"menu_popup_quick_list_customize\">编辑dock</string>\n    <string name=\"result_text_size\">文字大小</string>\n    <string name=\"result_text2_size\">辅助文字大小</string>\n    <string name=\"invalid_rename_tag\">已经存在 \\\"%s\\\" 标签</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/xml/backup_descriptor.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<full-backup-content>\n    <!-- Include everything by default -->\n    <!-- Exclude specific shared preferences that contain GCM registration Id -->\n</full-backup-content>\n"
  },
  {
    "path": "app/src/main/res/xml/data_extraction_rules.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<data-extraction-rules>\n    <cloud-backup>\n        <!-- Include everything by default -->\n    </cloud-backup>\n    <device-transfer>\n        <!-- Include everything by default -->\n    </device-transfer>\n</data-extraction-rules>"
  },
  {
    "path": "app/src/main/res/xml/file_paths.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<paths>\n    <cache-path\n        name=\"settings\"\n        path=\"settings/\" />\n</paths>"
  },
  {
    "path": "app/src/main/res/xml/policies.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<device-admin>\n    <uses-policies>\n        <force-lock />\n    </uses-policies>\n</device-admin>\n"
  },
  {
    "path": "app/src/main/res/xml/preference_features.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.preference.PreferenceScreen xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:key=\"feature-holder\"\n    android:summary=\"@string/features_summary\"\n    android:title=\"@string/title_features\">\n\n    <!-- Search bar -->\n    <PreferenceCategory\n        android:icon=\"@drawable/ic_search_bar\"\n        android:key=\"search-bar-section\"\n        android:title=\"@string/search_bar_section\">\n\n        <SwitchPreference\n            android:defaultValue=\"false\"\n            android:disableDependentsState=\"true\"\n            android:key=\"search-bar-gradient\"\n            android:title=\"@string/gradient\" />\n\n        <rocks.tbog.tblauncher.preference.CustomDialogPreference\n            android:defaultValue=\"@integer/default_corner_radius\"\n            android:dialogLayout=\"@layout/pref_slider\"\n            android:key=\"search-bar-radius\"\n            android:title=\"@string/corner_radius\"\n            android:widgetLayout=\"@layout/pref_size_preview\" />\n\n        <rocks.tbog.tblauncher.preference.CustomDialogPreference\n            android:defaultValue=\"@integer/default_search_bar_height\"\n            android:dialogLayout=\"@layout/pref_slider\"\n            android:key=\"search-bar-height\"\n            android:title=\"@string/search_bar_height\"\n            android:widgetLayout=\"@layout/pref_size_preview\" />\n\n        <androidx.preference.SwitchPreference\n            android:defaultValue=\"true\"\n            android:key=\"search-bar-animation\"\n            android:title=\"@string/search_bar_animation\" />\n\n        <rocks.tbog.tblauncher.preference.CustomDialogPreference\n            android:defaultValue=\"@integer/default_result_margin\"\n            android:dialogLayout=\"@layout/pref_slider\"\n            android:key=\"search-bar-margin-vertical\"\n            android:title=\"@string/margin_vertical\"\n            android:widgetLayout=\"@layout/pref_size_preview\" />\n\n        <rocks.tbog.tblauncher.preference.CustomDialogPreference\n            android:defaultValue=\"@integer/default_result_margin\"\n            android:dialogLayout=\"@layout/pref_slider\"\n            android:key=\"search-bar-margin-horizontal\"\n            android:title=\"@string/margin_horizontal\"\n            android:widgetLayout=\"@layout/pref_size_preview\" />\n\n        <rocks.tbog.tblauncher.preference.CustomDialogPreference\n            android:defaultValue=\"@integer/default_size_text\"\n            android:dialogLayout=\"@layout/pref_slider\"\n            android:key=\"search-bar-text-size\"\n            android:title=\"@string/search_bar_text_size\"\n            android:widgetLayout=\"@layout/pref_size_preview\" />\n\n        <rocks.tbog.tblauncher.preference.CustomDialogPreference\n            android:dialogLayout=\"@layout/edit_search_engines\"\n            android:key=\"edit-search-hint\"\n            android:summary=\"@string/edit_search_hint_summary\"\n            android:title=\"@string/edit_search_hint\" />\n\n        <rocks.tbog.tblauncher.preference.CustomDialogPreference\n            android:dialogLayout=\"@layout/add_search_hint\"\n            android:key=\"add-search-hint\"\n            android:title=\"@string/add_search_hint\" />\n\n        <rocks.tbog.tblauncher.preference.CustomDialogPreference\n            android:dialogLayout=\"@layout/edit_search_engines\"\n            android:key=\"reset-search-hint\"\n            android:summary=\"@string/reset_search_hint_summary\"\n            android:title=\"@string/reset_search_hint\" />\n\n        <androidx.preference.SwitchPreference\n            android:defaultValue=\"true\"\n            android:key=\"search-bar-at-bottom\"\n            android:title=\"@string/search_bar_at_bottom\" />\n\n        <ListPreference\n            android:defaultValue=\"btn-text-menu\"\n            android:key=\"search-bar-layout\"\n            android:title=\"@string/search_bar_layout\"\n            android:entries=\"@array/searchBarLayoutEntries\"\n            android:entryValues=\"@array/searchBarLayoutValues\"/>\n\n        <!-- This is here just to set the default value and be saved in backup, not visible for user -->\n        <MultiSelectListPreference\n            android:defaultValue=\"@array/defaultSearchHints\"\n            android:key=\"selected-search-hints\"\n            app:isPreferenceVisible=\"false\" />\n\n        <!-- This is here just to set the default value and be saved in backup, not visible for user -->\n        <MultiSelectListPreference\n            android:defaultValue=\"@array/defaultSearchHints\"\n            android:key=\"available-search-hints\"\n            app:isPreferenceVisible=\"false\" />\n\n    </PreferenceCategory>\n\n    <!-- Result list -->\n    <PreferenceCategory\n        android:icon=\"@drawable/ic_list\"\n        android:key=\"result-list-section\"\n        android:title=\"@string/result_list_section\">\n\n        <androidx.preference.SwitchPreference\n            android:defaultValue=\"true\"\n            android:key=\"result-first-at-bottom\"\n            android:title=\"@string/result_first_at_bottom\" />\n\n        <androidx.preference.SwitchPreference\n            android:defaultValue=\"true\"\n            android:key=\"result-right-to-left\"\n            android:summary=\"@string/result_right_to_left_summary\"\n            android:title=\"@string/result_right_to_left\" />\n\n        <androidx.preference.SwitchPreference\n            android:defaultValue=\"false\"\n            android:key=\"result-fading-edge\"\n            android:summary=\"@string/result_fading_edge_summary\"\n            android:title=\"@string/result_fading_edge\" />\n\n        <rocks.tbog.tblauncher.preference.CustomDialogPreference\n            android:defaultValue=\"@integer/default_corner_radius\"\n            android:dialogLayout=\"@layout/pref_slider\"\n            android:key=\"result-list-radius\"\n            android:title=\"@string/corner_radius\"\n            android:widgetLayout=\"@layout/pref_size_preview\" />\n\n        <androidx.preference.SwitchPreference\n            android:defaultValue=\"false\"\n            android:key=\"result-list-row-height-manual\"\n            android:title=\"@string/result_list_row_height_manual\" />\n\n        <rocks.tbog.tblauncher.preference.CustomDialogPreference\n            android:defaultValue=\"@integer/default_height_row\"\n            android:dependency=\"result-list-row-height-manual\"\n            android:dialogLayout=\"@layout/pref_slider\"\n            android:key=\"result-list-row-height\"\n            android:title=\"@string/result_list_row_height\"\n            android:widgetLayout=\"@layout/pref_size_preview\" />\n\n        <rocks.tbog.tblauncher.preference.CustomDialogPreference\n            android:defaultValue=\"@integer/default_result_margin\"\n            android:dialogLayout=\"@layout/pref_slider\"\n            android:key=\"result-list-margin-vertical\"\n            android:title=\"@string/margin_vertical\"\n            android:widgetLayout=\"@layout/pref_size_preview\" />\n\n        <rocks.tbog.tblauncher.preference.CustomDialogPreference\n            android:defaultValue=\"@integer/default_result_margin\"\n            android:dialogLayout=\"@layout/pref_slider\"\n            android:key=\"result-list-margin-horizontal\"\n            android:title=\"@string/margin_horizontal\"\n            android:widgetLayout=\"@layout/pref_size_preview\" />\n\n        <rocks.tbog.tblauncher.preference.CustomDialogPreference\n            android:defaultValue=\"@integer/default_result_history_size\"\n            android:dialogLayout=\"@layout/pref_slider\"\n            android:key=\"result-history-size\"\n            android:summary=\"@string/result_history_size_summary\"\n            android:title=\"@string/result_history_size\"\n            android:widgetLayout=\"@layout/pref_size_preview\" />\n\n        <rocks.tbog.tblauncher.preference.CustomDialogPreference\n            android:defaultValue=\"@integer/default_result_history_adaptive\"\n            android:dialogLayout=\"@layout/pref_slider\"\n            android:key=\"result-history-adaptive\"\n            android:summary=\"@string/result_history_adaptive_summary\"\n            android:title=\"@string/result_history_adaptive\"\n            android:widgetLayout=\"@layout/pref_size_preview\" />\n\n        <rocks.tbog.tblauncher.preference.CustomDialogPreference\n            android:defaultValue=\"@integer/default_result_searcher_cap\"\n            android:dialogLayout=\"@layout/pref_slider\"\n            android:key=\"result-search-cap\"\n            android:title=\"@string/result_search_cap\"\n            android:widgetLayout=\"@layout/pref_size_preview\" />\n\n        <rocks.tbog.tblauncher.preference.CustomDialogPreference\n            android:dialogLayout=\"@layout/pref_confirm\"\n            android:key=\"unlimited-search-cap\"\n            android:summary=\"@string/unlimited_search_cap_summary\"\n            android:title=\"@string/unlimited_search_cap\" />\n\n        <MultiSelectListPreference\n            android:key=\"result-popup-order\"\n            android:summary=\"@string/result_popup_order_summary\"\n            android:title=\"@string/result_popup_order\" />\n\n        <androidx.preference.SwitchPreference\n            android:defaultValue=\"true\"\n            android:key=\"icons-visible\"\n            android:title=\"@string/icons_visible\" />\n\n    </PreferenceCategory>\n\n    <!-- Dock / Quick List -->\n    <androidx.preference.PreferenceCategory\n        android:icon=\"@drawable/ic_quick\"\n        android:key=\"quick-list-section\"\n        android:title=\"@string/quick_list_section\">\n\n        <androidx.preference.SwitchPreference\n            android:defaultValue=\"true\"\n            android:key=\"quick-list-enabled\"\n            android:title=\"@string/quick_list_enabled\" />\n\n        <rocks.tbog.tblauncher.preference.CustomDialogPreference\n            android:defaultValue=\"@integer/default_dock_columns\"\n            android:dependency=\"quick-list-enabled\"\n            android:dialogLayout=\"@layout/pref_slider\"\n            android:key=\"quick-list-columns\"\n            android:summary=\"@string/quick_list_columns_summary\"\n            android:title=\"@string/quick_list_columns\"\n            android:widgetLayout=\"@layout/pref_size_preview\" />\n\n        <rocks.tbog.tblauncher.preference.CustomDialogPreference\n            android:defaultValue=\"@integer/default_dock_rows\"\n            android:dependency=\"quick-list-enabled\"\n            android:dialogLayout=\"@layout/pref_slider\"\n            android:key=\"quick-list-rows\"\n            android:summary=\"@string/quick_list_rows_summary\"\n            android:title=\"@string/quick_list_rows\"\n            android:widgetLayout=\"@layout/pref_size_preview\" />\n\n        <rocks.tbog.tblauncher.preference.CustomDialogPreference\n            android:defaultValue=\"@integer/default_dock_height\"\n            android:dependency=\"quick-list-enabled\"\n            android:dialogLayout=\"@layout/pref_slider\"\n            android:key=\"quick-list-height\"\n            android:title=\"@string/quick_list_height\"\n            android:widgetLayout=\"@layout/pref_size_preview\" />\n\n        <androidx.preference.SwitchPreference\n            android:defaultValue=\"false\"\n            android:dependency=\"quick-list-enabled\"\n            android:key=\"quick-list-rtl\"\n            android:summary=\"@string/quick_list_rtl_summary\"\n            android:title=\"@string/quick_list_rtl\" />\n\n        <androidx.preference.SwitchPreference\n            android:defaultValue=\"false\"\n            android:dependency=\"quick-list-enabled\"\n            android:key=\"quick-list-text-visible\"\n            android:summary=\"@string/quick_list_text_visible_summary\"\n            android:title=\"@string/quick_list_text_visible\" />\n\n        <androidx.preference.SwitchPreference\n            android:defaultValue=\"true\"\n            android:dependency=\"quick-list-enabled\"\n            android:key=\"quick-list-icons-visible\"\n            android:summary=\"@string/quick_list_icons_visible_summary\"\n            android:title=\"@string/quick_list_icons_visible\" />\n\n        <androidx.preference.SwitchPreference\n            android:defaultValue=\"true\"\n            android:dependency=\"quick-list-icons-visible\"\n            android:key=\"quick-list-show-badge\"\n            android:title=\"@string/quick_list_show_badge\" />\n\n        <androidx.preference.SwitchPreference\n            android:defaultValue=\"true\"\n            android:dependency=\"quick-list-enabled\"\n            android:key=\"quick-list-animation\"\n            android:title=\"@string/quick_list_animation\" />\n\n        <rocks.tbog.tblauncher.preference.CustomDialogPreference\n            android:defaultValue=\"@integer/default_corner_radius\"\n            android:dependency=\"quick-list-enabled\"\n            android:dialogLayout=\"@layout/pref_slider\"\n            android:key=\"quick-list-radius\"\n            android:title=\"@string/corner_radius\"\n            android:widgetLayout=\"@layout/pref_size_preview\" />\n\n        <rocks.tbog.tblauncher.preference.CustomDialogPreference\n            android:dependency=\"quick-list-enabled\"\n            android:dialogLayout=\"@layout/quick_list_editor\"\n            android:key=\"quick-list-content\"\n            android:summary=\"@string/quick_list_content_summary\"\n            android:title=\"@string/quick_list_content\" />\n\n        <ListPreference\n            android:defaultValue=\"under-result-list\"\n            android:dependency=\"quick-list-enabled\"\n            android:entries=\"@array/dockPositionEntries\"\n            android:entryValues=\"@array/dockPositionValues\"\n            android:key=\"quick-list-position\"\n            android:title=\"@string/quick_list_position\" />\n\n    </androidx.preference.PreferenceCategory>\n\n    <!-- Shortcut -->\n    <androidx.preference.PreferenceCategory\n        android:icon=\"@drawable/ic_shortcuts\"\n        android:key=\"shortcut-section\"\n        android:title=\"@string/shortcut_section\">\n\n        <androidx.preference.SwitchPreference\n            android:defaultValue=\"false\"\n            android:key=\"pin-auto-confirm\"\n            android:title=\"@string/shortcut_pin_auto_confirm\" />\n\n        <androidx.preference.SwitchPreference\n            android:defaultValue=\"true\"\n            android:key=\"shortcut-show-badge\"\n            android:summary=\"@string/shortcut_show_badge_summary\"\n            android:title=\"@string/shortcut_show_badge\" />\n\n        <androidx.preference.SwitchPreference\n            android:defaultValue=\"false\"\n            android:key=\"shortcut-dynamic-in-results\"\n            android:summary=\"@string/shortcut_dynamic_in_results_summary\"\n            android:title=\"@string/shortcut_dynamic_in_results\" />\n\n    </androidx.preference.PreferenceCategory>\n\n    <!-- Tags -->\n    <androidx.preference.PreferenceCategory\n        android:icon=\"@drawable/ic_tags\"\n        android:key=\"tags-section\"\n        android:title=\"@string/tags_section\">\n\n        <androidx.preference.SwitchPreference\n            android:defaultValue=\"true\"\n            android:key=\"tags-enabled\"\n            android:summary=\"@string/tags_enabled_summary\"\n            android:title=\"@string/tags_enabled\" />\n        <androidx.preference.SwitchPreference\n            android:defaultValue=\"true\"\n            android:dependency=\"tags-enabled\"\n            android:key=\"fuzzy-search-tags\"\n            android:summary=\"@string/fuzzy_search_tags_summary\"\n            android:title=\"@string/fuzzy_search_tags\" />\n\n    </androidx.preference.PreferenceCategory>\n\n    <!-- Tags Menu -->\n    <PreferenceCategory\n        android:key=\"tags-menu-section\"\n        android:title=\"@string/tags_menu_section\">\n\n        <androidx.preference.SwitchPreference\n            android:defaultValue=\"false\"\n            android:key=\"tags-menu-icons\"\n            android:title=\"@string/tags_menu_icons\" />\n\n        <MultiSelectListPreference\n            android:key=\"tags-menu-list\"\n            android:title=\"@string/tags_menu_list\" />\n\n        <androidx.preference.SwitchPreference\n            android:defaultValue=\"false\"\n            android:key=\"tags-menu-untagged\"\n            android:title=\"@string/tags_menu_untagged\" />\n\n        <!-- This is here just to set the default value and be saved in backup, not visible for user -->\n        <EditTextPreference\n            android:defaultValue=\"0\"\n            android:key=\"tags-menu-untagged-index\"\n            app:isPreferenceVisible=\"false\" />\n\n        <MultiSelectListPreference\n            android:key=\"tags-menu-order\"\n            android:title=\"@string/tags_menu_order\" />\n\n    </PreferenceCategory>\n\n    <!-- Wallpaper / LWP - LiveWallpaper -->\n    <PreferenceCategory\n        android:icon=\"@drawable/ic_wallpaper\"\n        android:key=\"wallpaper-holder\"\n        android:title=\"@string/title_wallpaper\">\n        <SwitchPreference\n            android:defaultValue=\"true\"\n            android:key=\"lwp-scroll-pages\"\n            android:title=\"@string/lwp_scroll_pages\" />\n        <ListPreference\n            android:defaultValue=\"1\"\n            android:dependency=\"lwp-scroll-pages\"\n            android:entries=\"@array/lwpPageCountVerticalEntries\"\n            android:entryValues=\"@array/lwpPageCountVerticalValues\"\n            android:key=\"lwp-page-count-vertical\"\n            android:title=\"@string/lwp_page_count_vertical\" />\n        <ListPreference\n            android:defaultValue=\"3\"\n            android:dependency=\"lwp-scroll-pages\"\n            android:entries=\"@array/lwpPageCountHorizontalEntries\"\n            android:entryValues=\"@array/lwpPageCountHorizontalValues\"\n            android:key=\"lwp-page-count-horizontal\"\n            android:title=\"@string/lwp_page_count_horizontal\" />\n        <SwitchPreference\n            android:defaultValue=\"true\"\n            android:key=\"lwp-touch\"\n            android:title=\"@string/lwp_touch\" />\n        <SwitchPreference\n            android:defaultValue=\"false\"\n            android:key=\"lwp-drag\"\n            android:summary=\"@string/lwp_drag_desc\"\n            android:title=\"@string/lwp_drag\" />\n        <SwitchPreference\n            android:defaultValue=\"false\"\n            android:key=\"wp-drag-animate\"\n            android:summary=\"@string/wp_drag_animate_desc\"\n            android:title=\"@string/wp_drag_animate\" />\n        <SwitchPreference\n            android:defaultValue=\"true\"\n            android:dependency=\"wp-drag-animate\"\n            android:key=\"wp-animate-center\"\n            android:summary=\"@string/wp_animate_center_desc\"\n            android:title=\"@string/wp_animate_center\" />\n        <SwitchPreference\n            android:defaultValue=\"false\"\n            android:dependency=\"wp-drag-animate\"\n            android:key=\"wp-animate-sides\"\n            android:summary=\"@string/wp_animate_sides_desc\"\n            android:title=\"@string/wp_animate_sides\" />\n        <SwitchPreference\n            android:defaultValue=\"true\"\n            android:key=\"lock-portrait\"\n            android:title=\"@string/lock_portrait\" />\n        <SwitchPreference\n            android:defaultValue=\"true\"\n            android:key=\"sensor-orientation\"\n            android:title=\"@string/sensor_orientation\" />\n    </PreferenceCategory>\n\n</androidx.preference.PreferenceScreen>\n"
  },
  {
    "path": "app/src/main/res/xml/preferences.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.preference.PreferenceScreen xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:pref=\"http://tbog.rocks/res/pref\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:key=\"preferences-root\"\n    android:title=\"@string/menu_popup_launcher_settings\"\n    tools:showIn=\"@layout/activity_settings\">\n\n    <PreferenceScreen\n        android:icon=\"@drawable/ic_phone_ui\"\n        android:key=\"ui-holder\"\n        android:summary=\"@string/user_interface_summary\"\n        android:title=\"@string/title_ui\">\n\n        <PreferenceScreen\n            android:key=\"ui-presets\"\n            android:summary=\"@string/summary_presets\"\n            android:title=\"@string/title_presets\">\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@integer/default_color\"\n                android:key=\"primary-color\"\n                android:title=\"@string/primary_color\"\n                android:widgetLayout=\"@layout/pref_color_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@integer/default_color_text\"\n                android:key=\"secondary-color\"\n                android:title=\"@string/secondary_color\"\n                android:widgetLayout=\"@layout/pref_color_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:dialogLayout=\"@layout/pref_confirm\"\n                android:key=\"generate-theme-simple\"\n                android:summary=\"@string/generate_theme_simple_summary\"\n                android:title=\"@string/generate_theme_simple\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:dialogLayout=\"@layout/pref_confirm\"\n                android:key=\"generate-theme-highlight\"\n                android:summary=\"@string/generate_theme_highlight_summary\"\n                android:title=\"@string/generate_theme_highlight\" />\n\n        </PreferenceScreen>\n\n        <!-- Icons -->\n        <PreferenceCategory\n            android:key=\"icons-section\"\n            android:title=\"@string/icons_section\"\n            app:initialExpandedChildrenCount=\"2\">\n\n            <ListPreference\n                android:defaultValue=\"3\"\n                android:entries=\"@array/adaptiveEntries\"\n                android:entryValues=\"@array/adaptiveValues\"\n                android:key=\"adaptive-shape\"\n                android:title=\"@string/adaptive_shape_name\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@integer/default_icon_background\"\n                android:dependency=\"adaptive-shape\"\n                android:key=\"icon-background-argb\"\n                android:title=\"@string/icon_background\"\n                android:widgetLayout=\"@layout/pref_color_preview\" />\n\n            <PreferenceScreen\n                android:key=\"icon-matrix-holder\"\n                android:summary=\"@string/matrix_summary\"\n                android:title=\"@string/title_matrix\">\n\n                <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                    android:dialogLayout=\"@layout/pref_confirm\"\n                    android:key=\"reset-matrix\"\n                    android:title=\"@string/reset_matrix\" />\n\n                <rocks.tbog.tblauncher.preference.PreviewImagePreference android:widgetLayout=\"@layout/pref_matrix_preview\" />\n\n                <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                    android:defaultValue=\"@integer/default_icon_scale\"\n                    android:dialogLayout=\"@layout/pref_slider\"\n                    android:key=\"icon-scale-red\"\n                    android:title=\"@string/icon_scale_red\"\n                    android:widgetLayout=\"@layout/pref_amount_preview\" />\n\n                <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                    android:defaultValue=\"@integer/default_icon_scale\"\n                    android:dialogLayout=\"@layout/pref_slider\"\n                    android:key=\"icon-scale-green\"\n                    android:title=\"@string/icon_scale_green\"\n                    android:widgetLayout=\"@layout/pref_amount_preview\" />\n\n                <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                    android:defaultValue=\"@integer/default_icon_scale\"\n                    android:dialogLayout=\"@layout/pref_slider\"\n                    android:key=\"icon-scale-blue\"\n                    android:title=\"@string/icon_scale_blue\"\n                    android:widgetLayout=\"@layout/pref_amount_preview\" />\n\n                <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                    android:defaultValue=\"@integer/default_icon_scale\"\n                    android:dialogLayout=\"@layout/pref_slider\"\n                    android:key=\"icon-scale-alpha\"\n                    android:title=\"@string/icon_scale_alpha\"\n                    android:widgetLayout=\"@layout/pref_amount_preview\" />\n\n                <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                    android:defaultValue=\"@integer/default_icon_hue\"\n                    android:dialogLayout=\"@layout/pref_slider\"\n                    android:key=\"icon-hue\"\n                    android:title=\"@string/icon_hue\"\n                    android:widgetLayout=\"@layout/pref_amount_preview\" />\n\n                <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                    android:defaultValue=\"@integer/default_icon_contrast\"\n                    android:dialogLayout=\"@layout/pref_slider\"\n                    android:key=\"icon-contrast\"\n                    android:title=\"@string/icon_contrast\"\n                    android:widgetLayout=\"@layout/pref_amount_preview\" />\n\n                <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                    android:defaultValue=\"@integer/default_icon_brightness\"\n                    android:dialogLayout=\"@layout/pref_slider\"\n                    android:key=\"icon-brightness\"\n                    android:title=\"@string/icon_brightness\"\n                    android:widgetLayout=\"@layout/pref_amount_preview\" />\n\n                <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                    android:defaultValue=\"@integer/default_icon_saturation\"\n                    android:dialogLayout=\"@layout/pref_slider\"\n                    android:key=\"icon-saturation\"\n                    android:title=\"@string/icon_saturation\"\n                    android:widgetLayout=\"@layout/pref_amount_preview\" />\n\n            </PreferenceScreen>\n\n            <SwitchPreference\n                android:defaultValue=\"false\"\n                android:key=\"matrix-contacts\"\n                android:summary=\"@string/matrix_contacts_summary\"\n                android:title=\"@string/matrix_contacts\" />\n\n            <SwitchPreference\n                android:defaultValue=\"true\"\n                android:disableDependentsState=\"true\"\n                android:key=\"force-adaptive\"\n                android:summary=\"@string/force_adaptive_summary\"\n                android:title=\"@string/force_adaptive\" />\n\n            <SwitchPreference\n                android:defaultValue=\"false\"\n                android:dependency=\"force-adaptive\"\n                android:key=\"force-shape\"\n                android:summary=\"@string/force_shape_summary\"\n                android:title=\"@string/force_shape\" />\n\n            <ListPreference\n                android:defaultValue=\"none\"\n                android:entries=\"@array/loadingIconEntries\"\n                android:entryValues=\"@array/loadingIconValues\"\n                android:key=\"loading-icon\"\n                android:title=\"@string/loading_icon\" />\n\n        </PreferenceCategory>\n\n        <!-- Icon pack -->\n        <PreferenceCategory\n            android:key=\"icon-pack-section\"\n            android:title=\"@string/icon_pack_section\"\n            app:initialExpandedChildrenCount=\"1\">\n\n            <ListPreference\n                android:defaultValue=\"default\"\n                android:key=\"icons-pack\"\n                android:title=\"@string/icons_pack\" />\n\n            <SwitchPreference\n                android:defaultValue=\"true\"\n                android:disableDependentsState=\"true\"\n                android:key=\"contact-pack-mask\"\n                android:summary=\"@string/contact_pack_mask_summary\"\n                android:title=\"@string/contact_pack_mask\" />\n\n            <ListPreference\n                android:defaultValue=\"0\"\n                android:dependency=\"contact-pack-mask\"\n                android:entries=\"@array/adaptiveEntries\"\n                android:entryValues=\"@array/adaptiveValues\"\n                android:key=\"contacts-shape\"\n                android:summary=\"@string/contacts_shape_summary\"\n                android:title=\"@string/contacts_shape_name\" />\n\n            <SwitchPreference\n                android:defaultValue=\"true\"\n                android:disableDependentsState=\"true\"\n                android:key=\"shortcut-pack-mask\"\n                android:summary=\"@string/shortcut_pack_mask_summary\"\n                android:title=\"@string/shortcut_pack_mask\" />\n\n            <ListPreference\n                android:defaultValue=\"0\"\n                android:dependency=\"shortcut-pack-mask\"\n                android:entries=\"@array/adaptiveEntries\"\n                android:entryValues=\"@array/adaptiveValues\"\n                android:key=\"shortcut-shape\"\n                android:summary=\"@string/shortcut_shape_summary\"\n                android:title=\"@string/shortcut_shape_name\" />\n\n            <SwitchPreference\n                android:defaultValue=\"true\"\n                android:key=\"shortcut-pack-badge-mask\"\n                android:summary=\"@string/shortcut_pack_badge_mask_summary\"\n                android:title=\"@string/shortcut_pack_badge_mask\" />\n\n        </PreferenceCategory>\n\n        <!-- Notification bar -->\n        <PreferenceCategory\n            android:key=\"notification-bar-section\"\n            android:title=\"@string/notification_bar_section\">\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@integer/default_background_argb\"\n                android:key=\"notification-bar-argb\"\n                android:title=\"@string/color_and_opacity\"\n                android:widgetLayout=\"@layout/pref_color_preview\" />\n\n            <SwitchPreference\n                android:defaultValue=\"true\"\n                android:key=\"notification-bar-gradient\"\n                android:title=\"@string/gradient\" />\n\n            <SwitchPreference\n                android:defaultValue=\"false\"\n                android:key=\"black-notification-icons\"\n                android:title=\"@string/black_notification_icons\" />\n\n        </PreferenceCategory>\n\n        <PreferenceCategory\n            android:key=\"navigation-bar-section\"\n            android:title=\"@string/navigation_bar_section\">\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@integer/default_background_argb\"\n                android:key=\"navigation-bar-argb\"\n                android:title=\"@string/color_and_opacity\"\n                android:widgetLayout=\"@layout/pref_color_preview\" />\n\n        </PreferenceCategory>\n\n        <!-- Search bar -->\n        <PreferenceCategory\n            android:icon=\"@drawable/ic_search_bar\"\n            android:key=\"search-bar-section\"\n            android:title=\"@string/search_bar_section\">\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@integer/default_background_argb\"\n                android:key=\"search-bar-argb\"\n                android:title=\"@string/color_and_opacity\"\n                android:widgetLayout=\"@layout/pref_color_preview\" />\n\n            <SwitchPreference\n                android:defaultValue=\"false\"\n                android:disableDependentsState=\"true\"\n                android:key=\"search-bar-gradient\"\n                android:title=\"@string/gradient\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@integer/default_corner_radius\"\n                android:dialogLayout=\"@layout/pref_slider\"\n                android:key=\"search-bar-radius\"\n                android:title=\"@string/corner_radius\"\n                android:widgetLayout=\"@layout/pref_size_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@integer/default_color_text\"\n                android:key=\"search-bar-text-color\"\n                android:title=\"@string/search_bar_text_color\"\n                android:widgetLayout=\"@layout/pref_color_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@integer/default_color_text\"\n                android:key=\"search-bar-icon-color\"\n                android:title=\"@string/search_bar_icon_color\"\n                android:widgetLayout=\"@layout/pref_color_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@integer/default_color_highlight\"\n                android:key=\"search-bar-ripple-color\"\n                android:title=\"@string/search_bar_ripple_color\"\n                android:widgetLayout=\"@layout/pref_color_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@integer/default_color_highlight\"\n                android:key=\"search-bar-cursor-argb\"\n                android:title=\"@string/search_bar_cursor_argb\"\n                android:widgetLayout=\"@layout/pref_color_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"0\"\n                android:key=\"search-bar-shadow-color\"\n                android:title=\"@string/search_bar_shadow_color\"\n                android:widgetLayout=\"@layout/pref_color_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@dimen/default_result_shadow_dx\"\n                android:dialogLayout=\"@layout/pref_shadow\"\n                android:key=\"search-bar-shadow-dx\"\n                android:title=\"@string/search_bar_shadow_radius_dx_dy\"\n                android:widgetLayout=\"@layout/pref_shadow_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@dimen/default_result_shadow_dy\"\n                android:dialogLayout=\"@layout/pref_shadow\"\n                android:key=\"search-bar-shadow-dy\"\n                app:isPreferenceVisible=\"false\"\n                android:widgetLayout=\"@layout/pref_size_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@dimen/default_result_shadow_radius\"\n                android:dialogLayout=\"@layout/pref_shadow\"\n                android:key=\"search-bar-shadow-radius\"\n                app:isPreferenceVisible=\"false\"\n                android:widgetLayout=\"@layout/pref_size_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@integer/default_search_bar_height\"\n                android:dialogLayout=\"@layout/pref_slider\"\n                android:key=\"search-bar-height\"\n                android:title=\"@string/search_bar_height\"\n                android:widgetLayout=\"@layout/pref_size_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@integer/default_result_margin\"\n                android:dialogLayout=\"@layout/pref_slider\"\n                android:key=\"search-bar-margin-vertical\"\n                android:title=\"@string/margin_vertical\"\n                android:widgetLayout=\"@layout/pref_size_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@integer/default_result_margin\"\n                android:dialogLayout=\"@layout/pref_slider\"\n                android:key=\"search-bar-margin-horizontal\"\n                android:title=\"@string/margin_horizontal\"\n                android:widgetLayout=\"@layout/pref_size_preview\" />\n\n        </PreferenceCategory>\n\n        <!-- Result list -->\n        <PreferenceCategory\n            android:icon=\"@drawable/ic_list\"\n            android:key=\"result-list-section\"\n            android:title=\"@string/result_list_section\">\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@integer/default_background_argb\"\n                android:key=\"result-list-argb\"\n                android:title=\"@string/result_list_argb\"\n                android:widgetLayout=\"@layout/pref_color_preview\" />\n\n            <androidx.preference.SwitchPreference\n                android:defaultValue=\"false\"\n                android:key=\"result-fading-edge\"\n                android:summary=\"@string/result_fading_edge_summary\"\n                android:title=\"@string/result_fading_edge\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@integer/default_corner_radius\"\n                android:dialogLayout=\"@layout/pref_slider\"\n                android:key=\"result-list-radius\"\n                android:title=\"@string/corner_radius\"\n                android:widgetLayout=\"@layout/pref_size_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@integer/default_color_highlight\"\n                android:key=\"result-ripple-color\"\n                android:title=\"@string/result_ripple_color\"\n                android:widgetLayout=\"@layout/pref_color_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@integer/default_color_highlight\"\n                android:key=\"result-highlight-color\"\n                android:title=\"@string/result_highlight_color\"\n                android:widgetLayout=\"@layout/pref_color_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@integer/default_color_text\"\n                android:key=\"result-text-color\"\n                android:title=\"@string/result_text_color\"\n                android:widgetLayout=\"@layout/pref_color_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@integer/default_size_text\"\n                android:dialogLayout=\"@layout/pref_slider\"\n                android:key=\"result-text-size\"\n                android:title=\"@string/result_text_size\"\n                android:widgetLayout=\"@layout/pref_size_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@integer/default_color_text2\"\n                android:key=\"result-text2-color\"\n                android:title=\"@string/result_text2_color\"\n                android:widgetLayout=\"@layout/pref_color_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@integer/default_size_text2\"\n                android:dialogLayout=\"@layout/pref_slider\"\n                android:key=\"result-text2-size\"\n                android:title=\"@string/result_text2_size\"\n                android:widgetLayout=\"@layout/pref_size_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"0\"\n                android:key=\"result-shadow-color\"\n                android:title=\"@string/result_shadow_color\"\n                android:widgetLayout=\"@layout/pref_color_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@dimen/default_result_shadow_dx\"\n                android:dialogLayout=\"@layout/pref_shadow\"\n                android:key=\"result-shadow-dx\"\n                android:title=\"@string/result_shadow_radius_dx_dy\"\n                android:widgetLayout=\"@layout/pref_shadow_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@dimen/default_result_shadow_dy\"\n                android:dialogLayout=\"@layout/pref_shadow\"\n                android:key=\"result-shadow-dy\"\n                app:isPreferenceVisible=\"false\"\n                android:widgetLayout=\"@layout/pref_size_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@dimen/default_result_shadow_radius\"\n                android:dialogLayout=\"@layout/pref_slider\"\n                android:key=\"result-shadow-radius\"\n                app:isPreferenceVisible=\"false\"\n                android:widgetLayout=\"@layout/pref_size_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@integer/default_color_text\"\n                android:key=\"contact-action-color\"\n                android:title=\"@string/contact_action_color\"\n                android:widgetLayout=\"@layout/pref_color_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@integer/default_size_icon\"\n                android:dialogLayout=\"@layout/pref_slider\"\n                android:key=\"result-icon-size\"\n                android:title=\"@string/result_icon_size\"\n                android:widgetLayout=\"@layout/pref_size_preview\" />\n\n            <androidx.preference.SwitchPreference\n                android:defaultValue=\"false\"\n                android:key=\"result-list-row-height-manual\"\n                android:title=\"@string/result_list_row_height_manual\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@integer/default_height_row\"\n                android:dependency=\"result-list-row-height-manual\"\n                android:dialogLayout=\"@layout/pref_slider\"\n                android:key=\"result-list-row-height\"\n                android:title=\"@string/result_list_row_height\"\n                android:widgetLayout=\"@layout/pref_size_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@integer/default_result_margin\"\n                android:dialogLayout=\"@layout/pref_slider\"\n                android:key=\"result-list-margin-vertical\"\n                android:title=\"@string/margin_vertical\"\n                android:widgetLayout=\"@layout/pref_size_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@integer/default_result_margin\"\n                android:dialogLayout=\"@layout/pref_slider\"\n                android:key=\"result-list-margin-horizontal\"\n                android:title=\"@string/margin_horizontal\"\n                android:widgetLayout=\"@layout/pref_size_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@dimen/default_result_margin_offset\"\n                android:dialogLayout=\"@layout/pref_margin_offset\"\n                android:key=\"result-list-margin-offset-dx\"\n                android:title=\"@string/margin_offset\"\n                android:widgetLayout=\"@layout/pref_offset_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@dimen/default_result_margin_offset\"\n                android:key=\"result-list-margin-offset-dy\"\n                app:isPreferenceVisible=\"false\" />\n\n        </PreferenceCategory>\n\n        <!-- Dock / Quick List -->\n        <PreferenceCategory\n            android:icon=\"@drawable/ic_quick\"\n            android:key=\"quick-list-section\"\n            android:title=\"@string/quick_list_section\">\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@integer/default_background_argb\"\n                android:key=\"quick-list-argb\"\n                android:title=\"@string/quick_list_color\"\n                android:widgetLayout=\"@layout/pref_color_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@integer/default_color_highlight\"\n                android:key=\"quick-list-toggle-color\"\n                android:title=\"@string/quick_list_toggle_color\"\n                android:widgetLayout=\"@layout/pref_color_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@integer/default_color_highlight\"\n                android:key=\"quick-list-ripple-color\"\n                android:title=\"@string/quick_list_ripple_color\"\n                android:widgetLayout=\"@layout/pref_color_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@integer/max_size_icon\"\n                android:dialogLayout=\"@layout/pref_slider\"\n                android:key=\"quick-list-icon-size\"\n                android:title=\"@string/quick_list_icon_size\"\n                android:widgetLayout=\"@layout/pref_size_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@integer/default_dock_height\"\n                android:dialogLayout=\"@layout/pref_slider\"\n                android:key=\"quick-list-height\"\n                android:title=\"@string/quick_list_height\"\n                android:widgetLayout=\"@layout/pref_size_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@integer/default_corner_radius\"\n                android:dialogLayout=\"@layout/pref_slider\"\n                android:key=\"quick-list-radius\"\n                android:title=\"@string/corner_radius\"\n                android:widgetLayout=\"@layout/pref_size_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@integer/default_result_margin\"\n                android:dialogLayout=\"@layout/pref_slider\"\n                android:key=\"quick-list-margin-vertical\"\n                android:title=\"@string/margin_vertical\"\n                android:widgetLayout=\"@layout/pref_size_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@integer/default_result_margin\"\n                android:dialogLayout=\"@layout/pref_slider\"\n                android:key=\"quick-list-margin-horizontal\"\n                android:title=\"@string/margin_horizontal\"\n                android:widgetLayout=\"@layout/pref_size_preview\" />\n\n        </PreferenceCategory>\n\n        <!-- Popup -->\n        <PreferenceCategory\n            android:icon=\"@drawable/ic_popup\"\n            android:key=\"popup-section\"\n            android:title=\"@string/popup_section\">\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@integer/default_corner_radius\"\n                android:dialogLayout=\"@layout/pref_slider\"\n                android:key=\"popup-corner-radius\"\n                android:title=\"@string/corner_radius\"\n                android:widgetLayout=\"@layout/pref_size_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:key=\"popup-border-argb\"\n                android:title=\"@string/popup_border_argb\"\n                android:widgetLayout=\"@layout/pref_color_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@integer/default_popup_background\"\n                android:key=\"popup-background-argb\"\n                android:title=\"@string/popup_background_argb\"\n                android:widgetLayout=\"@layout/pref_color_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@integer/default_color_highlight\"\n                android:key=\"popup-ripple-color\"\n                android:title=\"@string/popup_ripple_color\"\n                android:widgetLayout=\"@layout/pref_color_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@integer/default_color_text\"\n                android:key=\"popup-text-color\"\n                android:title=\"@string/popup_text_color\"\n                android:widgetLayout=\"@layout/pref_color_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@integer/default_color_text2\"\n                android:key=\"popup-title-color\"\n                android:title=\"@string/popup_title_color\"\n                android:widgetLayout=\"@layout/pref_color_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"0\"\n                android:key=\"popup-shadow-color\"\n                android:title=\"@string/popup_shadow_color\"\n                android:widgetLayout=\"@layout/pref_color_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@dimen/default_result_shadow_dx\"\n                android:dialogLayout=\"@layout/pref_shadow\"\n                android:key=\"popup-shadow-dx\"\n                android:title=\"@string/popup_shadow_radius_dx_dy\"\n                android:widgetLayout=\"@layout/pref_shadow_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@dimen/default_result_shadow_dy\"\n                android:dialogLayout=\"@layout/pref_shadow\"\n                android:key=\"popup-shadow-dy\"\n                app:isPreferenceVisible=\"false\"\n                android:widgetLayout=\"@layout/pref_size_preview\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@dimen/default_result_shadow_radius\"\n                android:dialogLayout=\"@layout/pref_slider\"\n                android:key=\"popup-shadow-radius\"\n                app:isPreferenceVisible=\"false\"\n                android:widgetLayout=\"@layout/pref_size_preview\" />\n\n        </PreferenceCategory>\n\n        <!-- Tags Menu -->\n        <PreferenceCategory\n            android:key=\"tags-menu-section\"\n            android:title=\"@string/tags_menu_section\">\n\n            <!-- This is here just to be able to use dependency, not visible for user -->\n            <androidx.preference.SwitchPreference\n                android:defaultValue=\"false\"\n                android:key=\"tags-menu-icons\"\n                android:title=\"@string/tags_menu_icons\"\n                app:isPreferenceVisible=\"false\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:defaultValue=\"@integer/default_size_icon\"\n                android:dependency=\"tags-menu-icons\"\n                android:dialogLayout=\"@layout/pref_slider\"\n                android:key=\"tags-menu-icon-size\"\n                android:title=\"@string/tags_menu_icon_size\"\n                android:widgetLayout=\"@layout/pref_size_preview\" />\n\n        </PreferenceCategory>\n\n\n        <!--        <PreferenceCategory android:title=\"@string/misc\">-->\n        <!--            <SwitchPreference-->\n        <!--                android:defaultValue=\"false\"-->\n        <!--                android:key=\"pref-swap-kiss-button-with-menu\"-->\n        <!--                android:title=\"@string/swap_kiss_button_with_menu\" />-->\n        <!--        </PreferenceCategory>-->\n    </PreferenceScreen>\n\n    <!-- We hold everything from features in a different xml because we have some interface settings there and we\n    can't have the same keys used in an xml file -->\n    <PreferenceScreen\n        android:icon=\"@drawable/ic_features\"\n        android:key=\"feature-holder\"\n        android:summary=\"@string/features_summary\"\n        android:title=\"@string/title_features\">\n        <!-- include preference_features.xml -->\n        <Preference />\n\n    </PreferenceScreen>\n\n    <PreferenceScreen\n        android:icon=\"@drawable/ic_behaviour\"\n        android:key=\"behaviour-holder\"\n        android:summary=\"@string/behaviour_summary\"\n        android:title=\"@string/title_behaviour\">\n\n        <!-- Gestures -->\n        <PreferenceCategory\n            android:icon=\"@drawable/ic_gesture\"\n            android:key=\"gesture-section\"\n            android:title=\"@string/gesture_section\">\n            <ListPreference\n                android:defaultValue=\"toggleSearchAndWidget\"\n                android:entries=\"@array/gestureEntries\"\n                android:entryValues=\"@array/gestureValues\"\n                android:key=\"gesture-click\"\n                android:title=\"@string/gesture_click\" />\n            <ListPreference\n                android:key=\"gesture-click-app-to-run\"\n                android:title=\"@string/action_app_to_run\"\n                app:isPreferenceVisible=\"false\" />\n            <ListPreference\n                android:key=\"gesture-click-shortcut-to-run\"\n                android:title=\"@string/action_shortcut_to_run\"\n                app:isPreferenceVisible=\"false\" />\n            <ListPreference\n                android:key=\"gesture-click-entry-to-show\"\n                android:title=\"@string/action_entry_to_show\"\n                app:isPreferenceVisible=\"false\" />\n\n            <ListPreference\n                android:defaultValue=\"none\"\n                android:entries=\"@array/gestureEntries\"\n                android:entryValues=\"@array/gestureValues\"\n                android:key=\"gesture-double-click\"\n                android:title=\"@string/gesture_double_click\" />\n            <ListPreference\n                android:key=\"gesture-double-click-app-to-run\"\n                android:title=\"@string/action_app_to_run\"\n                app:isPreferenceVisible=\"false\" />\n            <ListPreference\n                android:key=\"gesture-double-click-shortcut-to-run\"\n                android:title=\"@string/action_shortcut_to_run\"\n                app:isPreferenceVisible=\"false\" />\n            <ListPreference\n                android:key=\"gesture-double-click-entry-to-show\"\n                android:title=\"@string/action_entry_to_show\"\n                app:isPreferenceVisible=\"false\" />\n\n            <ListPreference\n                android:defaultValue=\"expandNotificationsPanel\"\n                android:entries=\"@array/gestureEntries\"\n                android:entryValues=\"@array/gestureValues\"\n                android:key=\"gesture-fling-down-left\"\n                android:summary=\"@string/gesture_fling_down_left_summary\"\n                android:title=\"@string/gesture_fling_down_left\" />\n            <ListPreference\n                android:key=\"gesture-fling-down-left-app-to-run\"\n                android:title=\"@string/action_app_to_run\"\n                app:isPreferenceVisible=\"false\" />\n            <ListPreference\n                android:key=\"gesture-fling-down-left-shortcut-to-run\"\n                android:title=\"@string/action_shortcut_to_run\"\n                app:isPreferenceVisible=\"false\" />\n            <ListPreference\n                android:key=\"gesture-fling-down-left-entry-to-show\"\n                android:title=\"@string/action_entry_to_show\"\n                app:isPreferenceVisible=\"false\" />\n\n            <ListPreference\n                android:defaultValue=\"expandNotificationsPanel\"\n                android:entries=\"@array/gestureEntries\"\n                android:entryValues=\"@array/gestureValues\"\n                android:key=\"gesture-fling-down-right\"\n                android:summary=\"@string/gesture_fling_down_right_summary\"\n                android:title=\"@string/gesture_fling_down_right\" />\n            <ListPreference\n                android:key=\"gesture-fling-down-right-app-to-run\"\n                android:title=\"@string/action_app_to_run\"\n                app:isPreferenceVisible=\"false\" />\n            <ListPreference\n                android:key=\"gesture-fling-down-right-shortcut-to-run\"\n                android:title=\"@string/action_shortcut_to_run\"\n                app:isPreferenceVisible=\"false\" />\n            <ListPreference\n                android:key=\"gesture-fling-down-right-entry-to-show\"\n                android:title=\"@string/action_entry_to_show\"\n                app:isPreferenceVisible=\"false\" />\n\n            <ListPreference\n                android:defaultValue=\"none\"\n                android:entries=\"@array/gestureEntries\"\n                android:entryValues=\"@array/gestureValues\"\n                android:key=\"gesture-fling-up\"\n                android:title=\"@string/gesture_fling_up\" />\n            <ListPreference\n                android:key=\"gesture-fling-up-app-to-run\"\n                android:title=\"@string/action_app_to_run\"\n                app:isPreferenceVisible=\"false\" />\n            <ListPreference\n                android:key=\"gesture-fling-up-shortcut-to-run\"\n                android:title=\"@string/action_shortcut_to_run\"\n                app:isPreferenceVisible=\"false\" />\n            <ListPreference\n                android:key=\"gesture-fling-up-entry-to-show\"\n                android:title=\"@string/action_entry_to_show\"\n                app:isPreferenceVisible=\"false\" />\n\n            <ListPreference\n                android:defaultValue=\"none\"\n                android:entries=\"@array/gestureEntries\"\n                android:entryValues=\"@array/gestureValues\"\n                android:key=\"gesture-fling-left\"\n                android:title=\"@string/gesture_fling_left\" />\n            <ListPreference\n                android:key=\"gesture-fling-left-app-to-run\"\n                android:title=\"@string/action_app_to_run\"\n                app:isPreferenceVisible=\"false\" />\n            <ListPreference\n                android:key=\"gesture-fling-left-shortcut-to-run\"\n                android:title=\"@string/action_shortcut_to_run\"\n                app:isPreferenceVisible=\"false\" />\n            <ListPreference\n                android:key=\"gesture-fling-left-entry-to-show\"\n                android:title=\"@string/action_entry_to_show\"\n                app:isPreferenceVisible=\"false\" />\n\n            <ListPreference\n                android:defaultValue=\"none\"\n                android:entries=\"@array/gestureEntries\"\n                android:entryValues=\"@array/gestureValues\"\n                android:key=\"gesture-fling-right\"\n                android:title=\"@string/gesture_fling_right\" />\n            <ListPreference\n                android:key=\"gesture-fling-right-app-to-run\"\n                android:title=\"@string/action_app_to_run\"\n                app:isPreferenceVisible=\"false\" />\n            <ListPreference\n                android:key=\"gesture-fling-right-shortcut-to-run\"\n                android:title=\"@string/action_shortcut_to_run\"\n                app:isPreferenceVisible=\"false\" />\n            <ListPreference\n                android:key=\"gesture-fling-right-entry-to-show\"\n                android:title=\"@string/action_entry_to_show\"\n                app:isPreferenceVisible=\"false\" />\n        </PreferenceCategory>\n\n        <!-- Keyboard -->\n        <androidx.preference.PreferenceCategory\n            android:icon=\"@drawable/ic_keyboard\"\n            android:key=\"keyboard-section\"\n            android:title=\"@string/keyboard_section\">\n\n            <androidx.preference.SwitchPreference\n                android:defaultValue=\"true\"\n                android:key=\"behaviour-link-keyboard-search-bar\"\n                android:title=\"@string/behaviour_link_keyboard_search_bar\" />\n\n            <androidx.preference.SwitchPreference\n                android:defaultValue=\"true\"\n                android:key=\"behaviour-link-close-keyboard-back-button\"\n                android:title=\"@string/behaviour_link_close_keyboard_back_button\" />\n\n            <androidx.preference.SwitchPreference\n                android:defaultValue=\"false\"\n                android:key=\"enable-suggestions-keyboard\"\n                android:title=\"@string/keyboard_suggestions\" />\n\n            <ListPreference\n                android:defaultValue=\"phone\"\n                android:entries=\"@array/contactActionEntries\"\n                android:entryValues=\"@array/contactActionValues\"\n                android:key=\"default-contact-action\"\n                android:title=\"@string/done_key_contact_action\"/>\n\n        </androidx.preference.PreferenceCategory>\n\n        <!-- Behaviour -->\n        <androidx.preference.PreferenceCategory\n            android:icon=\"@drawable/ic_behaviour\"\n            android:key=\"behaviour-section\"\n            android:title=\"@string/behaviour_section\">\n\n            <ListPreference\n                android:defaultValue=\"showAllAppsZA\"\n                android:entries=\"@array/gestureEntries\"\n                android:entryValues=\"@array/gestureValues\"\n                android:key=\"button-launcher\"\n                android:title=\"@string/button_launcher\" />\n            <ListPreference\n                android:key=\"button-launcher-app-to-run\"\n                android:title=\"@string/action_app_to_run\"\n                app:isPreferenceVisible=\"false\" />\n            <ListPreference\n                android:key=\"button-launcher-shortcut-to-run\"\n                android:title=\"@string/action_shortcut_to_run\"\n                app:isPreferenceVisible=\"false\" />\n            <ListPreference\n                android:key=\"button-launcher-entry-to-show\"\n                android:title=\"@string/action_entry_to_show\"\n                app:isPreferenceVisible=\"false\" />\n\n            <ListPreference\n                android:defaultValue=\"showSearchBar\"\n                android:entries=\"@array/gestureEntries\"\n                android:entryValues=\"@array/gestureValues\"\n                android:key=\"button-home\"\n                android:title=\"@string/button_home\" />\n\n            <androidx.preference.SwitchPreference\n                android:defaultValue=\"true\"\n                android:key=\"behaviour-widget-after-launch\"\n                android:title=\"@string/behaviour_widget_after_launch\" />\n\n            <androidx.preference.SwitchPreference\n                android:defaultValue=\"true\"\n                android:key=\"behaviour-clear-search-after-launch\"\n                android:title=\"@string/behaviour_clear_search_after_launch\" />\n\n            <ListPreference\n                android:defaultValue=\"showSearchBar\"\n                android:entries=\"@array/desktopEntries\"\n                android:entryValues=\"@array/desktopValues\"\n                android:key=\"initial-desktop\"\n                android:title=\"@string/initial_desktop\" />\n\n            <!-- ‹Empty› desktop mode -->\n            <PreferenceScreen\n                android:key=\"desktop-mode-empty-holder\"\n                android:title=\"@string/title_desktop_mode_empty\">\n\n                <androidx.preference.SwitchPreference\n                    android:defaultValue=\"false\"\n                    android:dependency=\"quick-list-enabled\"\n                    android:key=\"dm-empty-quick-list\"\n                    android:title=\"@string/dm_empty_quick_list\" />\n\n                <androidx.preference.SwitchPreference\n                    android:defaultValue=\"true\"\n                    android:key=\"dm-empty-fullscreen\"\n                    android:title=\"@string/dm_empty_fullscreen\" />\n\n                <ListPreference\n                    android:defaultValue=\"showWidgets\"\n                    android:entries=\"@array/gestureEntries\"\n                    android:entryValues=\"@array/gestureValues\"\n                    android:key=\"dm-empty-back\"\n                    android:title=\"@string/back_device_key\" />\n\n                <!-- This is here just to be able to use dependency, not visible for user -->\n                <androidx.preference.SwitchPreference\n                    android:defaultValue=\"true\"\n                    android:key=\"quick-list-enabled\"\n                    android:title=\"@string/quick_list_enabled\"\n                    app:isPreferenceVisible=\"false\" />\n\n            </PreferenceScreen>\n\n            <!-- ‹Search› desktop mode -->\n            <PreferenceScreen\n                android:key=\"desktop-mode-search-holder\"\n                android:title=\"@string/title_desktop_mode_search\">\n\n                <androidx.preference.SwitchPreference\n                    android:defaultValue=\"true\"\n                    android:dependency=\"quick-list-enabled\"\n                    android:key=\"dm-search-quick-list\"\n                    android:title=\"@string/dm_search_quick_list\" />\n\n                <rocks.tbog.tblauncher.preference.MultiDependenciesSwitchPreference\n                    android:defaultValue=\"false\"\n                    android:key=\"quick-list-only-for-results\"\n                    android:summary=\"@string/quick_list_only_for_results_summary\"\n                    android:title=\"@string/quick_list_only_for_results\"\n                    pref:dependencies=\"quick-list-enabled,dm-search-quick-list\" />\n\n                <androidx.preference.SwitchPreference\n                    android:defaultValue=\"false\"\n                    android:key=\"dm-search-fullscreen\"\n                    android:summary=\"@string/dm_search_fullscreen_summary\"\n                    android:title=\"@string/dm_search_fullscreen\" />\n\n                <ListPreference\n                    android:defaultValue=\"showEmpty\"\n                    android:entries=\"@array/gestureEntries\"\n                    android:entryValues=\"@array/gestureValues\"\n                    android:key=\"dm-search-back\"\n                    android:title=\"@string/back_device_key\" />\n\n                <ListPreference\n                    android:defaultValue=\"showHistoryByRecency\"\n                    android:entries=\"@array/resultEntries\"\n                    android:entryValues=\"@array/resultValues\"\n                    android:key=\"dm-search-open-result\"\n                    android:title=\"@string/dm_search_open_result\" />\n                <ListPreference\n                    android:key=\"dm-search-open-result-entry-to-show\"\n                    android:title=\"@string/action_entry_to_show\"\n                    app:isPreferenceVisible=\"false\" />\n\n                <!-- This is here just to be able to use dependency, not visible for user -->\n                <androidx.preference.SwitchPreference\n                    android:defaultValue=\"true\"\n                    android:key=\"quick-list-enabled\"\n                    android:title=\"@string/quick_list_enabled\"\n                    app:isPreferenceVisible=\"false\" />\n\n            </PreferenceScreen>\n\n            <!-- ‹Widget› desktop mode -->\n            <PreferenceScreen\n                android:key=\"desktop-mode-widget-holder\"\n                android:title=\"@string/title_desktop_mode_widget\">\n\n                <androidx.preference.SwitchPreference\n                    android:defaultValue=\"false\"\n                    android:dependency=\"quick-list-enabled\"\n                    android:key=\"dm-widget-quick-list\"\n                    android:title=\"@string/dm_widget_quick_list\" />\n\n                <androidx.preference.SwitchPreference\n                    android:defaultValue=\"false\"\n                    android:key=\"dm-widget-fullscreen\"\n                    android:title=\"@string/dm_widget_fullscreen\" />\n\n                <ListPreference\n                    android:defaultValue=\"showSearchBar\"\n                    android:entries=\"@array/gestureEntries\"\n                    android:entryValues=\"@array/gestureValues\"\n                    android:key=\"dm-widget-back\"\n                    android:title=\"@string/back_device_key\" />\n\n                <!-- This is here just to be able to use dependency, not visible for user -->\n                <androidx.preference.SwitchPreference\n                    android:defaultValue=\"true\"\n                    android:key=\"quick-list-enabled\"\n                    android:title=\"@string/quick_list_enabled\"\n                    app:isPreferenceVisible=\"false\" />\n\n            </PreferenceScreen>\n\n        </androidx.preference.PreferenceCategory>\n\n    </PreferenceScreen>\n\n    <PreferenceScreen\n        android:icon=\"@drawable/ic_settings\"\n        android:key=\"options-section\"\n        android:title=\"@string/options_section\">\n\n        <rocks.tbog.tblauncher.preference.CustomDialogPreference\n            android:dialogLayout=\"@layout/pref_confirm\"\n            android:key=\"device-admin\"\n            android:summary=\"@string/device_admin_explanation\"\n            android:title=\"@string/device_admin\" />\n\n        <androidx.preference.SwitchPreference\n            android:defaultValue=\"false\"\n            android:key=\"root-mode\"\n            android:summary=\"@string/root_mode_summary\"\n            android:title=\"@string/root_mode\" />\n\n        <!-- Memory -->\n        <androidx.preference.PreferenceCategory\n            android:icon=\"@drawable/ic_memory\"\n            android:key=\"memory-section\"\n            android:title=\"@string/memory_section\">\n\n            <androidx.preference.SwitchPreference\n                android:defaultValue=\"true\"\n                android:key=\"cache-drawable\"\n                android:title=\"@string/cache_drawable\" />\n\n            <androidx.preference.SwitchPreference\n                android:defaultValue=\"true\"\n                android:dependency=\"cache-drawable\"\n                android:key=\"cache-half-apps\"\n                android:summary=\"@string/cache_half_apps_summary\"\n                android:title=\"@string/cache_half_apps\" />\n\n            <androidx.preference.SwitchPreference\n                android:defaultValue=\"false\"\n                android:dependency=\"cache-drawable\"\n                android:key=\"screen-off-cache-clear\"\n                android:summary=\"@string/screen_off_cache_clear_summary\"\n                android:title=\"@string/screen_off_cache_clear\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:dialogLayout=\"@layout/pref_confirm\"\n                android:key=\"reset-cached-app-icons\"\n                android:title=\"@string/reset_cached_app_icons\" />\n\n        </androidx.preference.PreferenceCategory>\n\n        <!-- Providers -->\n        <androidx.preference.PreferenceCategory\n            android:key=\"provider-section\"\n            android:title=\"@string/provider_section\"\n            app:initialExpandedChildrenCount=\"4\">\n\n            <androidx.preference.SwitchPreference\n                android:defaultValue=\"true\"\n                android:key=\"enable-calculator\"\n                android:title=\"@string/enable_calculator\" />\n\n            <androidx.preference.SwitchPreference\n                android:defaultValue=\"true\"\n                android:key=\"enable-dial\"\n                android:title=\"@string/enable_dial\" />\n\n            <androidx.preference.SwitchPreference\n                android:defaultValue=\"true\"\n                android:key=\"enable-contacts\"\n                android:title=\"@string/enable_contacts\" />\n\n            <androidx.preference.SwitchPreference\n                android:defaultValue=\"true\"\n                android:dependency=\"enable-search\"\n                android:key=\"enable-url\"\n                android:title=\"@string/enable_url\" />\n\n            <androidx.preference.SwitchPreference\n                android:defaultValue=\"true\"\n                android:key=\"enable-search\"\n                android:title=\"@string/enable_search\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:dependency=\"enable-search\"\n                android:dialogLayout=\"@layout/edit_search_engines\"\n                android:key=\"edit-search-engines\"\n                android:summary=\"@string/edit_search_engines_summary\"\n                android:title=\"@string/edit_search_engines\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:dependency=\"enable-search\"\n                android:dialogLayout=\"@layout/add_search_engine\"\n                android:key=\"add-search-engine\"\n                android:title=\"@string/add_search_engine\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:dependency=\"enable-search\"\n                android:dialogLayout=\"@layout/edit_search_engines\"\n                android:key=\"reset-search-engines\"\n                android:summary=\"@string/reset_search_engines_summary\"\n                android:title=\"@string/reset_search_engines\" />\n\n            <MultiSelectListPreference\n                android:defaultValue=\"@array/defaultContactMimeTypes\"\n                android:dependency=\"enable-contacts\"\n                android:key=\"selected-contact-mime-types\"\n                android:title=\"@string/selected_contact_mime_types\" />\n\n            <!-- This is here just to set the default value and be saved in backup, not visible for user -->\n            <MultiSelectListPreference\n                android:defaultValue=\"@array/defaultSearchProviders\"\n                android:key=\"available-search-providers\"\n                app:isPreferenceVisible=\"false\" />\n\n            <!-- This is here just to set the default value and be saved in backup, not visible for user -->\n            <MultiSelectListPreference\n                android:key=\"selected-search-provider-names\"\n                app:isPreferenceVisible=\"false\" />\n\n            <!-- This is here just to set the default value and be saved in backup, not visible for user -->\n            <Preference\n                android:key=\"default-search-provider\"\n                app:isPreferenceVisible=\"false\" />\n\n        </androidx.preference.PreferenceCategory>\n\n        <!-- Debug -->\n        <androidx.preference.PreferenceCategory\n            android:icon=\"@drawable/ic_bug\"\n            android:key=\"debug-section\"\n            android:title=\"@string/debug_section\"\n            app:initialExpandedChildrenCount=\"0\">\n\n            <androidx.preference.SwitchPreference\n                android:defaultValue=\"false\"\n                android:key=\"debug-provider-status\"\n                android:title=\"@string/debug_provider_status\" />\n\n            <androidx.preference.SwitchPreference\n                android:defaultValue=\"false\"\n                android:key=\"debug-widget-add-info\"\n                android:title=\"@string/debug_widget_add_info\" />\n\n            <androidx.preference.SwitchPreference\n                android:defaultValue=\"false\"\n                android:key=\"debug-widget-info\"\n                android:title=\"@string/debug_widget_info\" />\n\n            <androidx.preference.SwitchPreference\n                android:defaultValue=\"false\"\n                android:key=\"debug-item-relevance\"\n                android:title=\"@string/debug_item_relevance\" />\n\n            <androidx.preference.SwitchPreference\n                android:defaultValue=\"false\"\n                android:key=\"debug-item-icon-info\"\n                android:title=\"@string/debug_item_icon_info\" />\n\n            <androidx.preference.SwitchPreference\n                android:defaultValue=\"false\"\n                android:key=\"debug-ksh-touch\"\n                android:summary=\"@string/debug_ksh_touch_summary\"\n                android:title=\"@string/debug_ksh_touch\" />\n\n            <androidx.preference.SwitchPreference\n                android:defaultValue=\"false\"\n                android:key=\"debug-favorites\"\n                android:summary=\"@string/debug_favorites_summary\"\n                android:title=\"@string/debug_favorites\" />\n\n        </androidx.preference.PreferenceCategory>\n\n    </PreferenceScreen>\n\n    <PreferenceScreen\n        android:icon=\"@drawable/ic_backup\"\n        android:key=\"backup-holder\"\n        android:summary=\"@string/backup_summary\"\n        android:title=\"@string/title_backup\">\n\n        <rocks.tbog.tblauncher.preference.CustomDialogPreference\n            android:dialogLayout=\"@layout/pref_confirm\"\n            android:key=\"reset-preferences\"\n            android:title=\"@string/reset_preferences\" />\n\n        <!-- Export -->\n        <PreferenceCategory\n            android:key=\"export-settings-section\"\n            android:title=\"@string/export_settings_section\">\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:dialogLayout=\"@layout/pref_confirm\"\n                android:key=\"export-tags\"\n                android:summary=\"@string/export_tags_summary\"\n                android:title=\"@string/export_tags\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:dialogLayout=\"@layout/pref_confirm\"\n                android:key=\"export-modifications\"\n                android:summary=\"@string/export_modifications_summary\"\n                android:title=\"@string/export_modifications\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:dialogLayout=\"@layout/pref_confirm\"\n                android:key=\"export-apps\"\n                android:summary=\"@string/export_apps_summary\"\n                android:title=\"@string/export_apps\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:dialogLayout=\"@layout/pref_confirm\"\n                android:key=\"export-interface\"\n                android:summary=\"@string/export_interface_summary\"\n                android:title=\"@string/export_interface\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:dialogLayout=\"@layout/pref_confirm\"\n                android:key=\"export-preferences\"\n                android:summary=\"@string/export_preferences_summary\"\n                android:title=\"@string/export_preferences\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:dialogLayout=\"@layout/pref_confirm\"\n                android:key=\"export-widgets\"\n                android:summary=\"@string/export_widgets_summary\"\n                android:title=\"@string/export_widgets\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:dialogLayout=\"@layout/pref_confirm\"\n                android:key=\"export-history\"\n                android:summary=\"@string/export_history_summary\"\n                android:title=\"@string/export_history\" />\n\n            <rocks.tbog.tblauncher.preference.CustomDialogPreference\n                android:dialogLayout=\"@layout/pref_confirm\"\n                android:key=\"export-backup\"\n                android:summary=\"@string/export_backup_summary\"\n                android:title=\"@string/export_backup\" />\n\n        </PreferenceCategory>\n\n        <!-- Import -->\n        <PreferenceCategory\n            android:key=\"import-settings-section\"\n            android:title=\"@string/import_settings_section\">\n\n            <Preference\n                android:key=\"import-settings-set\"\n                android:summary=\"@string/import_settings_set_summary\"\n                android:title=\"@string/import_settings_set\" />\n\n            <Preference\n                android:key=\"import-settings-overwrite\"\n                android:summary=\"@string/import_settings_overwrite_summary\"\n                android:title=\"@string/import_settings_overwrite\" />\n\n            <Preference\n                android:key=\"import-settings-append\"\n                android:summary=\"@string/import_settings_append_summary\"\n                android:title=\"@string/import_settings_append\" />\n\n        </PreferenceCategory>\n\n    </PreferenceScreen>\n\n    <!--    <PreferenceScreen-->\n    <!--        android:key=\"ui-custom-apps\"-->\n    <!--        android:summary=\"Custom app icons and names\"-->\n    <!--        android:title=\"Customized apps\">-->\n\n    <!--        &lt;!&ndash; Custom Icons &ndash;&gt;-->\n    <!--        <PreferenceCategory-->\n    <!--            android:key=\"custom-icons-section\"-->\n    <!--            android:title=\"Icons\">-->\n    <!--            <MultiSelectListPreference-->\n    <!--                android:key=\"custom-icons-list\"/>-->\n    <!--        </PreferenceCategory>-->\n\n    <!--        &lt;!&ndash; Custom app name &ndash;&gt;-->\n    <!--        <PreferenceCategory-->\n    <!--            android:key=\"custom-names-section\"-->\n    <!--            android:title=\"Names\">-->\n    <!--        </PreferenceCategory>-->\n\n    <!--    </PreferenceScreen>-->\n\n    <!-- \"settings-theme\" ListPreference is only used if the ActionBar menu is unavailable. This also helps to backup this preference. -->\n    <ListPreference\n        android:defaultValue=\"default\"\n        android:entries=\"@array/settingsThemeEntries\"\n        android:entryValues=\"@array/settingsThemeValues\"\n        android:key=\"settings-theme\"\n        android:title=\"@string/settings_theme\" />\n\n    <rocks.tbog.tblauncher.preference.CustomDialogPreference\n        android:dialogLayout=\"@layout/pref_confirm\"\n        android:key=\"reset-default-launcher\"\n        android:title=\"@string/reset_default_launcher\" />\n\n    <rocks.tbog.tblauncher.preference.CustomDialogPreference\n        android:dialogLayout=\"@layout/pref_confirm\"\n        android:key=\"exit-app\"\n        android:title=\"@string/exit_the_app\" />\n\n    <rocks.tbog.tblauncher.preference.CustomDialogPreference\n        android:dialogLayout=\"@layout/pref_confirm\"\n        android:key=\"crash-app\"\n        android:title=\"\\u2620 \\uE525 \\uD83D\\uDD71\" />\n\n    <Preference\n        android:key=\"rate-app\"\n        android:title=\"@string/rate_the_app\">\n        <intent\n            android:action=\"android.intent.action.VIEW\"\n            android:data=\"market://details?id=rocks.tbog.tblauncher\" />\n    </Preference>\n\n    <Preference\n        android:key=\"privacy-policy\"\n        android:title=\"@string/privacy_policy\">\n        <intent\n            android:action=\"android.intent.action.VIEW\"\n            android:data=\"https://tbog.github.io/TBLauncher/Privacy-Policy\" />\n    </Preference>\n\n    <Preference\n        android:enabled=\"false\"\n        android:key=\"app-version\"\n        android:summary=\"@string/app_version_summary\"\n        android:title=\"@string/app_version\">\n        <intent\n            android:action=\"android.intent.action.VIEW\"\n            android:data=\"https://tbog.github.io/TBLauncher/\" />\n    </Preference>\n\n</androidx.preference.PreferenceScreen>"
  },
  {
    "path": "app/src/main/res/xml/search_pill_scene.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<MotionScene xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    app:defaultDuration=\"1000\">\n\n    <ConstraintSet android:id=\"@+id/base\">\n        <Constraint\n            android:id=\"@+id/menuButton\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"0dp\"\n            app:layout_constraintBottom_toBottomOf=\"@id/launcherButton\"\n            app:layout_constraintDimensionRatio=\"1:1\"\n            app:layout_constraintRight_toRightOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"@id/launcherButton\"\n            app:visibilityMode=\"ignore\">\n            <PropertySet\n                app:applyMotionScene=\"false\"\n                app:visibilityMode=\"ignore\" />\n        </Constraint>\n        <Constraint\n            android:id=\"@+id/clearButton\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"0dp\"\n            app:layout_constraintBottom_toBottomOf=\"@id/menuButton\"\n            app:layout_constraintEnd_toEndOf=\"@id/menuButton\"\n            app:layout_constraintStart_toStartOf=\"@id/menuButton\"\n            app:layout_constraintTop_toTopOf=\"@id/menuButton\"\n            app:visibilityMode=\"ignore\">\n            <PropertySet\n                app:applyMotionScene=\"false\"\n                app:visibilityMode=\"ignore\" />\n        </Constraint>\n    </ConstraintSet>\n\n    <ConstraintSet\n        android:id=\"@+id/searchPillExpanded\"\n        app:deriveConstraintsFrom=\"@id/base\">\n        <Constraint\n            android:id=\"@+id/launcherButton\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"0dp\"\n            android:adjustViewBounds=\"true\"\n            android:clickable=\"true\"\n            android:contentDescription=\"@string/cd_show_all_apps\"\n            android:focusable=\"true\"\n            android:scaleType=\"fitXY\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            app:layout_constraintDimensionRatio=\"1.5:1\"\n            app:layout_constraintLeft_toRightOf=\"@id/launcherSearch\"\n            app:layout_constraintRight_toLeftOf=\"@id/menuButton\"\n            app:layout_constraintTop_toTopOf=\"parent\" />\n        <Constraint\n            android:id=\"@+id/launcherTime\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"0dp\"\n            android:gravity=\"center\"\n            android:textAlignment=\"center\"\n            android:visibility=\"invisible\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            app:layout_constraintLeft_toLeftOf=\"@id/menuButton\"\n            app:layout_constraintRight_toRightOf=\"@id/menuButton\"\n            app:layout_constraintTop_toTopOf=\"parent\" />\n    </ConstraintSet>\n\n    <ConstraintSet\n        android:id=\"@+id/searchPillCollapsed\"\n        app:deriveConstraintsFrom=\"@id/base\">\n        <Constraint\n            android:id=\"@+id/launcherButton\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"0dp\"\n            android:adjustViewBounds=\"true\"\n            android:clickable=\"true\"\n            android:contentDescription=\"@string/cd_show_all_apps\"\n            android:focusable=\"true\"\n            android:scaleType=\"fitXY\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            app:layout_constraintDimensionRatio=\"1.5:1\"\n            app:layout_constraintLeft_toLeftOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\" />\n        <Constraint\n            android:id=\"@+id/launcherTime\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"0dp\"\n            android:gravity=\"center\"\n            android:textAlignment=\"center\"\n            android:visibility=\"visible\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            app:layout_constraintLeft_toRightOf=\"@id/launcherButton\"\n            app:layout_constraintRight_toLeftOf=\"@id/menuButton\"\n            app:layout_constraintTop_toTopOf=\"parent\" />\n    </ConstraintSet>\n\n    <Transition\n        app:constraintSetEnd=\"@+id/searchPillExpanded\"\n        app:constraintSetStart=\"@+id/searchPillCollapsed\">\n        <OnSwipe\n            app:dragDirection=\"dragRight\"\n            app:dragScale=\"2\"\n            app:onTouchUp=\"autoCompleteToEnd\"\n            app:touchAnchorId=\"@id/launcherButton\"\n            app:touchAnchorSide=\"left\" />\n    </Transition>\n\n</MotionScene>\n"
  },
  {
    "path": "build.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n    ext.kotlin_version = '2.0.0'\n    ext.multidex_version = '2.0.1'\n    ext.preference_version = '1.2.1'\n    ext.lifecycle_version = '2.8.0'\n    ext.acra_version = '5.11.3'\n\n    repositories {\n        mavenCentral()\n        google()\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:8.4.1'\n        classpath \"org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version\"\n        // see https://keepsafe.github.io/dexcount-gradle-plugin/ for how to use dex method count\n        classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:4.0.0'\n        classpath 'com.dipien:bye-bye-jetifier:1.2.2'\n\n        // NOTE: Do not place your application dependencies here; they belong\n        // in the individual module build.gradle files\n    }\n}\n\nallprojects {\n    repositories {\n        google()\n        maven { url \"https://jitpack.io\" }\n    }\n}\n\ntasks.register('clean', Delete) {\n    delete rootProject.layout.buildDirectory\n}\n\ntasks.withType(JavaCompile).configureEach {\n    options.compilerArgs += ['-Xlint:deprecation', '-Xlint:unchecked']\n}"
  },
  {
    "path": "fastlane/Appfile",
    "content": "json_key_file(\"~/Git/api-6639387416894723381-146876-dc4a54b09905.json\") # Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one\npackage_name(\"rocks.tbog.tblauncher\") # e.g. com.krausefx.app\n"
  },
  {
    "path": "fastlane/Fastfile",
    "content": "# This file contains the fastlane.tools configuration\n# You can find the documentation at https://docs.fastlane.tools\n#\n# For a list of all available actions, check out\n#\n#     https://docs.fastlane.tools/actions\n#\n# For a list of all available plugins, check out\n#\n#     https://docs.fastlane.tools/plugins/available-plugins\n#\n\n# Uncomment the line if you want fastlane to automatically update itself\n# update_fastlane\n\ndefault_platform(:android)\n\nplatform :android do\n  #desc \"Runs all the tests\"\n  #lane :test do\n  #  gradle(task: \"test\")\n  #end\n\n  #desc \"Run lint\"\n  #lane :lint do\n  #  gradle(task: \"lint\")\n  #end\n\n  desc \"Deploy a new Beta build to PlayStore\"\n  lane :beta do\n    # Read clangelog from file system\n    #   note the `..`, since fastlane runs in the _fastlane_ directory\n    #changelog = File.read(\"../Changelog.txt\")\n    \n    #gradle(task: \"clean assembleRelease\")\n    #gradle(task: 'assemble', build_type: 'Release')\n    \n    gradle(task: \"clean bundleRelease\")\n    upload_to_play_store(track: 'beta')\n    \n    #crashlytics\n    \n    # sh \"your_script.sh\"\n  end\n\n  desc \"Deploy a new version to the Google Play\"\n  lane :deploy do\n    gradle(task: \"clean assembleRelease\")\n    upload_to_play_store\n  end\nend\n"
  },
  {
    "path": "fastlane/README.md",
    "content": "fastlane documentation\n================\n# Installation\n\nMake sure you have the latest version of the Xcode command line tools installed:\n\n```\nxcode-select --install\n```\n\nInstall _fastlane_ using\n```\n[sudo] gem install fastlane -NV\n```\nor alternatively using `brew install fastlane`\n\n# Available Actions\n## Android\n### android beta\n```\nfastlane android beta\n```\nDeploy a new Beta build to PlayStore\n### android deploy\n```\nfastlane android deploy\n```\nDeploy a new version to the Google Play\n\n----\n\nThis README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run.\nMore information about fastlane can be found on [fastlane.tools](https://fastlane.tools).\nThe documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools).\n"
  },
  {
    "path": "fastlane/metadata/android/de-DE/changelogs/40.txt",
    "content": "* Aktualisiere Übersetzungen\n* Verbessern der Farbwähler-Größe\n"
  },
  {
    "path": "fastlane/metadata/android/de-DE/full_description.txt",
    "content": "Das ist der Launcher verwendet und entwickelt von\n██████████████████████▀█\n█─▄─▄─█▄─▄─▀█─▄▄─█─▄▄▄▄█\n███─████─▄─▀█─██─█─██▄─█\n▀▀▄▄▄▀▀▄▄▄▄▀▀▄▄▄▄▀▄▄▄▄▄▀\n\nWarum ich meinen eigenen Launcher entwickle:\n🔹Sauberes Hauptschirm um das Hintergrundbild zu genießen\n🔸Schneller Appzugriff durch suchS\n🔹Icon Pack kompatibel\n🔸Möglichkeit die Farben und das Verhalten anzupassen\n\nNeue Funktionen:\n⦾ Updaten der Appliste bei Änderungen\n⦿ Adaptive Icon-Formen\n⦾ Cachen von Icons für ein schnelleres darstellen\n⦿ Tags zum besseren organisieren von Apps\n⦾ Hefte Elemente zum filtern an\n⦿ Farbeintellungsvorschau\n⦾ Verknüpfung für Apps, Kontakte, etc.\n\nProgrammcode https://github.com/TBog/TBLauncher\nUrsprüngliche Idee und Code von https://play.google.com/store/apps/details?id=fr.neamar.kiss\n"
  },
  {
    "path": "fastlane/metadata/android/de-DE/short_description.txt",
    "content": "Geringer Speicherbedarf, Icon-Pack Unterstützung, Farben und Verhalten anpassbar\n"
  },
  {
    "path": "fastlane/metadata/android/de-DE/title.txt",
    "content": "TinyBit Launcher\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/31.txt",
    "content": "* fix Android 12 manifest error\n* add \"show un-tagged\" action in tag manager and tags menu\n* add option to choose contacts mime type\n* add simple and highlight theme generators\n* add dial (search) provider\n* allow custom icons for search results\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/32.txt",
    "content": "Please report any issues you encounter, anything and everything can go wrong because of version changes and minify\n* enable minify on release build (apk size should go down)\n* make Dial contact icon customizable\n* add grid layout for results\n* add storage icon picker\n* add dock and search bar independent height and text size preferences\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/33.txt",
    "content": "* new icon\n* new default theme\n* add gestures\n* fix crashes\n* add image picker for icons\n* add more UI settings\n* merge color and opacity settings\n* add settings migration (allow old backups that had separate color and opacity to import properly)\n* add settings activity to the activity stack\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/34.txt",
    "content": " * add search bar pill layout preference\n * fix keyboard open and close action\n * add dock position preference\n * fix popup background on Lollipop\n * fix edit tag item background\n * change switch preference (toggle) thumb and track for older Android\n * fix edit search engines dialog\n * fix manifest shortcuts appearing in results\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/35.txt",
    "content": "* add support for icon pack studio\n* move close keyboard action to the new listener (fix #276)\n* fix keyboard not showing when search pill expanded\n* fix Device Admin not opening window\n* add `Show «Widget» desktop center`\n* fix `Show «Search» desktop with keyboard`\n* fix Scroll in Result Lists (issue #285)\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/36.txt",
    "content": "* fix Android Q crash\n* see v6.13 changelog\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/37.txt",
    "content": "* add search bar expand/collapse animation toggle preference\n* ignore keyboard close event when dialog open\n* add dynamic calendar icon support\n* refactor `asyncSetEntryIcon`\n* ensure edit text is focused during pill animation\n* rename preference screens `UI` and `Features`\n* add `Options` screen to preferences\n* rename \"User interface\" to \"Theme\" (see issue #271)\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/38.txt",
    "content": "* added dock pages\n* allow for custom button icons\n* text shadow preferences\n* split query by words\n* fix keyboard related issues\n* change color chooser dialog internally\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/39.txt",
    "content": "* added app version in the settings\n* fix action mode bar background\n* fix live wallpaper drag animation crash\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/40.txt",
    "content": "* update translations\n* fix color chooser dialog dimensions\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/41.txt",
    "content": "* bump library versions\n* improve preference color preview\n* add and update translations\n* fix edit tags dialog\n* fix color picker dialog animation\n* customize widget picker\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/42.txt",
    "content": "* Bump gradle wrapper version from  7.6 to 8.3\n* Bump JDK from 11 to 17\n* Bump androidx.annotation:annotation from 1.5.0 to 1.6.0\n* Bump lifecycle_version from 2.5.1 to 2.6.1\n* Bump com.android.tools:desugar_jdk_libs from 1.1.8 to 2.0.3\n* Bump androidx.core:core-ktx from 1.9.0 to 1.10.0\n* Bump org.jetbrains.kotlin:kotlin-gradle-plugin from 1.7.22 to 1.8.21\n* Bump com.android.tools.build:gradle from 7.2.2 to 8.0.1\n* Bump com.google.android.material:material from 1.8.0 to 1.9.0\n* Bump androidx.preference:preference from 1.2.0 to 1.2.1\n* Bump androidx.core:core-ktx from 1.10.0 to 1.10.1\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/43.txt",
    "content": "* Fix search hint rename by @TBog in https://github.com/TBog/TBLauncher/pull/413\n* Bump org.jetbrains.kotlin:kotlin-gradle-plugin from 1.8.21 to 1.9.10 by @dependabot in https://github.com/TBog/TBLauncher/pull/400\n* Bump lifecycle_version from 2.6.1 to 2.6.2 by @dependabot in https://github.com/TBog/TBLauncher/pull/404\n* Bump androidx.core:core-ktx from 1.10.1 to 1.12.0 by @dependabot in https://github.com/TBog/TBLauncher/pull/405\n* Bump androidx.annotation:annotation from 1.6.0 to 1.7.0 by @dependabot in https://github.com/TBog/TBLauncher/pull/406\n* Bump com.android.tools.build:gradle from 8.1.1 to 8.1.2 by @dependabot in https://github.com/TBog/TBLauncher/pull/421\n* fix possible NPE in MatchInfo ResultRelevance compare by @TBog in https://github.com/TBog/TBLauncher/pull/427\n* ModProvider setting the custom icons twice by @TBog in https://github.com/TBog/TBLauncher/pull/428\n* Recycler adapter base class by @TBog in https://github.com/TBog/TBLauncher/pull/429\n* Icon pack problems by @TBog in https://github.com/TBog/TBLauncher/pull/407\n* Fix invisible widgets by @TBog in https://github.com/TBog/TBLauncher/pull/411\n* Merge devtest into master by @TBog in https://github.com/TBog/TBLauncher/pull/457\n* Translations update from Hosted Weblate by @weblate in https://github.com/TBog/TBLauncher/pull/447\n* Fix stall on launcher resume e48d451fe160a39984596ba7e04ded0d462716f4\n* Cherrypicked share text from https://github.com/Neamar/KISS/pull/2111\n* Fix Android 14 local broadcast in 54e7b2a09b7c3f4a3931c01dcdbefcf3df913f16"
  },
  {
    "path": "fastlane/metadata/android/en-US/full_description.txt",
    "content": "𝙏𝙝𝙞𝙨 𝙞𝙨 𝙩𝙝𝙚 𝙡𝙖𝙪𝙣𝙘𝙝𝙚𝙧 𝙪𝙨𝙚𝙙 𝙖𝙣𝙙 𝙙𝙚𝙫𝙚𝙡𝙤𝙥𝙚𝙙 𝙗𝙮\n██████████████████████▀█\n█─▄─▄─█▄─▄─▀█─▄▄─█─▄▄▄▄█\n███─████─▄─▀█─██─█─██▄─█\n▀▀▄▄▄▀▀▄▄▄▄▀▀▄▄▄▄▀▄▄▄▄▄▀\n\nMotives for developing my own launcher:\n🔹Clean main screen to enjoy the wallpaper\n🔸Fast access to apps by searching\n🔹Icon Pack compatible\n🔸Ability to customize colors and behaviors\n\nNew Features:\n⦾ Update app list when changes detected\n⦿ Adaptive icon shapes\n⦾ Cache icons for fast result display\n⦿ Tags to better organize apps\n⦾ Dock items to filter results\n⦿ Preference color preview\n⦾ Shortcuts for apps, contacts, etc.\n\nSource code https://github.com/TBog/TBLauncher\nThe initial idea and code came from https://play.google.com/store/apps/details?id=fr.neamar.kiss"
  },
  {
    "path": "fastlane/metadata/android/en-US/short_description.txt",
    "content": "Small memory footprint, icon pack support, color and behaviour customizable"
  },
  {
    "path": "fastlane/metadata/android/en-US/title.txt",
    "content": "TinyBit Launcher"
  },
  {
    "path": "fastlane/metadata/android/en-US/video.txt",
    "content": ""
  },
  {
    "path": "fastlane/metadata/android/fr/changelogs/31.txt",
    "content": "* Correction de l'erreur manifeste d'Android 12\n* Ajout de l'action \"Afficher les actions non marquées\" dans le gestionnaire de balises et le menu des balises.\n* ajout d'une option pour choisir le type de mime des contacts\n* ajout de générateurs de thèmes simples et surlignés\n* ajout d'un fournisseur de numérotation (recherche)\n* autoriser les icônes personnalisées pour les résultats de recherche\n"
  },
  {
    "path": "fastlane/metadata/android/fr/changelogs/32.txt",
    "content": "Veuillez rapporter tout problème que vous rencontrez, tout et n'importe quoi peut aller mal à cause des changements de version et de minify.\n* Activez minify sur la version release (la taille de l'apk devrait diminuer).\n* L'icône du contact Dial est personnalisable\n* Ajout d'une grille pour les résultats\n* Ajout d'un sélecteur d'icône de stockage\n* Ajout d'un dock et d'une barre de recherche indépendants de la hauteur et des préférences de taille de texte\n"
  },
  {
    "path": "fastlane/metadata/android/fr/changelogs/33.txt",
    "content": "* nouvelle icône\n* nouveau thème par défaut\n* ajout de gestes\n* Correction de pannes\n* ajout d'un sélecteur d'image pour les icônes\n* ajouter plus de paramètres d'interface utilisateur\n* fusion des paramètres de couleur et d'opacité\n* ajouter la migration des paramètres (permettre aux anciennes sauvegardes qui avaient une couleur et une opacité séparées d'être importées correctement)\n* ajouter l'activité des paramètres à la pile d'activités\n"
  },
  {
    "path": "fastlane/metadata/android/fr/changelogs/34.txt",
    "content": "* ajout d'une préférence pour la disposition des pilules de la barre de recherche\n * Correction de l'action d'ouverture et de fermeture du clavier\n * ajout d'une préférence pour la position du dock\n * Correction de l'arrière-plan des popups sur Lollipop\n * Correction de l'arrière-plan des éléments de la balise d'édition\n * modification de la préférence de commutation (basculer) pouce et piste pour les anciens Android\n * Correction de la boîte de dialogue d'édition des moteurs de recherche\n * Correction des raccourcis manifestes apparaissant dans les résultats\n"
  },
  {
    "path": "fastlane/metadata/android/fr/changelogs/35.txt",
    "content": "* Ajout du support pour le studio icon pack\n* déplacement de l'action de fermeture du clavier vers le nouvel écouteur (correction #276)\n* Correction du clavier qui ne s'affiche pas lorsque le pilier de recherche est étendu\n* Correction de la fenêtre de l'administrateur de périphérique qui ne s'ouvre pas\n* Ajout de l'option \"Afficher le centre du bureau \"Widget\"\".\n* Correction de l'option \"Afficher le bureau \"Recherche\" avec le clavier\".\n* Correction du défilement dans les listes de résultats (problème n° 285)\n"
  },
  {
    "path": "fastlane/metadata/android/fr/changelogs/36.txt",
    "content": "* Correction de la panne d'Android Q\n* voir le journal des modifications de la version 6.13\n"
  },
  {
    "path": "fastlane/metadata/android/fr/full_description.txt",
    "content": "Il s'agit du lanceur utilisé et développé par\n██████████████████████▀█\n█─▄─▄─█▄─▄─▀█─▄▄─█─▄▄▄▄█\n███─████─▄─▀█─██─█─██▄─█\n▀▀▄▄▄▀▀▄▄▄▄▀▀▄▄▄▄▀▄▄▄▄▄▀\n\nMotifs du développement de mon propre lanceur :\n🔹Propreté de l'écran principal pour profiter du fond d'écran.\n🔸Accès rapide aux applications par recherche\n🔹Compatibilité avec le Pack d'icônes\n🔸Possibilité de personnaliser les couleurs et les comportements.\n\nNouvelles fonctionnalités :\n⦾ Mise à jour de la liste des apps lorsque des changements sont détectés.\n⦿ Formes d'icônes adaptatives\n⦾ Cache des icônes pour un affichage rapide des résultats.\n⦿ Balises pour mieux organiser les applis.\n⦾ Éléments du dock pour filtrer les résultats\n⦿ Aperçu des couleurs des préférences\n⦾ Raccourcis pour les apps, les contacts, etc.\n\nCode source https://github.com/TBog/TBLauncher\nL'idée initiale et le code proviennent de https://play.google.com/store/apps/details?id=fr.neamar.kiss\n"
  },
  {
    "path": "fastlane/metadata/android/fr/short_description.txt",
    "content": "Faible empreinte mémoire, prise en charge des packs d'icônes, personnalisation des couleurs et du comportement\n"
  },
  {
    "path": "fastlane/metadata/android/fr/title.txt",
    "content": "Lanceur TinyBit\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/full_description.txt",
    "content": "Dette er oppstarteren brukt og utviklet av\n██████████████████████▀█\n█─▄─▄─█▄─▄─▀█─▄▄─█─▄▄▄▄█\n███─████─▄─▀█─██─█─██▄─█\n▀▀▄▄▄▀▀▄▄▄▄▀▀▄▄▄▄▀▄▄▄▄▄▀\n\nHvorfor utvikle nytt:\n🔹Ren hovedskjerm for å sette pris på skjermbildet\n🔸Rask tilgang til programmer med søk\n🔹Støtte for ikonpakker\n🔸Mulighet til å tilpasse farger og oppførsel\n\nNye funksjoner:\n⦾ Oppdater programliste når endringer oppdages\n⦿ Tilpassbare ikonformer\n⦾ Hurtiglagre ikoner for rask visning\n⦿ Etiketter for bedre organisering av programmer\n⦾ Hurtigliste over elementer for å filtrere resultater\n⦿ Forhåndsvisning av foretrukket farge\n⦾ Snarveier til programmer, kontakter, osv.\n\nKildekode https://github.com/TBog/TBLauncher\nOpprinnelig idé og kode fra https://f-droid.org/en/packages/fr.neamar.kiss/\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/short_description.txt",
    "content": "Lite minnebruk, støtte for ikonpakker, farger, og tilpassbar oppførsel\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/title.txt",
    "content": "TinyBit-oppstarter\n"
  },
  {
    "path": "fastlane/metadata/android/nb_NO-V26/title.txt",
    "content": "TinyBit Launcher\n"
  },
  {
    "path": "fastlane/metadata/android/pt-BR/changelogs/31.txt",
    "content": "* corrigir erro de manifesto do Android 12\n* adicione a ação \"mostrar não marcada\" no gerenciador de tags e no menu de tags\n* adicionar opção para escolher o tipo de mime dos contatos\n* adicionar geradores de temas simples e de destaque\n* adicionar provedor de discagem (pesquisa)\n* permitir ícones personalizados para resultados de pesquisa\n"
  },
  {
    "path": "fastlane/metadata/android/pt-BR/full_description.txt",
    "content": "𝙏𝙝𝙞𝙨 𝙞𝙨 𝙩𝙝𝙚 𝙡𝙖𝙪𝙣𝙘𝙝𝙚𝙧 𝙪𝙨𝙚𝙙 𝙡𝙤𝙥𝙚𝙙 𝙗𝙮\n██████████████████████▀█\n█─▄─▄─█▄─▄─▀█─▄▄─█─▄▄▄▄█\n███─████─▄─▀█─██─█─██▄─█\n▀▀▄▄▄▀▀▄▄▄▄▀▀▄▄▄▄▀▄▄▄▄▄▀\n\nMotivos para desenvolver meu próprio launcher:\n🔹Limpe a tela principal para aproveitar o papel de parede\n🔸Acesso rápido a aplicativos pesquisando\n🔹Compatível com pacote de ícones\n🔸Capacidade de personalizar cores e comportamentos\n\nNovas características:\n⦾ Atualize a lista de aplicativos quando forem detectadas alterações\n⦿ Formas de ícones adaptáveis\n⦾ Ícones de cache para exibição rápida de resultados\n⦿ Tags para organizar melhor os apps\n⦾ Encaixar itens para filtrar resultados\n⦿ Pré-visualização de cores de preferência\n⦾ Atalhos para aplicativos, contatos, etc.\n\nCódigo-fonte https://github.com/TBog/TBLauncher\nA ideia inicial e o código vieram de https://play.google.com/store/apps/details?id=fr.neamar.kiss\n"
  },
  {
    "path": "fastlane/metadata/android/pt-BR/short_description.txt",
    "content": "Ocupa pouco espaço de memória, suporte para pacote de ícones, cor e comportamento personalizáveis\n"
  },
  {
    "path": "fastlane/metadata/android/pt-BR/title.txt",
    "content": "Iniciador TinyBit\n"
  },
  {
    "path": "fastlane/metadata/android/pt-PT/changelogs/31.txt",
    "content": "* corrigir erro de manifesto do Android 12\n* adicionar a ação \"mostrar não marcada\" no gestor de tags e no menu de tags\n* adicionar opção para escolher o tipo de mime dos contactos\n* adicionar geradores de temas simples e de destaque\n* adicionar provedor de discagem (pesquisa)\n* permitir ícones personalizados para resultados de pesquisa\n"
  },
  {
    "path": "fastlane/metadata/android/pt-PT/changelogs/36.txt",
    "content": "* corrigir travar no Android Q\n* veja o changelog do v6.13\n"
  },
  {
    "path": "fastlane/metadata/android/pt-PT/changelogs/40.txt",
    "content": "* atualização de traduções\n* corrigir as dimensões da caixa de diálogo do escolhedor de cores\n"
  },
  {
    "path": "fastlane/metadata/android/pt-PT/short_description.txt",
    "content": "Ocupa pouco espaço de memória, suporta pacotes de ícones, cor e comportamento personalizáveis\n"
  },
  {
    "path": "fastlane/metadata/android/pt-PT/title.txt",
    "content": "Lançador tinyBit\n"
  },
  {
    "path": "fastlane/metadata/android/ro/title.txt",
    "content": "TinyBit Launcher\n"
  },
  {
    "path": "fastlane/metadata/android/tr/changelogs/36.txt",
    "content": "* Android Q çökmesini düzeltildi\n* v6.13 değişiklik günlüğüne bakın\n"
  },
  {
    "path": "fastlane/metadata/android/tr/changelogs/39.txt",
    "content": "* ayarlara uygulama sürümü eklendi\n* eylem modu çubuğu arka planını düzeltildi\n* canlı duvar kağıdı sürükleme animasyonu çökmesi düzeltildi\n"
  },
  {
    "path": "fastlane/metadata/android/tr/changelogs/40.txt",
    "content": "* çeviriler güncellendi\n* renk seçici iletişim kutusu boyutlarını düzeltildi\n"
  },
  {
    "path": "fastlane/metadata/android/tr/full_description.txt",
    "content": "𝘽𝙪 𝙗𝙖ş𝙡𝙖𝙩ı𝙘ı 𝙖ş𝙖ğı𝙙𝙖𝙠𝙞 𝙠𝙞ş𝙞 𝙩𝙖𝙧𝙖𝙛ı𝙣𝙙𝙖𝙣 𝙠𝙪𝙡𝙡𝙖𝙣ı𝙡ı𝙧 𝙫𝙚 𝙜𝙚𝙡𝙞ş𝙩𝙞𝙧𝙞𝙡𝙞𝙧\n██████████████████████▀█\n█─▄─▄─█▄─▄─▀█─▄▄─█─▄▄▄▄█\n███─████─▄─▀█─██─█─██▄─█\n▀▀▄▄▄▀▀▄▄▄▄▀▀▄▄▄▄▀▄▄▄▄▄▀\n\nKendi başlatıcımı geliştirme nedenlerim:\n🔹Duvar kağıdının keyfini çıkarmak için temiz ana ekran\n🔸Arama yaparak uygulamalara hızlı erişim\n🔹Simge Paketi ile uyumlu\n🔸Renkleri ve davranışları özelleştirme imkanı\n\nYeni Özellikler:\n⦾ Değişiklikler algılandığında uygulama listesini güncelle\n⦿ Uyarlanabilir simge şekilleri\n⦾ Sonuçları daha hızlı gösytermek için simgeleri ön belleğe al\n⦿ Uygulamaları daha iyi organize etmek için etiketler\n⦾ Sonuçları filtrelemek için öğeleri yerleştirin\n⦿ Tercih renk önizlemesi\n⦾ Uygulamalar, kişiler vb. için kısayollar.\n\nKaynak kodu https://github.com/TBog/TBLauncher\nİlk fikir ve kod https://play.google.com/store/apps/details?id=fr.neamar.kiss adresinden geldi.\n"
  },
  {
    "path": "fastlane/metadata/android/tr/short_description.txt",
    "content": "Küçük bellek alanı, simge paketi desteği, özelleştirilebilir renk ve davranış\n"
  },
  {
    "path": "fastlane/metadata/android/tr/title.txt",
    "content": "TinyBit Başlatıcı\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "#Sat Aug 19 17:52:24 EEST 2023\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-8.7-all.zip\ndistributionSha256Sum=194717442575a6f96e1c1befa2c30e9a4fc90f701d7aee33eb879b79e7ff05c0\n"
  },
  {
    "path": "gradle.properties",
    "content": "# Project-wide Gradle settings.\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx1536m\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\n# AndroidX package structure to make it clearer which packages are bundled with the\n# Android operating system, and which are packaged with your app's APK\n# https://developer.android.com/topic/libraries/support-library/androidx-rn\nandroid.useAndroidX=true\n# Automatically convert third-party libraries to use AndroidX\n# test by running task 'canISayByeByeJetifier' (see https://github.com/dipien/bye-bye-jetifier)\nandroid.enableJetifier=false\n\n# org.gradle.warning.mode=(all,none,summary)\norg.gradle.warning.mode=all\n"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env sh\n\n#\n# Copyright 2015 the original author or authors.\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#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\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\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\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='\"-Xmx64m\" \"-Xms64m\"'\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\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\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\" -a \"$nonstop\" = \"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 or MSYS, switch paths to Windows format before running java\nif [ \"$cygwin\" = \"true\" -o \"$msys\" = \"true\" ] ; 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=`expr $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# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=`save \"$@\"`\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\r\n@rem you may not use this file except in compliance with the License.\r\n@rem You may obtain a copy of the License at\r\n@rem\r\n@rem      https://www.apache.org/licenses/LICENSE-2.0\r\n@rem\r\n@rem Unless required by applicable law or agreed to in writing, software\r\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\r\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n@rem See the License for the specific language governing permissions and\r\n@rem limitations under the License.\r\n@rem\r\n\r\n@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:init\r\n@rem Get command-line arguments, handling Windows variants\r\n\r\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\r\n\r\n:win9xME_args\r\n@rem Slurp the command line arguments.\r\nset CMD_LINE_ARGS=\r\nset _SKIP=2\r\n\r\n:win9xME_args_slurp\r\nif \"x%~1\" == \"x\" goto execute\r\n\r\nset CMD_LINE_ARGS=%*\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "settings.gradle",
    "content": "rootProject.name='TBLauncher'\ninclude ':app'\n"
  }
]