[
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release CI\n\non:\n  push:\n    tags:\n      - '*'\n\njobs:\n  build-arm32:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n      - name: Build arm32 docker\n        run: docker build -t arm32 -f Dockerfile .\n      - name: Compile arm32 build\n        run: |\n          docker run --rm \\\n            -v /opt/dist:/opt/dist \\\n            -e DIST_DIR='/opt/dist' \\\n            -e RELEASE_TAG=\"$GITHUB_REF_NAME\" \\\n            -e NATIVE_ARCH=\"arm\" \\\n            -e ABI_ID=1 \\\n            -e ABI_TARGET=\"armeabi-v7a\" \\\n            -e KS_FILE=\"${{ secrets.KS_FILE }}\" \\\n            -e KS_PASSWORD=\"${{ secrets.KS_PASSWORD }}\" \\\n            -e KEY_ALIAS=\"${{ secrets.KEY_ALIAS }}\" \\\n            -e KEY_PASSWORD=\"${{ secrets.KEY_PASSWORD }}\" \\\n            arm32\n      - name: Upload arm32 artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: arm32-build\n          path: /opt/dist\n\n  build-arm64:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n      - name: Build arm64 docker\n        run: docker build -t arm64 -f Dockerfile .\n      - name: Compile arm64 build\n        run: |\n          docker run --rm \\\n            -v /opt/dist:/opt/dist \\\n            -e DIST_DIR='/opt/dist' \\\n            -e RELEASE_TAG=\"$GITHUB_REF_NAME\" \\\n            -e NATIVE_ARCH=\"arm64\" \\\n            -e ABI_ID=2 \\\n            -e ABI_TARGET=\"arm64-v8a\" \\\n            -e KS_FILE=\"${{ secrets.KS_FILE }}\" \\\n            -e KS_PASSWORD=\"${{ secrets.KS_PASSWORD }}\" \\\n            -e KEY_ALIAS=\"${{ secrets.KEY_ALIAS }}\" \\\n            -e KEY_PASSWORD=\"${{ secrets.KEY_PASSWORD }}\" \\\n            arm64\n      - name: Upload arm64 artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: arm64-build\n          path: /opt/dist\n\n  build-x86:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n      - name: Build x86 docker\n        run: docker build -t x86 -f Dockerfile .\n      - name: Compile x86 build\n        run: |\n          docker run --rm \\\n            -v /opt/dist:/opt/dist \\\n            -e DIST_DIR='/opt/dist' \\\n            -e RELEASE_TAG=\"$GITHUB_REF_NAME\" \\\n            -e NATIVE_ARCH=\"386\" \\\n            -e ABI_ID=3 \\\n            -e ABI_TARGET=\"x86\" \\\n            -e KS_FILE=\"${{ secrets.KS_FILE }}\" \\\n            -e KS_PASSWORD=\"${{ secrets.KS_PASSWORD }}\" \\\n            -e KEY_ALIAS=\"${{ secrets.KEY_ALIAS }}\" \\\n            -e KEY_PASSWORD=\"${{ secrets.KEY_PASSWORD }}\" \\\n            x86\n      - name: Upload x86 artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: x86-build\n          path: /opt/dist\n\n  build-amd64:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n      - name: Build amd64 docker\n        run: docker build -t amd64 -f Dockerfile .\n      - name: Compile amd64 build\n        run: |\n          docker run --rm \\\n            -v /opt/dist:/opt/dist \\\n            -e DIST_DIR='/opt/dist' \\\n            -e RELEASE_TAG=\"$GITHUB_REF_NAME\" \\\n            -e NATIVE_ARCH=\"amd64\" \\\n            -e ABI_ID=4 \\\n            -e ABI_TARGET=\"x86_64\" \\\n            -e KS_FILE=\"${{ secrets.KS_FILE }}\" \\\n            -e KS_PASSWORD=\"${{ secrets.KS_PASSWORD }}\" \\\n            -e KEY_ALIAS=\"${{ secrets.KEY_ALIAS }}\" \\\n            -e KEY_PASSWORD=\"${{ secrets.KEY_PASSWORD }}\" \\\n            amd64\n      - name: Upload amd64 artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: amd64-build\n          path: /opt/dist\n\n  publish:\n    runs-on: ubuntu-latest\n    needs:\n      - build-arm32\n      - build-arm64\n      - build-x86\n      - build-amd64\n    permissions:\n      contents: write\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n      - name: Download arm32 artifact\n        uses: actions/download-artifact@v4\n        with:\n          name: arm32-build\n          path: dist\n      - name: Download arm64 artifact\n        uses: actions/download-artifact@v4\n        with:\n          name: arm64-build\n          path: dist\n      - name: Download x86 artifact\n        uses: actions/download-artifact@v4\n        with:\n          name: x86-build\n          path: dist\n      - name: Download amd64 artifact\n        uses: actions/download-artifact@v4\n        with:\n          name: amd64-build\n          path: dist\n      - name: Set VERSION_CODE\n        run: |\n          ALL_VARIANTS=4\n          VERSION_CODE=$(cat \"$GITHUB_WORKSPACE/app/versionCode.txt\")\n          ((VERSION_CODE += ALL_VARIANTS))\n          echo \"VERSION_CODE=$VERSION_CODE\" >> $GITHUB_ENV\n      - name: Publish release\n        uses: softprops/action-gh-release@v1\n        with:\n          tag_name: ${{ github.ref_name }}\n          name: ${{ github.ref_name }}\n          prerelease: false\n          draft: false\n          files: \"dist/*.apk\"\n          body_path: ${{ github.workspace }}/metadata/en-US/changelogs/${{ env.VERSION_CODE }}.txt\n"
  },
  {
    "path": ".gitignore",
    "content": "*.iml\n.idea\n.kotlin\n.gradle\n/local.properties\n/.idea/caches\n/.idea/libraries\n/.idea/modules.xml\n/.idea/workspace.xml\n/.idea/navEditor.xml\n/.idea/assetWizardSettings.xml\n.DS_Store\n/build\n/captures\n.externalNativeBuild\n.cxx\nlocal.properties\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"app/src/main/jni/hev-socks5-tunnel\"]\n\tpath = app/src/main/jni/hev-socks5-tunnel\n\turl = https://github.com/heiher/hev-socks5-tunnel.git\n[submodule \"XrayCore/libXray\"]\n\tpath = XrayCore/libXray\n\turl = https://github.com/XTLS/libXray.git\n[submodule \"XrayCore/Xray-core\"]\n\tpath = XrayCore/Xray-core\n\turl = https://github.com/XTLS/Xray-core.git\n[submodule \"XrayHelper\"]\n\tpath = XrayHelper\n\turl = https://github.com/SaeedDev94/XrayHelper.git\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM debian:trixie\n\nENV LANG=C.UTF-8 \\\n    DEBIAN_FRONTEND=noninteractive\n\nCOPY build-xray.sh /build-xray.sh\n\nENTRYPOINT [\"/build-xray.sh\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2024 SaeedDev94\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Xray\n<img src=\"metadata/en-US/images/featureGraphic.png\" alt=\"App Cover\" height=\"150\" />  \nThis is a simple GUI client for <a href=\"https://github.com/XTLS/Xray-core\">XTLS/Xray-core</a>\n\n# Screenshots\n<img src=\"metadata/en-US/images/phoneScreenshots/screenshot-01-home.png\" alt=\"MainActivity\" height=\"666\" /><img src=\"metadata/en-US/images/phoneScreenshots/screenshot-02-assets.png\" alt=\"AssetsActivity\" height=\"666\" /><img src=\"metadata/en-US/images/phoneScreenshots/screenshot-03-settings-basic.png\" alt=\"SettingsActivity: Basic Tab\" height=\"666\" /><img src=\"metadata/en-US/images/phoneScreenshots/screenshot-04-settings-advanced.png\" alt=\"SettingsActivity: Advanced Tab\" height=\"666\" />\n\n# APK variants guide\n- arm32 => versionCode + 1\n- arm64 => versionCode + 2\n- x86 => versionCode + 3\n- amd64 => versionCode + 4\n\n# Download\n[![Release CI](https://github.com/SaeedDev94/Xray/actions/workflows/release.yml/badge.svg)](https://github.com/SaeedDev94/Xray/actions)  \n<a href=\"https://github.com/SaeedDev94/Xray/releases\"><img src=\"get-it-on-github.png\" alt=\"Get it on GitHub\" height=\"100\" /></a>\n<a href=\"https://f-droid.org/packages/io.github.saeeddev94.xray\"><img src=\"get-it-on-fdroid.png\" alt=\"Get it on F-Droid\" height=\"100\" /></a>\n"
  },
  {
    "path": "XrayCore/go.mod",
    "content": "module XrayCore\n\ngo 1.26.2\n\nreplace github.com/xtls/xray-core => ./Xray-core\n\nreplace github.com/xtls/libxray => ./libXray\n\nrequire (\n\tgithub.com/xtls/libxray v0.0.0-00010101000000-000000000000\n\tgithub.com/xtls/xray-core v1.260327.0\n)\n\nrequire (\n\tgithub.com/andybalholm/brotli v1.0.6 // indirect\n\tgithub.com/apernet/quic-go v0.59.1-0.20260330051153-c402ee641eb6 // indirect\n\tgithub.com/cloudflare/circl v1.6.3 // indirect\n\tgithub.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 // indirect\n\tgithub.com/google/btree v1.1.2 // indirect\n\tgithub.com/gorilla/websocket v1.5.3 // indirect\n\tgithub.com/juju/ratelimit v1.0.2 // indirect\n\tgithub.com/klauspost/compress v1.17.4 // indirect\n\tgithub.com/klauspost/cpuid/v2 v2.3.0 // indirect\n\tgithub.com/miekg/dns v1.1.72 // indirect\n\tgithub.com/pelletier/go-toml v1.9.5 // indirect\n\tgithub.com/pires/go-proxyproto v0.12.0 // indirect\n\tgithub.com/quic-go/qpack v0.6.0 // indirect\n\tgithub.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af // indirect\n\tgithub.com/sagernet/sing v0.5.1 // indirect\n\tgithub.com/sagernet/sing-shadowsocks v0.2.7 // indirect\n\tgithub.com/vishvananda/netlink v1.3.1 // indirect\n\tgithub.com/vishvananda/netns v0.0.5 // indirect\n\tgithub.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f // indirect\n\tgo4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect\n\tgolang.org/x/crypto v0.50.0 // indirect\n\tgolang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect\n\tgolang.org/x/mobile v0.0.0-20260410095206-2cfb76559b7b // indirect\n\tgolang.org/x/mod v0.35.0 // indirect\n\tgolang.org/x/net v0.53.0 // indirect\n\tgolang.org/x/sync v0.20.0 // indirect\n\tgolang.org/x/sys v0.43.0 // indirect\n\tgolang.org/x/text v0.36.0 // indirect\n\tgolang.org/x/time v0.12.0 // indirect\n\tgolang.org/x/tools v0.44.0 // indirect\n\tgolang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect\n\tgolang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb // indirect\n\tgolang.zx2c4.com/wireguard/windows v0.6.1 // indirect\n\tgoogle.golang.org/genproto v0.0.0-20260414002931-afd174a4e478 // indirect\n\tgoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d // indirect\n\tgoogle.golang.org/grpc v1.80.0 // indirect\n\tgoogle.golang.org/protobuf v1.36.11 // indirect\n\tgopkg.in/yaml.v2 v2.4.0 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n\tgvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 // indirect\n\tlukechampine.com/blake3 v1.4.1 // indirect\n)\n"
  },
  {
    "path": "XrayCore/go.sum",
    "content": "github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=\ngithub.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=\ngithub.com/apernet/quic-go v0.59.1-0.20260330051153-c402ee641eb6 h1:cbF95uMsQwCwAzH2i8+2lNO2TReoELLuqeeMfyBjFbY=\ngithub.com/apernet/quic-go v0.59.1-0.20260330051153-c402ee641eb6/go.mod h1:Npbg8qBtAZlsAB3FWmqwlVh5jtVG6a4DlYsOylUpvzA=\ngithub.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=\ngithub.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=\ngithub.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=\ngithub.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=\ngithub.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=\ngithub.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=\ngithub.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=\ngithub.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=\ngithub.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=\ngithub.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=\ngithub.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=\ngithub.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=\ngithub.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=\ngithub.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI=\ngithub.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=\ngithub.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=\ngithub.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=\ngithub.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=\ngithub.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=\ngithub.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=\ngithub.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=\ngithub.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=\ngithub.com/pires/go-proxyproto v0.12.0 h1:TTCxD66dU898tahivkqc3hoceZp7P44FnorWyo9d5vM=\ngithub.com/pires/go-proxyproto v0.12.0/go.mod h1:qUvfqUMEoX7T8g0q7TQLDnhMjdTrxnG0hvpMn+7ePNI=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=\ngithub.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=\ngithub.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af h1:er2acxbi3N1nvEq6HXHUAR1nTWEJmQfqiGR8EVT9rfs=\ngithub.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=\ngithub.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=\ngithub.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=\ngithub.com/sagernet/sing v0.5.1 h1:mhL/MZVq0TjuvHcpYcFtmSD1BFOxZ/+8ofbNZcg1k1Y=\ngithub.com/sagernet/sing v0.5.1/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=\ngithub.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=\ngithub.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngithub.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=\ngithub.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=\ngithub.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=\ngithub.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=\ngithub.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f h1:iy2JRioxmUpoJ3SzbFPyTxHZMbR/rSHP7dOOgYaq1O8=\ngithub.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f/go.mod h1:DsJblcWDGt76+FVqBVwbwRhxyyNJsGV48gJLch0OOWI=\ngo.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=\ngo.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=\ngo.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=\ngo.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=\ngo.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=\ngo.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=\ngo.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=\ngo.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=\ngo.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=\ngo.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=\ngo.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=\ngo.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=\ngo.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=\ngo.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=\ngo4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=\ngo4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=\ngolang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=\ngolang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=\ngolang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=\ngolang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=\ngolang.org/x/mobile v0.0.0-20260410095206-2cfb76559b7b h1:Qt2eaXcZ8x20iAcoZ6AceeMMtnjuPHvC51KRCH1DKSQ=\ngolang.org/x/mobile v0.0.0-20260410095206-2cfb76559b7b/go.mod h1:5Fu78lew5ucMXt8w2KYcwvxu2rkC/liHzUvaoiI+H/M=\ngolang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=\ngolang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=\ngolang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=\ngolang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=\ngolang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=\ngolang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=\ngolang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=\ngolang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=\ngolang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=\ngolang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=\ngolang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=\ngolang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=\ngolang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=\ngolang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=\ngolang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=\ngolang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=\ngolang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=\ngolang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=\ngolang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=\ngolang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=\ngolang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A=\ngolang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=\ngolang.zx2c4.com/wireguard/windows v0.6.1 h1:XMaKojH1Hs/raMrmnir4n35nTvzvWj7NmSYzHn2F4qU=\ngolang.zx2c4.com/wireguard/windows v0.6.1/go.mod h1:04aqInu5GYuTFvMuDw/rKBAF7mHrltW/3rekpfbbZDM=\ngonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=\ngonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=\ngoogle.golang.org/genproto v0.0.0-20260414002931-afd174a4e478 h1:aLsVTW0lZ8+IY5u/ERjZSCvAmhuR7slKzyha3YikDNA=\ngoogle.golang.org/genproto v0.0.0-20260414002931-afd174a4e478/go.mod h1:YJAzKjfHIUHb9T+bfu8L7mthAp7VVXQBUs1PLdBWS7M=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 h1:sNrWoksmOyF5bvJUcnmbeAmQi8baNhqg5IWaI3llQqU=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d h1:wT2n40TBqFY6wiwazVK9/iTWbsQrgk5ZfCSVFLO9LQA=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=\ngoogle.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=\ngoogle.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=\ngoogle.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=\ngoogle.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 h1:Lk6hARj5UPY47dBep70OD/TIMwikJ5fGUGX0Rm3Xigk=\ngvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0/go.mod h1:QkHjoMIBaYtpVufgwv3keYAbln78mBoCuShZrPrer1Q=\nlukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=\nlukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo=\n"
  },
  {
    "path": "XrayCore/lib/core.go",
    "content": "package lib\n\nimport (\n\t\"github.com/xtls/xray-core/common/cmdarg\"\n\t\"github.com/xtls/xray-core/core\"\n\t_ \"github.com/xtls/xray-core/main/distro/all\"\n)\n\nvar coreServer *core.Instance\n\nfunc Server(config string) (*core.Instance, error) {\n\tfile := cmdarg.Arg{config}\n\tjson, err := core.LoadConfig(\"json\", file)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tserver, err := core.New(json)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn server, nil\n}\n\nfunc Start(dir string, config string) (err error) {\n\tSetEnv(dir)\n\tcoreServer, err = Server(config)\n\tif err != nil {\n\t\treturn\n\t}\n\tif err = coreServer.Start(); err != nil {\n\t\treturn\n\t}\n\treturn nil\n}\n\nfunc Stop() error {\n\tif coreServer != nil {\n\t\terr := coreServer.Close()\n\t\tcoreServer = nil\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc Version() string {\n\treturn core.Version()\n}\n"
  },
  {
    "path": "XrayCore/lib/env.go",
    "content": "package lib\n\nimport \"os\"\n\nfunc SetEnv(dir string) {\n\tos.Setenv(\"xray.location.asset\", dir)\n}\n"
  },
  {
    "path": "XrayCore/lib/error.go",
    "content": "package lib\n\nfunc WrapError(err error) string {\n\tif err != nil {\n\t\treturn err.Error()\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "XrayCore/lib/test.go",
    "content": "package lib\n\nfunc Test(dir string, config string) error {\n\tSetEnv(dir)\n\t_, err := Server(config)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "XrayCore/main.go",
    "content": "package XrayCore\n\nimport (\n\t\"github.com/xtls/xray-core/infra/conf\"\n\t\"github.com/xtls/libxray/nodep\"\n\t\"github.com/xtls/libxray/share\"\n\t\"XrayCore/lib\"\n)\n\nfunc Test(dir string, config string) string {\n\terr := lib.Test(dir, config)\n\treturn lib.WrapError(err)\n}\n\nfunc Start(dir string, config string) string {\n\terr := lib.Start(dir, config)\n\treturn lib.WrapError(err)\n}\n\nfunc Stop() string {\n\terr := lib.Stop()\n\treturn lib.WrapError(err)\n}\n\nfunc Version() string {\n\treturn lib.Version()\n}\n\nfunc Json(link string) string {\n\tvar response nodep.CallResponse[*conf.Config]\n\txrayJson, err := share.ConvertShareLinksToXrayJson(link)\n\treturn response.EncodeToBase64(xrayJson, err)\n}\n"
  },
  {
    "path": "app/.gitignore",
    "content": "/build\n/release\n"
  },
  {
    "path": "app/build.gradle.kts",
    "content": "plugins {\n    alias(libs.plugins.android.application)\n    alias(libs.plugins.kotlin.parcelize)\n    alias(libs.plugins.google.ksp)\n}\n\nval abiId: String by project\nval abiTarget: String by project\n\nfun calcVersionCode(): Int {\n    val versionCodeFile = file(\"versionCode.txt\")\n    val versionCode = versionCodeFile.readText().trim().toInt()\n    return versionCode + abiId.toInt()\n}\n\nandroid {\n    namespace = \"io.github.saeeddev94.xray\"\n    compileSdk = 36\n\n    defaultConfig {\n        applicationId = \"io.github.saeeddev94.xray\"\n        minSdk = 26\n        targetSdk = 36\n        versionCode = calcVersionCode()\n        versionName = \"12.2.0\"\n    }\n\n    buildFeatures {\n        buildConfig = true\n        viewBinding = true\n    }\n\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_21\n        targetCompatibility = JavaVersion.VERSION_21\n    }\n\n    externalNativeBuild {\n        ndkVersion = \"28.2.13676358\"\n        ndkBuild {\n            path = file(\"src/main/jni/Android.mk\")\n        }\n    }\n\n    splits {\n        abi {\n            isEnable = true\n            isUniversalApk = false\n            reset()\n            //noinspection ChromeOsAbiSupport\n            include(*abiTarget.split(\",\").toTypedArray())\n        }\n    }\n\n    dependenciesInfo {\n        includeInApk = false\n        includeInBundle = false\n    }\n\n    signingConfigs {\n        create(\"release\") {\n            storeFile = file(\"/tmp/xray.jks\")\n            storePassword = System.getenv(\"KS_PASSWORD\")\n            keyAlias = System.getenv(\"KEY_ALIAS\")\n            keyPassword = System.getenv(\"KEY_PASSWORD\")\n        }\n    }\n\n    buildTypes {\n        release {\n            signingConfig = signingConfigs.getByName(\"release\")\n        }\n    }\n}\n\nkotlin {\n    compilerOptions {\n        languageVersion = org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_3\n        jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21\n    }\n}\n\ndependencies {\n    implementation(fileTree(mapOf(\"dir\" to \"libs\", \"include\" to listOf(\"*.aar\", \"*.jar\"))))\n    implementation(libs.androidx.activity.ktx)\n    implementation(libs.androidx.core.ktx)\n    implementation(libs.androidx.appcompat)\n    implementation(libs.androidx.lifecycle.viewmodel.ktx)\n    implementation(libs.androidx.room.ktx)\n    implementation(libs.androidx.room.runtime)\n    ksp(libs.androidx.room.compiler)\n    implementation(libs.blacksquircle.ui.editorkit)\n    implementation(libs.blacksquircle.ui.language.json)\n    implementation(libs.google.material)\n    implementation(libs.topjohnwu.libsu.core)\n    implementation(libs.yuriy.budiyev.code.scanner)\n}\n"
  },
  {
    "path": "app/libs/.gitignore",
    "content": "*.aar\n*.jar\n"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <uses-feature android:name=\"android.software.leanback\" android:required=\"false\" />\n    <uses-feature android:name=\"android.hardware.touchscreen\" android:required=\"false\" />\n    <uses-feature android:name=\"android.hardware.camera\" android:required=\"false\" />\n\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n    <uses-permission android:name=\"android.permission.RECEIVE_BOOT_COMPLETED\" />\n    <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />\n    <uses-permission android:name=\"android.permission.CAMERA\" />\n    <uses-permission android:name=\"android.permission.POST_NOTIFICATIONS\" />\n    <uses-permission android:name=\"android.permission.FOREGROUND_SERVICE\" />\n    <uses-permission android:name=\"android.permission.FOREGROUND_SERVICE_SPECIAL_USE\"\n        android:minSdkVersion=\"34\" />\n    <uses-permission android:name=\"android.permission.QUERY_ALL_PACKAGES\"\n        tools:ignore=\"QueryAllPackagesPermission\" />\n\n    <application\n        android:name=\".Xray\"\n        android:allowBackup=\"true\"\n        android:icon=\"@drawable/ic_launcher\"\n        android:banner=\"@mipmap/banner\"\n        android:networkSecurityConfig=\"@xml/network_security_config\"\n        android:label=\"@string/appName\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/AppTheme\">\n        <activity\n            android:name=\".activity.MainActivity\"\n            android:exported=\"true\"\n            android:theme=\"@style/AppTheme\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n                <action android:name=\"android.service.quicksettings.action.QS_TILE_PREFERENCES\" />\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n                <category android:name=\"android.intent.category.LEANBACK_LAUNCHER\" />\n            </intent-filter>\n            <intent-filter android:label=\"@string/appName\">\n                <action android:name=\"android.intent.action.VIEW\" />\n                <category android:name=\"android.intent.category.DEFAULT\" />\n                <category android:name=\"android.intent.category.BROWSABLE\" />\n                <data\n                    android:scheme=\"xray\"\n                    android:host=\"import-profile\" />\n            </intent-filter>\n        </activity>\n        <activity\n            android:name=\".activity.ProfileActivity\"\n            android:parentActivityName=\".activity.MainActivity\"\n            android:label=\"@string/openJson\"\n            android:exported=\"true\">\n            <intent-filter tools:ignore=\"AppLinkUrlError\">\n                <action android:name=\"android.intent.action.VIEW\" />\n                <category android:name=\"android.intent.category.DEFAULT\" />\n                <data android:mimeType=\"application/json\" />\n            </intent-filter>\n        </activity>\n        <activity\n            android:name=\".activity.LinksManagerActivity\"\n            android:theme=\"@style/TransparentTheme\" />\n        <activity\n            android:name=\".activity.ScannerActivity\"\n            android:parentActivityName=\".activity.MainActivity\" />\n        <activity\n            android:name=\".activity.AssetsActivity\"\n            android:parentActivityName=\".activity.MainActivity\" />\n        <activity\n            android:name=\".activity.LinksActivity\"\n            android:parentActivityName=\".activity.MainActivity\" />\n        <activity\n            android:name=\".activity.LogsActivity\"\n            android:parentActivityName=\".activity.MainActivity\" />\n        <activity\n            android:name=\".activity.AppsRoutingActivity\"\n            android:parentActivityName=\".activity.MainActivity\" />\n        <activity\n            android:name=\".activity.ConfigsActivity\"\n            android:parentActivityName=\".activity.MainActivity\" />\n        <activity\n            android:name=\".activity.SettingsActivity\"\n            android:parentActivityName=\".activity.MainActivity\" />\n        <service\n            android:name=\".service.TProxyService\"\n            android:exported=\"true\"\n            android:foregroundServiceType=\"specialUse\"\n            android:permission=\"android.permission.BIND_VPN_SERVICE\">\n            <intent-filter>\n                <action android:name=\"android.net.VpnService\" />\n            </intent-filter>\n            <property\n                android:name=\"android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE\"\n                android:value=\"Xray VPN Service\" />\n        </service>\n        <service\n            android:name=\".service.VpnTileService\"\n            android:exported=\"true\"\n            android:label=\"@string/appName\"\n            android:icon=\"@drawable/vpn_key\"\n            android:permission=\"android.permission.BIND_QUICK_SETTINGS_TILE\">\n            <intent-filter>\n                <action android:name=\"android.service.quicksettings.action.QS_TILE\" />\n            </intent-filter>\n            <meta-data\n                android:name=\"android.service.quicksettings.ACTIVE_TILE\"\n                android:value=\"true\" />\n            <meta-data\n                android:name=\"android.service.quicksettings.TOGGLEABLE_TILE\"\n                android:value=\"true\" />\n        </service>\n        <receiver\n            android:name=\".receiver.VpnActionReceiver\"\n            android:exported=\"false\">\n            <intent-filter>\n                <action android:name=\"io.github.saeeddev94.xray.VpnStart\" />\n                <action android:name=\"io.github.saeeddev94.xray.VpnStop\" />\n                <action android:name=\"io.github.saeeddev94.xray.NewConfig\" />\n            </intent-filter>\n        </receiver>\n        <receiver\n            android:name=\".receiver.BootReceiver\"\n            android:exported=\"true\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.BOOT_COMPLETED\" />\n            </intent-filter>\n        </receiver>\n    </application>\n</manifest>\n"
  },
  {
    "path": "app/src/main/assets/.gitignore",
    "content": "xrayhelper\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/Settings.kt",
    "content": "package io.github.saeeddev94.xray\n\nimport android.content.Context\nimport androidx.core.content.edit\nimport java.io.File\n\nclass Settings(private val context: Context) {\n\n    private val sharedPreferences = context.getSharedPreferences(\"settings\", Context.MODE_PRIVATE)\n\n    /** Active Link ID */\n    var selectedLink: Long\n        get() = sharedPreferences.getLong(\"selectedLink\", 0L)\n        set(value) = sharedPreferences.edit { putLong(\"selectedLink\", value) }\n\n    /** Active Profile ID */\n    var selectedProfile: Long\n        get() = sharedPreferences.getLong(\"selectedProfile\", 0L)\n        set(value) = sharedPreferences.edit { putLong(\"selectedProfile\", value) }\n\n    /** The time of last refresh */\n    var lastRefreshLinks: Long\n        get() = sharedPreferences.getLong(\"lastRefreshLinks\", 0L)\n        set(value) = sharedPreferences.edit { putLong(\"lastRefreshLinks\", value) }\n\n    /** XrayHelper Version Code */\n    var xrayHelperVersionCode: Int\n        get() = sharedPreferences.getInt(\"xrayHelperVersionCode\", 0)\n        set(value) = sharedPreferences.edit { putInt(\"xrayHelperVersionCode\", value) }\n\n    /**\n     * Apps Routing\n     * Mode: true -> exclude, false -> include\n     * Default: exclude\n     */\n    var appsRoutingMode: Boolean\n        get() = sharedPreferences.getBoolean(\"appsRoutingMode\", true)\n        set(value) = sharedPreferences.edit { putBoolean(\"appsRoutingMode\", value) }\n    var appsRouting: String\n        get() = sharedPreferences.getString(\"excludedApps\", \"\")!!\n        set(value) = sharedPreferences.edit { putString(\"excludedApps\", value) }\n\n    /** Tun Routes */\n    var tunRoutes: Set<String>\n        get() = sharedPreferences.getStringSet(\n            \"tunRoutes\",\n            context.resources.getStringArray(R.array.publicIpAddresses).toSet()\n        )!!\n        set(value) = sharedPreferences.edit { putStringSet(\"tunRoutes\", value) }\n\n    /** Basic */\n    var socksAddress: String\n        get() = sharedPreferences.getString(\"socksAddress\", \"127.0.0.1\")!!\n        set(value) = sharedPreferences.edit { putString(\"socksAddress\", value) }\n    var socksPort: String\n        get() = sharedPreferences.getString(\"socksPort\", \"10808\")!!\n        set(value) = sharedPreferences.edit { putString(\"socksPort\", value) }\n    var socksUsername: String\n        get() = sharedPreferences.getString(\"socksUsername\", \"\")!!\n        set(value) = sharedPreferences.edit { putString(\"socksUsername\", value) }\n    var socksPassword: String\n        get() = sharedPreferences.getString(\"socksPassword\", \"\")!!\n        set(value) = sharedPreferences.edit { putString(\"socksPassword\", value) }\n    var geoIpAddress: String\n        get() = sharedPreferences.getString(\n            \"geoIpAddress\",\n            \"https://github.com/v2fly/geoip/releases/latest/download/geoip.dat\"\n        )!!\n        set(value) = sharedPreferences.edit { putString(\"geoIpAddress\", value) }\n    var geoSiteAddress: String\n        get() = sharedPreferences.getString(\n            \"geoSiteAddress\",\n            \"https://github.com/v2fly/domain-list-community/releases/latest/download/dlc.dat\"\n        )!!\n        set(value) = sharedPreferences.edit { putString(\"geoSiteAddress\", value) }\n    var pingAddress: String\n        get() = sharedPreferences.getString(\"pingAddress\", \"https://www.google.com\")!!\n        set(value) = sharedPreferences.edit { putString(\"pingAddress\", value) }\n    var pingTimeout: Int\n        get() = sharedPreferences.getInt(\"pingTimeout\", 5)\n        set(value) = sharedPreferences.edit { putInt(\"pingTimeout\", value) }\n    var refreshLinksInterval: Int\n        get() = sharedPreferences.getInt(\"refreshLinksInterval\", 60)\n        set(value) = sharedPreferences.edit { putInt(\"refreshLinksInterval\", value) }\n    var bypassLan: Boolean\n        get() = sharedPreferences.getBoolean(\"bypassLan\", true)\n        set(value) = sharedPreferences.edit { putBoolean(\"bypassLan\", value) }\n    var enableIpV6: Boolean\n        get() = sharedPreferences.getBoolean(\"enableIpV6\", true)\n        set(value) = sharedPreferences.edit { putBoolean(\"enableIpV6\", value) }\n    var socksUdp: Boolean\n        get() = sharedPreferences.getBoolean(\"socksUdp\", true)\n        set(value) = sharedPreferences.edit { putBoolean(\"socksUdp\", value) }\n    var tun2socks: Boolean\n        get() = sharedPreferences.getBoolean(\"tun2socks\", true)\n        set(value) = sharedPreferences.edit { putBoolean(\"tun2socks\", value) }\n    var bootAutoStart: Boolean\n        get() = sharedPreferences.getBoolean(\"bootAutoStart\", false)\n        set(value) = sharedPreferences.edit { putBoolean(\"bootAutoStart\", value) }\n    var refreshLinksOnOpen: Boolean\n        get() = sharedPreferences.getBoolean(\"refreshLinksOnOpen\", false)\n        set(value) = sharedPreferences.edit { putBoolean(\"refreshLinksOnOpen\", value) }\n\n    /** Advanced */\n    var primaryDns: String\n        get() = sharedPreferences.getString(\"primaryDns\", \"1.1.1.1\")!!\n        set(value) = sharedPreferences.edit { putString(\"primaryDns\", value) }\n    var secondaryDns: String\n        get() = sharedPreferences.getString(\"secondaryDns\", \"1.0.0.1\")!!\n        set(value) = sharedPreferences.edit { putString(\"secondaryDns\", value) }\n    var primaryDnsV6: String\n        get() = sharedPreferences.getString(\"primaryDnsV6\", \"2606:4700:4700::1111\")!!\n        set(value) = sharedPreferences.edit { putString(\"primaryDnsV6\", value) }\n    var secondaryDnsV6: String\n        get() = sharedPreferences.getString(\"secondaryDnsV6\", \"2606:4700:4700::1001\")!!\n        set(value) = sharedPreferences.edit { putString(\"secondaryDnsV6\", value) }\n    var tunName: String\n        get() = sharedPreferences.getString(\"tunName\", \"tun0\")!!\n        set(value) = sharedPreferences.edit { putString(\"tunName\", value) }\n    var tunMtu: Int\n        get() = sharedPreferences.getInt(\"tunMtu\", 8500)\n        set(value) = sharedPreferences.edit { putInt(\"tunMtu\", value) }\n    var tunAddress: String\n        get() = sharedPreferences.getString(\"tunAddress\", \"10.10.10.10\")!!\n        set(value) = sharedPreferences.edit { putString(\"tunAddress\", value) }\n    var tunPrefix: Int\n        get() = sharedPreferences.getInt(\"tunPrefix\", 32)\n        set(value) = sharedPreferences.edit { putInt(\"tunPrefix\", value) }\n    var tunAddressV6: String\n        get() = sharedPreferences.getString(\"tunAddressV6\", \"fc00::1\")!!\n        set(value) = sharedPreferences.edit { putString(\"tunAddressV6\", value) }\n    var tunPrefixV6: Int\n        get() = sharedPreferences.getInt(\"tunPrefixV6\", 128)\n        set(value) = sharedPreferences.edit { putInt(\"tunPrefixV6\", value) }\n    var hotspotInterface\n        get() = sharedPreferences.getString(\"hotspotInterface\", \"wlan2\")!!\n        set(value) = sharedPreferences.edit { putString(\"hotspotInterface\", value) }\n    var tetheringInterface\n        get() = sharedPreferences.getString(\"tetheringInterface\", \"rndis0\")!!\n        set(value) = sharedPreferences.edit { putString(\"tetheringInterface\", value) }\n    var tproxyAddress: String\n        get() = sharedPreferences.getString(\"tproxyAddress\", \"127.0.0.1\")!!\n        set(value) = sharedPreferences.edit { putString(\"tproxyAddress\", value) }\n    var tproxyPort: Int\n        get() = sharedPreferences.getInt(\"tproxyPort\", 10888)\n        set(value) = sharedPreferences.edit { putInt(\"tproxyPort\", value) }\n    var tproxyBypassWiFi: Set<String>\n        get() = sharedPreferences.getStringSet(\"tproxyBypassWiFi\", mutableSetOf<String>())!!\n        set(value) = sharedPreferences.edit { putStringSet(\"tproxyBypassWiFi\", value) }\n    var tproxyAutoConnect: Boolean\n        get() = sharedPreferences.getBoolean(\"tproxyAutoConnect\", false)\n        set(value) = sharedPreferences.edit { putBoolean(\"tproxyAutoConnect\", value) }\n    var tproxyHotspot: Boolean\n        get() = sharedPreferences.getBoolean(\"tproxyHotspot\", false)\n        set(value) = sharedPreferences.edit { putBoolean(\"tproxyHotspot\", value) }\n    var tproxyTethering: Boolean\n        get() = sharedPreferences.getBoolean(\"tproxyTethering\", false)\n        set(value) = sharedPreferences.edit { putBoolean(\"tproxyTethering\", value) }\n    var transparentProxy: Boolean\n        get() = sharedPreferences.getBoolean(\"transparentProxy\", false)\n        set(value) = sharedPreferences.edit { putBoolean(\"transparentProxy\", value) }\n\n    fun baseDir(): File = context.filesDir\n    fun xrayCoreFile(): File = File(baseDir(), \"xray\")\n    fun xrayHelperFile(): File = File(baseDir(), \"xrayhelper\")\n    fun testConfig(): File = File(baseDir(), \"test.json\")\n    fun xrayConfig(): File = File(baseDir(), \"config.json\")\n    fun tun2socksConfig(): File = File(baseDir(), \"tun2socks.yml\")\n    fun xrayHelperConfig(): File = File(baseDir(), \"config.yml\")\n    fun xrayCorePid(): File = File(baseDir(), \"core.pid\")\n    fun networkMonitorPid(): File = File(baseDir(), \"monitor.pid\")\n    fun networkMonitorScript(): File = File(baseDir(), \"monitor.sh\")\n    fun xrayCoreLogs(): File = File(baseDir(), \"error.log\")\n}\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/Xray.kt",
    "content": "package io.github.saeeddev94.xray\n\nimport android.app.Application\nimport io.github.saeeddev94.xray.database.XrayDatabase\nimport io.github.saeeddev94.xray.repository.ConfigRepository\nimport io.github.saeeddev94.xray.repository.LinkRepository\nimport io.github.saeeddev94.xray.repository.ProfileRepository\n\nclass Xray : Application() {\n\n    private val xrayDatabase by lazy { XrayDatabase.ref(this) }\n    val configRepository by lazy { ConfigRepository(xrayDatabase.configDao()) }\n    val linkRepository by lazy { LinkRepository(xrayDatabase.linkDao()) }\n    val profileRepository by lazy { ProfileRepository(xrayDatabase.profileDao()) }\n}\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/activity/AppsRoutingActivity.kt",
    "content": "package io.github.saeeddev94.xray.activity\n\nimport android.Manifest\nimport android.annotation.SuppressLint\nimport android.content.pm.PackageManager\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.view.View\nimport android.widget.ImageView\nimport android.widget.Toast\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.appcompat.widget.SearchView\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport io.github.saeeddev94.xray.R\nimport io.github.saeeddev94.xray.Settings\nimport io.github.saeeddev94.xray.adapter.AppsRoutingAdapter\nimport io.github.saeeddev94.xray.databinding.ActivityAppsRoutingBinding\nimport io.github.saeeddev94.xray.dto.AppList\nimport io.github.saeeddev94.xray.helper.TransparentProxyHelper\nimport io.github.saeeddev94.xray.service.TProxyService\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\n\nclass AppsRoutingActivity : AppCompatActivity() {\n\n    private val settings by lazy { Settings(applicationContext) }\n    private val transparentProxyHelper by lazy { TransparentProxyHelper(this, settings) }\n    private lateinit var binding: ActivityAppsRoutingBinding\n    private lateinit var appsList: RecyclerView\n    private lateinit var appsRoutingAdapter: AppsRoutingAdapter\n    private lateinit var apps: ArrayList<AppList>\n    private lateinit var filtered: MutableList<AppList>\n    private lateinit var appsRouting: MutableSet<String>\n    private lateinit var menu: Menu\n    private var appsRoutingMode: Boolean = true\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        title = \"\"\n        binding = ActivityAppsRoutingBinding.inflate(layoutInflater)\n        appsRoutingMode = settings.appsRoutingMode\n        setContentView(binding.root)\n        setSupportActionBar(binding.toolbar)\n        supportActionBar?.setDisplayHomeAsUpEnabled(true)\n\n        binding.search.focusable = View.NOT_FOCUSABLE\n        binding.search.setOnQueryTextListener(object : SearchView.OnQueryTextListener {\n            override fun onQueryTextChange(newText: String?): Boolean {\n                search(newText)\n                return false\n            }\n\n            override fun onQueryTextSubmit(query: String?): Boolean {\n                binding.search.clearFocus()\n                search(query)\n                return false\n            }\n        })\n        binding.search.findViewById<ImageView>(androidx.appcompat.R.id.search_close_btn)\n            ?.setOnClickListener {\n                binding.search.setQuery(\"\", false)\n                binding.search.clearFocus()\n            }\n\n        getApps()\n    }\n\n    override fun onCreateOptionsMenu(menu: Menu): Boolean {\n        this.menu = menu\n        menuInflater.inflate(R.menu.menu_apps_routing, menu)\n        handleMode()\n        return true\n    }\n\n    override fun onOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.appsRoutingSave -> saveAppsRouting()\n            R.id.appsRoutingExcludeMode -> setMode(false)\n            R.id.appsRoutingIncludeMode -> setMode(true)\n            else -> finish()\n        }\n        return true\n    }\n\n    private fun setMode(appsRoutingMode: Boolean) {\n        this.appsRoutingMode = appsRoutingMode\n        handleMode().also { message ->\n            Toast.makeText(applicationContext, message, Toast.LENGTH_SHORT).show()\n        }\n    }\n\n    private fun handleMode(): String {\n        val excludeItem = menu.findItem(R.id.appsRoutingExcludeMode)\n        val includeItem = menu.findItem(R.id.appsRoutingIncludeMode)\n        return when (this.appsRoutingMode) {\n            true -> {\n                excludeItem.isVisible = true\n                includeItem.isVisible = false\n                getString(R.string.appsRoutingExcludeMode)\n            }\n\n            false -> {\n                excludeItem.isVisible = false\n                includeItem.isVisible = true\n                getString(R.string.appsRoutingIncludeMode)\n            }\n        }\n    }\n\n    @SuppressLint(\"NotifyDataSetChanged\")\n    private fun search(query: String?) {\n        val keyword = query?.trim()?.lowercase() ?: \"\"\n        if (keyword.isEmpty()) {\n            if (apps.size > filtered.size) {\n                filtered.clear()\n                filtered.addAll(apps.toMutableList())\n                appsRoutingAdapter.notifyDataSetChanged()\n            }\n            return\n        }\n        val list = ArrayList<AppList>()\n        apps.forEach {\n            if (it.appName.lowercase().contains(keyword) || it.packageName.contains(keyword)) {\n                list.add(it)\n            }\n        }\n        filtered.clear()\n        filtered.addAll(list.toMutableList())\n        appsRoutingAdapter.notifyDataSetChanged()\n    }\n\n    private fun getApps() {\n        lifecycleScope.launch {\n            val selected = ArrayList<AppList>()\n            val unselected = ArrayList<AppList>()\n            packageManager.getInstalledPackages(PackageManager.GET_PERMISSIONS).forEach {\n                val permissions = it.requestedPermissions\n                if (\n                    permissions == null || !permissions.contains(Manifest.permission.INTERNET)\n                ) return@forEach\n                val appIcon = it.applicationInfo!!.loadIcon(packageManager)\n                val appName = it.applicationInfo!!.loadLabel(packageManager).toString()\n                val packageName = it.packageName\n                val app = AppList(appIcon, appName, packageName)\n                val isSelected = settings.appsRouting.contains(packageName)\n                if (isSelected) selected.add(app) else unselected.add(app)\n            }\n            withContext(Dispatchers.Main) {\n                apps = ArrayList(selected + unselected)\n                filtered = apps.toMutableList()\n                appsRouting = settings.appsRouting.split(\"\\n\").toMutableSet()\n                appsList = binding.appsList\n                appsRoutingAdapter = AppsRoutingAdapter(\n                    this@AppsRoutingActivity, filtered, appsRouting\n                )\n                appsList.adapter = appsRoutingAdapter\n                appsList.layoutManager = LinearLayoutManager(applicationContext)\n            }\n        }\n    }\n\n    private fun saveAppsRouting() {\n        val appsRoutingMode = this.appsRoutingMode\n        val appsRouting = this.appsRouting.joinToString(\"\\n\")\n\n        lifecycleScope.launch {\n            val tproxySettingsChanged = settings.appsRoutingMode != appsRoutingMode ||\n                    settings.appsRouting != appsRouting\n            val stopService = tproxySettingsChanged && settings.xrayCorePid().exists()\n            if (tproxySettingsChanged) transparentProxyHelper.kill()\n            withContext(Dispatchers.Main) {\n                binding.search.clearFocus()\n                settings.appsRoutingMode = appsRoutingMode\n                settings.appsRouting = appsRouting\n                if (stopService) TProxyService.stop(this@AppsRoutingActivity)\n                finish()\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/activity/AssetsActivity.kt",
    "content": "package io.github.saeeddev94.xray.activity\n\nimport android.annotation.SuppressLint\nimport android.net.Uri\nimport android.os.Bundle\nimport android.view.View\nimport android.widget.LinearLayout\nimport android.widget.ProgressBar\nimport android.widget.Toast\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.core.view.isVisible\nimport androidx.lifecycle.lifecycleScope\nimport com.topjohnwu.superuser.Shell\nimport io.github.saeeddev94.xray.R\nimport io.github.saeeddev94.xray.Settings\nimport io.github.saeeddev94.xray.databinding.ActivityAssetsBinding\nimport io.github.saeeddev94.xray.helper.DownloadHelper\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport java.io.File\nimport java.io.FileOutputStream\nimport java.text.SimpleDateFormat\nimport java.util.Date\nimport kotlin.text.toRegex\n\nclass AssetsActivity : AppCompatActivity() {\n\n    private lateinit var binding: ActivityAssetsBinding\n    private var downloading: Boolean = false\n\n    private val settings by lazy { Settings(applicationContext) }\n    private val geoIpLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) {\n        writeToFile(it, geoIpFile())\n    }\n    private val geoSiteLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) {\n        writeToFile(it, geoSiteFile())\n    }\n    private val xrayCoreLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) {\n        val file = settings.xrayCoreFile()\n        writeToFile(it, file) { makeExeFile(file) }\n    }\n\n    private fun geoIpFile(): File = File(applicationContext.filesDir, \"geoip.dat\")\n    private fun geoSiteFile(): File = File(applicationContext.filesDir, \"geosite.dat\")\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        val mimeType = \"application/octet-stream\"\n        title = getString(R.string.assets)\n        binding = ActivityAssetsBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n        setSupportActionBar(binding.toolbar)\n        supportActionBar?.setDisplayHomeAsUpEnabled(true)\n        setAssetStatus()\n\n        // GeoIP\n        binding.geoIpDownload.setOnClickListener {\n            download(settings.geoIpAddress, geoIpFile(), binding.geoIpSetup, binding.geoIpProgress)\n        }\n        binding.geoIpFile.setOnClickListener { geoIpLauncher.launch(mimeType) }\n        binding.geoIpDelete.setOnClickListener { delete(geoIpFile()) }\n\n        // GeoSite\n        binding.geoSiteDownload.setOnClickListener {\n            download(\n                settings.geoSiteAddress,\n                geoSiteFile(),\n                binding.geoSiteSetup,\n                binding.geoSiteProgress\n            )\n        }\n        binding.geoSiteFile.setOnClickListener { geoSiteLauncher.launch(mimeType) }\n        binding.geoSiteDelete.setOnClickListener { delete(geoSiteFile()) }\n\n        // XTLS/Xray-core\n        binding.xrayCoreFile.setOnClickListener { runAsRoot { xrayCoreLauncher.launch(mimeType) } }\n        binding.xrayCoreDelete.setOnClickListener { delete(settings.xrayCoreFile()) }\n    }\n\n    @SuppressLint(\"SimpleDateFormat\")\n    private fun getFileDate(file: File): String {\n        return if (file.exists()) {\n            val date = Date(file.lastModified())\n            SimpleDateFormat(\"yyyy/MM/dd HH:mm:ss\").format(date)\n        } else {\n            getString(R.string.noValue)\n        }\n    }\n\n    private fun getXrayCoreVersion(file: File): String {\n        return getExeVersion(file, \"${file.absolutePath} version\")\n    }\n\n    private fun getExeVersion(file: File, cmd: String): String {\n        val exists = file.exists()\n        val invalid = {\n            delete(file)\n            \"Invalid\"\n        }\n        return if (exists) {\n            val result = Shell.cmd(cmd).exec()\n            if (result.isSuccess) {\n                val txt = result.out.first()\n                val match = \"Xray (.*?) \".toRegex().find(txt)\n                match?.groups?.get(1)?.value ?: invalid()\n            } else invalid()\n        } else getString(R.string.noValue)\n    }\n\n    private fun setAssetStatus() {\n        val geoIp = geoIpFile()\n        val geoIpExists = geoIp.exists()\n        binding.geoIpDate.text = getFileDate(geoIp)\n        binding.geoIpSetup.visibility = if (geoIpExists) View.GONE else View.VISIBLE\n        binding.geoIpInstalled.visibility = if (geoIpExists) View.VISIBLE else View.GONE\n        binding.geoIpProgress.visibility = View.GONE\n\n        val geoSite = geoSiteFile()\n        val geoSiteExists = geoSite.exists()\n        binding.geoSiteDate.text = getFileDate(geoSite)\n        binding.geoSiteSetup.visibility = if (geoSiteExists) View.GONE else View.VISIBLE\n        binding.geoSiteInstalled.visibility = if (geoSiteExists) View.VISIBLE else View.GONE\n        binding.geoSiteProgress.visibility = View.GONE\n\n        val xrayCore = settings.xrayCoreFile()\n        val xrayCoreExists = xrayCore.exists()\n        binding.xrayCoreVersion.text = getXrayCoreVersion(xrayCore)\n        binding.xrayCoreSetup.isVisible = !xrayCoreExists\n        binding.xrayCoreInstalled.isVisible = xrayCoreExists\n    }\n\n    private fun download(url: String, file: File, setup: LinearLayout, progressBar: ProgressBar) {\n        if (downloading) {\n            Toast.makeText(\n                applicationContext, \"Another download is running, please wait\", Toast.LENGTH_SHORT\n            ).show()\n            return\n        }\n\n        setup.visibility = View.GONE\n        progressBar.visibility = View.VISIBLE\n        progressBar.progress = 0\n\n        downloading = true\n        DownloadHelper(lifecycleScope, url, file, object : DownloadHelper.DownloadListener {\n            override fun onProgress(progress: Int) {\n                progressBar.progress = progress\n            }\n\n            override fun onError(exception: Exception) {\n                downloading = false\n                Toast.makeText(applicationContext, exception.message, Toast.LENGTH_SHORT).show()\n                setAssetStatus()\n            }\n\n            override fun onComplete() {\n                downloading = false\n                setAssetStatus()\n            }\n        }).start()\n    }\n\n    private fun writeToFile(uri: Uri?, file: File, cb: (() -> Unit)? = null) {\n        if (uri == null) return\n        lifecycleScope.launch {\n            contentResolver.openInputStream(uri).use { input ->\n                FileOutputStream(file).use { output ->\n                    input?.copyTo(output)\n                }\n            }\n            if (cb != null) cb()\n            withContext(Dispatchers.Main) {\n                setAssetStatus()\n            }\n        }\n    }\n\n    private fun makeExeFile(file: File) {\n        Shell.cmd(\"chown root:root ${file.absolutePath}\").exec()\n        Shell.cmd(\"chmod +x ${file.absolutePath}\").exec()\n    }\n\n    private fun delete(file: File) {\n        lifecycleScope.launch {\n            file.delete()\n            withContext(Dispatchers.Main) {\n                setAssetStatus()\n            }\n        }\n    }\n\n    private fun runAsRoot(cb: () -> Unit) {\n        val result = Shell.cmd(\"whoami\").exec()\n        if (result.isSuccess && result.out.first() == \"root\") {\n            cb()\n            return\n        }\n        Toast.makeText(this, \"Root Required\", Toast.LENGTH_SHORT).show()\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/activity/ConfigsActivity.kt",
    "content": "package io.github.saeeddev94.xray.activity\n\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.view.View\nimport android.widget.RadioButton\nimport android.widget.RadioGroup\nimport android.widget.Toast\nimport androidx.activity.viewModels\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.lifecycle.lifecycleScope\nimport com.blacksquircle.ui.editorkit.plugin.autoindent.autoIndentation\nimport com.blacksquircle.ui.editorkit.plugin.base.PluginSupplier\nimport com.blacksquircle.ui.editorkit.plugin.delimiters.highlightDelimiters\nimport com.blacksquircle.ui.editorkit.plugin.linenumbers.lineNumbers\nimport com.blacksquircle.ui.editorkit.widget.TextProcessor\nimport com.blacksquircle.ui.language.json.JsonLanguage\nimport com.google.android.material.radiobutton.MaterialRadioButton\nimport io.github.saeeddev94.xray.R\nimport io.github.saeeddev94.xray.adapter.ConfigAdapter\nimport io.github.saeeddev94.xray.database.Config\nimport io.github.saeeddev94.xray.databinding.ActivityConfigsBinding\nimport io.github.saeeddev94.xray.viewmodel.ConfigViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport org.json.JSONArray\nimport org.json.JSONObject\nimport kotlin.getValue\nimport kotlin.reflect.cast\n\nclass ConfigsActivity : AppCompatActivity() {\n\n    private lateinit var binding: ActivityConfigsBinding\n    private lateinit var config: Config\n    private lateinit var adapter: ConfigAdapter\n    private val configViewModel: ConfigViewModel by viewModels()\n    private val radioGroup = mutableMapOf<String, RadioGroup>()\n    private val configEditor = mutableMapOf<String, TextProcessor>()\n    private val indentSpaces = 4\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        title = getString(R.string.configs)\n        binding = ActivityConfigsBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n        setSupportActionBar(binding.toolbar)\n        supportActionBar?.setDisplayHomeAsUpEnabled(true)\n        lifecycleScope.launch {\n            config = configViewModel.get()\n            withContext(Dispatchers.Main) {\n                val context = this@ConfigsActivity\n                val tabs = listOf(\"log\", \"dns\", \"inbounds\", \"outbounds\", \"routing\")\n                adapter = ConfigAdapter(context, tabs) { tab, view -> setup(tab, view) }\n                binding.viewPager.adapter = adapter\n                binding.tabLayout.setupWithViewPager(binding.viewPager)\n            }\n        }\n    }\n\n    override fun onCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.menu_configs, menu)\n        return true\n    }\n\n    override fun onOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.saveConfigs -> saveConfigs()\n            else -> finish()\n        }\n        return true\n    }\n\n    private fun setup(tab: String, view: View) {\n        val mode = getMode(tab)\n        val config = getConfig(tab)\n\n        val modeRadioGroup = view.findViewById<RadioGroup>(R.id.modeRadioGroup)\n        modeRadioGroup.removeAllViews()\n        Config.Mode.entries.forEach {\n            val radio = MaterialRadioButton(this)\n            radio.text = it.name\n            radio.tag = it\n            modeRadioGroup.addView(radio)\n            if (it == mode) modeRadioGroup.check(radio.id)\n        }\n        radioGroup.put(tab, modeRadioGroup)\n\n        val editor = view.findViewById<TextProcessor>(R.id.config)\n        val pluginSupplier = PluginSupplier.create {\n            lineNumbers {\n                lineNumbers = true\n                highlightCurrentLine = true\n            }\n            highlightDelimiters()\n            autoIndentation {\n                autoIndentLines = true\n                autoCloseBrackets = true\n                autoCloseQuotes = true\n            }\n        }\n        editor.language = JsonLanguage()\n        editor.setTextContent(config)\n        editor.plugins(pluginSupplier)\n        configEditor.put(tab, editor)\n    }\n\n    private fun getConfig(tab: String): String {\n        val editor = configEditor[tab]\n        val default = \"\"\n        return if (editor == null) {\n            when (tab) {\n                \"log\" -> config.log\n                \"dns\" -> config.dns\n                \"inbounds\" -> config.inbounds\n                \"outbounds\" -> config.outbounds\n                \"routing\" -> config.routing\n                else -> default\n            }\n        } else getViewConfig(tab, default)\n    }\n\n    private fun getMode(tab: String): Config.Mode {\n        val group = configEditor[tab]\n        val default = Config.Mode.Disable\n        return if (group == null) {\n            when (tab) {\n                \"log\" -> config.logMode\n                \"dns\" -> config.dnsMode\n                \"inbounds\" -> config.inboundsMode\n                \"outbounds\" -> config.outboundsMode\n                \"routing\" -> config.routingMode\n                else -> default\n            }\n        } else getViewMode(tab, default)\n    }\n\n    private fun getViewConfig(tab: String, default: String): String {\n        val editor = configEditor[tab]\n        if (editor == null) return default\n        return editor.text.toString()\n    }\n\n    private fun getViewMode(tab: String, default: Config.Mode): Config.Mode {\n        val group = radioGroup[tab]\n        if (group == null) return default\n        val modeRadioButton = group.findViewById<RadioButton>(\n            group.checkedRadioButtonId\n        )\n        return Config.Mode::class.cast(modeRadioButton.tag)\n    }\n\n    private fun formatConfig(tab: String, default: String): String {\n        val json = getViewConfig(tab, default)\n        if (arrayOf(\"inbounds\", \"outbounds\").contains(tab)) return JSONArray(json).toString(indentSpaces)\n        return JSONObject(json).toString(indentSpaces)\n    }\n\n    private fun saveConfigs() {\n        runCatching {\n            config.log = formatConfig(\"log\", config.log)\n            config.dns = formatConfig(\"dns\", config.dns)\n            config.inbounds = formatConfig(\"inbounds\", config.inbounds)\n            config.outbounds = formatConfig(\"outbounds\", config.outbounds)\n            config.routing = formatConfig(\"routing\", config.routing)\n            config.logMode = getViewMode(\"log\", config.logMode)\n            config.dnsMode = getViewMode(\"dns\", config.dnsMode)\n            config.inboundsMode = getViewMode(\"inbounds\", config.inboundsMode)\n            config.outboundsMode = getViewMode(\"outbounds\", config.outboundsMode)\n            config.routingMode = getViewMode(\"routing\", config.routingMode)\n            config\n        }.onSuccess {\n            configViewModel.update(it)\n            finish()\n        }.onFailure {\n            Toast.makeText(\n                this, \"Invalid config\", Toast.LENGTH_SHORT\n            ).show()\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/activity/LinksActivity.kt",
    "content": "package io.github.saeeddev94.xray.activity\n\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuItem\nimport androidx.activity.viewModels\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.lifecycleScope\nimport androidx.lifecycle.repeatOnLifecycle\nimport androidx.recyclerview.widget.DefaultItemAnimator\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport io.github.saeeddev94.xray.R\nimport io.github.saeeddev94.xray.adapter.LinkAdapter\nimport io.github.saeeddev94.xray.database.Link\nimport io.github.saeeddev94.xray.databinding.ActivityLinksBinding\nimport io.github.saeeddev94.xray.viewmodel.LinkViewModel\nimport kotlinx.coroutines.flow.collectLatest\nimport kotlinx.coroutines.launch\n\nclass LinksActivity : AppCompatActivity() {\n\n    private val linkViewModel: LinkViewModel by viewModels()\n    private val adapter by lazy { LinkAdapter() }\n    private val linksRecyclerView by lazy { findViewById<RecyclerView>(R.id.linksRecyclerView) }\n    private var links: MutableList<Link> = mutableListOf()\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        title = getString(R.string.links)\n        val binding = ActivityLinksBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n        setSupportActionBar(binding.toolbar)\n        supportActionBar?.setDisplayHomeAsUpEnabled(true)\n        adapter.onEditClick = { link -> openLink(link) }\n        adapter.onDeleteClick = { link -> deleteLink(link) }\n        linksRecyclerView.layoutManager = LinearLayoutManager(this)\n        linksRecyclerView.itemAnimator = DefaultItemAnimator()\n        linksRecyclerView.adapter = adapter\n        lifecycleScope.launch {\n            lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {\n                linkViewModel.links.collectLatest {\n                    links = it.toMutableList()\n                    adapter.submitList(it)\n                }\n            }\n        }\n    }\n\n    override fun onCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.menu_links, menu)\n        return true\n    }\n\n    override fun onOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.refreshLinks -> refreshLinks()\n            R.id.newLink -> openLink()\n            else -> finish()\n        }\n        return true\n    }\n\n    private fun refreshLinks() {\n        val intent = LinksManagerActivity.refreshLinks(applicationContext)\n        startActivity(intent)\n    }\n\n    private fun openLink(link: Link = Link()) {\n        val intent = LinksManagerActivity.openLink(applicationContext, link)\n        startActivity(intent)\n    }\n\n    private fun deleteLink(link: Link) {\n        val intent = LinksManagerActivity.deleteLink(applicationContext, link)\n        startActivity(intent)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/activity/LinksManagerActivity.kt",
    "content": "package io.github.saeeddev94.xray.activity\n\nimport android.app.Dialog\nimport android.content.Context\nimport android.content.Intent\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.widget.LinearLayout\nimport androidx.activity.viewModels\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.lifecycle.lifecycleScope\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport io.github.saeeddev94.xray.R\nimport io.github.saeeddev94.xray.Settings\nimport io.github.saeeddev94.xray.database.Link\nimport io.github.saeeddev94.xray.database.Profile\nimport io.github.saeeddev94.xray.fragment.LinkFormFragment\nimport io.github.saeeddev94.xray.helper.HttpHelper\nimport io.github.saeeddev94.xray.helper.IntentHelper\nimport io.github.saeeddev94.xray.helper.LinkHelper\nimport io.github.saeeddev94.xray.service.TProxyService\nimport io.github.saeeddev94.xray.viewmodel.LinkViewModel\nimport io.github.saeeddev94.xray.viewmodel.ProfileViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport org.json.JSONArray\nimport org.json.JSONObject\nimport kotlin.reflect.cast\n\nclass LinksManagerActivity : AppCompatActivity() {\n\n    companion object {\n        private const val LINK_REF = \"ref\"\n        private const val DELETE_ACTION = \"delete\"\n\n        fun refreshLinks(context: Context): Intent {\n            return Intent(context, LinksManagerActivity::class.java)\n        }\n\n        fun openLink(context: Context, link: Link = Link()): Intent {\n            return Intent(context, LinksManagerActivity::class.java).apply {\n                putExtra(LINK_REF, link)\n            }\n        }\n\n        fun deleteLink(context: Context, link: Link): Intent {\n            return Intent(context, LinksManagerActivity::class.java).apply {\n                putExtra(LINK_REF, link)\n                putExtra(DELETE_ACTION, true)\n            }\n        }\n    }\n\n    private val settings by lazy { Settings(applicationContext) }\n    private val linkViewModel: LinkViewModel by viewModels()\n    private val profileViewModel: ProfileViewModel by viewModels()\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        val link: Link? = IntentHelper.getParcelable(intent, LINK_REF, Link::class.java)\n        val deleteAction = intent.getBooleanExtra(DELETE_ACTION, false)\n\n        if (link == null) {\n            refreshLinks()\n            return\n        }\n\n        if (deleteAction) {\n            deleteLink(link)\n            return\n        }\n\n        LinkFormFragment(link) {\n            if (link.id == 0L) {\n                linkViewModel.insert(link)\n            } else {\n                linkViewModel.update(link)\n            }\n            setResult(RESULT_OK)\n            finish()\n        }.show(supportFragmentManager, null)\n    }\n\n    private fun loadingDialog(): Dialog {\n        val dialogView = LayoutInflater.from(this).inflate(\n            R.layout.loading_dialog,\n            LinearLayout(this)\n        )\n        return MaterialAlertDialogBuilder(this)\n            .setView(dialogView)\n            .setCancelable(false)\n            .create()\n    }\n\n    private fun refreshLinks() {\n        val loadingDialog = loadingDialog()\n        loadingDialog.show()\n        lifecycleScope.launch {\n            val links = linkViewModel.activeLinks()\n            links.forEach { link ->\n                val profiles = profileViewModel.linkProfiles(link.id)\n                runCatching {\n                    val content = HttpHelper.get(link.address, link.userAgent).trim()\n                    val newProfiles = if (link.type == Link.Type.Json) {\n                        jsonProfiles(link, content)\n                    } else {\n                        subscriptionProfiles(link, content)\n                    }\n                    if (newProfiles.isNotEmpty()) {\n                        val linkProfiles = profiles.filter { it.linkId == link.id }\n                        manageProfiles(link, linkProfiles, newProfiles)\n                    }\n                }\n            }\n            withContext(Dispatchers.Main) {\n                settings.lastRefreshLinks = System.currentTimeMillis()\n                TProxyService.newConfig(applicationContext)\n                loadingDialog.dismiss()\n                finish()\n            }\n        }\n    }\n\n    private fun jsonProfiles(link: Link, value: String): List<Profile> {\n        val list = arrayListOf<Profile>()\n        val configs = runCatching { JSONArray(value) }.getOrNull() ?: JSONArray()\n        for (i in 0 until configs.length()) {\n            runCatching { JSONObject::class.cast(configs[i]) }.getOrNull()?.let { configuration ->\n                val label = if (configuration.has(\"remarks\")) {\n                    val remarks = configuration.getString(\"remarks\")\n                    configuration.remove(\"remarks\")\n                    remarks\n                } else {\n                    LinkHelper.REMARK_DEFAULT\n                }\n                val json = configuration.toString(2)\n                val profile = Profile().apply {\n                    linkId = link.id\n                    name = label\n                    config = json\n                }\n                list.add(profile)\n            }\n        }\n        return list.reversed().toList()\n    }\n\n    private fun subscriptionProfiles(link: Link, value: String): List<Profile> {\n        val decoded = runCatching { LinkHelper.tryDecodeBase64(value).trim() }.getOrNull() ?: \"\"\n        return decoded.split(\"\\n\")\n            .reversed()\n            .map { LinkHelper(settings, it) }\n            .filter { it.isValid() }\n            .map { linkHelper ->\n                val profile = Profile()\n                profile.linkId = link.id\n                profile.config = linkHelper.json()\n                profile.name = linkHelper.remark()\n                profile\n            }\n    }\n\n    private suspend fun manageProfiles(\n        link: Link, linkProfiles: List<Profile>, newProfiles: List<Profile>\n    ) {\n        if (newProfiles.size >= linkProfiles.size) {\n            newProfiles.forEachIndexed { index, newProfile ->\n                if (index >= linkProfiles.size) {\n                    newProfile.linkId = link.id\n                    insertProfile(newProfile)\n                } else {\n                    val linkProfile = linkProfiles[index]\n                    updateProfile(linkProfile, newProfile)\n                }\n            }\n            return\n        }\n        linkProfiles.forEachIndexed { index, linkProfile ->\n            if (index >= newProfiles.size) {\n                deleteProfile(linkProfile)\n            } else {\n                val newProfile = newProfiles[index]\n                updateProfile(linkProfile, newProfile)\n            }\n        }\n    }\n\n    private suspend fun insertProfile(newProfile: Profile) {\n        profileViewModel.create(newProfile)\n    }\n\n    private suspend fun updateProfile(linkProfile: Profile, newProfile: Profile) {\n        linkProfile.name = newProfile.name\n        linkProfile.config = newProfile.config\n        profileViewModel.update(linkProfile)\n    }\n\n    private suspend fun deleteProfile(linkProfile: Profile) {\n        profileViewModel.remove(linkProfile)\n        withContext(Dispatchers.Main) {\n            val selectedProfile = settings.selectedProfile\n            if (selectedProfile == linkProfile.id) {\n                settings.selectedProfile = 0L\n            }\n        }\n    }\n\n    private fun deleteLink(link: Link) {\n        lifecycleScope.launch {\n            profileViewModel.linkProfiles(link.id)\n                .forEach { linkProfile ->\n                    deleteProfile(linkProfile)\n                }\n            linkViewModel.delete(link)\n            withContext(Dispatchers.Main) {\n                finish()\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/activity/LogsActivity.kt",
    "content": "package io.github.saeeddev94.xray.activity\n\nimport android.annotation.SuppressLint\nimport android.content.ClipData\nimport android.content.ClipboardManager\nimport android.content.Context\nimport android.os.Bundle\nimport android.util.Log\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.view.View\nimport android.widget.Toast\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.lifecycle.lifecycleScope\nimport io.github.saeeddev94.xray.BuildConfig\nimport io.github.saeeddev94.xray.R\nimport io.github.saeeddev94.xray.Settings\nimport io.github.saeeddev94.xray.databinding.ActivityLogsBinding\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport java.io.BufferedReader\nimport java.io.IOException\nimport java.io.InputStreamReader\nimport java.nio.charset.StandardCharsets\n\nclass LogsActivity : AppCompatActivity() {\n\n    private lateinit var binding: ActivityLogsBinding\n    private val settings by lazy { Settings(applicationContext) }\n\n    companion object {\n        private const val MAX_BUFFERED_LINES = (1 shl 14) - 1\n    }\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        title = getString(R.string.logs)\n        binding = ActivityLogsBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n        setSupportActionBar(binding.toolbar)\n        supportActionBar?.setDisplayHomeAsUpEnabled(true)\n\n        lifecycleScope.launch(Dispatchers.IO) { streamingLog() }\n    }\n\n    override fun onCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.menu_logs, menu)\n        return true\n    }\n\n    override fun onOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.deleteLogs -> flush()\n            R.id.copyLogs -> copyToClipboard(binding.logsTextView.text.toString())\n            else -> finish()\n        }\n        return true\n    }\n\n    private fun flush() {\n        lifecycleScope.launch(Dispatchers.IO) {\n            val command = if (settings.transparentProxy) {\n                listOf(\"echo\", \"''\", \">\", settings.xrayCoreLogs().absolutePath)\n            } else {\n                listOf(\"logcat\", \"-c\")\n            }\n            val process = ProcessBuilder(command).start()\n            process.waitFor()\n            withContext(Dispatchers.Main) {\n                binding.logsTextView.text = \"\"\n            }\n        }\n    }\n\n    private fun copyToClipboard(text: String) {\n        if (text.isBlank()) return\n        try {\n            val clipData = ClipData.newPlainText(null, text)\n            val clipboardManager = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager\n            clipboardManager.setPrimaryClip(clipData)\n            Toast.makeText(applicationContext, \"Logs copied\", Toast.LENGTH_SHORT).show()\n        } catch (error: Exception) {\n            error.printStackTrace()\n        }\n    }\n\n    @SuppressLint(\"SetTextI18n\")\n    private suspend fun streamingLog() = withContext(Dispatchers.IO) {\n        val cmd = if (settings.transparentProxy) {\n            listOf(\"tail\", \"-f\", settings.xrayCoreLogs().absolutePath)\n        } else {\n            listOf(\"logcat\", \"-v\", \"time\", \"-s\", \"GoLog,${BuildConfig.APPLICATION_ID}\")\n        }\n        val builder = ProcessBuilder(cmd)\n        builder.environment()[\"LC_ALL\"] = \"C\"\n        var process: Process? = null\n        try {\n            process = try {\n                builder.start()\n            } catch (e: IOException) {\n                Log.e(packageName, Log.getStackTraceString(e))\n                return@withContext\n            }\n\n            val stdout = BufferedReader(\n                InputStreamReader(process!!.inputStream, StandardCharsets.UTF_8)\n            )\n            val bufferedLogLines = arrayListOf<String>()\n\n            var timeLastNotify = System.nanoTime()\n            // The timeout is initially small so that the view gets populated immediately.\n            var timeout = 1000000000L / 2\n\n            while (true) {\n                val line = stdout.readLine() ?: break\n                bufferedLogLines.add(line)\n                val timeNow = System.nanoTime()\n\n                if (\n                    bufferedLogLines.size < MAX_BUFFERED_LINES &&\n                    (timeNow - timeLastNotify) < timeout && stdout.ready()\n                ) continue\n\n                // Increase the timeout after the initial view has something in it.\n                timeout = 1000000000L * 5 / 2\n                timeLastNotify = timeNow\n\n                withContext(Dispatchers.Main) {\n                    val contentHeight = binding.logsTextView.height\n                    val scrollViewHeight = binding.logsScrollView.height\n                    val isScrolledToBottomAlready =\n                        (binding.logsScrollView.scrollY + scrollViewHeight) >= contentHeight * 0.95\n                    binding.logsTextView.text =\n                        binding.logsTextView.text.toString() + bufferedLogLines.joinToString(\n                            separator = \"\\n\",\n                            postfix = \"\\n\"\n                        )\n                    bufferedLogLines.clear()\n                    if (isScrolledToBottomAlready) {\n                        binding.logsScrollView.post {\n                            binding.logsScrollView.fullScroll(View.FOCUS_DOWN)\n                        }\n                    }\n                }\n            }\n        } finally {\n            process?.destroy()\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/activity/MainActivity.kt",
    "content": "package io.github.saeeddev94.xray.activity\n\nimport XrayCore.XrayCore\nimport android.content.BroadcastReceiver\nimport android.content.ClipboardManager\nimport android.content.Context\nimport android.content.Intent\nimport android.content.IntentFilter\nimport android.content.res.ColorStateList\nimport android.net.VpnService\nimport android.os.Build\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.widget.Toast\nimport androidx.activity.result.contract.ActivityResultContracts.RequestPermission\nimport androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult\nimport androidx.activity.viewModels\nimport androidx.appcompat.app.ActionBarDrawerToggle\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.core.content.ContextCompat\nimport androidx.core.content.edit\nimport androidx.core.view.GravityCompat\nimport androidx.core.view.isVisible\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.lifecycleScope\nimport androidx.lifecycle.repeatOnLifecycle\nimport androidx.recyclerview.widget.ItemTouchHelper\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport com.google.android.material.navigation.NavigationView\nimport com.google.android.material.tabs.TabLayout\nimport io.github.saeeddev94.xray.BuildConfig\nimport io.github.saeeddev94.xray.R\nimport io.github.saeeddev94.xray.Settings\nimport io.github.saeeddev94.xray.adapter.ProfileAdapter\nimport io.github.saeeddev94.xray.database.Link\nimport io.github.saeeddev94.xray.databinding.ActivityMainBinding\nimport io.github.saeeddev94.xray.dto.ProfileList\nimport io.github.saeeddev94.xray.helper.HttpHelper\nimport io.github.saeeddev94.xray.helper.LinkHelper\nimport io.github.saeeddev94.xray.helper.ProfileTouchHelper\nimport io.github.saeeddev94.xray.helper.TransparentProxyHelper\nimport io.github.saeeddev94.xray.service.TProxyService\nimport io.github.saeeddev94.xray.viewmodel.LinkViewModel\nimport io.github.saeeddev94.xray.viewmodel.ProfileViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.collectLatest\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport java.net.URI\n\nclass MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener {\n\n    private val clipboardManager by lazy { getSystemService(ClipboardManager::class.java) }\n    private val settings by lazy { Settings(applicationContext) }\n    private val transparentProxyHelper by lazy { TransparentProxyHelper(this, settings) }\n    private val linkViewModel: LinkViewModel by viewModels()\n    private val profileViewModel: ProfileViewModel by viewModels()\n    private var isRunning: Boolean = false\n\n    private lateinit var binding: ActivityMainBinding\n    private lateinit var profileAdapter: ProfileAdapter\n    private lateinit var tabs: List<Link>\n    private val profilesRecyclerView by lazy { findViewById<RecyclerView>(R.id.profilesRecyclerView) }\n    private val profiles = arrayListOf<ProfileList>()\n\n    private var cameraPermission = registerForActivityResult(RequestPermission()) {\n        if (!it) return@registerForActivityResult\n        scannerLauncher.launch(\n            Intent(applicationContext, ScannerActivity::class.java)\n        )\n    }\n    private val notificationPermission = registerForActivityResult(RequestPermission()) {\n        onToggleButtonClick()\n    }\n    private val linksManager = registerForActivityResult(StartActivityForResult()) {\n        if (it.resultCode != RESULT_OK) return@registerForActivityResult\n        refreshLinks()\n    }\n    private var scannerLauncher = registerForActivityResult(StartActivityForResult()) {\n        val link = it.data?.getStringExtra(\"link\")\n        if (it.resultCode != RESULT_OK || link == null) return@registerForActivityResult\n        this@MainActivity.processLink(link)\n    }\n    private val vpnLauncher = registerForActivityResult(StartActivityForResult()) {\n        if (it.resultCode != RESULT_OK) return@registerForActivityResult\n        toggleVpnService()\n    }\n\n    private val vpnServiceEventReceiver: BroadcastReceiver = object : BroadcastReceiver() {\n        override fun onReceive(context: Context?, intent: Intent?) {\n            if (context == null || intent == null) return\n            when (intent.action) {\n                TProxyService.START_VPN_SERVICE_ACTION_NAME -> vpnStartStatus()\n                TProxyService.STOP_VPN_SERVICE_ACTION_NAME -> vpnStopStatus()\n                TProxyService.STATUS_VPN_SERVICE_ACTION_NAME -> {\n                    intent.getBooleanExtra(\"isRunning\", false).let { isRunning ->\n                        if (isRunning) vpnStartStatus()\n                        else vpnStopStatus()\n                    }\n                }\n            }\n        }\n    }\n\n    private val linksTabListener = object : TabLayout.OnTabSelectedListener {\n        override fun onTabSelected(tab: TabLayout.Tab?) {\n            if (tab == null) return\n            settings.selectedLink = tab.tag.toString().toLong()\n            profileViewModel.next(settings.selectedLink)\n        }\n\n        override fun onTabUnselected(tab: TabLayout.Tab?) {\n        }\n\n        override fun onTabReselected(tab: TabLayout.Tab?) {\n        }\n    }\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        binding = ActivityMainBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n        setSupportActionBar(binding.toolbar)\n        binding.toggleButton.setOnClickListener { onToggleButtonClick() }\n        binding.pingBox.setOnClickListener { ping() }\n        binding.navView.menu.findItem(R.id.appVersion).title = BuildConfig.VERSION_NAME\n        binding.navView.menu.findItem(R.id.xrayVersion).title = XrayCore.version()\n        binding.navView.setNavigationItemSelectedListener(this)\n        ActionBarDrawerToggle(\n            this, binding.drawerLayout, binding.toolbar,\n            R.string.drawerOpen, R.string.drawerClose\n        ).also {\n            binding.drawerLayout.addDrawerListener(it)\n            it.syncState()\n        }\n        profileAdapter = ProfileAdapter(\n            lifecycleScope,\n            settings,\n            profileViewModel,\n            profiles,\n            { index, profile -> profileSelect(index, profile) },\n            { profile -> profileEdit(profile) },\n            { profile -> profileDelete(profile) },\n        )\n        profilesRecyclerView.adapter = profileAdapter\n        profilesRecyclerView.layoutManager = LinearLayoutManager(applicationContext)\n        ItemTouchHelper(ProfileTouchHelper(profileAdapter)).also {\n            it.attachToRecyclerView(profilesRecyclerView)\n        }\n        lifecycleScope.launch {\n            lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {\n                linkViewModel.tabs.collectLatest { onNewTabs(it) }\n            }\n        }\n        lifecycleScope.launch {\n            lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {\n                profileViewModel.filtered.collectLatest { onNewProfiles(it) }\n            }\n        }\n        lifecycleScope.launch {\n            lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {\n                profileViewModel.profiles.collectLatest {\n                    val tabs = if (::tabs.isInitialized) tabs else linkViewModel.activeLinks()\n                    val list = tabsList(tabs)\n                    val index = tabsIndex(list)\n                    profileViewModel.next(list[index].id)\n                }\n            }\n        }\n        intent?.data?.let { deepLink ->\n            val pathSegments = deepLink.pathSegments\n            if (pathSegments.isNotEmpty()) processLink(pathSegments[0])\n        }\n    }\n\n    override fun onResume() {\n        super.onResume()\n        lifecycleScope.launch {\n            if (settings.transparentProxy) transparentProxyHelper.install()\n        }\n    }\n\n    override fun onStart() {\n        super.onStart()\n        IntentFilter().also {\n            it.addAction(TProxyService.START_VPN_SERVICE_ACTION_NAME)\n            it.addAction(TProxyService.STOP_VPN_SERVICE_ACTION_NAME)\n            it.addAction(TProxyService.STATUS_VPN_SERVICE_ACTION_NAME)\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {\n                registerReceiver(vpnServiceEventReceiver, it, RECEIVER_NOT_EXPORTED)\n            } else {\n                @Suppress(\"UnspecifiedRegisterReceiverFlag\")\n                registerReceiver(vpnServiceEventReceiver, it)\n            }\n        }\n        Intent(this, TProxyService::class.java).also {\n            it.action = TProxyService.STATUS_VPN_SERVICE_ACTION_NAME\n            startService(it)\n        }\n        if (settings.refreshLinksOnOpen) {\n            val interval = (settings.refreshLinksInterval * 60 * 1000).toLong()\n            val diff = System.currentTimeMillis() - settings.lastRefreshLinks\n            if (diff >= interval) refreshLinks()\n        }\n    }\n\n    override fun onStop() {\n        super.onStop()\n        unregisterReceiver(vpnServiceEventReceiver)\n    }\n\n    override fun onCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.menu_main, menu)\n        return true\n    }\n\n    override fun onOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.refreshLinks -> refreshLinks()\n            R.id.newProfile -> startActivity(ProfileActivity.getIntent(applicationContext))\n            R.id.scanQrCode -> cameraPermission.launch(android.Manifest.permission.CAMERA)\n            R.id.fromClipboard -> {\n                runCatching {\n                    clipboardManager.primaryClip!!.getItemAt(0).text.toString().trim()\n                }.getOrNull()?.let { processLink(it) }\n            }\n        }\n        return true\n    }\n\n    override fun onNavigationItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.assets -> Intent(applicationContext, AssetsActivity::class.java)\n            R.id.links -> Intent(applicationContext, LinksActivity::class.java)\n            R.id.logs -> Intent(applicationContext, LogsActivity::class.java)\n            R.id.appsRouting -> Intent(applicationContext, AppsRoutingActivity::class.java)\n            R.id.configs -> Intent(applicationContext, ConfigsActivity::class.java)\n            R.id.settings -> Intent(applicationContext, SettingsActivity::class.java)\n            else -> null\n        }?.let { startActivity(it) }\n        binding.drawerLayout.closeDrawer(GravityCompat.START)\n        return true\n    }\n\n    private fun tabsList(list: List<Link>): List<Link> {\n        tabs = list\n        return listOf(Link(name = \"All\")) + tabs\n    }\n\n    private fun tabsIndex(list: List<Link>): Int {\n        return list.indexOfFirst { it.id == settings.selectedLink }.takeIf { it != -1 } ?: 0\n    }\n\n    private fun onNewTabs(value: List<Link>) {\n        binding.linksTab.removeOnTabSelectedListener(linksTabListener)\n        binding.linksTab.removeAllTabs()\n        binding.linksTab.isVisible = !value.isEmpty()\n        val list = tabsList(value)\n        val index = tabsIndex(list)\n        list.forEach {\n            val tab = binding.linksTab.newTab()\n            tab.tag = it.id\n            tab.text = it.name\n            binding.linksTab.addTab(tab)\n        }\n        binding.linksTab.selectTab(binding.linksTab.getTabAt(index))\n        binding.linksTab.addOnTabSelectedListener(linksTabListener)\n    }\n\n    private fun onNewProfiles(value: List<ProfileList>) {\n        profiles.clear()\n        profiles.addAll(ArrayList(value))\n        @Suppress(\"NotifyDataSetChanged\")\n        profileAdapter.notifyDataSetChanged()\n    }\n\n    private fun vpnStartStatus() {\n        isRunning = true\n        binding.toggleButton.text = getString(R.string.vpnStop)\n        binding.toggleButton.backgroundTintList = ColorStateList.valueOf(\n            ContextCompat.getColor(this, R.color.primaryColor)\n        )\n        binding.pingResult.text = getString(R.string.pingConnected)\n    }\n\n    private fun vpnStopStatus() {\n        isRunning = false\n        binding.toggleButton.text = getString(R.string.vpnStart)\n        binding.toggleButton.backgroundTintList = ColorStateList.valueOf(\n            ContextCompat.getColor(this, R.color.btnColor)\n        )\n        binding.pingResult.text = getString(R.string.pingNotConnected)\n    }\n\n    private fun onToggleButtonClick() {\n        if (!settings.tun2socks || settings.transparentProxy) {\n            toggleVpnService()\n            return\n        }\n\n        if (!hasPostNotification()) return\n        VpnService.prepare(this).also {\n            if (it == null) {\n                toggleVpnService()\n                return\n            }\n            vpnLauncher.launch(it)\n        }\n    }\n\n    private fun toggleVpnService() {\n        if (isRunning) {\n            TProxyService.stop(applicationContext)\n            return\n        }\n        TProxyService.start(applicationContext, false)\n    }\n\n    private fun profileSelect(index: Int, profile: ProfileList) {\n        val selectedProfile = settings.selectedProfile\n        lifecycleScope.launch {\n            val ref = if (selectedProfile > 0L) profileViewModel.find(selectedProfile) else null\n            withContext(Dispatchers.Main) {\n                if (selectedProfile == profile.id) return@withContext\n                settings.selectedProfile = profile.id\n                profileAdapter.notifyItemChanged(index)\n                if (isRunning) TProxyService.newConfig(applicationContext)\n                if (ref == null || ref.id == profile.id) return@withContext\n                profiles.indexOfFirst { it.id == ref.id }.let {\n                    if (it != -1) profileAdapter.notifyItemChanged(it)\n                }\n            }\n        }\n    }\n\n    private fun profileEdit(profile: ProfileList) {\n        if (isRunning && settings.selectedProfile == profile.id) return\n        startActivity(ProfileActivity.getIntent(applicationContext, profile.id))\n    }\n\n    private fun profileDelete(profile: ProfileList) {\n        if (isRunning && settings.selectedProfile == profile.id) return\n        MaterialAlertDialogBuilder(this)\n            .setTitle(\"Delete Profile#${profile.index + 1} ?\")\n            .setMessage(\"\\\"${profile.name}\\\" will delete forever !!\")\n            .setNegativeButton(\"No\", null)\n            .setPositiveButton(\"Yes\") { _, _ ->\n                lifecycleScope.launch {\n                    val ref = profileViewModel.find(profile.id)\n                    val id = ref.id\n                    profileViewModel.remove(ref)\n                    withContext(Dispatchers.Main) {\n                        val selectedProfile = settings.selectedProfile\n                        if (selectedProfile == id) {\n                            settings.selectedProfile = 0L\n                        }\n                    }\n                }\n            }.show()\n    }\n\n    private fun processLink(link: String) {\n        val uri = runCatching { URI(link) }.getOrNull() ?: return\n        if (uri.scheme == \"http\") {\n            Toast.makeText(\n                applicationContext, getString(R.string.forbiddenHttp), Toast.LENGTH_SHORT\n            ).show()\n            return\n        }\n        if (uri.scheme == \"https\") {\n            openLink(uri)\n            return\n        }\n        val linkHelper = LinkHelper(settings, link)\n        if (!linkHelper.isValid()) {\n            Toast.makeText(\n                applicationContext, getString(R.string.invalidLink), Toast.LENGTH_SHORT\n            ).show()\n            return\n        }\n        val json = linkHelper.json()\n        val name = linkHelper.remark()\n        startActivity(ProfileActivity.getIntent(applicationContext, name = name, config = json))\n    }\n\n    private fun refreshLinks() {\n        startActivity(LinksManagerActivity.refreshLinks(applicationContext))\n    }\n\n    private fun openLink(uri: URI) {\n        val link = Link()\n        link.name = LinkHelper.remark(uri, LinkHelper.LINK_DEFAULT)\n        link.address = uri.toString()\n        val intent = LinksManagerActivity.openLink(applicationContext, link)\n        linksManager.launch(intent)\n    }\n\n    private fun ping() {\n        if (!isRunning) return\n        binding.pingResult.text = getString(R.string.pingTesting)\n        HttpHelper(lifecycleScope, settings).measureDelay(!settings.transparentProxy) {\n            binding.pingResult.text = it\n        }\n    }\n\n    private fun hasPostNotification(): Boolean {\n        val sharedPref = getSharedPreferences(\"app\", MODE_PRIVATE)\n        val key = \"request_notification_permission\"\n        val askedBefore = sharedPref.getBoolean(key, false)\n        if (askedBefore) return true\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {\n            sharedPref.edit { putBoolean(key, true) }\n            notificationPermission.launch(android.Manifest.permission.POST_NOTIFICATIONS)\n            return false\n        }\n        return true\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/activity/ProfileActivity.kt",
    "content": "package io.github.saeeddev94.xray.activity\n\nimport XrayCore.XrayCore\nimport android.content.Context\nimport android.content.Intent\nimport android.net.Uri\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuItem\nimport androidx.activity.viewModels\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.lifecycle.lifecycleScope\nimport com.blacksquircle.ui.editorkit.plugin.autoindent.autoIndentation\nimport com.blacksquircle.ui.editorkit.plugin.base.PluginSupplier\nimport com.blacksquircle.ui.editorkit.plugin.delimiters.highlightDelimiters\nimport com.blacksquircle.ui.editorkit.plugin.linenumbers.lineNumbers\nimport com.blacksquircle.ui.language.json.JsonLanguage\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport io.github.saeeddev94.xray.R\nimport io.github.saeeddev94.xray.Settings\nimport io.github.saeeddev94.xray.database.Config\nimport io.github.saeeddev94.xray.database.Profile\nimport io.github.saeeddev94.xray.databinding.ActivityProfileBinding\nimport io.github.saeeddev94.xray.helper.ConfigHelper\nimport io.github.saeeddev94.xray.helper.FileHelper\nimport io.github.saeeddev94.xray.viewmodel.ConfigViewModel\nimport io.github.saeeddev94.xray.viewmodel.ProfileViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport java.io.BufferedReader\nimport java.io.InputStreamReader\n\nclass ProfileActivity : AppCompatActivity() {\n\n    companion object {\n        private const val PROFILE_ID = \"id\"\n        private const val PROFILE_NAME = \"name\"\n        private const val PROFILE_CONFIG = \"config\"\n\n        fun getIntent(\n            context: Context, id: Long = 0L, name: String = \"\", config: String = \"\"\n        ) = Intent(context, ProfileActivity::class.java).also {\n            it.putExtra(PROFILE_ID, id)\n            if (name.isNotEmpty()) it.putExtra(PROFILE_NAME, name)\n            if (config.isNotEmpty()) it.putExtra(\n                PROFILE_CONFIG,\n                config.replace(\"\\\\/\", \"/\")\n            )\n        }\n    }\n\n    private val settings by lazy { Settings(applicationContext) }\n    private val configViewModel: ConfigViewModel by viewModels()\n    private val profileViewModel: ProfileViewModel by viewModels()\n    private lateinit var binding: ActivityProfileBinding\n    private lateinit var config: Config\n    private lateinit var profile: Profile\n    private var id: Long = 0L\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        id = intent.getLongExtra(PROFILE_ID, 0L)\n        title = if (isNew()) getString(R.string.newProfile) else getString(R.string.editProfile)\n        binding = ActivityProfileBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n        setSupportActionBar(binding.toolbar)\n        supportActionBar?.setDisplayHomeAsUpEnabled(true)\n\n        lifecycleScope.launch {\n            val config = configViewModel.get()\n            withContext(Dispatchers.Main) {\n                this@ProfileActivity.config = config\n            }\n        }\n\n        val jsonUri = intent.data\n        if (Intent.ACTION_VIEW == intent.action && jsonUri != null) {\n            val profile = Profile()\n            profile.config = readJsonFile(jsonUri)\n            resolved(profile)\n        } else if (isNew()) {\n            val profile = Profile()\n            profile.name = intent.getStringExtra(PROFILE_NAME) ?: \"\"\n            profile.config = intent.getStringExtra(PROFILE_CONFIG) ?: \"\"\n            resolved(profile)\n        } else {\n            lifecycleScope.launch {\n                val profile = profileViewModel.find(id)\n                withContext(Dispatchers.Main) {\n                    resolved(profile)\n                }\n            }\n        }\n    }\n\n    override fun onCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.menu_profile, menu)\n        return true\n    }\n\n    override fun onOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.saveProfile -> save()\n            else -> finish()\n        }\n        return true\n    }\n\n    private fun isNew() = id == 0L\n\n    private fun readJsonFile(uri: Uri): String {\n        val content = StringBuilder()\n        try {\n            contentResolver.openInputStream(uri)?.use { input ->\n                BufferedReader(InputStreamReader(input)).forEachLine { content.append(\"$it\\n\") }\n            }\n        } catch (error: Exception) {\n            error.printStackTrace()\n        }\n        return content.toString()\n    }\n\n    private fun resolved(value: Profile) {\n        profile = value\n        binding.profileName.setText(profile.name)\n\n        val editor = binding.profileConfig\n        val pluginSupplier = PluginSupplier.create {\n            lineNumbers {\n                lineNumbers = true\n                highlightCurrentLine = true\n            }\n            highlightDelimiters()\n            autoIndentation {\n                autoIndentLines = true\n                autoCloseBrackets = true\n                autoCloseQuotes = true\n            }\n        }\n        editor.language = JsonLanguage()\n        editor.setTextContent(profile.config)\n        editor.plugins(pluginSupplier)\n    }\n\n    private fun save(check: Boolean = true) {\n        profile.name = binding.profileName.text.toString()\n        profile.config = binding.profileConfig.text.toString()\n        lifecycleScope.launch {\n            val configHelper = runCatching { ConfigHelper(settings, config, profile.config) }\n            val error = if (configHelper.isSuccess) {\n                isValid(configHelper.getOrNull().toString())\n            } else {\n                configHelper.exceptionOrNull()?.message ?: getString(R.string.invalidProfile)\n            }\n            if (check && error.isNotEmpty()) {\n                withContext(Dispatchers.Main) {\n                    showError(error)\n                }\n                return@launch\n            }\n            if (profile.id == 0L) {\n                profileViewModel.create(profile)\n            } else {\n                profileViewModel.update(profile)\n            }\n            withContext(Dispatchers.Main) {\n                finish()\n            }\n        }\n    }\n\n    private suspend fun isValid(json: String): String {\n        return withContext(Dispatchers.IO) {\n            val pwd = filesDir.absolutePath\n            val testConfig = settings.testConfig()\n            FileHelper.createOrUpdate(testConfig, json)\n            XrayCore.test(pwd, testConfig.absolutePath)\n        }\n    }\n\n    private fun showError(message: String) {\n        MaterialAlertDialogBuilder(this)\n            .setTitle(getString(R.string.invalidProfile))\n            .setMessage(message)\n            .setNegativeButton(getString(R.string.cancel)) { _, _ -> }\n            .setPositiveButton(getString(R.string.ignore)) { _, _ -> save(false) }\n            .show()\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/activity/ScannerActivity.kt",
    "content": "package io.github.saeeddev94.xray.activity\n\nimport android.content.Intent\nimport android.os.Bundle\nimport android.widget.Toast\nimport androidx.appcompat.app.AppCompatActivity\nimport com.budiyev.android.codescanner.AutoFocusMode\nimport com.budiyev.android.codescanner.CodeScanner\nimport com.budiyev.android.codescanner.CodeScannerView\nimport com.budiyev.android.codescanner.DecodeCallback\nimport com.budiyev.android.codescanner.ErrorCallback\nimport com.budiyev.android.codescanner.ScanMode\nimport io.github.saeeddev94.xray.R\nimport io.github.saeeddev94.xray.databinding.ActivityScannerBinding\n\nclass ScannerActivity : AppCompatActivity() {\n\n    private lateinit var binding: ActivityScannerBinding\n    private lateinit var codeScanner: CodeScanner\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        binding = ActivityScannerBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n\n        val scannerView = findViewById<CodeScannerView>(R.id.scannerView)\n        codeScanner = CodeScanner(this, scannerView)\n        codeScanner.camera = CodeScanner.CAMERA_BACK\n        codeScanner.formats = CodeScanner.ALL_FORMATS\n        codeScanner.autoFocusMode = AutoFocusMode.SAFE\n        codeScanner.scanMode = ScanMode.SINGLE\n        codeScanner.isAutoFocusEnabled = true\n        codeScanner.isFlashEnabled = false\n\n        codeScanner.decodeCallback = DecodeCallback {\n            runOnUiThread {\n                val intent = Intent().also { intent -> intent.putExtra(\"link\", it.text) }\n                setResult(RESULT_OK, intent)\n                finish()\n            }\n        }\n        codeScanner.errorCallback = ErrorCallback {\n            runOnUiThread {\n                Toast.makeText(\n                    this, \"Camera initialization error: ${it.message}\",\n                    Toast.LENGTH_LONG\n                ).show()\n            }\n        }\n\n        scannerView.setOnClickListener { codeScanner.startPreview() }\n    }\n\n    override fun onPause() {\n        codeScanner.releaseResources()\n        super.onPause()\n    }\n\n    override fun onResume() {\n        super.onResume()\n        codeScanner.startPreview()\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/activity/SettingsActivity.kt",
    "content": "package io.github.saeeddev94.xray.activity\n\nimport android.annotation.SuppressLint\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.view.View\nimport android.widget.EditText\nimport android.widget.LinearLayout\nimport android.widget.Toast\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.lifecycle.lifecycleScope\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport com.google.android.material.materialswitch.MaterialSwitch\nimport io.github.saeeddev94.xray.R\nimport io.github.saeeddev94.xray.Settings\nimport io.github.saeeddev94.xray.adapter.SettingAdapter\nimport io.github.saeeddev94.xray.databinding.ActivitySettingsBinding\nimport io.github.saeeddev94.xray.helper.TransparentProxyHelper\nimport io.github.saeeddev94.xray.service.TProxyService\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\n\nclass SettingsActivity : AppCompatActivity() {\n\n    private val settings by lazy { Settings(applicationContext) }\n    private val transparentProxyHelper by lazy { TransparentProxyHelper(this, settings) }\n    private lateinit var binding: ActivitySettingsBinding\n    private lateinit var adapter: SettingAdapter\n    private lateinit var basic: View\n    private lateinit var advanced: View\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        title = getString(R.string.settings)\n        binding = ActivitySettingsBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n        setSupportActionBar(binding.toolbar)\n        supportActionBar?.setDisplayHomeAsUpEnabled(true)\n        val tabs = listOf(\"Basic\", \"Advanced\")\n        val layouts = listOf(R.layout.tab_basic_settings, R.layout.tab_advanced_settings)\n        adapter = SettingAdapter(this, tabs, layouts, object : SettingAdapter.ViewsReady {\n            override fun rendered(views: List<View>) {\n                basic = views[0]\n                advanced = views[1]\n                setupBasic()\n                setupAdvanced()\n            }\n        })\n        binding.viewPager.adapter = adapter\n        binding.tabLayout.setupWithViewPager(binding.viewPager)\n    }\n\n    override fun onCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.menu_settings, menu)\n        return true\n    }\n\n    override fun onOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.saveSettings -> applySettings()\n            else -> finish()\n        }\n        return true\n    }\n\n    @SuppressLint(\"SetTextI18n\")\n    private fun setupBasic() {\n        basic.findViewById<EditText>(R.id.socksAddress).setText(settings.socksAddress)\n        basic.findViewById<EditText>(R.id.socksPort).setText(settings.socksPort)\n        basic.findViewById<EditText>(R.id.socksUsername).setText(settings.socksUsername)\n        basic.findViewById<EditText>(R.id.socksPassword).setText(settings.socksPassword)\n        basic.findViewById<EditText>(R.id.geoIpAddress).setText(settings.geoIpAddress)\n        basic.findViewById<EditText>(R.id.geoSiteAddress).setText(settings.geoSiteAddress)\n        basic.findViewById<EditText>(R.id.pingAddress).setText(settings.pingAddress)\n        basic.findViewById<EditText>(R.id.pingTimeout).setText(settings.pingTimeout.toString())\n        basic.findViewById<EditText>(R.id.refreshLinksInterval)\n            .setText(settings.refreshLinksInterval.toString())\n        basic.findViewById<MaterialSwitch>(R.id.bypassLan).isChecked = settings.bypassLan\n        basic.findViewById<MaterialSwitch>(R.id.enableIpV6).isChecked = settings.enableIpV6\n        basic.findViewById<MaterialSwitch>(R.id.socksUdp).isChecked = settings.socksUdp\n        basic.findViewById<MaterialSwitch>(R.id.tun2socks).isChecked = settings.tun2socks\n        basic.findViewById<MaterialSwitch>(R.id.bootAutoStart).isChecked = settings.bootAutoStart\n        basic.findViewById<MaterialSwitch>(R.id.refreshLinksOnOpen).isChecked =\n            settings.refreshLinksOnOpen\n    }\n\n    @SuppressLint(\"SetTextI18n\")\n    private fun setupAdvanced() {\n        advanced.findViewById<EditText>(R.id.primaryDns).setText(settings.primaryDns)\n        advanced.findViewById<EditText>(R.id.secondaryDns).setText(settings.secondaryDns)\n        advanced.findViewById<EditText>(R.id.primaryDnsV6).setText(settings.primaryDnsV6)\n        advanced.findViewById<EditText>(R.id.secondaryDnsV6).setText(settings.secondaryDnsV6)\n        advanced.findViewById<EditText>(R.id.tunName).setText(settings.tunName)\n        advanced.findViewById<EditText>(R.id.tunMtu).setText(settings.tunMtu.toString())\n        advanced.findViewById<EditText>(R.id.tunAddress).setText(settings.tunAddress)\n        advanced.findViewById<EditText>(R.id.tunPrefix).setText(settings.tunPrefix.toString())\n        advanced.findViewById<EditText>(R.id.tunAddressV6).setText(settings.tunAddressV6)\n        advanced.findViewById<EditText>(R.id.tunPrefixV6).setText(settings.tunPrefixV6.toString())\n        advanced.findViewById<EditText>(R.id.hotspotInterface).setText(settings.hotspotInterface)\n        advanced.findViewById<EditText>(R.id.tetheringInterface).setText(settings.tetheringInterface)\n        advanced.findViewById<EditText>(R.id.tproxyAddress).setText(settings.tproxyAddress)\n        advanced.findViewById<EditText>(R.id.tproxyPort).setText(settings.tproxyPort.toString())\n        advanced.findViewById<EditText>(R.id.tproxyBypassWiFi).setText(settings.tproxyBypassWiFi.joinToString(\", \"))\n        advanced.findViewById<MaterialSwitch>(R.id.tproxyAutoConnect).isChecked =\n            settings.tproxyAutoConnect\n        advanced.findViewById<MaterialSwitch>(R.id.tproxyHotspot).isChecked =\n            settings.tproxyHotspot\n        advanced.findViewById<MaterialSwitch>(R.id.tproxyTethering).isChecked =\n            settings.tproxyTethering\n        advanced.findViewById<MaterialSwitch>(R.id.transparentProxy).isChecked =\n            settings.transparentProxy\n\n        advanced.findViewById<LinearLayout>(R.id.tunRoutes).setOnClickListener { tunRoutes() }\n    }\n\n    private fun tunRoutes() {\n        val tunRoutes = resources.getStringArray(R.array.publicIpAddresses).toSet()\n        val layout = layoutInflater.inflate(R.layout.layout_tun_routes, null)\n        val editText = layout.findViewById<EditText>(R.id.tunRoutesEditText)\n        val getValue = {\n            editText.text.toString().lines().map { it.trim() }.filter { it.isNotBlank() }.toSet()\n        }\n        editText.setText(settings.tunRoutes.joinToString(\"\\n\"))\n        MaterialAlertDialogBuilder(this)\n            .setTitle(R.string.tunRoutes)\n            .setView(layout)\n            .setPositiveButton(\"Save\") { _, _ -> settings.tunRoutes = getValue() }\n            .setNeutralButton(\"Reset\") { _, _ -> settings.tunRoutes = tunRoutes }\n            .setNegativeButton(\"Close\", null)\n            .show()\n    }\n\n    private fun applySettings() {\n        val transparentProxy = advanced.findViewById<MaterialSwitch>(R.id.transparentProxy).isChecked\n        if (transparentProxy && !settings.xrayCoreFile().exists()) {\n            Toast.makeText(\n                applicationContext, \"Install the assets\", Toast.LENGTH_SHORT\n            ).show()\n            return\n        }\n        saveSettings()\n    }\n\n    private fun saveSettings() {\n        /** Basic */\n        settings.socksAddress = basic.findViewById<EditText>(R.id.socksAddress).text.toString()\n        settings.socksPort = basic.findViewById<EditText>(R.id.socksPort).text.toString()\n        settings.socksUsername = basic.findViewById<EditText>(R.id.socksUsername).text.toString()\n        settings.socksPassword = basic.findViewById<EditText>(R.id.socksPassword).text.toString()\n        settings.geoIpAddress = basic.findViewById<EditText>(R.id.geoIpAddress).text.toString()\n        settings.geoSiteAddress = basic.findViewById<EditText>(R.id.geoSiteAddress).text.toString()\n        settings.pingAddress = basic.findViewById<EditText>(R.id.pingAddress).text.toString()\n        settings.pingTimeout = basic.findViewById<EditText>(R.id.pingTimeout).text.toString().toInt()\n        settings.refreshLinksInterval =\n            basic.findViewById<EditText>(R.id.refreshLinksInterval).text.toString().toInt()\n        settings.bypassLan = basic.findViewById<MaterialSwitch>(R.id.bypassLan).isChecked\n        val enableIpV6 = basic.findViewById<MaterialSwitch>(R.id.enableIpV6).isChecked\n        settings.socksUdp = basic.findViewById<MaterialSwitch>(R.id.socksUdp).isChecked\n        settings.tun2socks = basic.findViewById<MaterialSwitch>(R.id.tun2socks).isChecked\n        settings.bootAutoStart = basic.findViewById<MaterialSwitch>(R.id.bootAutoStart).isChecked\n        settings.refreshLinksOnOpen =\n            basic.findViewById<MaterialSwitch>(R.id.refreshLinksOnOpen).isChecked\n\n        /** Advanced */\n        settings.primaryDns = advanced.findViewById<EditText>(R.id.primaryDns).text.toString()\n        settings.secondaryDns = advanced.findViewById<EditText>(R.id.secondaryDns).text.toString()\n        settings.primaryDnsV6 = advanced.findViewById<EditText>(R.id.primaryDnsV6).text.toString()\n        settings.secondaryDnsV6 = advanced.findViewById<EditText>(R.id.secondaryDnsV6).text.toString()\n        settings.tunName = advanced.findViewById<EditText>(R.id.tunName).text.toString()\n        settings.tunMtu = advanced.findViewById<EditText>(R.id.tunMtu).text.toString().toInt()\n        settings.tunAddress = advanced.findViewById<EditText>(R.id.tunAddress).text.toString()\n        settings.tunPrefix = advanced.findViewById<EditText>(R.id.tunPrefix).text.toString().toInt()\n        settings.tunAddressV6 = advanced.findViewById<EditText>(R.id.tunAddressV6).text.toString()\n        settings.tunPrefixV6 = advanced.findViewById<EditText>(R.id.tunPrefixV6).text.toString().toInt()\n        val hotspotInterface = advanced.findViewById<EditText>(R.id.hotspotInterface).text.toString()\n        val tetheringInterface = advanced.findViewById<EditText>(R.id.tetheringInterface).text.toString()\n        val tproxyAddress = advanced.findViewById<EditText>(R.id.tproxyAddress).text.toString()\n        val tproxyPort = advanced.findViewById<EditText>(R.id.tproxyPort).text.toString().toInt()\n        val tproxyBypassWiFi = advanced.findViewById<EditText>(R.id.tproxyBypassWiFi).text\n            .toString()\n            .split(\",\")\n            .map { it.trim() }\n            .filter { it.isNotBlank() }\n            .toSet()\n        val tproxyAutoConnect = advanced.findViewById<MaterialSwitch>(R.id.tproxyAutoConnect).isChecked\n        val tproxyHotspot = advanced.findViewById<MaterialSwitch>(R.id.tproxyHotspot).isChecked\n        val tproxyTethering = advanced.findViewById<MaterialSwitch>(R.id.tproxyTethering).isChecked\n        val transparentProxy = advanced.findViewById<MaterialSwitch>(R.id.transparentProxy).isChecked\n\n        lifecycleScope.launch {\n            val tproxySettingsChanged = settings.enableIpV6 != enableIpV6 ||\n                    settings.hotspotInterface != hotspotInterface ||\n                    settings.tetheringInterface != tetheringInterface ||\n                    settings.tproxyAddress != tproxyAddress ||\n                    settings.tproxyPort != tproxyPort ||\n                    settings.tproxyBypassWiFi != tproxyBypassWiFi ||\n                    settings.tproxyAutoConnect != tproxyAutoConnect ||\n                    settings.tproxyHotspot != tproxyHotspot ||\n                    settings.tproxyTethering != tproxyTethering ||\n                    settings.transparentProxy != transparentProxy\n            val stopService = tproxySettingsChanged && settings.xrayCorePid().exists()\n            if (tproxySettingsChanged) transparentProxyHelper.kill()\n            withContext(Dispatchers.Main) {\n                settings.enableIpV6 = enableIpV6\n                settings.hotspotInterface = hotspotInterface\n                settings.tetheringInterface = tetheringInterface\n                settings.tproxyAddress = tproxyAddress\n                settings.tproxyPort = tproxyPort\n                settings.tproxyBypassWiFi = tproxyBypassWiFi\n                settings.tproxyAutoConnect = tproxyAutoConnect\n                settings.tproxyHotspot = tproxyHotspot\n                settings.tproxyTethering = tproxyTethering\n                settings.transparentProxy = transparentProxy\n                if (stopService) TProxyService.stop(this@SettingsActivity)\n                finish()\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/adapter/AppsRoutingAdapter.kt",
    "content": "package io.github.saeeddev94.xray.adapter\n\nimport android.content.Context\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.ImageView\nimport android.widget.LinearLayout\nimport android.widget.TextView\nimport androidx.recyclerview.widget.RecyclerView\nimport com.google.android.material.checkbox.MaterialCheckBox\nimport io.github.saeeddev94.xray.R\nimport io.github.saeeddev94.xray.dto.AppList\n\nclass AppsRoutingAdapter(\n    private var context: Context,\n    private var apps: MutableList<AppList>,\n    private var appsRouting: MutableSet<String>,\n) : RecyclerView.Adapter<AppsRoutingAdapter.ViewHolder>() {\n\n    override fun onCreateViewHolder(container: ViewGroup, type: Int): ViewHolder {\n        val linearLayout = LinearLayout(context)\n        val item: View = LayoutInflater.from(context).inflate(\n            R.layout.item_recycler_exclude, linearLayout, false\n        )\n        return ViewHolder(item)\n    }\n\n    override fun getItemCount(): Int {\n        return apps.size\n    }\n\n    override fun onBindViewHolder(holder: ViewHolder, index: Int) {\n        val app = apps[index]\n        val isSelected = appsRouting.contains(app.packageName)\n        holder.appIcon.setImageDrawable(app.appIcon)\n        holder.appName.text = app.appName\n        holder.packageName.text = app.packageName\n        holder.isSelected.isChecked = isSelected\n        holder.appContainer.setOnClickListener {\n            if (isSelected) {\n                appsRouting.remove(app.packageName)\n            } else {\n                appsRouting.add(app.packageName)\n            }\n            notifyItemChanged(index)\n        }\n    }\n\n    class ViewHolder(item: View) : RecyclerView.ViewHolder(item) {\n        var appContainer: LinearLayout = item.findViewById(R.id.appContainer)\n        var appIcon: ImageView = item.findViewById(R.id.appIcon)\n        var appName: TextView = item.findViewById(R.id.appName)\n        var packageName: TextView = item.findViewById(R.id.packageName)\n        var isSelected: MaterialCheckBox = item.findViewById(R.id.isSelected)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/adapter/ConfigAdapter.kt",
    "content": "package io.github.saeeddev94.xray.adapter\n\nimport android.content.Context\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.viewpager.widget.PagerAdapter\nimport io.github.saeeddev94.xray.R\n\nclass ConfigAdapter(\n    private var context: Context,\n    private var tabs: List<String>,\n    private var setup: (tab: String, view: View) -> Unit,\n) : PagerAdapter() {\n\n    override fun instantiateItem(container: ViewGroup, position: Int): Any {\n        val view = LayoutInflater.from(context).inflate(R.layout.tab_config, container, false)\n        container.addView(view)\n\n        setup(tabs[position], view)\n\n        return view\n    }\n\n    override fun getCount(): Int {\n        return tabs.size\n    }\n\n    override fun getPageTitle(position: Int): CharSequence {\n        return tabs[position]\n    }\n\n    override fun isViewFromObject(view: View, `object`: Any): Boolean {\n        return view == `object`\n    }\n\n    override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {\n        container.removeView(`object` as View)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/adapter/LinkAdapter.kt",
    "content": "package io.github.saeeddev94.xray.adapter\n\nimport android.content.res.ColorStateList\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.LinearLayout\nimport android.widget.TextView\nimport androidx.cardview.widget.CardView\nimport androidx.core.content.ContextCompat\nimport androidx.recyclerview.widget.DiffUtil\nimport androidx.recyclerview.widget.ListAdapter\nimport androidx.recyclerview.widget.RecyclerView\nimport io.github.saeeddev94.xray.R\nimport io.github.saeeddev94.xray.database.Link\n\nclass LinkAdapter : ListAdapter<Link, LinkAdapter.LinkHolder>(diffCallback) {\n\n    var onEditClick: (link: Link) -> Unit = {}\n    var onDeleteClick: (link: Link) -> Unit = {}\n\n    override fun onCreateViewHolder(parent: ViewGroup, type: Int) = LinkHolder(\n        LayoutInflater.from(parent.context).inflate(\n            R.layout.layout_link_item, parent, false\n        )\n    )\n\n    override fun onBindViewHolder(holder: LinkHolder, position: Int) {\n        holder.bind(position)\n    }\n\n    inner class LinkHolder(view: View) : RecyclerView.ViewHolder(view) {\n        private val card = view.findViewById<CardView>(R.id.linkCard)\n        private val name = view.findViewById<TextView>(R.id.linkName)\n        private val type = view.findViewById<TextView>(R.id.linkType)\n        private val edit = view.findViewById<LinearLayout>(R.id.linkEdit)\n        private val delete = view.findViewById<LinearLayout>(R.id.linkDelete)\n\n        fun bind(index: Int) {\n            val link = getItem(index)\n            val color = if (link.isActive) R.color.btnColor else R.color.btnColorDisabled\n            card.backgroundTintList = ColorStateList.valueOf(\n                ContextCompat.getColor(card.context, color)\n            )\n            name.text = link.name\n            type.text = link.type.name\n            edit.setOnClickListener { onEditClick(link) }\n            delete.setOnClickListener { onDeleteClick(link) }\n        }\n    }\n\n    companion object {\n        private val diffCallback = object : DiffUtil.ItemCallback<Link>() {\n            override fun areItemsTheSame(oldItem: Link, newItem: Link): Boolean =\n                oldItem.id == newItem.id\n\n            override fun areContentsTheSame(oldItem: Link, newItem: Link): Boolean =\n                oldItem == newItem\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/adapter/ProfileAdapter.kt",
    "content": "package io.github.saeeddev94.xray.adapter\n\nimport android.content.res.ColorStateList\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.LinearLayout\nimport android.widget.TextView\nimport androidx.cardview.widget.CardView\nimport androidx.core.content.ContextCompat\nimport androidx.recyclerview.widget.RecyclerView\nimport io.github.saeeddev94.xray.R\nimport io.github.saeeddev94.xray.Settings\nimport io.github.saeeddev94.xray.dto.ProfileList\nimport io.github.saeeddev94.xray.helper.ProfileTouchHelper\nimport io.github.saeeddev94.xray.viewmodel.ProfileViewModel\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.launch\n\nclass ProfileAdapter(\n    private val scope: CoroutineScope,\n    private val settings: Settings,\n    private val profileViewModel: ProfileViewModel,\n    private val profiles: ArrayList<ProfileList>,\n    private val profileSelect: (index: Int, profile: ProfileList) -> Unit,\n    private val profileEdit: (profile: ProfileList) -> Unit,\n    private val profileDelete: (profile: ProfileList) -> Unit,\n) : RecyclerView.Adapter<ProfileAdapter.ViewHolder>(), ProfileTouchHelper.ProfileTouchCallback {\n\n    override fun onCreateViewHolder(container: ViewGroup, type: Int): ViewHolder {\n        val linearLayout = LinearLayout(container.context)\n        val item: View = LayoutInflater.from(container.context).inflate(\n            R.layout.item_recycler_main, linearLayout, false\n        )\n        return ViewHolder(item)\n    }\n\n    override fun getItemCount(): Int {\n        return profiles.size\n    }\n\n    override fun onBindViewHolder(holder: ViewHolder, index: Int) {\n        val profile = profiles[index]\n        val color =\n            if (settings.selectedProfile == profile.id) R.color.primaryColor else R.color.btnColor\n        holder.activeIndicator.backgroundTintList = ColorStateList.valueOf(\n            ContextCompat.getColor(holder.profileCard.context, color)\n        )\n        holder.profileName.text = profile.name\n        holder.profileCard.setOnClickListener {\n            profileSelect(index, profile)\n        }\n        holder.profileEdit.setOnClickListener {\n            profileEdit(profile)\n        }\n        holder.profileDelete.setOnClickListener {\n            profileDelete(profile)\n        }\n    }\n\n    override fun onItemMoved(fromPosition: Int, toPosition: Int): Boolean {\n        profiles.add(toPosition, profiles.removeAt(fromPosition))\n        notifyItemMoved(fromPosition, toPosition)\n        if (toPosition > fromPosition) {\n            notifyItemRangeChanged(fromPosition, toPosition - fromPosition + 1)\n        } else {\n            notifyItemRangeChanged(toPosition, fromPosition - toPosition + 1)\n        }\n        return true\n    }\n\n    override fun onItemMoveCompleted(startPosition: Int, endPosition: Int) {\n        val isMoveUp = startPosition > endPosition\n        val start = if (isMoveUp) profiles[endPosition + 1] else profiles[endPosition - 1]\n        val end = profiles[endPosition]\n        scope.launch {\n            if (isMoveUp) profileViewModel.moveUp(start.index, end.index, end.id)\n            else profileViewModel.moveDown(start.index, end.index, end.id)\n        }\n    }\n\n    class ViewHolder(item: View) : RecyclerView.ViewHolder(item) {\n        var activeIndicator: LinearLayout = item.findViewById(R.id.activeIndicator)\n        var profileCard: CardView = item.findViewById(R.id.profileCard)\n        var profileName: TextView = item.findViewById(R.id.profileName)\n        var profileEdit: LinearLayout = item.findViewById(R.id.profileEdit)\n        var profileDelete: LinearLayout = item.findViewById(R.id.profileDelete)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/adapter/SettingAdapter.kt",
    "content": "package io.github.saeeddev94.xray.adapter\n\nimport android.content.Context\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.viewpager.widget.PagerAdapter\n\nclass SettingAdapter(\n    private var context: Context,\n    private var tabs: List<String>,\n    private var layouts: List<Int>,\n    private var callback: ViewsReady,\n) : PagerAdapter() {\n\n    private val views: MutableList<View> = mutableListOf()\n\n    override fun instantiateItem(container: ViewGroup, position: Int): Any {\n        val layoutInflater =\n            context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater\n        val view = layoutInflater.inflate(layouts[position], container, false)\n        container.addView(view)\n        views.add(view)\n        if (views.size == tabs.size) {\n            callback.rendered(views)\n        }\n        return view\n    }\n\n    override fun getCount(): Int {\n        return tabs.size\n    }\n\n    override fun getPageTitle(position: Int): CharSequence {\n        return tabs[position]\n    }\n\n    override fun isViewFromObject(view: View, `object`: Any): Boolean {\n        return view == `object`\n    }\n\n    interface ViewsReady {\n        fun rendered(views: List<View>)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/component/EmptySubmitSearchView.kt",
    "content": "package io.github.saeeddev94.xray.component\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport androidx.appcompat.widget.SearchView\n\nclass EmptySubmitSearchView : SearchView {\n\n    constructor(context: Context) : super(context)\n    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)\n    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(\n        context,\n        attrs,\n        defStyleAttr\n    )\n\n    @SuppressLint(\"RestrictedApi\")\n    override fun setOnQueryTextListener(listener: OnQueryTextListener?) {\n        super.setOnQueryTextListener(listener)\n        val searchAutoComplete =\n            this.findViewById<SearchAutoComplete>(androidx.appcompat.R.id.search_src_text)\n        searchAutoComplete.setOnEditorActionListener { _, _, _ ->\n            listener?.onQueryTextSubmit(query.toString())\n            return@setOnEditorActionListener true\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/database/Config.kt",
    "content": "package io.github.saeeddev94.xray.database\n\nimport android.os.Parcelable\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.PrimaryKey\nimport androidx.room.TypeConverter\nimport kotlinx.parcelize.Parcelize\n\n@Parcelize\n@Entity(tableName = \"configs\")\ndata class Config(\n    @PrimaryKey\n    @ColumnInfo(name = \"id\")\n    var id: Int = 1,\n    @ColumnInfo(name = \"log\")\n    var log: String = \"{}\",\n    @ColumnInfo(name = \"dns\")\n    var dns: String = \"{}\",\n    @ColumnInfo(name = \"inbounds\")\n    var inbounds: String = \"[]\",\n    @ColumnInfo(name = \"outbounds\")\n    var outbounds: String = \"[]\",\n    @ColumnInfo(name = \"routing\")\n    var routing: String = \"{}\",\n    @ColumnInfo(name = \"log_mode\")\n    var logMode: Mode = Mode.Disable,\n    @ColumnInfo(name = \"dns_mode\")\n    var dnsMode: Mode = Mode.Disable,\n    @ColumnInfo(name = \"inbounds_mode\")\n    var inboundsMode: Mode = Mode.Disable,\n    @ColumnInfo(name = \"outbounds_mode\")\n    var outboundsMode: Mode = Mode.Disable,\n    @ColumnInfo(name = \"routing_mode\")\n    var routingMode: Mode = Mode.Disable,\n) : Parcelable {\n    enum class Mode(val value: Int) {\n        Disable(0),\n        Replace(1),\n        Merge(2);\n\n        class Convertor {\n            @TypeConverter\n            fun fromMode(mode: Mode): Int = mode.value\n\n            @TypeConverter\n            fun toMode(value: Int): Mode = entries[value]\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/database/ConfigDao.kt",
    "content": "package io.github.saeeddev94.xray.database\n\nimport androidx.room.Dao\nimport androidx.room.Insert\nimport androidx.room.Query\nimport androidx.room.Update\n\n@Dao\ninterface ConfigDao {\n    @Query(\"SELECT * FROM configs WHERE id = 1\")\n    suspend fun get(): Config?\n\n    @Insert\n    suspend fun insert(config: Config)\n\n    @Update\n    suspend fun update(config: Config)\n}\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/database/Link.kt",
    "content": "package io.github.saeeddev94.xray.database\n\nimport android.os.Parcelable\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.PrimaryKey\nimport androidx.room.TypeConverter\nimport kotlinx.parcelize.Parcelize\n\n@Parcelize\n@Entity(tableName = \"links\")\ndata class Link(\n    @PrimaryKey(autoGenerate = true)\n    @ColumnInfo(name = \"id\")\n    var id: Long = 0L,\n    @ColumnInfo(name = \"name\")\n    var name: String = \"\",\n    @ColumnInfo(name = \"address\")\n    var address: String = \"\",\n    @ColumnInfo(name = \"type\")\n    var type: Type = Type.Json,\n    @ColumnInfo(name = \"is_active\")\n    var isActive: Boolean = false,\n    @ColumnInfo(name = \"user_agent\")\n    var userAgent: String? = null,\n) : Parcelable {\n    enum class Type(val value: Int) {\n        Json(0),\n        Subscription(1);\n\n        class Convertor {\n            @TypeConverter\n            fun fromType(type: Type): Int = type.value\n\n            @TypeConverter\n            fun toType(value: Int): Type = entries[value]\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/database/LinkDao.kt",
    "content": "package io.github.saeeddev94.xray.database\n\nimport androidx.room.Dao\nimport androidx.room.Delete\nimport androidx.room.Insert\nimport androidx.room.Query\nimport androidx.room.Update\nimport kotlinx.coroutines.flow.Flow\n\n@Dao\ninterface LinkDao {\n    @Query(\"SELECT * FROM links ORDER BY id ASC\")\n    fun all(): Flow<List<Link>>\n\n    @Query(\"SELECT * FROM links WHERE is_active = 1 ORDER BY id ASC\")\n    fun tabs(): Flow<List<Link>>\n\n    @Query(\"SELECT * FROM links WHERE is_active = 1 ORDER BY id ASC\")\n    suspend fun activeLinks(): List<Link>\n\n    @Insert\n    suspend fun insert(link: Link): Long\n\n    @Update\n    suspend fun update(link: Link)\n\n    @Delete\n    suspend fun delete(link: Link)\n}\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/database/Profile.kt",
    "content": "package io.github.saeeddev94.xray.database\n\nimport android.os.Parcelable\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.ForeignKey\nimport androidx.room.Index\nimport androidx.room.PrimaryKey\nimport kotlinx.parcelize.Parcelize\n\n@Parcelize\n@Entity(\n    tableName = \"profiles\",\n    foreignKeys = [\n        ForeignKey(\n            entity = Link::class,\n            parentColumns = [\"id\"],\n            childColumns = [\"link_id\"],\n            onUpdate = ForeignKey.CASCADE,\n            onDelete = ForeignKey.CASCADE,\n        ),\n    ],\n    indices = [\n        Index(\n            name = \"profiles_link_id_foreign\",\n            value = [\"link_id\"]\n        ),\n    ],\n)\ndata class Profile(\n    @PrimaryKey(autoGenerate = true)\n    @ColumnInfo(name = \"id\")\n    var id: Long = 0L,\n    @ColumnInfo(name = \"link_id\")\n    var linkId: Long? = null,\n    @ColumnInfo(name = \"index\")\n    var index: Int = -1,\n    @ColumnInfo(name = \"name\")\n    var name: String = \"\",\n    @ColumnInfo(name = \"config\")\n    var config: String = \"\",\n) : Parcelable\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/database/ProfileDao.kt",
    "content": "package io.github.saeeddev94.xray.database\n\nimport androidx.room.Dao\nimport androidx.room.Delete\nimport androidx.room.Insert\nimport androidx.room.Query\nimport androidx.room.Transaction\nimport androidx.room.Update\nimport io.github.saeeddev94.xray.dto.ProfileList\nimport kotlinx.coroutines.flow.Flow\n\n@Dao\ninterface ProfileDao {\n    @Query(\n        \"SELECT `profiles`.`id`, `profiles`.`index`, `profiles`.`name`, `profiles`.`link_id` AS `link`\" +\n        \"  FROM `profiles`\" +\n        \"  LEFT JOIN `links` ON `profiles`.`link_id` = `links`.`id`\" +\n        \"  WHERE `links`.`is_active` IS NULL OR `links`.`is_active` = 1\" +\n        \"  ORDER BY `profiles`.`index` ASC\"\n    )\n    fun all(): Flow<List<ProfileList>>\n\n    @Query(\"SELECT * FROM profiles WHERE link_id = :linkId ORDER BY `index` DESC\")\n    suspend fun linkProfiles(linkId: Long): List<Profile>\n\n    @Query(\"SELECT * FROM profiles WHERE `id` = :id\")\n    suspend fun find(id: Long): Profile\n\n    @Insert\n    suspend fun insert(profile: Profile): Long\n\n    @Update\n    suspend fun update(profile: Profile)\n\n    @Delete\n    suspend fun delete(profile: Profile)\n\n    @Query(\"UPDATE profiles SET `index` = :index WHERE `id` = :id\")\n    suspend fun updateIndex(index: Int, id: Long)\n\n    @Query(\"UPDATE profiles SET `index` = `index` + 1\")\n    suspend fun fixInsertIndex()\n\n    @Query(\"UPDATE profiles SET `index` = `index` - 1 WHERE `index` > :index\")\n    suspend fun fixDeleteIndex(index: Int)\n\n    @Query(\n        \"UPDATE profiles\" +\n        \"  SET `index` = `index` + 1\" +\n        \"  WHERE `index` >= :start\" +\n        \"  AND `index` < :end\" +\n        \"  AND `id` NOT IN (:exclude)\"\n    )\n    suspend fun fixMoveUpIndex(start: Int, end: Int, exclude: Long)\n\n    @Query(\n        \"UPDATE profiles\" +\n        \"  SET `index` = `index` - 1\" +\n        \"  WHERE `index` > :start\" +\n        \"  AND `index` <= :end\" +\n        \"  AND `id` NOT IN (:exclude)\"\n    )\n    suspend fun fixMoveDownIndex(start: Int, end: Int, exclude: Long)\n\n    @Transaction\n    suspend fun create(profile: Profile) {\n        insert(profile)\n        fixInsertIndex()\n    }\n\n    @Transaction\n    suspend fun remove(profile: Profile) {\n        delete(profile)\n        fixDeleteIndex(profile.index)\n    }\n\n    @Transaction\n    suspend fun moveUp(start: Int, end: Int, exclude: Long) {\n        updateIndex(start, exclude)\n        fixMoveUpIndex(start, end, exclude)\n    }\n\n    @Transaction\n    suspend fun moveDown(start: Int, end: Int, exclude: Long) {\n        updateIndex(start, exclude)\n        fixMoveDownIndex(end, start, exclude)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/database/XrayDatabase.kt",
    "content": "package io.github.saeeddev94.xray.database\n\nimport android.content.Context\nimport androidx.room.Database\nimport androidx.room.Room\nimport androidx.room.RoomDatabase\nimport androidx.room.TypeConverters\nimport androidx.room.migration.Migration\nimport androidx.sqlite.db.SupportSQLiteDatabase\n\n@Database(\n    entities = [\n        Config::class,\n        Link::class,\n        Profile::class,\n    ],\n    version = 4,\n    exportSchema = false,\n)\n@TypeConverters(\n    Link.Type.Convertor::class,\n    Config.Mode.Convertor::class,\n)\nabstract class XrayDatabase : RoomDatabase() {\n\n    abstract fun configDao(): ConfigDao\n    abstract fun linkDao(): LinkDao\n    abstract fun profileDao(): ProfileDao\n\n    companion object {\n        private val MIGRATION_1_2 = object : Migration(1, 2) {\n            override fun migrate(db: SupportSQLiteDatabase) {\n                // create links table\n                db.execSQL(\"\"\"\n                    CREATE TABLE links (\n                        id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,\n                        name TEXT NOT NULL,\n                        address TEXT NOT NULL,\n                        type INTEGER NOT NULL,\n                        is_active INTEGER NOT NULL\n                    )\n                \"\"\")\n\n                // add link_id to profiles table\n                db.execSQL(\"ALTER TABLE profiles ADD COLUMN link_id INTEGER\")\n\n                // create profiles_new table similar to profiles but with new column (link_id)\n                db.execSQL(\"\"\"\n                    CREATE TABLE profiles_new (\n                        id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,\n                        link_id INTEGER,\n                        \"index\" INTEGER NOT NULL,\n                        name TEXT NOT NULL,\n                        config TEXT NOT NULL,\n                        FOREIGN KEY (link_id) REFERENCES links(id) ON UPDATE CASCADE ON DELETE CASCADE\n                    )\n                \"\"\")\n\n                // create index for link_id\n                db.execSQL(\"CREATE INDEX profiles_link_id_foreign ON profiles_new(link_id)\")\n\n                // importing data from profile to profiles_new\n                db.execSQL(\"\"\"\n                   INSERT INTO profiles_new (id, link_id, \"index\", name, config)\n                   SELECT id, link_id, \"index\", name, config FROM profiles\n                \"\"\")\n\n                // drop profiles table\n                db.execSQL(\"DROP TABLE profiles\")\n\n                // rename profiles_new to profiles\n                db.execSQL(\"ALTER TABLE profiles_new RENAME TO profiles\")\n            }\n        }\n\n        private val MIGRATION_2_3 = object : Migration(2, 3) {\n            override fun migrate(db: SupportSQLiteDatabase) {\n                db.execSQL(\"ALTER TABLE links ADD COLUMN user_agent TEXT\")\n            }\n        }\n\n        private val MIGRATION_3_4 = object : Migration(3, 4) {\n            override fun migrate(db: SupportSQLiteDatabase) {\n                // create config table\n                db.execSQL(\"\"\"\n                    CREATE TABLE configs (\n                        id INTEGER PRIMARY KEY NOT NULL,\n                        log TEXT NOT NULL,\n                        dns TEXT NOT NULL,\n                        inbounds TEXT NOT NULL,\n                        outbounds TEXT NOT NULL,\n                        routing TEXT NOT NULL,\n                        log_mode INTEGER NOT NULL,\n                        dns_mode INTEGER NOT NULL,\n                        inbounds_mode INTEGER NOT NULL,\n                        outbounds_mode INTEGER NOT NULL,\n                        routing_mode INTEGER NOT NULL\n                    )\n                \"\"\")\n            }\n        }\n\n        @Volatile\n        private var db: XrayDatabase? = null\n\n        fun ref(context: Context): XrayDatabase {\n            if (db == null) {\n                synchronized(this) {\n                    if (db == null) {\n                        val migrations = arrayOf(\n                            MIGRATION_1_2,\n                            MIGRATION_2_3,\n                            MIGRATION_3_4,\n                        )\n                        db = Room.databaseBuilder(\n                            context.applicationContext,\n                            XrayDatabase::class.java,\n                            \"xray\"\n                        ).addMigrations(*migrations).build()\n                    }\n                }\n            }\n            return db!!\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/dto/AppList.kt",
    "content": "package io.github.saeeddev94.xray.dto\n\nimport android.graphics.drawable.Drawable\n\ndata class AppList(\n    var appIcon: Drawable,\n    var appName: String,\n    var packageName: String,\n)\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/dto/ProfileList.kt",
    "content": "package io.github.saeeddev94.xray.dto\n\ndata class ProfileList(\n    var id: Long,\n    var index: Int,\n    var name: String,\n    var link: Long?,\n)\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/dto/XrayConfig.kt",
    "content": "package io.github.saeeddev94.xray.dto\n\ndata class XrayConfig(\n    val dir: String,\n    val file: String,\n)\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/fragment/LinkFormFragment.kt",
    "content": "package io.github.saeeddev94.xray.fragment\n\nimport android.app.Dialog\nimport android.content.DialogInterface\nimport android.os.Bundle\nimport android.widget.EditText\nimport android.widget.LinearLayout\nimport android.widget.RadioButton\nimport android.widget.RadioGroup\nimport android.widget.Toast\nimport androidx.fragment.app.DialogFragment\nimport androidx.fragment.app.FragmentActivity\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport com.google.android.material.materialswitch.MaterialSwitch\nimport com.google.android.material.radiobutton.MaterialRadioButton\nimport io.github.saeeddev94.xray.R\nimport io.github.saeeddev94.xray.database.Link\nimport java.net.URI\nimport kotlin.reflect.cast\n\nclass LinkFormFragment(\n    private val link: Link,\n    private val onConfirm: () -> Unit,\n) : DialogFragment() {\n\n    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {\n        return openLink(requireActivity())\n    }\n\n    override fun onDismiss(dialog: DialogInterface) {\n        super.onDismiss(dialog)\n        requireActivity().finish()\n    }\n\n    private fun openLink(context: FragmentActivity): Dialog =\n        MaterialAlertDialogBuilder(context).apply {\n            val layout = context.layoutInflater.inflate(\n                R.layout.layout_link_form,\n                LinearLayout(context)\n            )\n\n            val typeRadioGroup = layout.findViewById<RadioGroup>(R.id.typeRadioGroup)\n            val nameEditText = layout.findViewById<EditText>(R.id.nameEditText)\n            val addressEditText = layout.findViewById<EditText>(R.id.addressEditText)\n            val userAgentEditText = layout.findViewById<EditText>(R.id.userAgentEditText)\n            val isActiveSwitch = layout.findViewById<MaterialSwitch>(R.id.isActiveSwitch)\n            Link.Type.entries.forEach {\n                val radio = MaterialRadioButton(context)\n                radio.text = it.name\n                radio.tag = it\n                typeRadioGroup.addView(radio)\n                if (it == link.type) typeRadioGroup.check(radio.id)\n            }\n            nameEditText.setText(link.name)\n            addressEditText.setText(link.address)\n            userAgentEditText.setText(link.userAgent)\n            isActiveSwitch.isChecked = if (link.id == 0L) {\n                true\n            } else {\n                link.isActive\n            }\n\n            setView(layout)\n            setTitle(\n                if (link.id == 0L) context.getString(R.string.newLink)\n                else context.getString(R.string.editLink)\n            )\n            setPositiveButton(\n                if (link.id == 0L) context.getString(R.string.createLink)\n                else context.getString(R.string.updateLink)\n            ) { _, _ ->\n                val address = addressEditText.text.toString()\n                val typeRadioButton = typeRadioGroup.findViewById<RadioButton>(\n                    typeRadioGroup.checkedRadioButtonId\n                )\n                val uri = runCatching { URI(address) }.getOrNull()\n                val invalidLink = context.getString(R.string.invalidLink)\n                val onlyHttps = context.getString(R.string.onlyHttps)\n                if (uri == null) {\n                    Toast.makeText(context, invalidLink, Toast.LENGTH_SHORT).show()\n                    return@setPositiveButton\n                }\n                if (uri.scheme != \"https\") {\n                    Toast.makeText(context, onlyHttps, Toast.LENGTH_SHORT).show()\n                    return@setPositiveButton\n                }\n                link.type = Link.Type::class.cast(typeRadioButton.tag)\n                link.name = nameEditText.text.toString()\n                link.address = address\n                link.userAgent = userAgentEditText.text.toString().ifBlank { null }\n                link.isActive = isActiveSwitch.isChecked\n                onConfirm()\n            }\n            setNegativeButton(context.getString(R.string.closeLink)) { _, _ -> }\n        }.create()\n}\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/helper/ConfigHelper.kt",
    "content": "package io.github.saeeddev94.xray.helper\n\nimport io.github.saeeddev94.xray.Settings\nimport io.github.saeeddev94.xray.database.Config\nimport org.json.JSONObject\n\nclass ConfigHelper(\n    settings: Settings,\n    config: Config,\n    base: String,\n) {\n    private val base: JSONObject = JsonHelper.makeObject(base)\n\n    init {\n        process(\"log\", config.log, config.logMode)\n        process(\"dns\", config.dns, config.dnsMode)\n        process(\"inbounds\", config.inbounds, config.inboundsMode)\n        process(\"outbounds\", config.outbounds, config.outboundsMode)\n        process(\"routing\", config.routing, config.routingMode)\n        if (settings.tproxyHotspot || settings.tproxyTethering) sharedInbounds()\n    }\n\n    override fun toString(): String {\n        return base.toString(4)\n    }\n\n    private fun process(key: String, config: String, mode: Config.Mode) {\n        if (mode == Config.Mode.Disable) return\n        if (arrayOf(\"inbounds\", \"outbounds\").contains(key)) {\n            processArray(key, config, mode)\n            return\n        }\n        processObject(key, config, mode)\n    }\n\n    private fun processObject(key: String, config: String, mode: Config.Mode) {\n        val oldValue = JsonHelper.getObject(base, key)\n        val newValue = JsonHelper.makeObject(config)\n        val final = if (mode == Config.Mode.Replace) newValue\n        else JsonHelper.mergeObjects(oldValue, newValue)\n        base.put(key, final)\n    }\n\n    private fun processArray(key: String, config: String, mode: Config.Mode) {\n        val oldValue = JsonHelper.getArray(base, key)\n        val newValue = JsonHelper.makeArray(config)\n        val final = if (mode == Config.Mode.Replace) newValue\n        else JsonHelper.mergeArrays(oldValue, newValue, \"protocol\")\n        base.put(key, final)\n    }\n\n    private fun sharedInbounds() {\n        val key = \"inbounds\"\n        val inbounds = JsonHelper.getArray(base, key)\n        for (i in 0 until inbounds.length()) {\n            val inbound = inbounds[i]\n            if (inbound is JSONObject && inbound.has(\"listen\")) {\n                inbound.remove(\"listen\")\n                inbounds.put(i, inbound)\n            }\n        }\n        base.put(key, inbounds)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/helper/DownloadHelper.kt",
    "content": "package io.github.saeeddev94.xray.helper\n\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport java.io.File\nimport java.io.FileOutputStream\nimport java.io.IOException\nimport java.io.InputStream\nimport java.io.OutputStream\nimport java.net.HttpURLConnection\nimport java.net.URL\n\nclass DownloadHelper(\n    private val scope: CoroutineScope,\n    private val url: String,\n    private val file: File,\n    private val callback: DownloadListener,\n) {\n\n    fun start() {\n        scope.launch(Dispatchers.IO) {\n            var input: InputStream? = null\n            var output: OutputStream? = null\n            var connection: HttpURLConnection? = null\n\n            try {\n                connection = URL(url).openConnection() as HttpURLConnection\n                connection.connect()\n\n                if (connection.responseCode != HttpURLConnection.HTTP_OK) {\n                    throw Exception(\"Expected HTTP ${HttpURLConnection.HTTP_OK} but received HTTP ${connection.responseCode}\")\n                }\n\n                input = connection.inputStream\n                output = FileOutputStream(file)\n\n                val fileLength = connection.contentLength\n                val data = ByteArray(4096)\n                var total: Long = 0\n                var count: Int\n                while (input.read(data).also { count = it } != -1) {\n                    total += count.toLong()\n                    if (fileLength > 0) {\n                        val progress = (total * 100 / fileLength).toInt()\n                        withContext(Dispatchers.Main) {\n                            callback.onProgress(progress)\n                        }\n                    }\n                    output.write(data, 0, count)\n                }\n                withContext(Dispatchers.Main) {\n                    callback.onComplete()\n                }\n            } catch (exception: Exception) {\n                withContext(Dispatchers.Main) {\n                    callback.onError(exception)\n                }\n            } finally {\n                try {\n                    output?.close()\n                    input?.close()\n                } catch (_: IOException) {\n                }\n\n                connection?.disconnect()\n            }\n        }\n    }\n\n    interface DownloadListener {\n        fun onProgress(progress: Int)\n        fun onError(exception: Exception)\n        fun onComplete()\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/helper/FileHelper.kt",
    "content": "package io.github.saeeddev94.xray.helper\n\nimport java.io.File\n\nclass FileHelper {\n\n    companion object {\n        fun createOrUpdate(file: File, content: String) {\n            val fileContent = if (file.exists()) file.bufferedReader().use { it.readText() } else \"\"\n            if (content != fileContent) file.writeText(content)\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/helper/HttpHelper.kt",
    "content": "package io.github.saeeddev94.xray.helper\n\nimport io.github.saeeddev94.xray.BuildConfig\nimport io.github.saeeddev94.xray.Settings\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport java.net.Authenticator\nimport java.net.HttpURLConnection\nimport java.net.InetSocketAddress\nimport java.net.PasswordAuthentication\nimport java.net.Proxy\nimport java.net.URL\n\nclass HttpHelper(\n    val scope: CoroutineScope,\n    val settings: Settings,\n) {\n\n    companion object {\n        private fun getConnection(\n            link: String,\n            method: String = \"GET\",\n            proxy: Proxy? = null,\n            timeout: Int = 5000,\n            userAgent: String? = null,\n        ): HttpURLConnection {\n            val url = URL(link)\n            val connection = if (proxy == null) {\n                url.openConnection() as HttpURLConnection\n            } else {\n                url.openConnection(proxy) as HttpURLConnection\n            }\n            connection.requestMethod = method\n            connection.connectTimeout = timeout\n            connection.readTimeout = timeout\n            userAgent?.let { connection.setRequestProperty(\"User-Agent\", it) }\n            connection.setRequestProperty(\"Connection\", \"close\")\n            return connection\n        }\n\n        suspend fun get(link: String, userAgent: String? = null): String {\n            return withContext(Dispatchers.IO) {\n                val defaultUserAgent = \"${BuildConfig.APPLICATION_ID}/${BuildConfig.VERSION_NAME}\"\n                val connection = getConnection(link, userAgent = userAgent ?: defaultUserAgent)\n                var responseCode = 0\n                val responseBody = try {\n                    connection.connect()\n                    responseCode = connection.responseCode\n                    connection.inputStream.bufferedReader().use { it.readText() }\n                } catch (_: Exception) {\n                    null\n                } finally {\n                    connection.disconnect()\n                }\n                if (responseCode != HttpURLConnection.HTTP_OK || responseBody == null) {\n                    throw Exception(\"HTTP Error: $responseCode\")\n                }\n                responseBody\n            }\n        }\n    }\n\n    fun measureDelay(proxy: Boolean, callback: (result: String) -> Unit) {\n        scope.launch(Dispatchers.IO) {\n            val start = System.currentTimeMillis()\n            val connection = getConnection(proxy)\n            var result = \"HTTP {status}, {delay} ms\"\n\n            result = try {\n                setSocksAuth(getSocksAuth())\n                val responseCode = connection.responseCode\n                result.replace(\"{status}\", \"$responseCode\")\n            } catch (error: Exception) {\n                error.message ?: \"Http delay measure failed\"\n            } finally {\n                connection.disconnect()\n                setSocksAuth(null)\n            }\n\n            val delay = System.currentTimeMillis() - start\n            withContext(Dispatchers.Main) {\n                callback(result.replace(\"{delay}\", \"$delay\"))\n            }\n        }\n    }\n\n    private suspend fun getConnection(withProxy: Boolean): HttpURLConnection {\n        return withContext(Dispatchers.IO) {\n            val link = settings.pingAddress\n            val method = \"HEAD\"\n            val address = InetSocketAddress(settings.socksAddress, settings.socksPort.toInt())\n            val proxy = if (withProxy) Proxy(Proxy.Type.SOCKS, address) else null\n            val timeout = settings.pingTimeout * 1000\n\n            getConnection(link, method, proxy, timeout)\n        }\n    }\n\n    private fun getSocksAuth(): Authenticator? {\n        if (\n            settings.socksUsername.trim().isEmpty() || settings.socksPassword.trim().isEmpty()\n        ) return null\n        return object : Authenticator() {\n            override fun getPasswordAuthentication(): PasswordAuthentication {\n                return PasswordAuthentication(\n                    settings.socksUsername,\n                    settings.socksPassword.toCharArray()\n                )\n            }\n        }\n    }\n\n    private fun setSocksAuth(auth: Authenticator?) {\n        Authenticator.setDefault(auth)\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/helper/IntentHelper.kt",
    "content": "package io.github.saeeddev94.xray.helper\n\nimport android.content.Intent\nimport android.os.Build\n\nclass IntentHelper {\n    companion object {\n        fun <T> getParcelable(intent: Intent, name: String, clazz: Class<T>): T? {\n            return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {\n                intent.getParcelableExtra(name, clazz)\n            } else {\n                @Suppress(\"deprecation\")\n                intent.getParcelableExtra(name)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/helper/JsonHelper.kt",
    "content": "package io.github.saeeddev94.xray.helper\n\nimport org.json.JSONArray\nimport org.json.JSONObject\n\nclass JsonHelper {\n    companion object {\n        fun makeObject(value: String) = JSONObject(value)\n\n        fun makeArray(value: String) = JSONArray(value)\n\n        fun getObject(value: JSONObject, key: String) =\n            value.optJSONObject(key) ?: JSONObject()\n\n        fun getArray(value: JSONObject, key: String) =\n            value.optJSONArray(key) ?: JSONArray()\n\n        fun mergeObjects(obj1: JSONObject, obj2: JSONObject): JSONObject {\n            val result = JSONObject(obj1.toString())\n\n            for (key in obj2.keys()) {\n                val value2 = obj2[key]\n                if (result.has(key)) {\n                    val value1 = result[key]\n                    when {\n                        value1 is JSONObject && value2 is JSONObject -> {\n                            result.put(key, mergeObjects(value1, value2))\n                        }\n                        value1 is JSONArray && value2 is JSONArray -> {\n                            result.put(key, mergeArrays(value1, value2))\n                        }\n                        else -> result.put(key, value2)\n                    }\n                } else result.put(key, value2)\n            }\n\n            return result\n        }\n\n        fun mergeArrays(arr1: JSONArray, arr2: JSONArray, mergeKey: String = \"\"): JSONArray {\n            val result = JSONArray()\n\n            for (i in 0 until arr1.length()) result.put(arr1[i])\n\n            for (i in 0 until arr2.length()) {\n                val value2 = arr2[i]\n                if (value2 is JSONObject && value2.has(mergeKey)) {\n                    val keyValue = value2[mergeKey]\n                    var merged = false\n                    for (j in 0 until result.length()) {\n                        val value1 = result[j]\n                        if (value1 is JSONObject && value1.has(mergeKey) && value1[mergeKey] == keyValue) {\n                            result.put(j, mergeObjects(value1, value2))\n                            merged = true\n                            break\n                        }\n                    }\n                    if (!merged) result.put(value2)\n                } else result.put(value2)\n            }\n\n            return result\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/helper/LinkHelper.kt",
    "content": "package io.github.saeeddev94.xray.helper\n\nimport XrayCore.XrayCore\nimport android.util.Base64\nimport io.github.saeeddev94.xray.Settings\nimport org.json.JSONArray\nimport org.json.JSONException\nimport org.json.JSONObject\nimport java.net.URI\n\nclass LinkHelper(\n    private val settings: Settings,\n    link: String\n) {\n\n    private val success: Boolean\n    private val outbound: JSONObject?\n    private var remark: String = REMARK_DEFAULT\n\n    init {\n        val base64: String = XrayCore.json(link)\n        val decoded = tryDecodeBase64(base64)\n        val response = try {\n            JSONObject(decoded)\n        } catch (_: JSONException) {\n            JSONObject()\n        }\n        val data = response.optJSONObject(\"data\") ?: JSONObject()\n        val outbounds = data.optJSONArray(\"outbounds\") ?: JSONArray()\n        success = response.optBoolean(\"success\", false)\n        outbound = if (outbounds.length() > 0) outbounds[0] as JSONObject else null\n    }\n\n    companion object {\n        const val REMARK_DEFAULT = \"New Profile\"\n        const val LINK_DEFAULT = \"New Link\"\n\n        fun remark(uri: URI, default: String = \"\"): String {\n            val name = uri.fragment ?: \"\"\n            return name.ifEmpty { default }\n        }\n\n        fun tryDecodeBase64(value: String): String {\n            return runCatching {\n                val byteArray = Base64.decode(value, Base64.DEFAULT)\n                String(byteArray)\n            }.getOrNull() ?: value\n        }\n    }\n\n    fun isValid(): Boolean {\n        return success && outbound != null\n    }\n\n    fun json(): String {\n        return config().toString(2) + \"\\n\"\n    }\n\n    fun remark(): String {\n        return remark\n    }\n\n    private fun log(): JSONObject {\n        val log = JSONObject()\n        log.put(\"loglevel\", \"warning\")\n        return log\n    }\n\n    private fun dns(): JSONObject {\n        val dns = JSONObject()\n        val servers = JSONArray()\n        servers.put(settings.primaryDns)\n        servers.put(settings.secondaryDns)\n        dns.put(\"servers\", servers)\n        return dns\n    }\n\n    private fun inbounds(): JSONArray {\n        val inbounds = JSONArray()\n\n        val sniffing = JSONObject()\n        sniffing.put(\"enabled\", true)\n        val sniffingDestOverride = JSONArray()\n        sniffingDestOverride.put(\"http\")\n        sniffingDestOverride.put(\"tls\")\n        sniffingDestOverride.put(\"quic\")\n        sniffing.put(\"destOverride\", sniffingDestOverride)\n\n        val tproxy = JSONObject()\n        tproxy.put(\"listen\", settings.tproxyAddress)\n        tproxy.put(\"port\", settings.tproxyPort)\n        tproxy.put(\"protocol\", \"dokodemo-door\")\n\n        val tproxySettings = JSONObject()\n        tproxySettings.put(\"network\", \"tcp,udp\")\n        tproxySettings.put(\"followRedirect\", true)\n\n        val tproxySockopt = JSONObject()\n        tproxySockopt.put(\"tproxy\", \"tproxy\")\n\n        val tproxyStreamSettings = JSONObject()\n        tproxyStreamSettings.put(\"sockopt\", tproxySockopt)\n\n        tproxy.put(\"settings\", tproxySettings)\n        tproxy.put(\"sniffing\", sniffing)\n        tproxy.put(\"streamSettings\", tproxyStreamSettings)\n        tproxy.put(\"tag\", \"all-in\")\n\n        val socks = JSONObject()\n        socks.put(\"listen\", settings.socksAddress)\n        socks.put(\"port\", settings.socksPort.toInt())\n        socks.put(\"protocol\", \"socks\")\n\n        val socksSettings = JSONObject()\n        socksSettings.put(\"udp\", true)\n        if (\n            settings.socksUsername.trim().isNotEmpty() &&\n            settings.socksPassword.trim().isNotEmpty()\n        ) {\n            val account = JSONObject()\n            account.put(\"user\", settings.socksUsername)\n            account.put(\"pass\", settings.socksPassword)\n            val accounts = JSONArray()\n            accounts.put(account)\n\n            socksSettings.put(\"auth\", \"password\")\n            socksSettings.put(\"accounts\", accounts)\n        }\n\n        socks.put(\"settings\", socksSettings)\n        socks.put(\"sniffing\", sniffing)\n        socks.put(\"tag\", \"socks\")\n\n        if (settings.transparentProxy) inbounds.put(tproxy)\n        else inbounds.put(socks)\n\n        return inbounds\n    }\n\n    private fun outbounds(): JSONArray {\n        val outbounds = JSONArray()\n\n        val proxy = JSONObject(outbound!!.toString())\n        if (proxy.has(\"sendThrough\")) {\n            remark = proxy.optString(\"sendThrough\", REMARK_DEFAULT)\n            proxy.remove(\"sendThrough\")\n        }\n        proxy.put(\"tag\", \"proxy\")\n\n        val direct = JSONObject()\n        direct.put(\"protocol\", \"freedom\")\n        direct.put(\"tag\", \"direct\")\n\n        val block = JSONObject()\n        block.put(\"protocol\", \"blackhole\")\n        block.put(\"tag\", \"block\")\n\n        val dns = JSONObject()\n        dns.put(\"protocol\", \"dns\")\n        dns.put(\"tag\", \"dns-out\")\n\n        outbounds.put(proxy)\n        outbounds.put(direct)\n        outbounds.put(block)\n        if (settings.transparentProxy) outbounds.put(dns)\n\n        return outbounds\n    }\n\n    private fun routing(): JSONObject {\n        val routing = JSONObject()\n        routing.put(\"domainStrategy\", \"IPIfNonMatch\")\n\n        val rules = JSONArray()\n\n        val proxyDns = JSONObject()\n\n        if (settings.transparentProxy) {\n            val inboundTag = JSONArray()\n            inboundTag.put(\"all-in\")\n            proxyDns.put(\"network\", \"udp\")\n            proxyDns.put(\"port\", 53)\n            proxyDns.put(\"inboundTag\", inboundTag)\n            proxyDns.put(\"outboundTag\", \"dns-out\")\n        } else {\n            val proxyDnsIp = JSONArray()\n            proxyDnsIp.put(settings.primaryDns)\n            proxyDnsIp.put(settings.secondaryDns)\n            proxyDns.put(\"ip\", proxyDnsIp)\n            proxyDns.put(\"port\", 53)\n            proxyDns.put(\"outboundTag\", \"proxy\")\n        }\n\n        val directPrivate = JSONObject()\n        val directPrivateIp = JSONArray()\n        directPrivateIp.put(\"geoip:private\")\n        directPrivate.put(\"ip\", directPrivateIp)\n        directPrivate.put(\"outboundTag\", \"direct\")\n\n        rules.put(proxyDns)\n        rules.put(directPrivate)\n\n        routing.put(\"rules\", rules)\n\n        return routing\n    }\n\n    private fun config(): JSONObject {\n        val config = JSONObject()\n        config.put(\"log\", log())\n        config.put(\"dns\", dns())\n        config.put(\"inbounds\", inbounds())\n        config.put(\"outbounds\", outbounds())\n        config.put(\"routing\", routing())\n        return config\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/helper/NetworkStateHelper.kt",
    "content": "package io.github.saeeddev94.xray.helper\n\nimport com.topjohnwu.superuser.Shell\nimport io.github.saeeddev94.xray.service.TProxyService\nimport java.io.File\n\nclass NetworkStateHelper() {\n\n    fun monitor(script: File, pid: File) {\n        if (!script.exists()) makeScript(script)\n        val file = \"/data/misc/net/rt_tables\"\n        val cmd = \"nohup inotifyd ${script.absolutePath} $file:w\" +\n                \" > /dev/null 2>&1 & echo $! > ${pid.absolutePath}\"\n        Shell.cmd(cmd).exec()\n    }\n\n    fun getState(): NetworkState {\n        return NetworkState(getWifi(), getData())\n    }\n\n    fun isOnline(state: NetworkState): Boolean {\n        return state.wifi != null || state.data\n    }\n\n    private fun makeScript(file: File) {\n        val pkg = TProxyService.PKG_NAME\n        val action = TProxyService.NETWORK_UPDATE_SERVICE_ACTION_NAME\n        val content = arrayListOf(\n            \"#!/bin/sh\",\n            \"\",\n            \"am start-foreground-service -n $pkg/.service.TProxyService -a $action\",\n            \"\",\n        ).joinToString(\"\\n\")\n        FileHelper.createOrUpdate(file, content)\n        Shell.cmd(\"chown root:root ${file.absolutePath}\").exec()\n        Shell.cmd(\"chmod +x ${file.absolutePath}\").exec()\n    }\n\n    private fun getWifi(): String? {\n        val cmd = \"dumpsys wifi\" +\n                \" | grep 'mWifiInfo SSID'\" +\n                \" | awk -F 'SSID: ' '{print $2}'\" +\n                \" | awk -F ',' '{print $1}'\" +\n                \" | head -n 1\"\n        val result = Shell.cmd(cmd).exec()\n        if (!result.isSuccess || result.out.isEmpty()) return null\n        val ssid = result.out.first()\n        if (ssid == \"<unknown ssid>\") return null\n        return ssid.trim('\"')\n    }\n\n    private fun getData(): Boolean {\n        val cmd = \"settings get global mobile_data\" +\n                \" && settings get global mobile_data1\" +\n                \" && settings get global mobile_data2\"\n        val result = Shell.cmd(cmd).exec()\n        if (!result.isSuccess || result.out.isEmpty()) return false\n        return result.out.contains(\"1\")\n    }\n\n    data class NetworkState(\n        val wifi: String?,\n        val data: Boolean,\n    )\n}\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/helper/ProfileTouchHelper.kt",
    "content": "package io.github.saeeddev94.xray.helper\n\nimport androidx.recyclerview.widget.ItemTouchHelper\nimport androidx.recyclerview.widget.RecyclerView\n\nclass ProfileTouchHelper(private var adapter: ProfileTouchCallback) : ItemTouchHelper.Callback() {\n\n    private var startPosition: Int = -1\n\n    override fun getMovementFlags(\n        recyclerView: RecyclerView,\n        viewHolder: RecyclerView.ViewHolder\n    ): Int = makeMovementFlags(ItemTouchHelper.UP or ItemTouchHelper.DOWN, 0)\n\n    override fun onMove(\n        recyclerView: RecyclerView,\n        source: RecyclerView.ViewHolder,\n        target: RecyclerView.ViewHolder\n    ): Boolean = adapter.onItemMoved(source.absoluteAdapterPosition, target.absoluteAdapterPosition)\n\n    override fun onSwiped(\n        viewHolder: RecyclerView.ViewHolder,\n        direction: Int\n    ) {\n    }\n\n    override fun onSelectedChanged(\n        viewHolder: RecyclerView.ViewHolder?,\n        actionState: Int\n    ) {\n        if (actionState != ItemTouchHelper.ACTION_STATE_DRAG) return\n        startPosition = viewHolder!!.absoluteAdapterPosition\n    }\n\n    override fun clearView(\n        recyclerView: RecyclerView,\n        viewHolder: RecyclerView.ViewHolder\n    ) {\n        val endPosition = viewHolder.absoluteAdapterPosition\n        adapter.onItemMoveCompleted(startPosition, endPosition)\n    }\n\n    interface ProfileTouchCallback {\n        fun onItemMoved(fromPosition: Int, toPosition: Int): Boolean\n        fun onItemMoveCompleted(startPosition: Int, endPosition: Int)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/helper/TransparentProxyHelper.kt",
    "content": "package io.github.saeeddev94.xray.helper\n\nimport android.content.Context\nimport com.topjohnwu.superuser.Shell\nimport io.github.saeeddev94.xray.BuildConfig\nimport io.github.saeeddev94.xray.Settings\nimport io.github.saeeddev94.xray.service.TProxyService\nimport java.io.FileOutputStream\n\nclass TransparentProxyHelper(\n    private val context: Context,\n    private val settings: Settings,\n) {\n\n    private val networkStateHelper by lazy { NetworkStateHelper() }\n\n    fun isRunning(): Boolean = settings.xrayCorePid().exists()\n\n    fun startService() {\n        makeConfig()\n        Shell.cmd(\"${cmd()} service start\").exec()\n    }\n\n    fun stopService() {\n        Shell.cmd(\"${cmd()} service stop\").exec()\n    }\n\n    fun enableProxy() {\n        Shell.cmd(\"${cmd()} proxy enable\").exec()\n    }\n\n    fun disableProxy() {\n        Shell.cmd(\"${cmd()} proxy disable\").exec()\n    }\n\n    fun refreshProxy() {\n        Shell.cmd(\"${cmd()} proxy refresh\").exec()\n    }\n\n    fun kill() {\n        if (settings.networkMonitorPid().exists()) {\n            val path = settings.networkMonitorPid().absolutePath\n            Shell.cmd(\"kill $(cat $path) && rm $path\").exec()\n        }\n        if (settings.xrayCorePid().exists()) {\n            disableProxy()\n            stopService()\n        }\n    }\n\n    fun monitorNetwork() {\n        val script = settings.networkMonitorScript()\n        val pid = settings.networkMonitorPid()\n        if (!settings.tproxyAutoConnect || !settings.transparentProxy || pid.exists()) return\n        networkStateHelper.monitor(script, pid)\n    }\n\n    fun networkState(): NetworkStateHelper.NetworkState {\n        return networkStateHelper.getState()\n    }\n\n    fun bypassWiFi(state: NetworkStateHelper.NetworkState): Boolean {\n        val tproxyBypassWiFi = settings.tproxyBypassWiFi\n        val ssid = state.wifi ?: \"\"\n        return tproxyBypassWiFi.isNotEmpty() && tproxyBypassWiFi.contains(ssid)\n    }\n\n    fun networkUpdate(value: NetworkStateHelper.NetworkState? = null) {\n        if (!settings.tproxyAutoConnect) return\n        val state = value ?: networkState()\n        val isOnline = networkStateHelper.isOnline(state)\n        val isRunning = settings.xrayCorePid().exists()\n        if (!isOnline || bypassWiFi(state)) {\n            TProxyService.stop(context)\n            return\n        }\n        if (!isRunning) {\n            TProxyService.start(context, false)\n            return\n        }\n        refreshProxy()\n    }\n\n    fun install() {\n        val xrayHelper = settings.xrayHelperFile()\n        val appVersionCode = BuildConfig.VERSION_CODE\n        val xrayHelperVersionCode = settings.xrayHelperVersionCode\n        if (xrayHelper.exists() && xrayHelperVersionCode == appVersionCode) return\n        if (xrayHelper.exists()) xrayHelper.delete()\n        context.assets.open(xrayHelper.name).use { input ->\n            FileOutputStream(xrayHelper).use { output ->\n                input.copyTo(output)\n            }\n        }\n        Shell.cmd(\"chown root:root ${xrayHelper.absolutePath}\").exec()\n        Shell.cmd(\"chmod +x ${xrayHelper.absolutePath}\").exec()\n        settings.xrayHelperVersionCode = appVersionCode\n    }\n\n    private fun cmd(): String {\n        return arrayListOf(\n            settings.xrayHelperFile().absolutePath,\n            \"-c\",\n            settings.xrayHelperConfig().absolutePath,\n        ).joinToString(\" \")\n    }\n\n    private fun makeConfig() {\n        val yml = arrayListOf(\n            \"xrayHelper:\",\n            \"  coreType: xray\",\n            \"  corePath: ${settings.xrayCoreFile().absolutePath}\",\n            \"  coreConfig: ${settings.xrayConfig().absolutePath}\",\n            \"  dataDir: ${settings.baseDir().absolutePath}\",\n            \"  runDir: ${settings.baseDir().absolutePath}\",\n            \"proxy:\",\n            \"  method: tproxy\",\n            \"  tproxyPort: ${settings.tproxyPort}\",\n            \"  enableIPv6: ${settings.enableIpV6}\",\n            \"  mode: ${if (settings.appsRoutingMode) \"blacklist\" else \"whitelist\"}\",\n        )\n\n        val appsList = settings.appsRouting.split(\"\\n\")\n            .map { it.trim() }\n            .filter { it.trim().isNotBlank() }\n        if (appsList.isNotEmpty()) {\n            yml.add(\"  pkgList:\")\n            appsList.forEach { yml.add(\"    - $it\") }\n        }\n\n        val includedInterfaces = arrayListOf<String>()\n        val excludedInterfaces = arrayListOf<String>()\n        if (settings.tproxyHotspot) includedInterfaces.add(settings.hotspotInterface)\n        else excludedInterfaces.add(settings.hotspotInterface)\n        if (settings.tproxyTethering) includedInterfaces.add(settings.tetheringInterface)\n        else excludedInterfaces.add(settings.tetheringInterface)\n        if (includedInterfaces.isNotEmpty()) {\n            yml.add(\"  apList:\")\n            includedInterfaces.forEach { yml.add(\"    - $it\") }\n        }\n        if (excludedInterfaces.isNotEmpty()) {\n            yml.add(\"  ignoreList:\")\n            excludedInterfaces.forEach { yml.add(\"    - $it\") }\n        }\n\n        yml.add(\"\")\n        FileHelper.createOrUpdate(\n            settings.xrayHelperConfig(),\n            yml.joinToString(\"\\n\")\n        )\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/receiver/BootReceiver.kt",
    "content": "package io.github.saeeddev94.xray.receiver\n\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport android.content.Intent\nimport android.os.SystemClock\nimport io.github.saeeddev94.xray.Settings\nimport io.github.saeeddev94.xray.helper.TransparentProxyHelper\nimport io.github.saeeddev94.xray.service.TProxyService\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.SupervisorJob\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\n\nclass BootReceiver : BroadcastReceiver() {\n\n    override fun onReceive(context: Context?, intent: Intent?) {\n        val systemUpTime = SystemClock.elapsedRealtime()\n        val twoMinutes = 2L * 60L * 1000L\n        val isAppLaunch = systemUpTime > twoMinutes\n        if (\n            context == null ||\n            intent == null ||\n            intent.action != Intent.ACTION_BOOT_COMPLETED ||\n            isAppLaunch\n        ) return\n        val settings = Settings(context)\n        val xrayCorePid = settings.xrayCorePid()\n        val networkMonitorPid = settings.networkMonitorPid()\n        if (xrayCorePid.exists()) xrayCorePid.delete()\n        if (networkMonitorPid.exists()) networkMonitorPid.delete()\n        if (!settings.bootAutoStart) {\n            TProxyService.stop(context)\n            return\n        }\n        if (settings.transparentProxy) {\n            val pendingResult = goAsync()\n            val transparentProxyHelper = TransparentProxyHelper(context, settings)\n            CoroutineScope(SupervisorJob() + Dispatchers.IO).launch {\n                val state = transparentProxyHelper.networkState()\n                val bypassWiFi = transparentProxyHelper.bypassWiFi(state)\n                transparentProxyHelper.monitorNetwork()\n                withContext(Dispatchers.Main) {\n                    if (bypassWiFi) TProxyService.stop(context)\n                    else TProxyService.start(context, false)\n                    pendingResult.finish()\n                }\n            }\n            return\n        }\n        TProxyService.start(context, settings.tun2socks)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/receiver/VpnActionReceiver.kt",
    "content": "package io.github.saeeddev94.xray.receiver\n\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport android.content.Intent\nimport io.github.saeeddev94.xray.R\nimport io.github.saeeddev94.xray.service.TProxyService\nimport io.github.saeeddev94.xray.service.VpnTileService\n\nclass VpnActionReceiver : BroadcastReceiver() {\n\n    override fun onReceive(context: Context?, intent: Intent?) {\n        if (context == null || intent == null) return\n        val allowed = listOf(\n            TProxyService.START_VPN_SERVICE_ACTION_NAME,\n            TProxyService.STOP_VPN_SERVICE_ACTION_NAME,\n            TProxyService.NEW_CONFIG_SERVICE_ACTION_NAME,\n        )\n        val action = intent.action ?: \"\"\n        val label = intent.getStringExtra(\"profile\") ?: context.getString(R.string.appName)\n        if (!allowed.contains(action)) return\n        Intent(context, VpnTileService::class.java).also {\n            it.putExtra(\"action\", action)\n            it.putExtra(\"label\", label)\n            context.startService(it)\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/repository/ConfigRepository.kt",
    "content": "package io.github.saeeddev94.xray.repository\n\nimport io.github.saeeddev94.xray.database.Config\nimport io.github.saeeddev94.xray.database.ConfigDao\n\nclass ConfigRepository(private val configDao: ConfigDao) {\n\n    suspend fun get(): Config {\n        val config = configDao.get()\n        if (config != null) return config\n        return Config().also { configDao.insert(it) }\n    }\n\n    suspend fun update(config: Config) {\n        configDao.update(config)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/repository/LinkRepository.kt",
    "content": "package io.github.saeeddev94.xray.repository\n\nimport io.github.saeeddev94.xray.database.Link\nimport io.github.saeeddev94.xray.database.LinkDao\n\nclass LinkRepository(private val linkDao: LinkDao) {\n\n    val all = linkDao.all()\n    val tabs = linkDao.tabs()\n\n    suspend fun activeLinks(): List<Link> {\n        return linkDao.activeLinks()\n    }\n\n    suspend fun insert(link: Link) {\n        linkDao.insert(link)\n    }\n\n    suspend fun update(link: Link) {\n        linkDao.update(link)\n    }\n\n    suspend fun delete(link: Link) {\n        linkDao.delete(link)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/repository/ProfileRepository.kt",
    "content": "package io.github.saeeddev94.xray.repository\n\nimport io.github.saeeddev94.xray.database.Profile\nimport io.github.saeeddev94.xray.database.ProfileDao\n\nclass ProfileRepository(private val profileDao: ProfileDao) {\n\n    val all = profileDao.all()\n\n    suspend fun linkProfiles(linkId: Long): List<Profile> {\n        return profileDao.linkProfiles(linkId)\n    }\n\n    suspend fun find(id: Long): Profile {\n        return profileDao.find(id)\n    }\n\n    suspend fun update(profile: Profile) {\n        profileDao.update(profile)\n    }\n\n    suspend fun create(profile: Profile) {\n        profileDao.create(profile)\n    }\n\n    suspend fun remove(profile: Profile) {\n        profileDao.remove(profile)\n    }\n\n    suspend fun updateIndex(index: Int, id: Long) {\n        profileDao.updateIndex(index, id)\n    }\n\n    suspend fun moveUp(start: Int, end: Int, exclude: Long) {\n        profileDao.moveUp(start, end, exclude)\n    }\n\n    suspend fun moveDown(start: Int, end: Int, exclude: Long) {\n        profileDao.moveDown(start, end, exclude)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/service/TProxyService.kt",
    "content": "package io.github.saeeddev94.xray.service\n\nimport XrayCore.XrayCore\nimport android.annotation.SuppressLint\nimport android.app.Notification\nimport android.app.NotificationChannel\nimport android.app.NotificationManager\nimport android.app.PendingIntent\nimport android.content.Context\nimport android.content.Intent\nimport android.net.ConnectivityManager\nimport android.net.Network\nimport android.net.NetworkCapabilities\nimport android.net.NetworkRequest\nimport android.net.VpnService\nimport android.os.Build\nimport android.os.Handler\nimport android.os.Looper\nimport android.os.ParcelFileDescriptor\nimport android.util.Log\nimport android.widget.Toast\nimport androidx.core.app.NotificationCompat\nimport io.github.saeeddev94.xray.BuildConfig\nimport io.github.saeeddev94.xray.R\nimport io.github.saeeddev94.xray.Settings\nimport io.github.saeeddev94.xray.Xray\nimport io.github.saeeddev94.xray.activity.MainActivity\nimport io.github.saeeddev94.xray.database.Config\nimport io.github.saeeddev94.xray.database.Profile\nimport io.github.saeeddev94.xray.dto.XrayConfig\nimport io.github.saeeddev94.xray.helper.ConfigHelper\nimport io.github.saeeddev94.xray.helper.FileHelper\nimport io.github.saeeddev94.xray.helper.TransparentProxyHelper\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.SupervisorJob\nimport kotlinx.coroutines.cancel\nimport kotlinx.coroutines.launch\nimport java.io.File\nimport kotlin.reflect.cast\n\n@SuppressLint(\"VpnServicePolicy\")\nclass TProxyService : VpnService() {\n\n    companion object {\n        init {\n            System.loadLibrary(\"hev-socks5-tunnel\")\n        }\n\n        const val PKG_NAME = BuildConfig.APPLICATION_ID\n        const val STATUS_VPN_SERVICE_ACTION_NAME = \"$PKG_NAME.VpnStatus\"\n        const val STOP_VPN_SERVICE_ACTION_NAME = \"$PKG_NAME.VpnStop\"\n        const val START_VPN_SERVICE_ACTION_NAME = \"$PKG_NAME.VpnStart\"\n        const val NEW_CONFIG_SERVICE_ACTION_NAME = \"$PKG_NAME.NewConfig\"\n        const val NETWORK_UPDATE_SERVICE_ACTION_NAME = \"$PKG_NAME.NetworkUpdate\"\n        private const val VPN_SERVICE_NOTIFICATION_ID = 1\n        private const val OPEN_MAIN_ACTIVITY_ACTION_ID = 2\n        private const val STOP_VPN_SERVICE_ACTION_ID = 3\n\n        fun status(context: Context) = startCommand(context, STATUS_VPN_SERVICE_ACTION_NAME)\n        fun stop(context: Context) = startCommand(context, STOP_VPN_SERVICE_ACTION_NAME)\n        fun newConfig(context: Context) = startCommand(context, NEW_CONFIG_SERVICE_ACTION_NAME)\n\n        fun start(context: Context, check: Boolean) {\n            if (check && prepare(context) != null) {\n                Log.e(\n                    \"TProxyService\",\n                    \"Can't start: VpnService#prepare(): needs user permission\"\n                )\n                return\n            }\n            startCommand(context, START_VPN_SERVICE_ACTION_NAME, true)\n        }\n\n        private fun startCommand(context: Context, name: String, foreground: Boolean = false) {\n            Intent(context, TProxyService::class.java).also {\n                it.action = name\n                if (foreground) {\n                    context.startForegroundService(it)\n                } else {\n                    context.startService(it)\n                }\n            }\n        }\n    }\n\n    private val notificationManager by lazy { getSystemService(NotificationManager::class.java) }\n    private val connectivityManager by lazy { getSystemService(ConnectivityManager::class.java) }\n    private val settings by lazy { Settings(applicationContext) }\n    private val transparentProxyHelper by lazy { TransparentProxyHelper(this, settings) }\n    private val configRepository by lazy { Xray::class.cast(application).configRepository }\n    private val profileRepository by lazy { Xray::class.cast(application).profileRepository }\n    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)\n\n    private var isRunning: Boolean = false\n    private var tunDevice: ParcelFileDescriptor? = null\n    private var cellularCallback: ConnectivityManager.NetworkCallback? = null\n    private var toast: Toast? = null\n\n    private external fun TProxyStartService(configPath: String, fd: Int)\n    private external fun TProxyStopService()\n    private external fun TProxyGetStats(): LongArray\n\n    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {\n        scope.launch {\n            when (intent?.action) {\n                START_VPN_SERVICE_ACTION_NAME -> start(getProfile(), globalConfigs())\n                NEW_CONFIG_SERVICE_ACTION_NAME -> newConfig(getProfile(), globalConfigs())\n                STOP_VPN_SERVICE_ACTION_NAME -> stopVPN()\n                STATUS_VPN_SERVICE_ACTION_NAME -> broadcastStatus()\n                NETWORK_UPDATE_SERVICE_ACTION_NAME -> transparentProxyHelper.networkUpdate()\n            }\n        }\n        return START_STICKY\n    }\n\n    override fun onRevoke() {\n        stopVPN()\n    }\n\n    override fun onDestroy() {\n        scope.cancel()\n        cellularCallback?.let { connectivityManager.unregisterNetworkCallback(it) }\n        cellularCallback = null\n        toast = null\n        super.onDestroy()\n    }\n\n    private fun configName(profile: Profile?): String = profile?.name ?: settings.tunName\n\n    private fun getIsRunning(): Boolean {\n        return if (settings.transparentProxy) {\n            transparentProxyHelper.isRunning()\n        } else {\n            isRunning\n        }\n    }\n\n    private suspend fun getProfile(): Profile? {\n        return if (settings.selectedProfile == 0L) {\n            null\n        } else {\n            profileRepository.find(settings.selectedProfile)\n        }\n    }\n\n    private suspend fun globalConfigs(): Config {\n        return configRepository.get()\n    }\n\n    private fun getConfig(profile: Profile, globalConfigs: Config): XrayConfig? {\n        val dir: File = applicationContext.filesDir\n        val config: File = settings.xrayConfig()\n        val configHelper = runCatching { ConfigHelper(settings, globalConfigs, profile.config) }\n        val error: String = if (configHelper.isSuccess) {\n            FileHelper.createOrUpdate(config, configHelper.getOrNull().toString())\n            XrayCore.test(dir.absolutePath, config.absolutePath)\n        } else {\n            configHelper.exceptionOrNull()?.message ?: getString(R.string.invalidProfile)\n        }\n        if (error.isNotEmpty()) {\n            showToast(error)\n            return null\n        }\n        return XrayConfig(dir.absolutePath, config.absolutePath)\n    }\n\n    private fun start(profile: Profile?, globalConfigs: Config) {\n        if (profile == null) return\n        getConfig(profile, globalConfigs)?.let {\n            startXray(it)\n            startVPN(profile)\n        }\n    }\n\n    private fun newConfig(profile: Profile?, globalConfigs: Config) {\n        if (!getIsRunning() || profile == null) return\n        stopXray()\n        getConfig(profile, globalConfigs).also {\n            if (it == null) stopVPN() else startXray(it)\n        }?.let {\n            val name = configName(profile)\n            val notification = createNotification(name)\n            showToast(name)\n            broadcastStart(NEW_CONFIG_SERVICE_ACTION_NAME, name)\n            notificationManager.notify(VPN_SERVICE_NOTIFICATION_ID, notification)\n        }\n    }\n\n    private fun startXray(config: XrayConfig) {\n        if (settings.transparentProxy) transparentProxyHelper.startService()\n        else XrayCore.start(config.dir, config.file)\n    }\n\n    private fun stopXray() {\n        if (settings.transparentProxy) transparentProxyHelper.stopService()\n        else XrayCore.stop()\n    }\n\n    private fun startVPN(profile: Profile?) {\n        if (settings.transparentProxy) {\n            transparentProxyHelper.enableProxy()\n            transparentProxyHelper.monitorNetwork()\n        } else if (settings.tun2socks) {\n            /** Create Tun */\n            val tun = Builder()\n            val tunName = getString(R.string.appName)\n\n            /** Basic tun config */\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) tun.setMetered(false)\n            tun.setMtu(settings.tunMtu)\n            tun.setSession(tunName)\n\n            /** IPv4 */\n            tun.addAddress(settings.tunAddress, settings.tunPrefix)\n            tun.addDnsServer(settings.primaryDns)\n            tun.addDnsServer(settings.secondaryDns)\n\n            /** IPv6 */\n            if (settings.enableIpV6) {\n                tun.addAddress(settings.tunAddressV6, settings.tunPrefixV6)\n                tun.addDnsServer(settings.primaryDnsV6)\n                tun.addDnsServer(settings.secondaryDnsV6)\n                tun.addRoute(\"::\", 0)\n            }\n\n            /** Bypass LAN (IPv4) */\n            if (settings.bypassLan) {\n                settings.tunRoutes.forEach {\n                    val address = it.split('/')\n                    tun.addRoute(address[0], address[1].toInt())\n                }\n            } else {\n                tun.addRoute(\"0.0.0.0\", 0)\n            }\n\n            /** Apps Routing */\n            if (settings.appsRoutingMode) tun.addDisallowedApplication(applicationContext.packageName)\n            settings.appsRouting.split(\"\\n\").forEach {\n                val packageName = it.trim()\n                if (packageName.isBlank()) return@forEach\n                if (settings.appsRoutingMode) tun.addDisallowedApplication(packageName)\n                else tun.addAllowedApplication(packageName)\n            }\n\n            /** Build tun device */\n            tunDevice = tun.establish()\n\n            /** Check tun device */\n            if (tunDevice == null) {\n                Log.e(\"TProxyService\", \"tun#establish failed\")\n                return\n            }\n\n            /** Create, Update tun2socks config */\n            val tun2socksConfig = arrayListOf(\n                \"tunnel:\",\n                \"  name: $tunName\",\n                \"  mtu: ${settings.tunMtu}\",\n                \"socks5:\",\n                \"  address: ${settings.socksAddress}\",\n                \"  port: ${settings.socksPort}\",\n            )\n            if (\n                settings.socksUsername.trim().isNotEmpty() &&\n                settings.socksPassword.trim().isNotEmpty()\n            ) {\n                tun2socksConfig.add(\"  username: ${settings.socksUsername}\")\n                tun2socksConfig.add(\"  password: ${settings.socksPassword}\")\n            }\n            tun2socksConfig.add(if (settings.socksUdp) \"  udp: udp\" else \"  udp: tcp\")\n            tun2socksConfig.add(\"\")\n            FileHelper.createOrUpdate(\n                settings.tun2socksConfig(),\n                tun2socksConfig.joinToString(\"\\n\")\n            )\n\n            /** Start tun2socks */\n            TProxyStartService(settings.tun2socksConfig().absolutePath, tunDevice!!.fd)\n        }\n\n        /** Service Notification */\n        val name = configName(profile)\n        startForeground(VPN_SERVICE_NOTIFICATION_ID, createNotification(name))\n\n        /** Listen for cellular changes */\n        if (cellularCallback == null) {\n            val request = NetworkRequest.Builder()\n                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)\n                .build()\n            cellularCallback = object : ConnectivityManager.NetworkCallback() {\n                override fun onAvailable(network: Network) {\n                    this@TProxyService.transparentProxyHelper.networkUpdate()\n                }\n            }\n            connectivityManager.registerNetworkCallback(request, cellularCallback!!)\n        }\n\n        /** Broadcast start event */\n        showToast(\"Start VPN\")\n        isRunning = true\n        broadcastStart(START_VPN_SERVICE_ACTION_NAME, name)\n    }\n\n    private fun stopVPN() {\n        if (settings.transparentProxy) {\n            transparentProxyHelper.disableProxy()\n        } else {\n            TProxyStopService()\n            runCatching { tunDevice?.close() }\n            tunDevice = null\n            isRunning = false\n        }\n        stopXray()\n        stopForeground(STOP_FOREGROUND_REMOVE)\n        showToast(\"Stop VPN\")\n        broadcastStop()\n        stopSelf()\n    }\n\n    private fun broadcastStart(action: String, configName: String) {\n        Intent(action).also {\n            it.`package` = BuildConfig.APPLICATION_ID\n            it.putExtra(\"profile\", configName)\n            sendBroadcast(it)\n        }\n    }\n\n    private fun broadcastStop() {\n        Intent(STOP_VPN_SERVICE_ACTION_NAME).also {\n            it.`package` = BuildConfig.APPLICATION_ID\n            sendBroadcast(it)\n        }\n    }\n\n    private fun broadcastStatus() {\n        Intent(STATUS_VPN_SERVICE_ACTION_NAME).also {\n            it.`package` = BuildConfig.APPLICATION_ID\n            it.putExtra(\"isRunning\", getIsRunning())\n            sendBroadcast(it)\n        }\n    }\n\n    private fun createNotification(name: String): Notification {\n        val pendingActivity = PendingIntent.getActivity(\n            applicationContext,\n            OPEN_MAIN_ACTIVITY_ACTION_ID,\n            Intent(applicationContext, MainActivity::class.java),\n            PendingIntent.FLAG_IMMUTABLE\n        )\n\n        val pendingStop = PendingIntent.getService(\n            applicationContext,\n            STOP_VPN_SERVICE_ACTION_ID,\n            Intent(applicationContext, TProxyService::class.java).also {\n                it.action = STOP_VPN_SERVICE_ACTION_NAME\n            },\n            PendingIntent.FLAG_IMMUTABLE\n        )\n\n        return NotificationCompat\n            .Builder(applicationContext, createNotificationChannel())\n            .setSmallIcon(R.drawable.baseline_vpn_lock)\n            .setContentTitle(name)\n            .setContentIntent(pendingActivity)\n            .addAction(0, getString(R.string.vpnStop), pendingStop)\n            .setPriority(NotificationCompat.PRIORITY_MAX)\n            .setOngoing(true)\n            .build()\n    }\n\n    private fun createNotificationChannel(): String {\n        val id = \"XrayVpnServiceNotification\"\n        val name = \"Xray VPN Service\"\n        val channel = NotificationChannel(id, name, NotificationManager.IMPORTANCE_LOW)\n        notificationManager.createNotificationChannel(channel)\n        return id\n    }\n\n    private fun showToast(message: String) {\n        Handler(Looper.getMainLooper()).post {\n            toast?.cancel()\n            toast = Toast.makeText(applicationContext, message, Toast.LENGTH_SHORT).also {\n                it.show()\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/service/VpnTileService.kt",
    "content": "package io.github.saeeddev94.xray.service\n\nimport android.content.ComponentName\nimport android.content.Intent\nimport android.content.SharedPreferences\nimport android.graphics.drawable.Icon\nimport android.service.quicksettings.Tile\nimport android.service.quicksettings.TileService\nimport androidx.core.content.edit\nimport io.github.saeeddev94.xray.R\nimport io.github.saeeddev94.xray.Settings\n\nclass VpnTileService : TileService() {\n\n    private val settings by lazy { Settings(applicationContext) }\n\n    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {\n        requestListeningState(this, ComponentName(this, VpnTileService::class.java))\n        val action = intent?.getStringExtra(\"action\") ?: \"\"\n        val label = intent?.getStringExtra(\"label\") ?: \"\"\n        val sharedPref = sharedPref()\n        sharedPref.edit {\n            putString(\"action\", action)\n            putString(\"label\", label)\n        }\n        handleUpdate(action, label)\n        return START_STICKY\n    }\n\n    override fun onStartListening() {\n        super.onStartListening()\n        handleUpdate()\n    }\n\n    override fun onClick() {\n        super.onClick()\n        val proxy = !settings.tun2socks || settings.transparentProxy\n        when (qsTile?.state) {\n            Tile.STATE_INACTIVE -> {\n                TProxyService.start(applicationContext, !proxy)\n            }\n            Tile.STATE_ACTIVE -> TProxyService.stop(applicationContext)\n        }\n    }\n\n    private fun handleUpdate(newAction: String? = null, newLabel: String? = null) {\n        val sharedPref = sharedPref()\n        val action = newAction ?: sharedPref.getString(\"action\", \"\")!!\n        val label = newLabel ?: sharedPref.getString(\"label\", \"\")!!\n        if (action.isNotEmpty() && label.isNotEmpty()) {\n            when (action) {\n                TProxyService.START_VPN_SERVICE_ACTION_NAME,\n                TProxyService.NEW_CONFIG_SERVICE_ACTION_NAME -> updateTile(Tile.STATE_ACTIVE, label)\n\n                TProxyService.STOP_VPN_SERVICE_ACTION_NAME -> updateTile(Tile.STATE_INACTIVE, label)\n            }\n        }\n    }\n\n    private fun updateTile(newState: Int, newLabel: String) {\n        val tile = qsTile ?: return\n        tile.apply {\n            state = newState\n            label = newLabel\n            icon = Icon.createWithResource(applicationContext, R.drawable.vpn_key)\n            updateTile()\n        }\n    }\n\n    private fun sharedPref(): SharedPreferences {\n        return getSharedPreferences(\"vpn_tile\", MODE_PRIVATE)\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/viewmodel/ConfigViewModel.kt",
    "content": "package io.github.saeeddev94.xray.viewmodel\n\nimport android.app.Application\nimport androidx.lifecycle.AndroidViewModel\nimport androidx.lifecycle.viewModelScope\nimport io.github.saeeddev94.xray.Xray\nimport io.github.saeeddev94.xray.database.Config\nimport kotlinx.coroutines.launch\n\nclass ConfigViewModel(application: Application) : AndroidViewModel(application) {\n\n    private val configRepository by lazy { getApplication<Xray>().configRepository }\n\n    suspend fun get() = configRepository.get()\n\n    fun update(config: Config) = viewModelScope.launch {\n        configRepository.update(config)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/viewmodel/LinkViewModel.kt",
    "content": "package io.github.saeeddev94.xray.viewmodel\n\nimport android.app.Application\nimport androidx.lifecycle.AndroidViewModel\nimport androidx.lifecycle.viewModelScope\nimport io.github.saeeddev94.xray.Xray\nimport io.github.saeeddev94.xray.database.Link\nimport kotlinx.coroutines.launch\n\nclass LinkViewModel(application: Application) : AndroidViewModel(application) {\n\n    private val linkRepository by lazy { getApplication<Xray>().linkRepository }\n\n    val tabs = linkRepository.tabs\n    val links = linkRepository.all\n\n    suspend fun activeLinks(): List<Link> {\n        return linkRepository.activeLinks()\n    }\n\n    fun insert(link: Link) = viewModelScope.launch {\n        linkRepository.insert(link)\n    }\n\n    fun update(link: Link) = viewModelScope.launch {\n        linkRepository.update(link)\n    }\n\n    fun delete(link: Link) = viewModelScope.launch {\n        linkRepository.delete(link)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/github/saeeddev94/xray/viewmodel/ProfileViewModel.kt",
    "content": "package io.github.saeeddev94.xray.viewmodel\n\nimport android.app.Application\nimport androidx.lifecycle.AndroidViewModel\nimport androidx.lifecycle.viewModelScope\nimport io.github.saeeddev94.xray.Xray\nimport io.github.saeeddev94.xray.database.Profile\nimport io.github.saeeddev94.xray.dto.ProfileList\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableSharedFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.launch\n\nclass ProfileViewModel(application: Application) : AndroidViewModel(application) {\n\n    private val profileRepository by lazy { getApplication<Xray>().profileRepository }\n\n    val profiles = profileRepository.all.flowOn(Dispatchers.IO).stateIn(\n        viewModelScope,\n        SharingStarted.Eagerly,\n        listOf(),\n    )\n    val filtered = MutableSharedFlow<List<ProfileList>>()\n\n    fun next(link: Long) = viewModelScope.launch {\n        val all = profiles.value\n        fixIndex(all)\n        val list = all.filter { link == 0L || link == it.link }\n        filtered.emit(list)\n    }\n\n    suspend fun linkProfiles(linkId: Long): List<Profile> {\n        return profileRepository.linkProfiles(linkId)\n    }\n\n    suspend fun find(id: Long): Profile {\n        return profileRepository.find(id)\n    }\n\n    suspend fun create(profile: Profile) {\n        return profileRepository.create(profile)\n    }\n\n    suspend fun update(profile: Profile) {\n        profileRepository.update(profile)\n    }\n\n    suspend fun remove(profile: Profile) {\n        profileRepository.remove(profile)\n    }\n\n    suspend fun moveUp(start: Int, end: Int, exclude: Long) {\n        profileRepository.moveUp(start, end, exclude)\n    }\n\n    suspend fun moveDown(start: Int, end: Int, exclude: Long) {\n        profileRepository.moveDown(start, end, exclude)\n    }\n\n    private fun fixIndex(list: List<ProfileList>) = viewModelScope.launch {\n        list.forEachIndexed { index, profile ->\n            if (profile.index == index) return@forEachIndexed\n            profileRepository.updateIndex(index, profile.id)\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/jni/Android.mk",
    "content": "# Copyright (C) 2023 The Android Open Source Project\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\ninclude $(call all-subdir-makefiles)\n"
  },
  {
    "path": "app/src/main/jni/Application.mk",
    "content": "# Copyright (C) 2023 The Android Open Source Project\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\nAPP_OPTIM := release\nAPP_PLATFORM := android-26\nAPP_ABI := armeabi-v7a arm64-v8a x86 x86_64\nAPP_CFLAGS := -O3 -DPKGNAME=io/github/saeeddev94/xray/service\nAPP_CPPFLAGS := -O3 -std=c++11\nNDK_TOOLCHAIN_VERSION := clang\nLOCAL_LDFLAGS += -Wl,--build-id=none -Wl,--hash-style=gnu\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_adb.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\"\n    android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M200,440L200,400Q200,328 232.5,268.5Q265,209 320,171L245,96L280,60L365,145Q391,133 420.5,126.5Q450,120 480,120Q510,120 539.5,126.5Q569,133 595,145L680,60L715,96L640,171Q695,209 727.5,268.5Q760,328 760,400L760,440L200,440ZM600,360Q617,360 628.5,348.5Q640,337 640,320Q640,303 628.5,291.5Q617,280 600,280Q583,280 571.5,291.5Q560,303 560,320Q560,337 571.5,348.5Q583,360 600,360ZM360,360Q377,360 388.5,348.5Q400,337 400,320Q400,303 388.5,291.5Q377,280 360,280Q343,280 331.5,291.5Q320,303 320,320Q320,337 331.5,348.5Q343,360 360,360ZM480,920Q363,920 281.5,838.5Q200,757 200,640L200,480L760,480L760,640Q760,757 678.5,838.5Q597,920 480,920Z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_add.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    android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_alt_route.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:autoMirrored=\"true\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M440,880L440,680Q440,624 423,597Q406,570 378,544L435,487Q447,498 458,510.5Q469,523 480,537Q494,518 508.5,503.5Q523,489 538,475Q576,440 607,394Q638,348 640,233L577,296L520,240L680,80L840,240L784,296L720,233Q718,376 676,436.5Q634,497 592,535Q560,564 540,591.5Q520,619 520,680L520,880L440,880ZM248,327Q244,307 242.5,283Q241,259 240,233L176,296L120,240L280,80L440,240L383,296L320,234Q320,255 322,273.5Q324,292 326,308L248,327ZM334,503Q314,482 295.5,454Q277,426 263,385L340,366Q350,393 363,412Q376,431 391,446L334,503Z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_config.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\"\n    android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M686,828L444,584Q424,592 403.5,596Q383,600 360,600Q260,600 190,530Q120,460 120,360Q120,324 130,291.5Q140,259 158,230L304,376L376,304L230,158Q259,140 291.5,130Q324,120 360,120Q460,120 530,190Q600,260 600,360Q600,383 596,403.5Q592,424 584,444L828,686Q840,698 840,715Q840,732 828,744L744,828Q732,840 715,840Q698,840 686,828ZM715,743L742,716L486,460Q504,440 512,413.5Q520,387 520,360Q520,300 481.5,255.5Q443,211 386,202L460,276Q472,288 472,304Q472,320 460,332L332,460Q320,472 304,472Q288,472 276,460L202,386Q211,443 255.5,481.5Q300,520 360,520Q386,520 412,512Q438,504 459,487L715,743ZM472,472L472,472Q472,472 472,472Q472,472 472,472Q472,472 472,472Q472,472 472,472L472,472Q472,472 472,472Q472,472 472,472L472,472Q472,472 472,472Q472,472 472,472L472,472Q472,472 472,472Q472,472 472,472Q472,472 472,472Q472,472 472,472L472,472Z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_content_copy.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:autoMirrored=\"true\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M360,720Q327,720 303.5,696.5Q280,673 280,640L280,160Q280,127 303.5,103.5Q327,80 360,80L720,80Q753,80 776.5,103.5Q800,127 800,160L800,640Q800,673 776.5,696.5Q753,720 720,720L360,720ZM360,640L720,640Q720,640 720,640Q720,640 720,640L720,160Q720,160 720,160Q720,160 720,160L360,160Q360,160 360,160Q360,160 360,160L360,640Q360,640 360,640Q360,640 360,640ZM200,880Q167,880 143.5,856.5Q120,833 120,800L120,240L200,240L200,800Q200,800 200,800Q200,800 200,800L640,800L640,880L200,880ZM360,640Q360,640 360,640Q360,640 360,640L360,160Q360,160 360,160Q360,160 360,160L360,160Q360,160 360,160Q360,160 360,160L360,640Q360,640 360,640Q360,640 360,640L360,640Z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_delete.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    android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_done.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\"\n    android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M382,720L154,492L211,435L382,606L749,239L806,296L382,720Z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_download.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\"\n    android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M480,640L280,440L336,382L440,486L440,160L520,160L520,486L624,382L680,440L480,640ZM240,800Q207,800 183.5,776.5Q160,753 160,720L160,600L240,600L240,720Q240,720 240,720Q240,720 240,720L720,720Q720,720 720,720Q720,720 720,720L720,600L800,600L800,720Q800,753 776.5,776.5Q753,800 720,800L240,800Z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_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    android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_file_open.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\"\n    android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M240,880Q207,880 183.5,856.5Q160,833 160,800L160,160Q160,127 183.5,103.5Q207,80 240,80L560,80L800,320L800,560L720,560L720,360L520,360L520,160L240,160Q240,160 240,160Q240,160 240,160L240,800Q240,800 240,800Q240,800 240,800L600,800L600,880L240,880ZM878,895L760,777L760,866L680,866L680,640L906,640L906,720L816,720L934,838L878,895ZM240,800L240,560L240,560L240,360L240,160L240,160Q240,160 240,160Q240,160 240,160L240,800Q240,800 240,800Q240,800 240,800L240,800Z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_folder_open.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\"\n    android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M160,800Q127,800 103.5,776.5Q80,753 80,720L80,240Q80,207 103.5,183.5Q127,160 160,160L400,160L480,240L800,240Q833,240 856.5,263.5Q880,287 880,320L447,320L367,240L160,240Q160,240 160,240Q160,240 160,240L160,720Q160,720 160,720Q160,720 160,720L256,400L940,400L837,743Q829,769 807.5,784.5Q786,800 760,800L160,800ZM244,720L760,720L832,480L316,480L244,720ZM244,720L316,480L316,480L244,720L244,720ZM160,320L160,240Q160,240 160,240Q160,240 160,240L160,240L160,320L160,320Z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_link.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\"\n    android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M440,680L280,680Q197,680 138.5,621.5Q80,563 80,480Q80,397 138.5,338.5Q197,280 280,280L440,280L440,360L280,360Q230,360 195,395Q160,430 160,480Q160,530 195,565Q230,600 280,600L440,600L440,680ZM320,520L320,440L640,440L640,520L320,520ZM520,680L520,600L680,600Q730,600 765,565Q800,530 800,480Q800,430 765,395Q730,360 680,360L520,360L520,280L680,280Q763,280 821.5,338.5Q880,397 880,480Q880,563 821.5,621.5Q763,680 680,680L520,680Z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_refresh.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\"\n    android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M480,800Q346,800 253,707Q160,614 160,480Q160,346 253,253Q346,160 480,160Q549,160 612,188.5Q675,217 720,270L720,160L800,160L800,440L520,440L520,360L688,360Q656,304 600.5,272Q545,240 480,240Q380,240 310,310Q240,380 240,480Q240,580 310,650Q380,720 480,720Q557,720 619,676Q681,632 706,560L790,560Q762,666 676,733Q590,800 480,800Z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_settings.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    android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M19.14,12.94c0.04,-0.3 0.06,-0.61 0.06,-0.94c0,-0.32 -0.02,-0.64 -0.07,-0.94l2.03,-1.58c0.18,-0.14 0.23,-0.41 0.12,-0.61l-1.92,-3.32c-0.12,-0.22 -0.37,-0.29 -0.59,-0.22l-2.39,0.96c-0.5,-0.38 -1.03,-0.7 -1.62,-0.94L14.4,2.81c-0.04,-0.24 -0.24,-0.41 -0.48,-0.41h-3.84c-0.24,0 -0.43,0.17 -0.47,0.41L9.25,5.35C8.66,5.59 8.12,5.92 7.63,6.29L5.24,5.33c-0.22,-0.08 -0.47,0 -0.59,0.22L2.74,8.87C2.62,9.08 2.66,9.34 2.86,9.48l2.03,1.58C4.84,11.36 4.8,11.69 4.8,12s0.02,0.64 0.07,0.94l-2.03,1.58c-0.18,0.14 -0.23,0.41 -0.12,0.61l1.92,3.32c0.12,0.22 0.37,0.29 0.59,0.22l2.39,-0.96c0.5,0.38 1.03,0.7 1.62,0.94l0.36,2.54c0.05,0.24 0.24,0.41 0.48,0.41h3.84c0.24,0 0.44,-0.17 0.47,-0.41l0.36,-2.54c0.59,-0.24 1.13,-0.56 1.62,-0.94l2.39,0.96c0.22,0.08 0.47,0 0.59,-0.22l1.92,-3.32c0.12,-0.22 0.07,-0.47 -0.12,-0.61L19.14,12.94zM12,15.6c-1.98,0 -3.6,-1.62 -3.6,-3.6s1.62,-3.6 3.6,-3.6s3.6,1.62 3.6,3.6S13.98,15.6 12,15.6z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_vpn_key.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\"\n    android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M280,720Q180,720 110,650Q40,580 40,480Q40,380 110,310Q180,240 280,240Q346,240 401,273Q456,306 488,360L920,360L920,600L840,600L840,720L600,720L600,600L488,600Q456,654 401,687Q346,720 280,720ZM280,640Q346,640 386,599.5Q426,559 434,520L680,520L680,640L760,640L760,520L840,520L840,440L434,440Q426,401 386,360.5Q346,320 280,320Q214,320 167,367Q120,414 120,480Q120,546 167,593Q214,640 280,640ZM280,560Q313,560 336.5,536.5Q360,513 360,480Q360,447 336.5,423.5Q313,400 280,400Q247,400 223.5,423.5Q200,447 200,480Q200,513 223.5,536.5Q247,560 280,560ZM280,480Q280,480 280,480Q280,480 280,480Q280,480 280,480Q280,480 280,480Q280,480 280,480Q280,480 280,480L280,480L280,480L280,480L280,480L280,480L280,480L280,480Q280,480 280,480Q280,480 280,480Z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_vpn_lock.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\"\n    android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M480,880Q398,880 325,848.5Q252,817 197.5,762.5Q143,708 111.5,635Q80,562 80,480Q80,397 111.5,324.5Q143,252 197.5,197.5Q252,143 325,111.5Q398,80 480,80Q512,80 541.5,84.5Q571,89 600,98L600,200Q600,233 576.5,256.5Q553,280 520,280L440,280L440,360Q440,377 428.5,388.5Q417,400 400,400L320,400L320,480L560,480Q577,480 588.5,491.5Q600,503 600,520L600,640L640,640Q667,640 687.5,656Q708,672 716,696Q755,652 777.5,597.5Q800,543 800,480Q800,469 799,460Q798,451 796,440L878,440Q880,451 880,460Q880,469 880,480Q880,562 848.5,635Q817,708 762.5,762.5Q708,817 635.5,848.5Q563,880 480,880ZM440,798L440,720Q407,720 383.5,696.5Q360,673 360,640L360,600L168,408Q165,426 162.5,444Q160,462 160,480Q160,604 240.5,693.5Q321,783 440,798ZM720,360Q703,360 691.5,348.5Q680,337 680,320L680,200Q680,183 691.5,171.5Q703,160 720,160L720,160L720,120Q720,87 743.5,63.5Q767,40 800,40Q833,40 856.5,63.5Q880,87 880,120L880,160L880,160Q897,160 908.5,171.5Q920,183 920,200L920,320Q920,337 908.5,348.5Q897,360 880,360L720,360ZM760,160L840,160L840,120Q840,103 828.5,91.5Q817,80 800,80Q783,80 771.5,91.5Q760,103 760,120L760,160Z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_xray.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"108\"\n    android:viewportHeight=\"108\">\n  <path\n      android:pathData=\"m47.49,29.67c-2.23,6.41 -4.16,12.01 -4.31,12.44 -0.19,0.54 -3.08,2.12 -9.09,5.02l-8.82,4.25 13.09,0.12c7.16,0.04 13.13,0.04 13.21,-0.04 0.08,-0.08 0.08,-7.65 0.04,-16.8l-0.12,-16.65z\"\n      android:fillColor=\"#000000\"\n      android:strokeColor=\"#00000000\"/>\n  <path\n      android:pathData=\"m56.31,38.16l0,13.4l16.86,-0.04l16.83,-0l-9.16,-3.24c-5.01,-1.78 -10.63,-3.75 -12.47,-4.33l-3.31,-1.08 -1.27,-2.7c-0.65,-1.51 -2.62,-5.56 -4.35,-9.08l-3.12,-6.33z\"\n      android:fillColor=\"#000000\"\n      android:strokeColor=\"#00000000\"/>\n  <path\n      android:pathData=\"M27.16,59.48C32.17,61.26 37.79,63.23 39.64,63.81l3.31,1.08 1.27,2.7C44.87,69.06 46.84,73.16 48.57,76.67L51.69,83.01L51.69,69.61 51.69,56.2L34.86,56.24L18,56.24Z\"\n      android:fillColor=\"#000000\"\n      android:strokeColor=\"#00000000\"/>\n  <path\n      android:pathData=\"M56.35,73.08L56.35,90l3.23,-9.19c1.77,-5.02 3.73,-10.66 4.31,-12.52l1.08,-3.32 2.7,-1.27c1.5,-0.66 5.54,-2.63 9.05,-4.36l6.31,-3.13l-13.36,-0 -13.36,-0z\"\n      android:fillColor=\"#000000\"\n      android:strokeColor=\"#00000000\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable-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>\n    <inset\n        android:insetBottom=\"0dp\"\n        android:insetLeft=\"0dp\"\n        android:insetRight=\"0dp\"\n        android:insetTop=\"0dp\">\n      <shape android:shape=\"oval\">\n        <solid android:color=\"#FFFFFF\" />\n      </shape>\n    </inset>\n  </background>\n  <foreground android:drawable=\"@drawable/ic_xray\" />\n  <monochrome android:drawable=\"@drawable/ic_xray\" />\n</adaptive-icon>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_apps_routing.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    android:fitsSystemWindows=\"true\"\n    tools:context=\".activity.AppsRoutingActivity\">\n    <com.google.android.material.appbar.AppBarLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n        <androidx.appcompat.widget.Toolbar\n            android:id=\"@+id/toolbar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"?attr/actionBarSize\"\n            android:background=\"@android:color/black\">\n            <io.github.saeeddev94.xray.component.EmptySubmitSearchView\n                android:id=\"@+id/search\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                app:iconifiedByDefault=\"false\"\n                app:queryHint=\"Search\" />\n        </androidx.appcompat.widget.Toolbar>\n    </com.google.android.material.appbar.AppBarLayout>\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/appsList\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\" />\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_assets.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\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    android:orientation=\"vertical\"\n    android:fitsSystemWindows=\"true\"\n    tools:context=\".activity.AssetsActivity\">\n    <com.google.android.material.appbar.AppBarLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n        <androidx.appcompat.widget.Toolbar\n            android:id=\"@+id/toolbar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"?attr/actionBarSize\"\n            android:background=\"@android:color/black\" />\n    </com.google.android.material.appbar.AppBarLayout>\n    <ScrollView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\"\n        android:layout_margin=\"15dp\">\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\">\n            <androidx.cardview.widget.CardView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginVertical=\"5dp\"\n                app:cardCornerRadius=\"10dp\">\n                <LinearLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"72dp\"\n                    android:orientation=\"horizontal\"\n                    android:baselineAligned=\"false\">\n                    <LinearLayout\n                        android:layout_width=\"85dp\"\n                        android:layout_height=\"match_parent\"\n                        android:orientation=\"vertical\"\n                        android:gravity=\"center|start\"\n                        android:paddingStart=\"11dp\"\n                        android:paddingEnd=\"1dp\">\n                        <TextView\n                            android:layout_width=\"wrap_content\"\n                            android:layout_height=\"wrap_content\"\n                            android:textSize=\"18sp\"\n                            android:textStyle=\"bold\"\n                            android:text=\"@string/geoIp\" />\n                    </LinearLayout>\n                    <LinearLayout\n                        android:layout_width=\"0dp\"\n                        android:layout_height=\"match_parent\"\n                        android:layout_weight=\"1\"\n                        android:gravity=\"center\"\n                        android:orientation=\"vertical\"\n                        android:paddingStart=\"0dp\"\n                        android:paddingEnd=\"20dp\">\n                        <ProgressBar\n                            android:id=\"@+id/geoIpProgress\"\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\"\n                            style=\"?android:attr/progressBarStyleHorizontal\"\n                            android:max=\"100\" />\n                    </LinearLayout>\n                    <LinearLayout\n                        android:id=\"@+id/geoIpSetup\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"match_parent\"\n                        android:gravity=\"center\"\n                        android:orientation=\"horizontal\">\n                        <LinearLayout\n                            android:id=\"@+id/geoIpDownload\"\n                            android:layout_width=\"wrap_content\"\n                            android:layout_height=\"match_parent\"\n                            android:background=\"?android:attr/selectableItemBackground\"\n                            android:clickable=\"true\"\n                            android:focusable=\"true\"\n                            android:gravity=\"center\"\n                            android:orientation=\"vertical\"\n                            android:padding=\"8dp\">\n                            <ImageView\n                                android:layout_width=\"30dp\"\n                                android:layout_height=\"30dp\"\n                                android:src=\"@drawable/baseline_download\"\n                                android:contentDescription=\"@string/downloadIcon\" />\n                        </LinearLayout>\n                        <LinearLayout\n                            android:id=\"@+id/geoIpFile\"\n                            android:layout_width=\"wrap_content\"\n                            android:layout_height=\"match_parent\"\n                            android:background=\"?android:attr/selectableItemBackground\"\n                            android:clickable=\"true\"\n                            android:focusable=\"true\"\n                            android:gravity=\"center\"\n                            android:orientation=\"vertical\"\n                            android:padding=\"8dp\">\n                            <ImageView\n                                android:layout_width=\"30dp\"\n                                android:layout_height=\"30dp\"\n                                android:src=\"@drawable/baseline_file_open\"\n                                android:contentDescription=\"@string/fileOpenIcon\" />\n                        </LinearLayout>\n                    </LinearLayout>\n                    <LinearLayout\n                        android:id=\"@+id/geoIpInstalled\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"match_parent\"\n                        android:gravity=\"center\"\n                        android:orientation=\"horizontal\">\n                        <LinearLayout\n                            android:layout_width=\"wrap_content\"\n                            android:layout_height=\"match_parent\"\n                            android:background=\"?android:attr/selectableItemBackground\"\n                            android:clickable=\"true\"\n                            android:focusable=\"true\"\n                            android:gravity=\"center\"\n                            android:orientation=\"vertical\"\n                            android:padding=\"8dp\">\n                            <TextView\n                                android:id=\"@+id/geoIpDate\"\n                                android:layout_width=\"wrap_content\"\n                                android:layout_height=\"wrap_content\"\n                                android:textSize=\"16sp\" />\n                        </LinearLayout>\n                        <LinearLayout\n                            android:id=\"@+id/geoIpDelete\"\n                            android:layout_width=\"wrap_content\"\n                            android:layout_height=\"match_parent\"\n                            android:background=\"?android:attr/selectableItemBackground\"\n                            android:clickable=\"true\"\n                            android:focusable=\"true\"\n                            android:gravity=\"center\"\n                            android:orientation=\"vertical\"\n                            android:padding=\"8dp\">\n                            <ImageView\n                                android:layout_width=\"30dp\"\n                                android:layout_height=\"30dp\"\n                                android:src=\"@drawable/baseline_delete\"\n                                android:contentDescription=\"@string/deleteIcon\" />\n                        </LinearLayout>\n                    </LinearLayout>\n                </LinearLayout>\n            </androidx.cardview.widget.CardView>\n            <androidx.cardview.widget.CardView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginVertical=\"5dp\"\n                app:cardCornerRadius=\"10dp\">\n                <LinearLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"72dp\"\n                    android:orientation=\"horizontal\"\n                    android:baselineAligned=\"false\">\n                    <LinearLayout\n                        android:layout_width=\"85dp\"\n                        android:layout_height=\"match_parent\"\n                        android:orientation=\"vertical\"\n                        android:gravity=\"center|start\"\n                        android:paddingStart=\"11dp\"\n                        android:paddingEnd=\"1dp\">\n                        <TextView\n                            android:layout_width=\"wrap_content\"\n                            android:layout_height=\"wrap_content\"\n                            android:textSize=\"18sp\"\n                            android:textStyle=\"bold\"\n                            android:text=\"@string/geoSite\" />\n                    </LinearLayout>\n                    <LinearLayout\n                        android:layout_width=\"0dp\"\n                        android:layout_height=\"match_parent\"\n                        android:layout_weight=\"1\"\n                        android:gravity=\"center\"\n                        android:orientation=\"vertical\"\n                        android:paddingStart=\"0dp\"\n                        android:paddingEnd=\"20dp\">\n                        <ProgressBar\n                            android:id=\"@+id/geoSiteProgress\"\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\"\n                            style=\"?android:attr/progressBarStyleHorizontal\"\n                            android:max=\"100\" />\n                    </LinearLayout>\n                    <LinearLayout\n                        android:id=\"@+id/geoSiteSetup\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"match_parent\"\n                        android:gravity=\"center\"\n                        android:orientation=\"horizontal\">\n                        <LinearLayout\n                            android:id=\"@+id/geoSiteDownload\"\n                            android:layout_width=\"wrap_content\"\n                            android:layout_height=\"match_parent\"\n                            android:background=\"?android:attr/selectableItemBackground\"\n                            android:clickable=\"true\"\n                            android:focusable=\"true\"\n                            android:gravity=\"center\"\n                            android:orientation=\"vertical\"\n                            android:padding=\"8dp\">\n                            <ImageView\n                                android:layout_width=\"30dp\"\n                                android:layout_height=\"30dp\"\n                                android:src=\"@drawable/baseline_download\"\n                                android:contentDescription=\"@string/downloadIcon\" />\n                        </LinearLayout>\n                        <LinearLayout\n                            android:id=\"@+id/geoSiteFile\"\n                            android:layout_width=\"wrap_content\"\n                            android:layout_height=\"match_parent\"\n                            android:background=\"?android:attr/selectableItemBackground\"\n                            android:clickable=\"true\"\n                            android:focusable=\"true\"\n                            android:gravity=\"center\"\n                            android:orientation=\"vertical\"\n                            android:padding=\"8dp\">\n                            <ImageView\n                                android:layout_width=\"30dp\"\n                                android:layout_height=\"30dp\"\n                                android:src=\"@drawable/baseline_file_open\"\n                                android:contentDescription=\"@string/fileOpenIcon\" />\n                        </LinearLayout>\n                    </LinearLayout>\n                    <LinearLayout\n                        android:id=\"@+id/geoSiteInstalled\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"match_parent\"\n                        android:gravity=\"center\"\n                        android:orientation=\"horizontal\">\n                        <LinearLayout\n                            android:layout_width=\"wrap_content\"\n                            android:layout_height=\"match_parent\"\n                            android:background=\"?android:attr/selectableItemBackground\"\n                            android:clickable=\"true\"\n                            android:focusable=\"true\"\n                            android:gravity=\"center\"\n                            android:orientation=\"vertical\"\n                            android:padding=\"8dp\">\n                            <TextView\n                                android:id=\"@+id/geoSiteDate\"\n                                android:layout_width=\"wrap_content\"\n                                android:layout_height=\"wrap_content\"\n                                android:textSize=\"16sp\" />\n                        </LinearLayout>\n                        <LinearLayout\n                            android:id=\"@+id/geoSiteDelete\"\n                            android:layout_width=\"wrap_content\"\n                            android:layout_height=\"match_parent\"\n                            android:background=\"?android:attr/selectableItemBackground\"\n                            android:clickable=\"true\"\n                            android:focusable=\"true\"\n                            android:gravity=\"center\"\n                            android:orientation=\"vertical\"\n                            android:padding=\"8dp\">\n                            <ImageView\n                                android:layout_width=\"30dp\"\n                                android:layout_height=\"30dp\"\n                                android:src=\"@drawable/baseline_delete\"\n                                android:contentDescription=\"@string/deleteIcon\" />\n                        </LinearLayout>\n                    </LinearLayout>\n                </LinearLayout>\n            </androidx.cardview.widget.CardView>\n            <androidx.cardview.widget.CardView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginVertical=\"5dp\"\n                app:cardCornerRadius=\"10dp\">\n                <LinearLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"72dp\"\n                    android:orientation=\"horizontal\"\n                    android:baselineAligned=\"false\">\n                    <LinearLayout\n                        android:layout_width=\"144dp\"\n                        android:layout_height=\"match_parent\"\n                        android:orientation=\"vertical\"\n                        android:gravity=\"center|start\"\n                        android:paddingStart=\"11dp\"\n                        android:paddingEnd=\"1dp\">\n                        <TextView\n                            android:layout_width=\"wrap_content\"\n                            android:layout_height=\"wrap_content\"\n                            android:textSize=\"18sp\"\n                            android:textStyle=\"bold\"\n                            android:text=\"@string/xrayLabel\" />\n                    </LinearLayout>\n                    <LinearLayout\n                        android:layout_width=\"0dp\"\n                        android:layout_height=\"match_parent\"\n                        android:layout_weight=\"1\"\n                        android:gravity=\"center\"\n                        android:orientation=\"vertical\"\n                        android:paddingStart=\"0dp\"\n                        android:paddingEnd=\"20dp\" />\n                    <LinearLayout\n                        android:id=\"@+id/xrayCoreSetup\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"match_parent\"\n                        android:gravity=\"center\"\n                        android:orientation=\"horizontal\">\n                        <LinearLayout\n                            android:id=\"@+id/xrayCoreFile\"\n                            android:layout_width=\"wrap_content\"\n                            android:layout_height=\"match_parent\"\n                            android:background=\"?android:attr/selectableItemBackground\"\n                            android:clickable=\"true\"\n                            android:focusable=\"true\"\n                            android:gravity=\"center\"\n                            android:orientation=\"vertical\"\n                            android:padding=\"8dp\">\n                            <ImageView\n                                android:layout_width=\"30dp\"\n                                android:layout_height=\"30dp\"\n                                android:src=\"@drawable/baseline_file_open\"\n                                android:contentDescription=\"@string/fileOpenIcon\" />\n                        </LinearLayout>\n                    </LinearLayout>\n                    <LinearLayout\n                        android:id=\"@+id/xrayCoreInstalled\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"match_parent\"\n                        android:gravity=\"center\"\n                        android:orientation=\"horizontal\">\n                        <LinearLayout\n                            android:layout_width=\"wrap_content\"\n                            android:layout_height=\"match_parent\"\n                            android:background=\"?android:attr/selectableItemBackground\"\n                            android:clickable=\"true\"\n                            android:focusable=\"true\"\n                            android:gravity=\"center\"\n                            android:orientation=\"vertical\"\n                            android:padding=\"8dp\">\n                            <TextView\n                                android:id=\"@+id/xrayCoreVersion\"\n                                android:layout_width=\"wrap_content\"\n                                android:layout_height=\"wrap_content\"\n                                android:textSize=\"16sp\" />\n                        </LinearLayout>\n                        <LinearLayout\n                            android:id=\"@+id/xrayCoreDelete\"\n                            android:layout_width=\"wrap_content\"\n                            android:layout_height=\"match_parent\"\n                            android:background=\"?android:attr/selectableItemBackground\"\n                            android:clickable=\"true\"\n                            android:focusable=\"true\"\n                            android:gravity=\"center\"\n                            android:orientation=\"vertical\"\n                            android:padding=\"8dp\">\n                            <ImageView\n                                android:layout_width=\"30dp\"\n                                android:layout_height=\"30dp\"\n                                android:src=\"@drawable/baseline_delete\"\n                                android:contentDescription=\"@string/deleteIcon\" />\n                        </LinearLayout>\n                    </LinearLayout>\n                </LinearLayout>\n            </androidx.cardview.widget.CardView>\n        </LinearLayout>\n    </ScrollView>\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_configs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\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    android:orientation=\"vertical\"\n    android:fitsSystemWindows=\"true\"\n    tools:context=\".activity.ConfigsActivity\">\n    <com.google.android.material.appbar.AppBarLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n        <androidx.appcompat.widget.Toolbar\n            android:id=\"@+id/toolbar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"?attr/actionBarSize\"\n            android:background=\"@android:color/black\" />\n        <com.google.android.material.tabs.TabLayout\n            android:id=\"@+id/tabLayout\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            app:tabMode=\"fixed\"\n            app:tabGravity=\"fill\" />\n    </com.google.android.material.appbar.AppBarLayout>\n    <androidx.viewpager.widget.ViewPager\n        android:id=\"@+id/viewPager\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:layout_behavior=\"@string/appbar_scrolling_view_behavior\" />\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_links.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n    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:fitsSystemWindows=\"true\"\n    tools:context=\".activity.LinksActivity\">\n    <com.google.android.material.appbar.AppBarLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n        <androidx.appcompat.widget.Toolbar\n            android:id=\"@+id/toolbar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"?attr/actionBarSize\"\n            android:background=\"@android:color/black\" />\n    </com.google.android.material.appbar.AppBarLayout>\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/linksRecyclerView\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\" />\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_logs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n    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:fitsSystemWindows=\"true\"\n    tools:context=\".activity.LogsActivity\">\n    <com.google.android.material.appbar.AppBarLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n        <androidx.appcompat.widget.Toolbar\n            android:id=\"@+id/toolbar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"?attr/actionBarSize\"\n            android:background=\"@android:color/black\" />\n    </com.google.android.material.appbar.AppBarLayout>\n    <ScrollView\n        android:id=\"@+id/logsScrollView\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\">\n        <TextView\n            android:id=\"@+id/logsTextView\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:paddingHorizontal=\"15dp\" />\n    </ScrollView>\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.drawerlayout.widget.DrawerLayout 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/drawerLayout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:fitsSystemWindows=\"true\"\n    tools:context=\".activity.MainActivity\">\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\">\n        <com.google.android.material.appbar.AppBarLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\">\n            <androidx.appcompat.widget.Toolbar\n                android:id=\"@+id/toolbar\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"?attr/actionBarSize\"\n                android:background=\"@android:color/black\" />\n        </com.google.android.material.appbar.AppBarLayout>\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:orientation=\"vertical\">\n            <com.google.android.material.tabs.TabLayout\n                android:id=\"@+id/linksTab\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:padding=\"10dp\"\n                android:background=\"@android:color/transparent\"\n                app:tabMode=\"scrollable\" />\n            <androidx.recyclerview.widget.RecyclerView\n                android:id=\"@+id/profilesRecyclerView\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"0dp\"\n                android:layout_weight=\"1\"\n                android:nextFocusRight=\"@+id/toggleButton\" />\n            <Button\n                android:id=\"@+id/toggleButton\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"60dp\"\n                android:layout_marginHorizontal=\"10dp\"\n                android:text=\"@string/vpnStart\" />\n            <LinearLayout\n                android:id=\"@+id/pingBox\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"60dp\"\n                android:background=\"@android:color/black\"\n                android:gravity=\"center|start\"\n                android:nextFocusRight=\"@+id/toggleButton\"\n                android:nextFocusLeft=\"@+id/listRecycler\"\n                android:clickable=\"true\"\n                android:focusable=\"true\">\n                <TextView\n                    android:id=\"@+id/pingResult\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:maxLines=\"2\"\n                    android:minLines=\"1\"\n                    android:paddingHorizontal=\"20dp\"\n                    android:textAppearance=\"@style/TextAppearance.AppCompat.Small\"\n                    android:textColor=\"@android:color/white\" />\n            </LinearLayout>\n        </LinearLayout>\n    </LinearLayout>\n    <com.google.android.material.navigation.NavigationView\n        android:id=\"@+id/navView\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"match_parent\"\n        android:layout_gravity=\"start\"\n        app:menu=\"@menu/menu_drawer\" />\n</androidx.drawerlayout.widget.DrawerLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_profile.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n    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:fitsSystemWindows=\"true\"\n    tools:context=\".activity.ProfileActivity\">\n    <com.google.android.material.appbar.AppBarLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n        <androidx.appcompat.widget.Toolbar\n            android:id=\"@+id/toolbar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"?attr/actionBarSize\"\n            android:background=\"@android:color/black\" />\n    </com.google.android.material.appbar.AppBarLayout>\n    <ScrollView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\">\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\">\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:orientation=\"vertical\"\n                android:padding=\"15dp\">\n                <TextView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/profileName\"\n                    android:labelFor=\"@id/profileName\" />\n                <EditText\n                    android:id=\"@+id/profileName\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:inputType=\"text\"\n                    android:autofillHints=\"\" />\n                <TextView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/profileConfig\"\n                    android:labelFor=\"@id/profileConfig\" />\n            </LinearLayout>\n            <com.blacksquircle.ui.editorkit.widget.TextProcessor\n                android:id=\"@+id/profileConfig\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:inputType=\"textMultiLine\"\n                android:background=\"@null\" />\n        </LinearLayout>\n    </ScrollView>\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_scanner.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\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    android:fitsSystemWindows=\"true\"\n    tools:context=\".activity.ScannerActivity\">\n    <com.budiyev.android.codescanner.CodeScannerView\n        android:id=\"@+id/scannerView\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        app:autoFocusButtonColor=\"@android:color/white\"\n        app:autoFocusButtonVisible=\"true\"\n        app:flashButtonColor=\"@android:color/white\"\n        app:flashButtonVisible=\"true\"\n        app:frameColor=\"@android:color/white\"\n        app:frameCornersSize=\"50dp\"\n        app:frameCornersRadius=\"0dp\"\n        app:frameAspectRatioWidth=\"1\"\n        app:frameAspectRatioHeight=\"1\"\n        app:frameSize=\"0.75\"\n        app:frameThickness=\"2dp\"\n        app:frameVerticalBias=\"0.5\"\n        app:maskColor=\"#77000000\" />\n</FrameLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_settings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\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    android:orientation=\"vertical\"\n    android:fitsSystemWindows=\"true\"\n    tools:context=\".activity.SettingsActivity\">\n    <com.google.android.material.appbar.AppBarLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n        <androidx.appcompat.widget.Toolbar\n            android:id=\"@+id/toolbar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"?attr/actionBarSize\"\n            android:background=\"@android:color/black\" />\n        <com.google.android.material.tabs.TabLayout\n            android:id=\"@+id/tabLayout\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            app:tabMode=\"fixed\"\n            app:tabGravity=\"fill\" />\n    </com.google.android.material.appbar.AppBarLayout>\n    <androidx.viewpager.widget.ViewPager\n        android:id=\"@+id/viewPager\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\"\n        app:layout_behavior=\"@string/appbar_scrolling_view_behavior\" />\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_recycler_exclude.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/appContainer\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"horizontal\"\n    android:gravity=\"center\"\n    android:baselineAligned=\"false\"\n    android:background=\"?attr/selectableItemBackground\"\n    android:clickable=\"true\"\n    android:focusable=\"true\">\n    <LinearLayout\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\"\n        android:padding=\"10dp\">\n        <ImageView\n            android:id=\"@+id/appIcon\"\n            android:layout_width=\"60dp\"\n            android:layout_height=\"60dp\"\n            android:contentDescription=\"@string/appIcon\" />\n    </LinearLayout>\n    <LinearLayout\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_weight=\"1\"\n        android:orientation=\"vertical\">\n        <TextView\n            android:id=\"@+id/appName\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\" />\n        <TextView\n            android:id=\"@+id/packageName\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\" />\n    </LinearLayout>\n    <LinearLayout\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\">\n        <com.google.android.material.checkbox.MaterialCheckBox\n            android:id=\"@+id/isSelected\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:clickable=\"false\"\n            android:focusable=\"false\" />\n    </LinearLayout>\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_recycler_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.cardview.widget.CardView\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:id=\"@+id/profileCard\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginVertical=\"5dp\"\n    android:layout_marginHorizontal=\"10dp\"\n    android:foreground=\"?android:attr/selectableItemBackground\"\n    android:clickable=\"true\"\n    android:focusable=\"true\"\n    android:nextFocusRight=\"@+id/editProfile\"\n    app:cardCornerRadius=\"10dp\">\n    <LinearLayout\n        android:background=\"@color/btnColor\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"72dp\"\n        android:orientation=\"horizontal\"\n        android:baselineAligned=\"false\">\n        <LinearLayout\n            android:id=\"@+id/activeIndicator\"\n            android:layout_width=\"10dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_gravity=\"center\"\n            android:orientation=\"vertical\"\n            android:background=\"@color/btnColor\" />\n        <LinearLayout\n            android:layout_width=\"0dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_weight=\"1\"\n            android:orientation=\"vertical\"\n            android:gravity=\"center|start\"\n            android:paddingStart=\"11dp\"\n            android:paddingEnd=\"1dp\">\n            <TextView\n                android:id=\"@+id/profileName\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:textColor=\"@android:color/white\"\n                android:textSize=\"18sp\"\n                android:textStyle=\"bold\" />\n        </LinearLayout>\n        <LinearLayout\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"match_parent\"\n            android:gravity=\"center\"\n            android:orientation=\"horizontal\">\n            <LinearLayout\n                android:id=\"@+id/profileEdit\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"match_parent\"\n                android:background=\"?android:attr/selectableItemBackground\"\n                android:clickable=\"true\"\n                android:focusable=\"true\"\n                android:gravity=\"center\"\n                android:orientation=\"vertical\"\n                android:padding=\"8dp\">\n                <ImageView\n                    android:layout_width=\"24dp\"\n                    android:layout_height=\"24dp\"\n                    android:src=\"@drawable/baseline_edit\"\n                    android:contentDescription=\"@string/editIcon\" />\n            </LinearLayout>\n            <LinearLayout\n                android:id=\"@+id/profileDelete\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"72dp\"\n                android:background=\"?android:attr/selectableItemBackground\"\n                android:clickable=\"true\"\n                android:focusable=\"true\"\n                android:gravity=\"center\"\n                android:orientation=\"vertical\"\n                android:padding=\"8dp\">\n                <ImageView\n                    android:layout_width=\"24dp\"\n                    android:layout_height=\"24dp\"\n                    android:src=\"@drawable/baseline_delete\"\n                    android:contentDescription=\"@string/deleteIcon\" />\n            </LinearLayout>\n        </LinearLayout>\n    </LinearLayout>\n</androidx.cardview.widget.CardView>\n"
  },
  {
    "path": "app/src/main/res/layout/layout_link_form.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=\"vertical\"\n    android:paddingHorizontal=\"16dp\">\n    <RadioGroup\n        android:id=\"@+id/typeRadioGroup\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"horizontal\" />\n    <com.google.android.material.textfield.TextInputLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"5dp\">\n        <EditText\n            android:id=\"@+id/nameEditText\"\n            android:inputType=\"text\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:autofillHints=\"\"\n            android:hint=\"@string/linkName\" />\n    </com.google.android.material.textfield.TextInputLayout>\n    <com.google.android.material.textfield.TextInputLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"5dp\">\n        <EditText\n            android:id=\"@+id/addressEditText\"\n            android:inputType=\"text\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:autofillHints=\"\"\n            android:hint=\"@string/linkAddress\" />\n    </com.google.android.material.textfield.TextInputLayout>\n    <com.google.android.material.textfield.TextInputLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"5dp\">\n        <EditText\n            android:id=\"@+id/userAgentEditText\"\n            android:inputType=\"text\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:autofillHints=\"\"\n            android:hint=\"@string/linkUserAgent\" />\n    </com.google.android.material.textfield.TextInputLayout>\n    <com.google.android.material.materialswitch.MaterialSwitch\n        android:id=\"@+id/isActiveSwitch\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/linkIsActive\" />\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/layout_link_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    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"vertical\"\n    android:layout_marginHorizontal=\"15dp\"\n    android:layout_marginVertical=\"5dp\">\n    <androidx.cardview.widget.CardView\n        android:id=\"@+id/linkCard\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:cardCornerRadius=\"10dp\">\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"72dp\"\n            android:orientation=\"horizontal\"\n            android:baselineAligned=\"false\">\n            <LinearLayout\n                android:layout_width=\"0dp\"\n                android:layout_height=\"match_parent\"\n                android:layout_weight=\"1\"\n                android:orientation=\"vertical\"\n                android:gravity=\"center|start\"\n                android:paddingStart=\"11dp\"\n                android:paddingEnd=\"1dp\">\n                <TextView\n                    android:id=\"@+id/linkName\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:textColor=\"@android:color/white\"\n                    android:textSize=\"18sp\"\n                    android:textStyle=\"bold\" />\n            </LinearLayout>\n            <LinearLayout\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"match_parent\"\n                android:gravity=\"center\"\n                android:orientation=\"horizontal\">\n                <LinearLayout\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"match_parent\"\n                    android:gravity=\"center\"\n                    android:orientation=\"vertical\"\n                    android:padding=\"8dp\">\n                    <TextView\n                        android:id=\"@+id/linkType\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:textColor=\"@android:color/white\"\n                        android:textStyle=\"bold\" />\n                </LinearLayout>\n                <LinearLayout\n                    android:id=\"@+id/linkEdit\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"match_parent\"\n                    android:background=\"?android:attr/selectableItemBackground\"\n                    android:clickable=\"true\"\n                    android:focusable=\"true\"\n                    android:gravity=\"center\"\n                    android:orientation=\"vertical\"\n                    android:padding=\"8dp\">\n                    <ImageView\n                        android:layout_width=\"24dp\"\n                        android:layout_height=\"24dp\"\n                        android:src=\"@drawable/baseline_edit\"\n                        android:contentDescription=\"@string/editIcon\"\n                        app:tint=\"@android:color/white\" />\n                </LinearLayout>\n                <LinearLayout\n                    android:id=\"@+id/linkDelete\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"match_parent\"\n                    android:background=\"?android:attr/selectableItemBackground\"\n                    android:clickable=\"true\"\n                    android:focusable=\"true\"\n                    android:gravity=\"center\"\n                    android:orientation=\"vertical\"\n                    android:padding=\"8dp\">\n                    <ImageView\n                        android:layout_width=\"24dp\"\n                        android:layout_height=\"24dp\"\n                        android:src=\"@drawable/baseline_delete\"\n                        android:contentDescription=\"@string/deleteIcon\"\n                        app:tint=\"@android:color/white\" />\n                </LinearLayout>\n            </LinearLayout>\n        </LinearLayout>\n    </androidx.cardview.widget.CardView>\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/layout_tun_routes.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:orientation=\"vertical\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:paddingHorizontal=\"16dp\">\n    <EditText\n        android:id=\"@+id/tunRoutesEditText\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:inputType=\"textMultiLine\"\n        android:autofillHints=\"\"\n        tools:ignore=\"LabelFor\" />\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/loading_dialog.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:orientation=\"horizontal\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:padding=\"16dp\">\n    <ProgressBar\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\" />\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"10dp\"\n        android:layout_gravity=\"center\"\n        android:text=\"@string/loading\" />\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/tab_advanced_settings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.core.widget.NestedScrollView\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\"\n        android:padding=\"15dp\">\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"horizontal\"\n            android:baselineAligned=\"false\">\n            <LinearLayout\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:orientation=\"vertical\">\n                <TextView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/primaryDns\"\n                    android:labelFor=\"@id/primaryDns\" />\n                <EditText\n                    android:id=\"@+id/primaryDns\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:inputType=\"text\"\n                    android:autofillHints=\"\" />\n            </LinearLayout>\n            <LinearLayout\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:orientation=\"vertical\">\n                <TextView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/secondaryDns\"\n                    android:labelFor=\"@id/secondaryDns\" />\n                <EditText\n                    android:id=\"@+id/secondaryDns\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:inputType=\"text\"\n                    android:autofillHints=\"\" />\n            </LinearLayout>\n        </LinearLayout>\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"horizontal\"\n            android:baselineAligned=\"false\">\n            <LinearLayout\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:orientation=\"vertical\">\n                <TextView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/primaryDnsV6\"\n                    android:labelFor=\"@id/primaryDnsV6\" />\n                <EditText\n                    android:id=\"@+id/primaryDnsV6\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:inputType=\"text\"\n                    android:autofillHints=\"\" />\n            </LinearLayout>\n            <LinearLayout\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:orientation=\"vertical\">\n                <TextView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/secondaryDnsV6\"\n                    android:labelFor=\"@id/secondaryDnsV6\" />\n                <EditText\n                    android:id=\"@+id/secondaryDnsV6\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:inputType=\"text\"\n                    android:autofillHints=\"\" />\n            </LinearLayout>\n        </LinearLayout>\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"horizontal\"\n            android:baselineAligned=\"false\">\n            <LinearLayout\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:orientation=\"vertical\">\n                <TextView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/tunName\"\n                    android:labelFor=\"@id/tunName\" />\n                <EditText\n                    android:id=\"@+id/tunName\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:inputType=\"text\"\n                    android:autofillHints=\"\" />\n            </LinearLayout>\n            <LinearLayout\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:orientation=\"vertical\">\n                <TextView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/tunMtu\"\n                    android:labelFor=\"@id/tunMtu\" />\n                <EditText\n                    android:id=\"@+id/tunMtu\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:inputType=\"number\"\n                    android:autofillHints=\"\" />\n            </LinearLayout>\n        </LinearLayout>\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"horizontal\"\n            android:baselineAligned=\"false\">\n            <LinearLayout\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:orientation=\"vertical\">\n                <TextView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/tunAddress\"\n                    android:labelFor=\"@id/tunAddress\" />\n                <EditText\n                    android:id=\"@+id/tunAddress\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:inputType=\"text\"\n                    android:autofillHints=\"\" />\n            </LinearLayout>\n            <LinearLayout\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:orientation=\"vertical\">\n                <TextView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/tunPrefix\"\n                    android:labelFor=\"@id/tunPrefix\" />\n                <EditText\n                    android:id=\"@+id/tunPrefix\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:inputType=\"number\"\n                    android:autofillHints=\"\" />\n            </LinearLayout>\n        </LinearLayout>\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"horizontal\"\n            android:baselineAligned=\"false\">\n            <LinearLayout\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:orientation=\"vertical\">\n                <TextView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/tunAddressV6\"\n                    android:labelFor=\"@id/tunAddressV6\" />\n                <EditText\n                    android:id=\"@+id/tunAddressV6\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:inputType=\"text\"\n                    android:autofillHints=\"\" />\n            </LinearLayout>\n            <LinearLayout\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:orientation=\"vertical\">\n                <TextView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/tunPrefixV6\"\n                    android:labelFor=\"@id/tunPrefixV6\" />\n                <EditText\n                    android:id=\"@+id/tunPrefixV6\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:inputType=\"number\"\n                    android:autofillHints=\"\" />\n            </LinearLayout>\n        </LinearLayout>\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"horizontal\"\n            android:baselineAligned=\"false\">\n            <LinearLayout\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:orientation=\"vertical\">\n                <TextView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/hotspotInterface\"\n                    android:labelFor=\"@id/hotspotInterface\" />\n                <EditText\n                    android:id=\"@+id/hotspotInterface\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:inputType=\"text\"\n                    android:autofillHints=\"\" />\n            </LinearLayout>\n            <LinearLayout\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:orientation=\"vertical\">\n                <TextView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/tetheringInterface\"\n                    android:labelFor=\"@id/tetheringInterface\" />\n                <EditText\n                    android:id=\"@+id/tetheringInterface\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:inputType=\"text\"\n                    android:autofillHints=\"\" />\n            </LinearLayout>\n        </LinearLayout>\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"horizontal\"\n            android:baselineAligned=\"false\">\n            <LinearLayout\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:orientation=\"vertical\">\n                <TextView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/tproxyAddress\"\n                    android:labelFor=\"@id/tproxyAddress\" />\n                <EditText\n                    android:id=\"@+id/tproxyAddress\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:inputType=\"text\"\n                    android:autofillHints=\"\" />\n            </LinearLayout>\n            <LinearLayout\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:orientation=\"vertical\">\n                <TextView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/tproxyPort\"\n                    android:labelFor=\"@id/tproxyPort\" />\n                <EditText\n                    android:id=\"@+id/tproxyPort\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:inputType=\"number\"\n                    android:autofillHints=\"\" />\n            </LinearLayout>\n        </LinearLayout>\n        <TextView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/tproxyBypassWiFi\"\n            android:labelFor=\"@id/tproxyBypassWiFi\" />\n        <EditText\n            android:id=\"@+id/tproxyBypassWiFi\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:inputType=\"text\"\n            android:autofillHints=\"\" />\n        <com.google.android.material.materialswitch.MaterialSwitch\n            android:id=\"@+id/tproxyAutoConnect\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/tproxyAutoConnect\" />\n        <com.google.android.material.materialswitch.MaterialSwitch\n            android:id=\"@+id/tproxyHotspot\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/tproxyHotspot\" />\n        <com.google.android.material.materialswitch.MaterialSwitch\n            android:id=\"@+id/tproxyTethering\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/tproxyTethering\" />\n        <com.google.android.material.materialswitch.MaterialSwitch\n            android:id=\"@+id/transparentProxy\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/transparentProxy\" />\n        <LinearLayout\n            android:id=\"@+id/tunRoutes\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"horizontal\"\n            android:gravity=\"center_vertical\"\n            android:paddingVertical=\"10dp\"\n            android:clickable=\"true\"\n            android:focusable=\"true\"\n            android:background=\"?attr/selectableItemBackground\">\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/tunRoutes\" />\n            <Space\n                android:layout_width=\"0dp\"\n                android:layout_height=\"0dp\"\n                android:layout_weight=\"1\" />\n            <ImageView\n                android:layout_width=\"32dp\"\n                android:layout_height=\"32dp\"\n                android:src=\"@drawable/baseline_settings\"\n                android:contentDescription=\"@string/tunRoutes\" />\n        </LinearLayout>\n    </LinearLayout>\n</androidx.core.widget.NestedScrollView>\n"
  },
  {
    "path": "app/src/main/res/layout/tab_basic_settings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.core.widget.NestedScrollView\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\"\n        android:padding=\"15dp\">\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"horizontal\"\n            android:baselineAligned=\"false\">\n            <LinearLayout\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:orientation=\"vertical\">\n                <TextView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/socksAddress\"\n                    android:labelFor=\"@id/socksAddress\" />\n                <EditText\n                    android:id=\"@+id/socksAddress\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:inputType=\"text\"\n                    android:autofillHints=\"\" />\n            </LinearLayout>\n            <LinearLayout\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:orientation=\"vertical\">\n                <TextView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/socksPort\"\n                    android:labelFor=\"@id/socksPort\" />\n                <EditText\n                    android:id=\"@+id/socksPort\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:inputType=\"number\"\n                    android:autofillHints=\"\" />\n            </LinearLayout>\n        </LinearLayout>\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"horizontal\"\n            android:baselineAligned=\"false\">\n            <LinearLayout\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:orientation=\"vertical\">\n                <TextView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/socksUsername\"\n                    android:labelFor=\"@id/socksUsername\" />\n                <EditText\n                    android:id=\"@+id/socksUsername\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:inputType=\"text\"\n                    android:autofillHints=\"\" />\n            </LinearLayout>\n            <LinearLayout\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:orientation=\"vertical\">\n                <TextView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/socksPassword\"\n                    android:labelFor=\"@id/socksPassword\" />\n                <EditText\n                    android:id=\"@+id/socksPassword\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:inputType=\"textPassword\"\n                    android:autofillHints=\"\" />\n            </LinearLayout>\n        </LinearLayout>\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"horizontal\"\n            android:baselineAligned=\"false\">\n            <LinearLayout\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:orientation=\"vertical\">\n                <TextView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/geoIpAddress\"\n                    android:labelFor=\"@id/geoIpAddress\" />\n                <EditText\n                    android:id=\"@+id/geoIpAddress\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:inputType=\"text\"\n                    android:autofillHints=\"\" />\n            </LinearLayout>\n            <LinearLayout\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:orientation=\"vertical\">\n                <TextView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/geoSiteAddress\"\n                    android:labelFor=\"@id/geoSiteAddress\" />\n                <EditText\n                    android:id=\"@+id/geoSiteAddress\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:inputType=\"text\"\n                    android:autofillHints=\"\" />\n            </LinearLayout>\n        </LinearLayout>\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"horizontal\"\n            android:baselineAligned=\"false\">\n            <LinearLayout\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:orientation=\"vertical\">\n                <TextView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/pingAddress\"\n                    android:labelFor=\"@id/pingAddress\" />\n                <EditText\n                    android:id=\"@+id/pingAddress\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:inputType=\"text\"\n                    android:autofillHints=\"\" />\n            </LinearLayout>\n            <LinearLayout\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:orientation=\"vertical\">\n                <TextView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/pingTimeout\"\n                    android:labelFor=\"@id/pingTimeout\" />\n                <EditText\n                    android:id=\"@+id/pingTimeout\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:inputType=\"number\"\n                    android:autofillHints=\"\" />\n            </LinearLayout>\n        </LinearLayout>\n        <TextView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/refreshLinksInterval\"\n            android:labelFor=\"@id/refreshLinksInterval\" />\n        <EditText\n            android:id=\"@+id/refreshLinksInterval\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:inputType=\"number\"\n            android:autofillHints=\"\" />\n        <com.google.android.material.materialswitch.MaterialSwitch\n            android:id=\"@+id/bypassLan\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/bypassLan\" />\n        <com.google.android.material.materialswitch.MaterialSwitch\n            android:id=\"@+id/enableIpV6\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/enableIpV6\" />\n        <com.google.android.material.materialswitch.MaterialSwitch\n            android:id=\"@+id/socksUdp\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/socksUdp\" />\n        <com.google.android.material.materialswitch.MaterialSwitch\n            android:id=\"@+id/tun2socks\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/tun2socks\" />\n        <com.google.android.material.materialswitch.MaterialSwitch\n            android:id=\"@+id/bootAutoStart\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/bootAutoStart\" />\n        <com.google.android.material.materialswitch.MaterialSwitch\n            android:id=\"@+id/refreshLinksOnOpen\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/refreshLinksOnOpen\" />\n    </LinearLayout>\n</androidx.core.widget.NestedScrollView>\n"
  },
  {
    "path": "app/src/main/res/layout/tab_config.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=\"match_parent\"\n    android:orientation=\"vertical\">\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"horizontal\">\n        <RadioGroup\n            android:id=\"@+id/modeRadioGroup\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"horizontal\" />\n    </LinearLayout>\n    <com.blacksquircle.ui.editorkit.widget.TextProcessor\n        android:id=\"@+id/config\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\"\n        android:inputType=\"textMultiLine\"\n        android:background=\"@null\" />\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/menu/menu_apps_routing.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n    <item\n        android:id=\"@+id/appsRoutingSave\"\n        android:title=\"@string/appsRoutingSave\"\n        android:icon=\"@drawable/baseline_done\"\n        android:visible=\"true\"\n        app:showAsAction=\"never\" />\n    <item\n        android:id=\"@+id/appsRoutingExcludeMode\"\n        android:title=\"@string/appsRoutingExcludeMode\"\n        android:icon=\"@drawable/baseline_alt_route\"\n        android:visible=\"true\"\n        app:showAsAction=\"never\" />\n    <item\n        android:id=\"@+id/appsRoutingIncludeMode\"\n        android:title=\"@string/appsRoutingIncludeMode\"\n        android:icon=\"@drawable/baseline_alt_route\"\n        android:visible=\"false\"\n        app:showAsAction=\"never\" />\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/menu_configs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n    <item\n        android:id=\"@+id/saveConfigs\"\n        android:title=\"@string/saveConfigs\"\n        android:icon=\"@drawable/baseline_done\"\n        app:showAsAction=\"ifRoom\" />\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/menu_drawer.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <group android:id=\"@+id/toolsVersion\">\n        <item\n            android:id=\"@+id/appFullName\"\n            android:title=\"@string/appFullName\" />\n        <item\n            android:id=\"@+id/appVersion\"\n            android:title=\"@string/noValue\" />\n        <item\n            android:id=\"@+id/xrayLabel\"\n            android:title=\"@string/xrayLabel\" />\n        <item\n            android:id=\"@+id/xrayVersion\"\n            android:title=\"@string/noValue\" />\n        <item\n            android:id=\"@+id/tun2socksLabel\"\n            android:title=\"@string/tun2socksLabel\" />\n        <item\n            android:id=\"@+id/tun2socksVersion\"\n            android:title=\"@string/tun2socksVersion\" />\n    </group>\n    <group android:id=\"@+id/drawerMain\">\n        <item\n            android:id=\"@+id/assets\"\n            android:icon=\"@drawable/baseline_folder_open\"\n            android:title=\"@string/assets\" />\n        <item\n            android:id=\"@+id/links\"\n            android:icon=\"@drawable/baseline_link\"\n            android:title=\"@string/links\" />\n        <item\n            android:id=\"@+id/logs\"\n            android:icon=\"@drawable/baseline_adb\"\n            android:title=\"@string/logs\" />\n        <item\n            android:id=\"@+id/appsRouting\"\n            android:icon=\"@drawable/baseline_alt_route\"\n            android:title=\"@string/appsRouting\" />\n        <item\n            android:id=\"@+id/configs\"\n            android:icon=\"@drawable/baseline_config\"\n            android:title=\"@string/configs\" />\n        <item\n            android:id=\"@+id/settings\"\n            android:icon=\"@drawable/baseline_settings\"\n            android:title=\"@string/settings\" />\n    </group>\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/menu_links.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n    <item\n        android:id=\"@+id/refreshLinks\"\n        android:title=\"@string/refreshLinks\"\n        android:icon=\"@drawable/baseline_refresh\"\n        app:showAsAction=\"ifRoom\" />\n    <item\n        android:id=\"@+id/newLink\"\n        android:title=\"@string/newLink\"\n        android:icon=\"@drawable/baseline_add\"\n        app:showAsAction=\"ifRoom\" />\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/menu_logs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n    <item\n        android:id=\"@+id/deleteLogs\"\n        android:title=\"@string/deleteLogs\"\n        android:icon=\"@drawable/baseline_delete\"\n        app:showAsAction=\"ifRoom\" />\n    <item\n        android:id=\"@+id/copyLogs\"\n        android:title=\"@string/copyLogs\"\n        android:icon=\"@drawable/baseline_content_copy\"\n        app:showAsAction=\"ifRoom\" />\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/menu_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n    <item\n        android:id=\"@+id/refreshLinks\"\n        android:title=\"@string/refreshLinks\"\n        android:icon=\"@drawable/baseline_refresh\"\n        app:showAsAction=\"ifRoom\" />\n    <item\n        android:title=\"@string/newProfile\"\n        android:icon=\"@drawable/baseline_add\"\n        app:showAsAction=\"ifRoom\">\n        <menu>\n            <item\n                android:id=\"@+id/newProfile\"\n                android:title=\"@string/newProfile\" />\n            <item\n                android:id=\"@+id/scanQrCode\"\n                android:title=\"@string/scanQrCode\" />\n            <item\n                android:id=\"@+id/fromClipboard\"\n                android:title=\"@string/fromClipboard\" />\n        </menu>\n    </item>\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/menu_profile.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n    <item\n        android:id=\"@+id/saveProfile\"\n        android:title=\"@string/saveProfile\"\n        android:icon=\"@drawable/baseline_done\"\n        app:showAsAction=\"ifRoom\" />\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/menu_settings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n    <item\n        android:id=\"@+id/saveSettings\"\n        android:title=\"@string/saveSettings\"\n        android:icon=\"@drawable/baseline_done\"\n        app:showAsAction=\"ifRoom\" />\n</menu>\n"
  },
  {
    "path": "app/src/main/res/values/array.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string-array name=\"publicIpAddresses\">\n        <item>0.0.0.0/5</item>\n        <item>8.0.0.0/7</item>\n        <item>11.0.0.0/8</item>\n        <item>12.0.0.0/6</item>\n        <item>16.0.0.0/4</item>\n        <item>32.0.0.0/3</item>\n        <item>64.0.0.0/2</item>\n        <item>128.0.0.0/3</item>\n        <item>160.0.0.0/5</item>\n        <item>168.0.0.0/6</item>\n        <item>172.0.0.0/12</item>\n        <item>172.32.0.0/11</item>\n        <item>172.64.0.0/10</item>\n        <item>172.128.0.0/9</item>\n        <item>173.0.0.0/8</item>\n        <item>174.0.0.0/7</item>\n        <item>176.0.0.0/4</item>\n        <item>192.0.0.0/9</item>\n        <item>192.128.0.0/11</item>\n        <item>192.160.0.0/13</item>\n        <item>192.169.0.0/16</item>\n        <item>192.170.0.0/15</item>\n        <item>192.172.0.0/14</item>\n        <item>192.176.0.0/12</item>\n        <item>192.192.0.0/10</item>\n        <item>193.0.0.0/8</item>\n        <item>194.0.0.0/7</item>\n        <item>196.0.0.0/6</item>\n        <item>200.0.0.0/5</item>\n        <item>208.0.0.0/4</item>\n        <item>240.0.0.0/4</item>\n    </string-array>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"primaryColor\">#009966</color>\n    <color name=\"btnColor\">#636363</color>\n    <color name=\"btnColorDisabled\">#313131</color>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"appName\">Xray</string>\n    <string name=\"noValue\">-</string>\n    <string name=\"drawerOpen\">Open drawer</string>\n    <string name=\"drawerClose\">Close drawer</string>\n    <string name=\"scanQrCode\">Scan QrCode</string>\n    <string name=\"fromClipboard\">From Clipboard</string>\n    <string name=\"loading\">Loading…</string>\n    <string name=\"vpnStart\">START</string>\n    <string name=\"vpnStop\">STOP</string>\n    <string name=\"pingNotConnected\">Not Connected</string>\n    <string name=\"pingConnected\">Connected, tap to check connection</string>\n    <string name=\"pingTesting\">Testing…</string>\n    <string name=\"appFullName\">SaeedDev94/Xray</string>\n    <string name=\"xrayLabel\">XTLS/Xray-core</string>\n    <string name=\"tun2socksLabel\">heiher/hev-socks5-tunnel</string>\n    <string name=\"tun2socksVersion\">2.14.4</string>\n    <string name=\"assets\">Assets</string>\n    <string name=\"links\">Links</string>\n    <string name=\"logs\">Logs</string>\n    <string name=\"appsRouting\">Apps Routing</string>\n    <string name=\"configs\">Configs</string>\n    <string name=\"settings\">Settings</string>\n    <string name=\"geoIp\">GeoIP</string>\n    <string name=\"geoSite\">GeoSite</string>\n    <string name=\"refreshLinks\">Refresh Links</string>\n    <string name=\"newLink\">New Link</string>\n    <string name=\"editLink\">Edit Link</string>\n    <string name=\"linkName\">Name</string>\n    <string name=\"linkAddress\">Address</string>\n    <string name=\"linkUserAgent\">User-Agent</string>\n    <string name=\"linkIsActive\">Is Active?</string>\n    <string name=\"createLink\">Create</string>\n    <string name=\"updateLink\">Update</string>\n    <string name=\"closeLink\">Close</string>\n    <string name=\"newProfile\">New Profile</string>\n    <string name=\"editProfile\">Edit Profile</string>\n    <string name=\"profileName\">Name</string>\n    <string name=\"profileConfig\">Config</string>\n    <string name=\"editIcon\">Edit Icon</string>\n    <string name=\"deleteIcon\">Delete Icon</string>\n    <string name=\"downloadIcon\">Download Icon</string>\n    <string name=\"fileOpenIcon\">File Open Icon</string>\n    <string name=\"copyLogs\">Copy Logs</string>\n    <string name=\"deleteLogs\">Delete Logs</string>\n    <string name=\"appIcon\">App Icon</string>\n    <string name=\"openJson\">Open Json</string>\n    <string name=\"appsRoutingSave\">Save</string>\n    <string name=\"saveConfigs\">Save Configs</string>\n    <string name=\"appsRoutingExcludeMode\">Mode: Exclude</string>\n    <string name=\"appsRoutingIncludeMode\">Mode: Include</string>\n    <string name=\"saveSettings\">Save Settings</string>\n    <string name=\"saveProfile\">Save Profile</string>\n    <string name=\"invalidProfile\">Invalid Profile</string>\n    <string name=\"invalidLink\">Invalid Link</string>\n    <string name=\"forbiddenHttp\">HTTP is forbidden</string>\n    <string name=\"onlyHttps\">Only HTTPS is acceptable</string>\n    <string name=\"cancel\">Cancel</string>\n    <string name=\"ignore\">Ignore</string>\n    <string name=\"ok\">OK</string>\n\n    <!-- Basic Settings -->\n    <string name=\"socksAddress\">Socks Address</string>\n    <string name=\"socksPort\">Socks Port</string>\n    <string name=\"socksUsername\">Socks Username</string>\n    <string name=\"socksPassword\">Socks Password</string>\n    <string name=\"geoIpAddress\">GeoIP Address</string>\n    <string name=\"geoSiteAddress\">GeoSite Address</string>\n    <string name=\"pingAddress\">Ping Address</string>\n    <string name=\"pingTimeout\">Ping Timeout</string>\n    <string name=\"refreshLinksInterval\">Refresh Links Interval</string>\n    <string name=\"bypassLan\">Bypass LAN (IPv4)</string>\n    <string name=\"enableIpV6\">Enable IPv6</string>\n    <string name=\"socksUdp\">Socks UDP</string>\n    <string name=\"tun2socks\">Tun2socks</string>\n    <string name=\"bootAutoStart\">Boot Auto Start</string>\n    <string name=\"refreshLinksOnOpen\">Refresh Links On Open</string>\n\n    <!-- Advanced Settings -->\n    <string name=\"primaryDns\">Primary DNS (v4)</string>\n    <string name=\"secondaryDns\">Secondary DNS (v4)</string>\n    <string name=\"primaryDnsV6\">Primary DNS (v6)</string>\n    <string name=\"secondaryDnsV6\">Secondary DNS (v6)</string>\n    <string name=\"tunName\">Tun Name</string>\n    <string name=\"tunMtu\">Tun Mtu</string>\n    <string name=\"tunAddress\">Tun Address (IPv4)</string>\n    <string name=\"tunPrefix\">Prefix</string>\n    <string name=\"tunAddressV6\">Tun Address (IPv6)</string>\n    <string name=\"tunPrefixV6\">Prefix</string>\n    <string name=\"hotspotInterface\">Hotspot Interface</string>\n    <string name=\"tetheringInterface\">Tethering Interface</string>\n    <string name=\"tproxyAddress\">TPROXY Address</string>\n    <string name=\"tproxyPort\">TPROXY Port</string>\n    <string name=\"tproxyBypassWiFi\">TPROXY Bypass WiFi</string>\n    <string name=\"tproxyAutoConnect\">TPROXY Auto Connect</string>\n    <string name=\"tproxyHotspot\">TPROXY Hotspot</string>\n    <string name=\"tproxyTethering\">TPROXY Tethering</string>\n    <string name=\"transparentProxy\">Transparent Proxy</string>\n    <string name=\"tunRoutes\">Tun Routes</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/themes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <style name=\"AppTheme\" parent=\"Theme.Material3.Dark.NoActionBar\">\n        <item name=\"colorPrimaryDark\">@android:color/black</item>\n        <item name=\"colorPrimary\">@color/primaryColor</item>\n        <item name=\"colorOnPrimary\">@android:color/white</item>\n        <item name=\"android:windowBackground\">@android:color/black</item>\n    </style>\n    <style name=\"TransparentTheme\" parent=\"AppTheme\">\n        <item name=\"android:windowIsTranslucent\">true</item>\n        <item name=\"android:windowBackground\">@android:color/transparent</item>\n    </style>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/xml/network_security_config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<network-security-config\n    xmlns:tools=\"http://schemas.android.com/tools\">\n    <base-config>\n        <trust-anchors>\n            <certificates\n                src=\"system\" />\n            <certificates\n                src=\"user\"\n                tools:ignore=\"AcceptsUserCertificates\" />\n        </trust-anchors>\n    </base-config>\n</network-security-config>\n"
  },
  {
    "path": "app/versionCode.txt",
    "content": "1060\n"
  },
  {
    "path": "build-xray.sh",
    "content": "#!/bin/bash\n\n# Update repo\napt-get update\napt-get install -y ca-certificates\necho \"deb https://deb.debian.org/debian forky main\" > /etc/apt/sources.list.d/forky.list\napt-get update || apt-get update\napt-get dist-upgrade -y\n\n# Tools version\nANDROID_PLATFORM_VERSION=\"android-36\"\nANDROID_SDK_VERSION=\"36.0.0\"\nJAVA_VERSION=\"21\"\n\n# Install Tools\napt-get install -y git openjdk-$JAVA_VERSION-jdk-headless sdkmanager wget unzip gcc libc-dev golang-go\n\n# Define dirs\nHOME_DIR=\"/home/vagrant\"\nBUILD_DIR=\"$HOME_DIR/build\"\nREPO_DIR=\"$BUILD_DIR/io.github.saeeddev94.xray\"\n\n# Set vars\nexport JAVA_HOME=\"/usr/lib/jvm/java-$JAVA_VERSION-openjdk-amd64\"\nexport ANDROID_HOME=\"/opt/android-sdk\"\n\n# Set path\nexport PATH=\"$JAVA_HOME/bin:$PATH\"\nexport PATH=\"$ANDROID_HOME/platform-tools:$PATH\"\nexport PATH=\"$ANDROID_HOME/build-tools/$ANDROID_SDK_VERSION:$PATH\"\n\n# Clone repo\ngit clone https://github.com/SaeedDev94/Xray.git $REPO_DIR\ncd $REPO_DIR\ngit checkout \"$RELEASE_TAG\"\ngit submodule update --init --recursive\n\n# Setup SDK & NDK\nANDROID_NDK_VERSION=$(awk -F '\"' '/ndkVersion/ {print $2}' app/build.gradle.kts)\nsdkmanager \"platform-tools\" \"platforms;$ANDROID_PLATFORM_VERSION\" \"build-tools;$ANDROID_SDK_VERSION\"\nsdkmanager --install \"ndk;$ANDROID_NDK_VERSION\" --channel=3\n\n# Setup gradle\nGRADLE_DIR=\"$BUILD_DIR/gradle\"\nGRADLE_URL=$(grep distributionUrl gradle/wrapper/gradle-wrapper.properties | \\\n  cut -d '=' -f 2 | \\\n  sed 's#\\\\##g')\nGRADLE_ARCHIVE=$(basename $GRADLE_URL)\nGRADLE_VERSION=$(echo \"$GRADLE_ARCHIVE\" | sed -E 's/gradle-([0-9.]+)-bin\\.zip/\\1/')\nmkdir -p $GRADLE_DIR\npushd $GRADLE_DIR\nwget \"$GRADLE_URL\"\nunzip \"$GRADLE_ARCHIVE\"\nrm \"$GRADLE_ARCHIVE\"\nmv * \"$GRADLE_VERSION\"\npopd\nexport PATH=\"$GRADLE_DIR/$GRADLE_VERSION/bin:$PATH\"\n\n# Clean task\nrm gradle/wrapper/gradle-wrapper.jar\ngradle clean\n\n# Build dependencies\n./buildGo.sh $NATIVE_ARCH\n\n# Build app\necho \"$KS_FILE\" > /tmp/xray_base64.txt\nbase64 -d /tmp/xray_base64.txt > /tmp/xray.jks\ngradle -PabiId=$ABI_ID -PabiTarget=$ABI_TARGET assembleRelease\nrm /tmp/xray_base64.txt /tmp/xray.jks\n\n# Build name\nVERSION_CODE=$(cat app/versionCode.txt)\n((VERSION_CODE += ABI_ID))\nBUILD_NAME=\"Xray-$RELEASE_TAG-$VERSION_CODE.apk\"\nmv \"app/build/outputs/apk/release/app-$ABI_TARGET-release.apk\" \"$DIST_DIR/$BUILD_NAME\"\n"
  },
  {
    "path": "build.gradle.kts",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\nplugins {\n    alias(libs.plugins.android.application) apply false\n    alias(libs.plugins.kotlin.parcelize) apply false\n    alias(libs.plugins.google.ksp) apply false\n}\n"
  },
  {
    "path": "buildGo.sh",
    "content": "#!/bin/bash\n\n# Set vars\nexport GOROOT=\"$(realpath go-root)\"\nexport GOPATH=\"$(realpath go-path)\"\n\n# Set path\nexport PATH=\"$GOROOT/bin:$PATH\"\nexport PATH=\"$GOPATH/bin:$PATH\"\n\ngit clone https://github.com/golang/go.git $GOROOT\npushd $GOROOT\ngit checkout \"go$(sed -n -E 's/^go (.*)/\\1/p' ../XrayCore/go.mod)\"\ncd src\n./make.bash\npopd\n\n./buildXrayCore.sh $1\n./buildXrayHelper.sh $1\n"
  },
  {
    "path": "buildXrayCore.sh",
    "content": "#!/bin/bash\n\nTARGET=\"$1\"\nREFRESH=\"$2\"\nSETUP=\"$3\"\nARCHS=(arm arm64 386 amd64)\nDEST=\"../app/libs\"\n\nis_in_array() {\n  local value=\"$1\"\n  local array=(\"${@:2}\")\n\n  for item in \"${array[@]}\"; do\n    if [[ \"$item\" == \"$value\" ]]; then\n      return 0\n    fi\n  done\n\n  return 1\n}\n\ncheck_target() {\n  if ! is_in_array \"$TARGET\" \"${ARCHS[@]}\"; then\n    echo \"Not supported\"\n    exit 1\n  fi\n}\n\nprepare_go() {\n  echo \"Install dependencies\"\n  if [[ -n \"$SETUP\" ]]; then\n    rm go*\n    go mod init XrayCore\n    go mod edit -replace github.com/xtls/xray-core=./Xray-core\n    go mod edit -replace github.com/xtls/libxray=./libXray\n    go mod tidy\n    go get golang.org/x/mobile\n    go get google.golang.org/genproto\n  fi\n  local VERSION=$(awk -F ' ' '/golang.org\\/x\\/mobile/ {print $2}' go.mod)\n  go install golang.org/x/mobile/cmd/gomobile@$VERSION\n  go mod download\n}\n\nbuild_android() {\n  echo \"Building XrayCore for $TARGET\"\n  rm -f \"$DEST/XrayCore*\"\n  gomobile init\n  gomobile bind -o \"$DEST/XrayCore.aar\" -androidapi 26 -target \"android/$TARGET\" -ldflags=\"-buildid=\" -trimpath\n}\n\nrefresh_dependencies() {\n  echo \"Gradle: refresh dependencies\"\n  ./gradlew --refresh-dependencies clean\n}\n\ncheck_target\n\npushd XrayCore\nprepare_go\nbuild_android\npopd\n\nif [[ -n \"$REFRESH\" ]]; then\n  refresh_dependencies\nfi\n"
  },
  {
    "path": "buildXrayHelper.sh",
    "content": "#!/bin/bash\n\nTARGET=\"$1\"\nARCHS=(arm arm64 386 amd64)\nDEST=\"../app/src/main/assets\"\n\nis_in_array() {\n  local value=\"$1\"\n  local array=(\"${@:2}\")\n\n  for item in \"${array[@]}\"; do\n    if [[ \"$item\" == \"$value\" ]]; then\n      return 0\n    fi\n  done\n\n  return 1\n}\n\ncheck_target() {\n  if ! is_in_array \"$TARGET\" \"${ARCHS[@]}\"; then\n    echo \"Not supported\"\n    exit 1\n  fi\n}\n\nprepare_go() {\n  echo \"Install dependencies\"\n  go mod download\n}\n\nbuild_android() {\n  echo \"Building XrayHelper for $TARGET\"\n  local OUTPUT=\"$DEST/xrayhelper\"\n  rm -f $OUTPUT\n  CGO_ENABLED=0 GOOS=linux GOARCH=$TARGET \\\n    go build -v -o $OUTPUT \\\n    -ldflags \"-s -w -buildid=\" -buildvcs=false -trimpath ./main\n}\n\ncheck_target\n\npushd XrayHelper\nprepare_go\nbuild_android\npopd\n"
  },
  {
    "path": "gradle/libs.versions.toml",
    "content": "[versions]\nagp = \"9.1.1\"\nparcelize=\"2.3.20\"\nksp = \"2.3.6\"\nandroidxActivityKtx = \"1.13.0\"\nandroidxCoreKtx = \"1.18.0\"\nandroidxAppCompat = \"1.7.1\"\nandroidxLifecycleViewmodelKtx = \"2.10.0\"\nandroidxRoom = \"2.8.4\"\nblacksquircleUi = \"2.9.0\"\ngoogleMaterial = \"1.13.0\"\nlibsuCore = \"5.2.2\"\ncodeScanner=\"2.3.2\"\n\n[libraries]\nandroidx-activity-ktx = { group = \"androidx.activity\", name = \"activity-ktx\", version.ref = \"androidxActivityKtx\" }\nandroidx-core-ktx = { group = \"androidx.core\", name = \"core-ktx\", version.ref = \"androidxCoreKtx\" }\nandroidx-appcompat = { group = \"androidx.appcompat\", name = \"appcompat\", version.ref = \"androidxAppCompat\" }\nandroidx-lifecycle-viewmodel-ktx = { group = \"androidx.lifecycle\", name = \"lifecycle-viewmodel-ktx\", version.ref = \"androidxLifecycleViewmodelKtx\" }\nandroidx-room-ktx = { group = \"androidx.room\", name = \"room-ktx\", version.ref = \"androidxRoom\" }\nandroidx-room-runtime = { group = \"androidx.room\", name = \"room-runtime\", version.ref = \"androidxRoom\" }\nandroidx-room-compiler = { group = \"androidx.room\", name = \"room-compiler\", version.ref = \"androidxRoom\" }\nblacksquircle-ui-editorkit = { group = \"com.blacksquircle.ui\", name = \"editorkit\", version.ref = \"blacksquircleUi\" }\nblacksquircle-ui-language-json = { group = \"com.blacksquircle.ui\", name = \"language-json\", version.ref = \"blacksquircleUi\" }\ngoogle-material = { group = \"com.google.android.material\", name = \"material\", version.ref = \"googleMaterial\" }\ntopjohnwu-libsu-core = { module = \"com.github.topjohnwu.libsu:core\", version.ref = \"libsuCore\" }\nyuriy-budiyev-code-scanner = { group = \"com.github.yuriy-budiyev\", name = \"code-scanner\", version.ref = \"codeScanner\" }\n\n[plugins]\nandroid-application = { id = \"com.android.application\", version.ref = \"agp\" }\nkotlin-parcelize = { id = \"org.jetbrains.kotlin.plugin.parcelize\", version.ref = \"parcelize\" }\ngoogle-ksp = { id = \"com.google.devtools.ksp\", version.ref = \"ksp\" }\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-9.4.1-bin.zip\nnetworkTimeout=10000\nvalidateDistributionUrl=true\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\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=-Xmx2048m -Dfile.encoding=UTF-8\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# Kotlin code style for this project: \"official\" or \"obsolete\":\nkotlin.code.style=official\n# Enables namespacing of each library's R class so that its R class includes only the\n# resources declared in the library itself and none from the library's dependencies,\n# thereby reducing the size of the R class for that library\nandroid.nonTransitiveRClass=true\n# abiId to add versionCode (all: 0) (armeabi-v7a: 1) (arm64-v8a: 2) (x86: 3) (x86_64: 4)\nabiId=0\n# abiTarget on build time\nabiTarget=armeabi-v7a,arm64-v8a,x86,x86_64\n"
  },
  {
    "path": "gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015 the original 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# SPDX-License-Identifier: Apache-2.0\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\n# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)\nAPP_HOME=$( cd -P \"${APP_HOME:-./}\" > /dev/null && printf '%s\\n' \"$PWD\" ) || exit\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\n\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    if ! command -v java >/dev/null 2>&1\n    then\n        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.\"\n    fi\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Collect all arguments for the java command:\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,\n#     and any embedded shellness will be escaped.\n#   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be\n#     treated as '${Hostname}' itself on the command line.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -jar \"$APP_HOME/gradle/wrapper/gradle-wrapper.jar\" \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\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@rem SPDX-License-Identifier: Apache-2.0\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\n@rem This is normally unused\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\r\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\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% equ 0 goto execute\r\n\r\necho. 1>&2\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2\r\necho. 1>&2\r\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\r\necho location of your Java installation. 1>&2\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 execute\r\n\r\necho. 1>&2\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2\r\necho. 1>&2\r\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\r\necho location of your Java installation. 1>&2\r\n\r\ngoto fail\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\n\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -jar \"%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\" %*\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif %ERRORLEVEL% equ 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\nset EXIT_CODE=%ERRORLEVEL%\r\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\r\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\r\nexit /b %EXIT_CODE%\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "metadata/en-US/changelogs/1004.txt",
    "content": "- New setting: Tun2socks\n- TPROXY: Bypass NTP\n"
  },
  {
    "path": "metadata/en-US/changelogs/1014.txt",
    "content": "- Skip vpn permission check when tun2socks is disabled\n"
  },
  {
    "path": "metadata/en-US/changelogs/1024.txt",
    "content": "- Xray-core: v25.10.15\n"
  },
  {
    "path": "metadata/en-US/changelogs/1034.txt",
    "content": "- Xray-core: v25.12.2\n- hev-socks5-tunnel: v2.14.1\n"
  },
  {
    "path": "metadata/en-US/changelogs/104.txt",
    "content": "- Fresh new design\n- Profile CRUD\n- Ping active connection\n"
  },
  {
    "path": "metadata/en-US/changelogs/1044.txt",
    "content": "- Xray-core: v26.2.6\n- hev-socks5-tunnel: v2.14.4\n"
  },
  {
    "path": "metadata/en-US/changelogs/1054.txt",
    "content": "- Fix F-Droid Build Issue\n"
  },
  {
    "path": "metadata/en-US/changelogs/1064.txt",
    "content": "- Xray-core: v26.4.17\n"
  },
  {
    "path": "metadata/en-US/changelogs/114.txt",
    "content": "- minSdk 26 (Android 8)\n- Vpn service notification\n- New dark Material3 theme\n- New settings layout\n- New settings: Tun Name, Tun Mtu, Ping Address\n- New output format for ping result\n"
  },
  {
    "path": "metadata/en-US/changelogs/124.txt",
    "content": "- Increase profile CardView corner radius\n- Close drawer on app info items click\n- Profile delete confirm dialog\n- Allow selected profile to delete\n- Assets manager (download, select, delete) dat files (GeoIP, GeoSite)\n- Improve ping, thanks to @maskedeken\n"
  },
  {
    "path": "metadata/en-US/changelogs/134.txt",
    "content": "- List drag and drop\n- SmartTV app launcher support\n"
  },
  {
    "path": "metadata/en-US/changelogs/144.txt",
    "content": "- libXray: fix coreServer is always nil\n"
  },
  {
    "path": "metadata/en-US/changelogs/154.txt",
    "content": "- New Settings:\n- ++ GeoIP Address\n- ++ GeoSite Address\n"
  },
  {
    "path": "metadata/en-US/changelogs/164.txt",
    "content": "- Settings: [Basic, Advanced] Tabs\n- New Settings:\n- ++ Ping Timeout\n- ++ Enable IPv6\n- ++ IPv6 DNS\n- ++ Tun IPv4, IPv6 address\n"
  },
  {
    "path": "metadata/en-US/changelogs/174.txt",
    "content": "- Allow CRUD while VpnService is running except selected profile\n- Xray QS Tile\n"
  },
  {
    "path": "metadata/en-US/changelogs/184.txt",
    "content": "- Fix: selected profile save issue\n"
  },
  {
    "path": "metadata/en-US/changelogs/194.txt",
    "content": "- Fix ping: socks auth error\n- heiher/hev-socks5-tunnel @ 2.6.7\n- LogsActivity: Xray-core live logs!\n"
  },
  {
    "path": "metadata/en-US/changelogs/204.txt",
    "content": "- New Activity: Excluded Apps\n"
  },
  {
    "path": "metadata/en-US/changelogs/214.txt",
    "content": "- XTLS/Xray-core@v1.8.8\n"
  },
  {
    "path": "metadata/en-US/changelogs/224.txt",
    "content": "- Json config editor\n"
  },
  {
    "path": "metadata/en-US/changelogs/234.txt",
    "content": "- Profile: Full width Config Editor\n- Profile: Remove save button and add save to menu\n"
  },
  {
    "path": "metadata/en-US/changelogs/244.txt",
    "content": "- Open Json File With Xray\n"
  },
  {
    "path": "metadata/en-US/changelogs/254.txt",
    "content": "- Xray-core @ v1.8.9\n"
  },
  {
    "path": "metadata/en-US/changelogs/264.txt",
    "content": "- Fix qsTile problem on some devices\n- Optional notification permission\n"
  },
  {
    "path": "metadata/en-US/changelogs/274.txt",
    "content": "- QS Tile: Fix app crash on some scenarios\n- Fix UI update issue on VPN disconnect by system settings\n"
  },
  {
    "path": "metadata/en-US/changelogs/284.txt",
    "content": "- Xray-core @ 1.8.10\n- hev-socks5-tunnel @ 2.6.8\n- Fix: Excluded Apps: deselect issue (thanks to @maskedeken)\n"
  },
  {
    "path": "metadata/en-US/changelogs/294.txt",
    "content": "- Xray-core @ 1.8.11\n"
  },
  {
    "path": "metadata/en-US/changelogs/304.txt",
    "content": "- hev-socks5-tunnel @ v2.6.9\n"
  },
  {
    "path": "metadata/en-US/changelogs/314.txt",
    "content": "- Xray-core @ v1.8.13\n"
  },
  {
    "path": "metadata/en-US/changelogs/324.txt",
    "content": "- hev-socks5-tunnel @ v2.7.0\n"
  },
  {
    "path": "metadata/en-US/changelogs/334.txt",
    "content": "- Xray-core @ v1.8.16\n"
  },
  {
    "path": "metadata/en-US/changelogs/344.txt",
    "content": "- Xray-core @ v1.8.18\n- hev-socks5-tunnel @ v2.7.1\n"
  },
  {
    "path": "metadata/en-US/changelogs/354.txt",
    "content": "- Xray-core @ v1.8.19\n"
  },
  {
    "path": "metadata/en-US/changelogs/364.txt",
    "content": "- Xray-core @ v1.8.21\n"
  },
  {
    "path": "metadata/en-US/changelogs/374.txt",
    "content": "- Xray-core @ v1.8.23\n"
  },
  {
    "path": "metadata/en-US/changelogs/384.txt",
    "content": "- Xray-core @ v1.8.24\n- New: add config via url\n"
  },
  {
    "path": "metadata/en-US/changelogs/394.txt",
    "content": "- hev-socks5-tunnel @ v2.7.4\n- New Profile > From Clipboard: now supports import json config from http(s)\n- Added deep link: \"xray://import-profile/{LINK}\"\n"
  },
  {
    "path": "metadata/en-US/changelogs/404.txt",
    "content": "- Prevent slash character escaping on importing profile via link (Fix #31)\n"
  },
  {
    "path": "metadata/en-US/changelogs/414.txt",
    "content": "- Take uri fragment as profile name (#32)\n"
  },
  {
    "path": "metadata/en-US/changelogs/424.txt",
    "content": "- Logs optimizations by @maskedeken (#27)\n"
  },
  {
    "path": "metadata/en-US/changelogs/504.txt",
    "content": "- Xray-core @ v24.10.31\n"
  },
  {
    "path": "metadata/en-US/changelogs/514.txt",
    "content": "- Xray-core @ v24.11.5\n"
  },
  {
    "path": "metadata/en-US/changelogs/524.txt",
    "content": "- Xray-core @ v24.12.15\n"
  },
  {
    "path": "metadata/en-US/changelogs/534.txt",
    "content": "- Xray-core @ v24.12.18\n"
  },
  {
    "path": "metadata/en-US/changelogs/54.txt",
    "content": "- New package name `io.github.saeeddev94.xray`\n"
  },
  {
    "path": "metadata/en-US/changelogs/544.txt",
    "content": "- Xray-core @ v25.1.1\n- Improve importing config links\n- Android 15: Fix layouts overlap with status bar\n"
  },
  {
    "path": "metadata/en-US/changelogs/604.txt",
    "content": "- Migrate to kotlin CoroutineScope\n- New Activity: LinksActivity\n- Ability to add and manage \"Json and Subscription\" Links\n- Manual update profiles via links\n"
  },
  {
    "path": "metadata/en-US/changelogs/614.txt",
    "content": "- Some optimizations for lower android versions (8-12)\n"
  },
  {
    "path": "metadata/en-US/changelogs/624.txt",
    "content": "- Some optimizations for lower android versions (8-12)\n"
  },
  {
    "path": "metadata/en-US/changelogs/634.txt",
    "content": "- Some optimizations for lower android versions (8-12)\n"
  },
  {
    "path": "metadata/en-US/changelogs/64.txt",
    "content": "- New settings:\n- ++ Socks auth\n- ++ Bypass LAN\n- Set Xray-core version on activity start\n"
  },
  {
    "path": "metadata/en-US/changelogs/644.txt",
    "content": "- Remove redundant permissions\n- New Settings: Boot Auto Start\n- Fix notification stop action\n"
  },
  {
    "path": "metadata/en-US/changelogs/654.txt",
    "content": "- Breaking Change:\n- **The app expects a list of configs for a Json link**\n- The app doesn't accept HTTP link anymore\n- Details: https://github.com/XTLS/Xray-core/discussions/3765\n- Only prevent invalid config on start\n- Show Invalid Profile error message on save inside a dialog\n"
  },
  {
    "path": "metadata/en-US/changelogs/664.txt",
    "content": "- Rename Excluded Apps to Apps Routing\n- Apps Routing has 2 modes: Exclude, Include\n- Default mode is Exclude\n"
  },
  {
    "path": "metadata/en-US/changelogs/674.txt",
    "content": "- Xray-core @ v25.2.21\n- hev-socks5-tunnel @ v2.8.0\n"
  },
  {
    "path": "metadata/en-US/changelogs/684.txt",
    "content": "- Xray-core @ v25.3.31\n- hev-socks5-tunnel @ v2.10.0\n- open app on qs tile long press\n"
  },
  {
    "path": "metadata/en-US/changelogs/694.txt",
    "content": "- Allow user certificates\n- Custom \"User-Agent\" header for Links\n"
  },
  {
    "path": "metadata/en-US/changelogs/704.txt",
    "content": "- App default User-Agent header\n- Change default ping address to `https://www.google.com`\n- Xray-core @ v25.4.30\n"
  },
  {
    "path": "metadata/en-US/changelogs/714.txt",
    "content": "- Drop support for importing json url via clipboard (convert it to json sub instead)\n- Ability to add new link (Json, Subscription) via clipboard directly from MainActivity\n- Ability to refresh links directly from MainActivity\n- Show loading dialog while refreshing links instead of toast\n"
  },
  {
    "path": "metadata/en-US/changelogs/724.txt",
    "content": "- Shows active links as tabs\n- Shows only profiles with active links\n"
  },
  {
    "path": "metadata/en-US/changelogs/734.txt",
    "content": "- Shows active links as tabs\n- Shows only profiles with active links\n- New links sort: oldest first\n"
  },
  {
    "path": "metadata/en-US/changelogs/74.txt",
    "content": "- Split settings: Basic and Advanced\n- Advanced settings are hidden by default\n"
  },
  {
    "path": "metadata/en-US/changelogs/744.txt",
    "content": "- Xray-core @ v25.5.16\n"
  },
  {
    "path": "metadata/en-US/changelogs/754.txt",
    "content": "- Allow profile change while the app is in start mode\n"
  },
  {
    "path": "metadata/en-US/changelogs/764.txt",
    "content": "- Update qs tile and notification on profile select\n- Save last selected link tab\n"
  },
  {
    "path": "metadata/en-US/changelogs/774.txt",
    "content": "- Target SDK 36 (Android 16)\n- Migrate to Gradle kts and version catalogs\n"
  },
  {
    "path": "metadata/en-US/changelogs/784.txt",
    "content": "- Reload xray after links refresh\n- Show active profile name on new config\n"
  },
  {
    "path": "metadata/en-US/changelogs/794.txt",
    "content": "- Xray-core: v25.6.8\n- New setting: Refresh Links On Open\n"
  },
  {
    "path": "metadata/en-US/changelogs/804.txt",
    "content": "- New setting: Refresh Links Interval\n"
  },
  {
    "path": "metadata/en-US/changelogs/814.txt",
    "content": "- Xray-core: v25.8.3\n- hev-socks5-tunnel: v2.13.0\n"
  },
  {
    "path": "metadata/en-US/changelogs/824.txt",
    "content": "- Downgrade AGP to v8.11.1 to fix F-Droid build issue (#74)\n"
  },
  {
    "path": "metadata/en-US/changelogs/834.txt",
    "content": "- New experimental feature for root users: Transparent Proxy Support!\n- TPROXY Auto Connect\n- TPROXY Bypass WiFi\n"
  },
  {
    "path": "metadata/en-US/changelogs/844.txt",
    "content": "- Fix TPROXY Boot Auto Start\n- Stop network monitor when TPROXY is OFF\n- Stop when TPROXY Auto Connect is disabled\n"
  },
  {
    "path": "metadata/en-US/changelogs/854.txt",
    "content": "- New menu item: Scan QrCode\n"
  },
  {
    "path": "metadata/en-US/changelogs/864.txt",
    "content": "- New experimental feature: (Global) Configs (#36)\n"
  },
  {
    "path": "metadata/en-US/changelogs/874.txt",
    "content": "- Fix Configs crash on fresh installs\n"
  },
  {
    "path": "metadata/en-US/changelogs/884.txt",
    "content": "- Show Xray-core logs on \"Transparent Proxy\" mode\n"
  },
  {
    "path": "metadata/en-US/changelogs/894.txt",
    "content": "- Selected Profile is no longer optional! (For socks proxy just create a simple Xray-core config)\n- Share TPROXY for Hotspot and (USB) Tethering\n"
  },
  {
    "path": "metadata/en-US/changelogs/904.txt",
    "content": "- Selected Profile is no longer optional! (For socks proxy just create a simple Xray-core config)\n- Share TPROXY for Hotspot and (USB) Tethering\n- Call TProxyService#stop() on TPROXY Settings Change\n"
  },
  {
    "path": "metadata/en-US/changelogs/914.txt",
    "content": "- Xray-core: v25.8.31\n- Fix empty list if selected link become inactive\n- Profile: consider (global) \"Configs\" on validating\n- Call TProxyService#stop() on TPROXY Apps Routing Change\n"
  },
  {
    "path": "metadata/en-US/changelogs/924.txt",
    "content": "- Fix restore selected link tab on open\n"
  },
  {
    "path": "metadata/en-US/changelogs/934.txt",
    "content": "- Xray-core: v25.9.5\n- Customize tun routes (#71)\n"
  },
  {
    "path": "metadata/en-US/changelogs/944.txt",
    "content": "- Auto install xrayhelper asset\n"
  },
  {
    "path": "metadata/en-US/changelogs/954.txt",
    "content": "- Xray-core: v25.9.10\n"
  },
  {
    "path": "metadata/en-US/changelogs/964.txt",
    "content": "- Xray-core: v25.9.11\n"
  },
  {
    "path": "metadata/en-US/changelogs/974.txt",
    "content": "- Process non-base64 subs\n"
  },
  {
    "path": "metadata/en-US/changelogs/984.txt",
    "content": "- Fix profiles filter issue on app open\n"
  },
  {
    "path": "metadata/en-US/changelogs/994.txt",
    "content": "- Optimize build process\n"
  },
  {
    "path": "metadata/en-US/full_description.txt",
    "content": "This is a simple GUI client for XTLS/Xray-core\n"
  },
  {
    "path": "metadata/en-US/short_description.txt",
    "content": "Xray GUI Client For Android\n"
  },
  {
    "path": "settings.gradle.kts",
    "content": "pluginManagement {\n    repositories {\n        google()\n        mavenCentral()\n        gradlePluginPortal()\n    }\n}\ndependencyResolutionManagement {\n    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)\n    repositories {\n        google()\n        mavenCentral()\n        maven { setUrl(\"https://jitpack.io\")  }\n    }\n}\nrootProject.name = \"Xray\"\ninclude(\":app\")\n"
  }
]