cGFja2FnZSBjb20uaHRibC5hcHAKCmltcG9ydCBhbmRyb2lkLk1hbmlmZXN0CmltcG9ydCBhbmRyb2lkLmFubm90YXRpb24uU3VwcHJlc3NMaW50CmltcG9ydCBhbmRyb2lkLmNvbnRlbnQuQ29udGVudFZhbHVlcwppbXBvcnQgYW5kcm9pZC5jb250ZW50LkNvbnRleHQKaW1wb3J0IGFuZHJvaWQuY29udGVudC5JbnRlbnQKaW1wb3J0IGFuZHJvaWQuY29udGVudC5wbS5QYWNrYWdlTWFuYWdlcgppbXBvcnQgYW5kcm9pZC5kYXRhYmFzZS5DdXJzb3IKaW1wb3J0IGFuZHJvaWQuZ3JhcGhpY3MuQml0bWFwCmltcG9ydCBhbmRyb2lkLmdyYXBoaWNzLkJpdG1hcEZhY3RvcnkKaW1wb3J0IGFuZHJvaWQubWVkaWEuTWVkaWFTY2FubmVyQ29ubmVjdGlvbgppbXBvcnQgYW5kcm9pZC5uZXQuVXJpCmltcG9ydCBhbmRyb2lkLm9zLkJ1aWxkCmltcG9ydCBhbmRyb2lkLm9zLkJ1bmRsZQppbXBvcnQgYW5kcm9pZC5vcy5FbnZpcm9ubWVudAppbXBvcnQgYW5kcm9pZC5wcm92aWRlci5NZWRpYVN0b3JlCmltcG9ydCBhbmRyb2lkLnByb3ZpZGVyLk9wZW5hYmxlQ29sdW1ucwppbXBvcnQgYW5kcm9pZC51dGlsLkxvZwppbXBvcnQgYW5kcm9pZC53aWRnZXQuKgppbXBvcnQgYW5kcm9pZHguYWN0aXZpdHkucmVzdWx0LmNvbnRyYWN0LkFjdGl2aXR5UmVzdWx0Q29udHJhY3RzCmltcG9ydCBhbmRyb2lkeC5hcHBjb21wYXQuYXBwLkFsZXJ0RGlhbG9nCmltcG9ydCBhbmRyb2lkeC5hcHBjb21wYXQuYXBwLkFwcENvbXBhdEFjdGl2aXR5CmltcG9ydCBhbmRyb2lkeC5iaW9tZXRyaWMuQmlvbWV0cmljTWFuYWdlcgppbXBvcnQgYW5kcm9pZHguYmlvbWV0cmljLkJpb21ldHJpY1Byb21wdAppbXBvcnQgYW5kcm9pZHguY29yZS5hcHAuQWN0aXZpdHlDb21wYXQKaW1wb3J0IGFuZHJvaWR4LmNvcmUuY29udGVudC5Db250ZXh0Q29tcGF0CmltcG9ydCBhbmRyb2lkeC5jb3JlLmNvbnRlbnQuRmlsZVByb3ZpZGVyCmltcG9ydCBhbmRyb2lkeC5yZWN5Y2xlcnZpZXcud2lkZ2V0LkxpbmVhckxheW91dE1hbmFnZXIKaW1wb3J0IGFuZHJvaWR4LnJlY3ljbGVydmlldy53aWRnZXQuUmVjeWNsZXJWaWV3CmltcG9ydCBhbmRyb2lkeC5zZWN1cml0eS5jcnlwdG8uRW5jcnlwdGVkRmlsZQppbXBvcnQgYW5kcm9pZHguc2VjdXJpdHkuY3J5cHRvLk1hc3RlcktleQppbXBvcnQgY29tLmdvb2dsZS5tbGtpdC52aXNpb24uYmFyY29kZS5CYXJjb2RlU2Nhbm5pbmcKaW1wb3J0IGNvbS5nb29nbGUubWxraXQudmlzaW9uLmJhcmNvZGUuY29tbW9uLkJhcmNvZGUKaW1wb3J0IGNvbS5nb29nbGUubWxraXQudmlzaW9uLmNvbW1vbi5JbnB1dEltYWdlCmltcG9ydCBjb20uZ29vZ2xlLnp4aW5nLkJhcmNvZGVGb3JtYXQKaW1wb3J0IGNvbS5nb29nbGUuenhpbmcuTXVsdGlGb3JtYXRXcml0ZXIKaW1wb3J0IGNvbS5nb29nbGUuenhpbmcuY29tbW9uLkJpdE1hdHJpeAppbXBvcnQgamF2YS5pby4qCmltcG9ydCBqYXZhLnNlY3VyaXR5Lk1lc3NhZ2VEaWdlc3QKaW1wb3J0IGphdmEudGV4dC5TaW1wbGVEYXRlRm9ybWF0CmltcG9ydCBqYXZhLnV0aWwuKgppbXBvcnQgamF2YXguY3J5cHRvLlNlY3JldEtleUZhY3RvcnkKaW1wb3J0IGphdmF4LmNyeXB0by5zcGVjLlBCRUtleVNwZWMKCmNsYXNzIE1haW5BY3Rpdml0eSA6IEFwcENvbXBhdEFjdGl2aXR5KCkgewoKICAgIHByaXZhdGUgbGF0ZWluaXQgdmFyIHJlY3ljbGVyVmlldzogUmVjeWNsZXJWaWV3CiAgICBwcml2YXRlIGxhdGVpbml0IHZhciBhZGFwdGVyOiBGaWxlQWRhcHRlcgogICAgcHJpdmF0ZSB2YWwgZmlsZUxpc3QgPSBtdXRhYmxlTGlzdE9mJmx0O1ZhdWx0RmlsZSZndDsoKQogICAgCiAgICBwcml2YXRlIGxhdGVpbml0IHZhciBlbmNyeXB0aW9uTWFuYWdlcjogRW5jcnlwdGlvbk1hbmFnZXIKICAgIHByaXZhdGUgbGF0ZWluaXQgdmFyIHBhc3N3b3JkTWFuYWdlcjogUGFzc3dvcmRNYW5hZ2VyCiAgICBwcml2YXRlIGxhdGVpbml0IHZhciBiaW9tZXRyaWNBdXRoOiBCaW9tZXRyaWNBdXRoCiAgICAKICAgIHByaXZhdGUgdmFyIGlzVW5sb2NrZWQgPSBmYWxzZQogICAgcHJpdmF0ZSB2YXIgY3VycmVudEZpbHRlciA9ICZxdW90O2FsbCZxdW90OyAvLyBhbGwsIGltYWdlLCB2aWRlbywgYXVkaW8KICAgIAogICAgY29tcGFuaW9uIG9iamVjdCB7CiAgICAgICAgcHJpdmF0ZSBjb25zdCB2YWwgUFJFRlNfTkFNRSA9ICZxdW90O3ZhdWx0X3ByZWZzJnF1b3Q7CiAgICAgICAgcHJpdmF0ZSBjb25zdCB2YWwgUEFTU1dPUkRfS0VZID0gJnF1b3Q7cGFzc3dvcmRfaGFzaCZxdW90OwogICAgICAgIHByaXZhdGUgY29uc3QgdmFsIFNBTFRfS0VZID0gJnF1b3Q7c2FsdCZxdW90OwogICAgfQogICAgCiAgICBvdmVycmlkZSBmdW4gb25DcmVhdGUoc2F2ZWRJbnN0YW5jZVN0YXRlOiBCdW5kbGU/KSB7CiAgICAgICAgc3VwZXIub25DcmVhdGUoc2F2ZWRJbnN0YW5jZVN0YXRlKQogICAgICAgIHNldENvbnRlbnRWaWV3KFIubGF5b3V0LmFjdGl2aXR5X21haW4pCiAgICAgICAgCiAgICAgICAgZW5jcnlwdGlvbk1hbmFnZXIgPSBFbmNyeXB0aW9uTWFuYWdlcih0aGlzKQogICAgICAgIHBhc3N3b3JkTWFuYWdlciA9IFBhc3N3b3JkTWFuYWdlcih0aGlzKQogICAgICAgIGJpb21ldHJpY0F1dGggPSBCaW9tZXRyaWNBdXRoKHRoaXMpCiAgICAgICAgCiAgICAgICAgc2V0dXBVSSgpCiAgICAgICAgY2hlY2tQZXJtaXNzaW9ucygpCiAgICAgICAgCiAgICAgICAgLy8g2KXYsNinINmD2KfZhtiqINij2YjZhCDZhdix2KnYjCDZhti32YTYqCDYpdmG2LTYp9ihINmD2YTZhdipINmF2LHZiNixCiAgICAgICAgaWYgKCFwYXNzd29yZE1hbmFnZXIuaXNQYXNzd29yZFNldCgpKSB7CiAgICAgICAgICAgIHNob3dTZXR1cFBhc3N3b3JkRGlhbG9nKCkKICAgICAgICB9IGVsc2UgewogICAgICAgICAgICBhdXRoZW50aWNhdGVVc2VyKCkKICAgICAgICB9CiAgICB9CiAgICAKICAgIHByaXZhdGUgZnVuIHNldHVwVUkoKSB7CiAgICAgICAgcmVjeWNsZXJWaWV3ID0gZmluZFZpZXdCeUlkKFIuaWQucmVjeWNsZXJWaWV3KQogICAgICAgIHJlY3ljbGVyVmlldy5sYXlvdXRNYW5hZ2VyID0gTGluZWFyTGF5b3V0TWFuYWdlcih0aGlzKQogICAgICAgIGFkYXB0ZXIgPSBGaWxlQWRhcHRlcihmaWxlTGlzdCkgeyBmaWxlIC0mZ3Q7CiAgICAgICAgICAgIGlmIChpc1VubG9ja2VkKSB7CiAgICAgICAgICAgICAgICBzaG93RmlsZU9wdGlvbnMoZmlsZSkKICAgICAgICAgICAgfQogICAgICAgIH0KICAgICAgICByZWN5Y2xlclZpZXcuYWRhcHRlciA9IGFkYXB0ZXIKICAgICAgICAKICAgICAgICBmaW5kVmlld0J5SWQmbHQ7QnV0dG9uJmd0OyhSLmlkLmJ0bkFkZEZpbGUpLnNldE9uQ2xpY2tMaXN0ZW5lciB7IG9wZW5GaWxlUGlja2VyKCkgfQogICAgICAgIGZpbmRWaWV3QnlJZCZsdDtCdXR0b24mZ3Q7KFIuaWQuYnRuQWRkUVIpLnNldE9uQ2xpY2tMaXN0ZW5lciB7IG9wZW5RUlNjYW5uZXIoKSB9CiAgICAgICAgZmluZFZpZXdCeUlkJmx0O0J1dHRvbiZndDsoUi5pZC5idG5GaWx0ZXJBbGwpLnNldE9uQ2xpY2tMaXN0ZW5lciB7IGN1cnJlbnRGaWx0ZXIgPSAmcXVvdDthbGwmcXVvdDs7IHJlZnJlc2hGaWxlTGlzdCgpIH0KICAgICAgICBmaW5kVmlld0J5SWQmbHQ7QnV0dG9uJmd0OyhSLmlkLmJ0bkZpbHRlckltYWdlKS5zZXRPbkNsaWNrTGlzdGVuZXIgeyBjdXJyZW50RmlsdGVyID0gJnF1b3Q7aW1hZ2UmcXVvdDs7IHJlZnJlc2hGaWxlTGlzdCgpIH0KICAgICAgICBmaW5kVmlld0J5SWQmbHQ7QnV0dG9uJmd0OyhSLmlkLmJ0bkZpbHRlclZpZGVvKS5zZXRPbkNsaWNrTGlzdGVuZXIgeyBjdXJyZW50RmlsdGVyID0gJnF1b3Q7dmlkZW8mcXVvdDs7IHJlZnJlc2hGaWxlTGlzdCgpIH0KICAgICAgICBmaW5kVmlld0J5SWQmbHQ7QnV0dG9uJmd0OyhSLmlkLmJ0bkZpbHRlckF1ZGlvKS5zZXRPbkNsaWNrTGlzdGVuZXIgeyBjdXJyZW50RmlsdGVyID0gJnF1b3Q7YXVkaW8mcXVvdDs7IHJlZnJlc2hGaWxlTGlzdCgpIH0KICAgICAgICBmaW5kVmlld0J5SWQmbHQ7QnV0dG9uJmd0OyhSLmlkLmJ0bkxvY2spLnNldE9uQ2xpY2tMaXN0ZW5lciB7IGxvY2tWYXVsdCgpIH0KICAgICAgICBmaW5kVmlld0J5SWQmbHQ7QnV0dG9uJmd0OyhSLmlkLmJ0bkdlbmVyYXRlUVJDb2RlKS5zZXRPbkNsaWNrTGlzdGVuZXIgeyBzaG93R2VuZXJhdGVRUkRpYWxvZygpIH0KICAgIH0KICAgIAogICAgcHJpdmF0ZSBmdW4gY2hlY2tQZXJtaXNzaW9ucygpIHsKICAgICAgICBpZiAoQnVpbGQuVkVSU0lPTi5TREtfSU5UICZndDs9IEJ1aWxkLlZFUlNJT05fQ09ERVMuVElSQU1JU1UpIHsKICAgICAgICAgICAgdmFsIHBlcm1pc3Npb25zID0gYXJyYXlPZigKICAgICAgICAgICAgICAgIE1hbmlmZXN0LnBlcm1pc3Npb24uUkVBRF9NRURJQV9JTUFHRVMsCiAgICAgICAgICAgICAgICBNYW5pZmVzdC5wZXJtaXNzaW9uLlJFQURfTUVESUFfVklERU8sCiAgICAgICAgICAgICAgICBNYW5pZmVzdC5wZXJtaXNzaW9uLlJFQURfTUVESUFfQVVESU8sCiAgICAgICAgICAgICAgICBNYW5pZmVzdC5wZXJtaXNzaW9uLlVTRV9CSU9NRVRSSUMKICAgICAgICAgICAgKQogICAgICAgICAgICB2YWwgbmVlZFBlcm1pc3Npb25zID0gcGVybWlzc2lvbnMuZmlsdGVyIHsKICAgICAgICAgICAgICAgIENvbnRleHRDb21wYXQuY2hlY2tTZWxmUGVybWlzc2lvbih0aGlzLCBpdCkgIT0gUGFja2FnZU1hbmFnZXIuUEVSTUlTU0lPTl9HUkFOVEVECiAgICAgICAgICAgIH0udG9UeXBlZEFycmF5KCkKICAgICAgICAgICAgCiAgICAgICAgICAgIGlmIChuZWVkUGVybWlzc2lvbnMuaXNOb3RFbXB0eSgpKSB7CiAgICAgICAgICAgICAgICBBY3Rpdml0eUNvbXBhdC5yZXF1ZXN0UGVybWlzc2lvbnModGhpcywgbmVlZFBlcm1pc3Npb25zLCAxMDApCiAgICAgICAgICAgIH0KICAgICAgICB9IGVsc2UgewogICAgICAgICAgICB2YWwgcGVybWlzc2lvbnMgPSBhcnJheU9mKAogICAgICAgICAgICAgICAgTWFuaWZlc3QucGVybWlzc2lvbi5SRUFEX0VYVEVSTkFMX1NUT1JBR0UsCiAgICAgICAgICAgICAgICBNYW5pZmVzdC5wZXJtaXNzaW9uLldSSVRFX0VYVEVSTkFMX1NUT1JBR0UsCiAgICAgICAgICAgICAgICBNYW5pZmVzdC5wZXJtaXNzaW9uLlVTRV9CSU9NRVRSSUMKICAgICAgICAgICAgKQogICAgICAgICAgICB2YWwgbmVlZFBlcm1pc3Npb25zID0gcGVybWlzc2lvbnMuZmlsdGVyIHsKICAgICAgICAgICAgICAgIENvbnRleHRDb21wYXQuY2hlY2tTZWxmUGVybWlzc2lvbih0aGlzLCBpdCkgIT0gUGFja2FnZU1hbmFnZXIuUEVSTUlTU0lPTl9HUkFOVEVECiAgICAgICAgICAgIH0udG9UeXBlZEFycmF5KCkKICAgICAgICAgICAgCiAgICAgICAgICAgIGlmIChuZWVkUGVybWlzc2lvbnMuaXNOb3RFbXB0eSgpKSB7CiAgICAgICAgICAgICAgICBBY3Rpdml0eUNvbXBhdC5yZXF1ZXN0UGVybWlzc2lvbnModGhpcywgbmVlZFBlcm1pc3Npb25zLCAxMDApCiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICB9CiAgICAKICAgIHByaXZhdGUgZnVuIGF1dGhlbnRpY2F0ZVVzZXIoKSB7CiAgICAgICAgaWYgKGJpb21ldHJpY0F1dGguaXNCaW9tZXRyaWNBdmFpbGFibGUoKSkgewogICAgICAgICAgICBiaW9tZXRyaWNBdXRoLmF1dGhlbnRpY2F0ZSgKICAgICAgICAgICAgICAgIG9uU3VjY2VzcyA9IHsgCiAgICAgICAgICAgICAgICAgICAgaXNVbmxvY2tlZCA9IHRydWUKICAgICAgICAgICAgICAgICAgICByZWZyZXNoRmlsZUxpc3QoKQogICAgICAgICAgICAgICAgICAgIFRvYXN0Lm1ha2VUZXh0KHRoaXMsICZxdW90O9iq2YUg2YHYqtitINin2YTYrtiy2YbYqSDwn5STJnF1b3Q7LCBUb2FzdC5MRU5HVEhfU0hPUlQpLnNob3coKQogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgIG9uRmFpbGVkID0geyBzaG93UGFzc3dvcmREaWFsb2coKSB9CiAgICAgICAgICAgICkKICAgICAgICB9IGVsc2UgewogICAgICAgICAgICBzaG93UGFzc3dvcmREaWFsb2coKQogICAgICAgIH0KICAgIH0KICAgIAogICAgcHJpdmF0ZSBmdW4gc2hvd1Bhc3N3b3JkRGlhbG9nKCkgewogICAgICAgIHZhbCBpbnB1dCA9IEVkaXRUZXh0KHRoaXMpCiAgICAgICAgaW5wdXQuaW5wdXRUeXBlID0gYW5kcm9pZC50ZXh0LklucHV0VHlwZS5UWVBFX0NMQVNTX1RFWFQgb3IgYW5kcm9pZC50ZXh0LklucHV0VHlwZS5UWVBFX1RFWFRfVkFSSUFUSU9OX1BBU1NXT1JECiAgICAgICAgaW5wdXQuaGludCA9ICZxdW90O9ij2K/YrtmEINmD2YTZhdipINin2YTZhdix2YjYsSZxdW90OwogICAgICAgIAogICAgICAgIEFsZXJ0RGlhbG9nLkJ1aWxkZXIodGhpcykKICAgICAgICAgICAgLnNldFRpdGxlKCZxdW90O9mB2KrYrSDYp9mE2K7YstmG2KkmcXVvdDspCiAgICAgICAgICAgIC5zZXRNZXNzYWdlKCZxdW90O9in2YTYsdis2KfYoSDYpdiv2K7Yp9mEINmD2YTZhdipINin2YTZhdix2YjYsSZxdW90OykKICAgICAgICAgICAgLnNldFZpZXcoaW5wdXQpCiAgICAgICAgICAgIC5zZXRQb3NpdGl2ZUJ1dHRvbigmcXVvdDvZgdiq2K0mcXVvdDspIHsgXywgXyAtJmd0OwogICAgICAgICAgICAgICAgaWYgKHBhc3N3b3JkTWFuYWdlci52ZXJpZnlQYXNzd29yZChpbnB1dC50ZXh0LnRvU3RyaW5nKCkpKSB7CiAgICAgICAgICAgICAgICAgICAgaXNVbmxvY2tlZCA9IHRydWUKICAgICAgICAgICAgICAgICAgICByZWZyZXNoRmlsZUxpc3QoKQogICAgICAgICAgICAgICAgICAgIFRvYXN0Lm1ha2VUZXh0KHRoaXMsICZxdW90O9iq2YUg2YHYqtitINin2YTYrtiy2YbYqSDwn5STJnF1b3Q7LCBUb2FzdC5MRU5HVEhfU0hPUlQpLnNob3coKQogICAgICAgICAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgICAgICAgICBUb2FzdC5tYWtlVGV4dCh0aGlzLCAmcXVvdDvZg9mE2YXYqSDZhdix2YjYsSDYrtin2LfYptipIOKdjCZxdW90OywgVG9hc3QuTEVOR1RIX1NIT1JUKS5zaG93KCkKICAgICAgICAgICAgICAgICAgICBmaW5pc2goKQogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgICAgIC5zZXROZWdhdGl2ZUJ1dHRvbigmcXVvdDvYpdi62YTYp9mCJnF1b3Q7KSB7IF8sIF8gLSZndDsgZmluaXNoKCkgfQogICAgICAgICAgICAuc2hvdygpCiAgICB9CiAgICAKICAgIHByaXZhdGUgZnVuIHNob3dTZXR1cFBhc3N3b3JkRGlhbG9nKCkgewogICAgICAgIHZhbCBwYXNzd29yZElucHV0ID0gRWRpdFRleHQodGhpcykKICAgICAgICBwYXNzd29yZElucHV0LmlucHV0VHlwZSA9IGFuZHJvaWQudGV4dC5JbnB1dFR5cGUuVFlQRV9DTEFTU19URVhUIG9yIGFuZHJvaWQudGV4dC5JbnB1dFR5cGUuVFlQRV9URVhUX1ZBUklBVElPTl9QQVNTV09SRAogICAgICAgIHBhc3N3b3JkSW5wdXQuaGludCA9ICZxdW90O9mD2YTZhdipINin2YTZhdix2YjYsSDYp9mE2KzYr9mK2K/YqSZxdW90OwogICAgICAgIAogICAgICAgIHZhbCBjb25maXJtSW5wdXQgPSBFZGl0VGV4dCh0aGlzKQogICAgICAgIGNvbmZpcm1JbnB1dC5pbnB1dFR5cGUgPSBhbmRyb2lkLnRleHQuSW5wdXRUeXBlLlRZUEVfQ0xBU1NfVEVYVCBvciBhbmRyb2lkLnRleHQuSW5wdXRUeXBlLlRZUEVfVEVYVF9WQVJJQVRJT05fUEFTU1dPUkQKICAgICAgICBjb25maXJtSW5wdXQuaGludCA9ICZxdW90O9iq2KPZg9mK2K8g2YPZhNmF2Kkg2KfZhNmF2LHZiNixJnF1b3Q7CiAgICAgICAgCiAgICAgICAgdmFsIGxheW91dCA9IExpbmVhckxheW91dCh0aGlzKS5hcHBseSB7CiAgICAgICAgICAgIG9yaWVudGF0aW9uID0gTGluZWFyTGF5b3V0LlZFUlRJQ0FMCiAgICAgICAgICAgIGFkZFZpZXcocGFzc3dvcmRJbnB1dCkKICAgICAgICAgICAgYWRkVmlldyhjb25maXJtSW5wdXQpCiAgICAgICAgfQogICAgICAgIAogICAgICAgIEFsZXJ0RGlhbG9nLkJ1aWxkZXIodGhpcykKICAgICAgICAgICAgLnNldFRpdGxlKCZxdW90O9il2LnYr9in2K8g2KfZhNiu2LLZhtipJnF1b3Q7KQogICAgICAgICAgICAuc2V0TWVzc2FnZSgmcXVvdDvZgtmFINio2KXZhti02KfYoSDZg9mE2YXYqSDZhdix2YjYsSDZhNit2YXYp9mK2Kkg2YXZhNmB2KfYqtmDJnF1b3Q7KQogICAgICAgICAgICAuc2V0VmlldyhsYXlvdXQpCiAgICAgICAgICAgIC5zZXRQb3NpdGl2ZUJ1dHRvbigmcXVvdDvYpdmG2LTYp9ihJnF1b3Q7KSB7IF8sIF8gLSZndDsKICAgICAgICAgICAgICAgIHZhbCBwYXNzID0gcGFzc3dvcmRJbnB1dC50ZXh0LnRvU3RyaW5nKCkKICAgICAgICAgICAgICAgIHZhbCBjb25maXJtID0gY29uZmlybUlucHV0LnRleHQudG9TdHJpbmcoKQogICAgICAgICAgICAgICAgaWYgKHBhc3MuaXNOb3RFbXB0eSgpICZhbXA7JmFtcDsgcGFzcyA9PSBjb25maXJtKSB7CiAgICAgICAgICAgICAgICAgICAgcGFzc3dvcmRNYW5hZ2VyLnJlZ2lzdGVyUGFzc3dvcmQocGFzcykKICAgICAgICAgICAgICAgICAgICBhdXRoZW50aWNhdGVVc2VyKCkKICAgICAgICAgICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgICAgICAgICAgVG9hc3QubWFrZVRleHQodGhpcywgJnF1b3Q72YPZhNmF2Kkg2KfZhNmF2LHZiNixINi62YrYsSDZhdiq2LfYp9io2YLYqSDYo9mIINmB2KfYsdi62KkmcXVvdDssIFRvYXN0LkxFTkdUSF9TSE9SVCkuc2hvdygpCiAgICAgICAgICAgICAgICAgICAgZmluaXNoKCkKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQogICAgICAgICAgICAuc2V0TmVnYXRpdmVCdXR0b24oJnF1b3Q72KXZhNi62KfYoSZxdW90OykgeyBfLCBfIC0mZ3Q7IGZpbmlzaCgpIH0KICAgICAgICAgICAgLnNob3coKQogICAgfQogICAgCiAgICBwcml2YXRlIGZ1biBsb2NrVmF1bHQoKSB7CiAgICAgICAgaXNVbmxvY2tlZCA9IGZhbHNlCiAgICAgICAgZmlsZUxpc3QuY2xlYXIoKQogICAgICAgIGFkYXB0ZXIubm90aWZ5RGF0YVNldENoYW5nZWQoKQogICAgICAgIFRvYXN0Lm1ha2VUZXh0KHRoaXMsICZxdW90O9iq2YUg2YLZgdmEINin2YTYrtiy2YbYqSDwn5SSJnF1b3Q7LCBUb2FzdC5MRU5HVEhfU0hPUlQpLnNob3coKQogICAgfQogICAgCiAgICBwcml2YXRlIGZ1biByZWZyZXNoRmlsZUxpc3QoKSB7CiAgICAgICAgaWYgKCFpc1VubG9ja2VkKSByZXR1cm4KICAgICAgICBmaWxlTGlzdC5jbGVhcigpCiAgICAgICAgdmFsIHZhdWx0RGlyID0gRmlsZShmaWxlc0RpciwgJnF1b3Q7dmF1bHQmcXVvdDspCiAgICAgICAgaWYgKHZhdWx0RGlyLmV4aXN0cygpKSB7CiAgICAgICAgICAgIHNjYW5GaWxlcyh2YXVsdERpcikKICAgICAgICB9CiAgICAgICAgYWRhcHRlci5ub3RpZnlEYXRhU2V0Q2hhbmdlZCgpCiAgICB9CiAgICAKICAgIHByaXZhdGUgZnVuIHNjYW5GaWxlcyhkaXI6IEZpbGUpIHsKICAgICAgICBkaXIubGlzdEZpbGVzKCk/LmZvckVhY2ggeyBmaWxlIC0mZ3Q7CiAgICAgICAgICAgIGlmIChmaWxlLmlzRGlyZWN0b3J5KSB7CiAgICAgICAgICAgICAgICBzY2FuRmlsZXMoZmlsZSkKICAgICAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgICAgIHZhbCB0eXBlID0gZ2V0RmlsZVR5cGUoZmlsZS5uYW1lKQogICAgICAgICAgICAgICAgaWYgKGN1cnJlbnRGaWx0ZXIgPT0gJnF1b3Q7YWxsJnF1b3Q7IHx8IGN1cnJlbnRGaWx0ZXIgPT0gdHlwZSkgewogICAgICAgICAgICAgICAgICAgIGZpbGVMaXN0LmFkZChWYXVsdEZpbGUoZmlsZSwgdHlwZSkpCiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICB9CiAgICAKICAgIHByaXZhdGUgZnVuIGdldEZpbGVUeXBlKGZpbGVOYW1lOiBTdHJpbmcpOiBTdHJpbmcgewogICAgICAgIHJldHVybiB3aGVuIHsKICAgICAgICAgICAgZmlsZU5hbWUuZW5kc1dpdGgoJnF1b3Q7LmVuY19pbWFnZSZxdW90OykgLSZndDsgJnF1b3Q7aW1hZ2UmcXVvdDsKICAgICAgICAgICAgZmlsZU5hbWUuZW5kc1dpdGgoJnF1b3Q7LmVuY192aWRlbyZxdW90OykgLSZndDsgJnF1b3Q7dmlkZW8mcXVvdDsKICAgICAgICAgICAgZmlsZU5hbWUuZW5kc1dpdGgoJnF1b3Q7LmVuY19hdWRpbyZxdW90OykgLSZndDsgJnF1b3Q7YXVkaW8mcXVvdDsKICAgICAgICAgICAgZmlsZU5hbWUubWF0Y2hlcyhSZWdleCgmcXVvdDsuKlxcLihqcGd8anBlZ3xwbmd8Z2lmfGJtcCkkJnF1b3Q7LCBSZWdleE9wdGlvbi5JR05PUkVfQ0FTRSkpIC0mZ3Q7ICZxdW90O2ltYWdlJnF1b3Q7CiAgICAgICAgICAgIGZpbGVOYW1lLm1hdGNoZXMoUmVnZXgoJnF1b3Q7LipcXC4obXA0fGF2aXxta3Z8bW92fDNncCkkJnF1b3Q7LCBSZWdleE9wdGlvbi5JR05PUkVfQ0FTRSkpIC0mZ3Q7ICZxdW90O3ZpZGVvJnF1b3Q7CiAgICAgICAgICAgIGZpbGVOYW1lLm1hdGNoZXMoUmVnZXgoJnF1b3Q7LipcXC4obXAzfHdhdnxvZ2d8bTRhfGZsYWMpJCZxdW90OywgUmVnZXhPcHRpb24uSUdOT1JFX0NBU0UpKSAtJmd0OyAmcXVvdDthdWRpbyZxdW90OwogICAgICAgICAgICBlbHNlIC0mZ3Q7ICZxdW90O290aGVyJnF1b3Q7CiAgICAgICAgfQogICAgfQogICAgCiAgICBwcml2YXRlIHZhbCBmaWxlUGlja2VyTGF1bmNoZXIgPSByZWdpc3RlckZvckFjdGl2aXR5UmVzdWx0KEFjdGl2aXR5UmVzdWx0Q29udHJhY3RzLlN0YXJ0QWN0aXZpdHlGb3JSZXN1bHQoKSkgeyByZXN1bHQgLSZndDsKICAgICAgICBpZiAocmVzdWx0LnJlc3VsdENvZGUgPT0gUkVTVUxUX09LICZhbXA7JmFtcDsgcmVzdWx0LmRhdGEgIT0gbnVsbCkgewogICAgICAgICAgICB2YWwgdXJpID0gcmVzdWx0LmRhdGE/LmRhdGEKICAgICAgICAgICAgdXJpPy5sZXQgeyBzYXZlRmlsZVRvVmF1bHQoaXQpIH0KICAgICAgICB9CiAgICB9CiAgICAKICAgIHByaXZhdGUgZnVuIG9wZW5GaWxlUGlja2VyKCkgewogICAgICAgIHZhbCBpbnRlbnQgPSBJbnRlbnQoSW50ZW50LkFDVElPTl9HRVRfQ09OVEVOVCkuYXBwbHkgewogICAgICAgICAgICB0eXBlID0gJnF1b3Q7Ki8qJnF1b3Q7CiAgICAgICAgICAgIHB1dEV4dHJhKEludGVudC5FWFRSQV9NSU1FX1RZUEVTLCBhcnJheU9mKCZxdW90O2ltYWdlLyomcXVvdDssICZxdW90O3ZpZGVvLyomcXVvdDssICZxdW90O2F1ZGlvLyomcXVvdDspKQogICAgICAgIH0KICAgICAgICBmaWxlUGlja2VyTGF1bmNoZXIubGF1bmNoKEludGVudC5jcmVhdGVDaG9vc2VyKGludGVudCwgJnF1b3Q72KfYrtiq2LEg2YXZhNmB2KfZiyDZhNmE2K3Zgdi4JnF1b3Q7KSkKICAgIH0KICAgIAogICAgcHJpdmF0ZSBmdW4gc2F2ZUZpbGVUb1ZhdWx0KHVyaTogVXJpKSB7CiAgICAgICAgdHJ5IHsKICAgICAgICAgICAgdmFsIGZpbGVOYW1lID0gZ2V0RmlsZU5hbWUodXJpKQogICAgICAgICAgICB2YWwgdHlwZSA9IGdldE1pbWVUeXBlKHVyaSkKICAgICAgICAgICAgdmFsIGV4dGVuc2lvbiA9IHdoZW4gewogICAgICAgICAgICAgICAgdHlwZS5zdGFydHNXaXRoKCZxdW90O2ltYWdlLyZxdW90OykgLSZndDsgJnF1b3Q7LmVuY19pbWFnZSZxdW90OwogICAgICAgICAgICAgICAgdHlwZS5zdGFydHNXaXRoKCZxdW90O3ZpZGVvLyZxdW90OykgLSZndDsgJnF1b3Q7LmVuY192aWRlbyZxdW90OwogICAgICAgICAgICAgICAgdHlwZS5zdGFydHNXaXRoKCZxdW90O2F1ZGlvLyZxdW90OykgLSZndDsgJnF1b3Q7LmVuY19hdWRpbyZxdW90OwogICAgICAgICAgICAgICAgZWxzZSAtJmd0OyAmcXVvdDsuZW5jX2ZpbGUmcXVvdDsKICAgICAgICAgICAgfQogICAgICAgICAgICAKICAgICAgICAgICAgdmFsIGVuY3J5cHRlZE5hbWUgPSAmcXVvdDske1N5c3RlbS5jdXJyZW50VGltZU1pbGxpcygpfV8ke2ZpbGVOYW1lLnJlcGxhY2UoJnF1b3Q7ICZxdW90OywgJnF1b3Q7XyZxdW90Oyl9JGV4dGVuc2lvbiZxdW90OwogICAgICAgICAgICB2YWwgdGVtcEZpbGUgPSBGaWxlKGNhY2hlRGlyLCAmcXVvdDt0ZW1wXyR7U3lzdGVtLmN1cnJlbnRUaW1lTWlsbGlzKCl9JnF1b3Q7KQogICAgICAgICAgICAKICAgICAgICAgICAgY29udGVudFJlc29sdmVyLm9wZW5JbnB1dFN0cmVhbSh1cmkpPy51c2UgeyBpbnB1dCAtJmd0OwogICAgICAgICAgICAgICAgRmlsZU91dHB1dFN0cmVhbSh0ZW1wRmlsZSkudXNlIHsgb3V0cHV0IC0mZ3Q7CiAgICAgICAgICAgICAgICAgICAgaW5wdXQuY29weVRvKG91dHB1dCkKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQogICAgICAgICAgICAKICAgICAgICAgICAgdmFsIGVuY3J5cHRlZCA9IGVuY3J5cHRpb25NYW5hZ2VyLmVuY3J5cHRGaWxlKHRlbXBGaWxlLCBlbmNyeXB0ZWROYW1lKQogICAgICAgICAgICBpZiAoZW5jcnlwdGVkICE9IG51bGwpIHsKICAgICAgICAgICAgICAgIHRlbXBGaWxlLmRlbGV0ZSgpCiAgICAgICAgICAgICAgICBUb2FzdC5tYWtlVGV4dCh0aGlzLCAmcXVvdDvYqtmFINit2YHYuCDZiNiq2LTZgdmK2LEg2KfZhNmF2YTZgSDwn5SSJnF1b3Q7LCBUb2FzdC5MRU5HVEhfU0hPUlQpLnNob3coKQogICAgICAgICAgICAgICAgcmVmcmVzaEZpbGVMaXN0KCkKICAgICAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgICAgIFRvYXN0Lm1ha2VUZXh0KHRoaXMsICZxdW90O9mB2LTZhCDYp9mE2KrYtNmB2YrYsSZxdW90OywgVG9hc3QuTEVOR1RIX1NIT1JUKS5zaG93KCkKICAgICAgICAgICAgfQogICAgICAgIH0gY2F0Y2ggKGU6IEV4Y2VwdGlvbikgewogICAgICAgICAgICBlLnByaW50U3RhY2tUcmFjZSgpCiAgICAgICAgICAgIFRvYXN0Lm1ha2VUZXh0KHRoaXMsICZxdW90O9it2K/YqyDYrti32KM6ICR7ZS5tZXNzYWdlfSZxdW90OywgVG9hc3QuTEVOR1RIX1NIT1JUKS5zaG93KCkKICAgICAgICB9CiAgICB9CiAgICAKICAgIHByaXZhdGUgZnVuIGdldEZpbGVOYW1lKHVyaTogVXJpKTogU3RyaW5nIHsKICAgICAgICB2YXIgZmlsZU5hbWUgPSAmcXVvdDt1bmtub3duJnF1b3Q7CiAgICAgICAgY29udGVudFJlc29sdmVyLnF1ZXJ5KHVyaSwgbnVsbCwgbnVsbCwgbnVsbCwgbnVsbCk/LnVzZSB7IGN1cnNvciAtJmd0OwogICAgICAgICAgICBpZiAoY3Vyc29yLm1vdmVUb0ZpcnN0KCkpIHsKICAgICAgICAgICAgICAgIHZhbCBuYW1lSW5kZXggPSBjdXJzb3IuZ2V0Q29sdW1uSW5kZXgoT3BlbmFibGVDb2x1bW5zLkRJU1BMQVlfTkFNRSkKICAgICAgICAgICAgICAgIGlmIChuYW1lSW5kZXggIT0gLTEpIHsKICAgICAgICAgICAgICAgICAgICBmaWxlTmFtZSA9IGN1cnNvci5nZXRTdHJpbmcobmFtZUluZGV4KQogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgfQogICAgICAgIHJldHVybiBmaWxlTmFtZQogICAgfQogICAgCiAgICBwcml2YXRlIGZ1biBnZXRNaW1lVHlwZSh1cmk6IFVyaSk6IFN0cmluZyB7CiAgICAgICAgcmV0dXJuIGNvbnRlbnRSZXNvbHZlci5nZXRUeXBlKHVyaSkgPzogJnF1b3Q7YXBwbGljYXRpb24vb2N0ZXQtc3RyZWFtJnF1b3Q7CiAgICB9CiAgICAKICAgIHByaXZhdGUgZnVuIHNob3dGaWxlT3B0aW9ucyhmaWxlOiBWYXVsdEZpbGUpIHsKICAgICAgICB2YWwgb3B0aW9ucyA9IGFycmF5T2YoJnF1b3Q72LnYsdi2JnF1b3Q7LCAmcXVvdDvZhdi02KfYsdmD2KkmcXVvdDssICZxdW90O9it2LDZgSZxdW90OywgJnF1b3Q72KrYtdiv2YrYsSDYpdmE2Ykg2KfZhNis2YfYp9iyJnF1b3Q7KQogICAgICAgIEFsZXJ0RGlhbG9nLkJ1aWxkZXIodGhpcykKICAgICAgICAgICAgLnNldFRpdGxlKGZpbGUuZmlsZS5uYW1lKQogICAgICAgICAgICAuc2V0SXRlbXMob3B0aW9ucykgeyBfLCB3aGljaCAtJmd0OwogICAgICAgICAgICAgICAgd2hlbiAod2hpY2gpIHsKICAgICAgICAgICAgICAgICAgICAwIC0mZ3Q7IHZpZXdGaWxlKGZpbGUpCiAgICAgICAgICAgICAgICAgICAgMSAtJmd0OyBzaGFyZUZpbGUoZmlsZSkKICAgICAgICAgICAgICAgICAgICAyIC0mZ3Q7IGRlbGV0ZUZpbGUoZmlsZSkKICAgICAgICAgICAgICAgICAgICAzIC0mZ3Q7IGV4cG9ydFRvRGV2aWNlKGZpbGUpCiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KICAgICAgICAgICAgLnNob3coKQogICAgfQogICAgCiAgICBwcml2YXRlIGZ1biB2aWV3RmlsZShmaWxlOiBWYXVsdEZpbGUpIHsKICAgICAgICB2YWwgZGVjcnlwdGVkRmlsZSA9IEZpbGUoY2FjaGVEaXIsICZxdW90O2RlY3J5cHRfJHtTeXN0ZW0uY3VycmVudFRpbWVNaWxsaXMoKX0mcXVvdDspCiAgICAgICAgaWYgKGVuY3J5cHRpb25NYW5hZ2VyLmRlY3J5cHRGaWxlKGZpbGUuZmlsZS5uYW1lLCBkZWNyeXB0ZWRGaWxlKSkgewogICAgICAgICAgICB2YWwgdXJpID0gRmlsZVByb3ZpZGVyLmdldFVyaUZvckZpbGUodGhpcywgJnF1b3Q7JHtwYWNrYWdlTmFtZX0ucHJvdmlkZXImcXVvdDssIGRlY3J5cHRlZEZpbGUpCiAgICAgICAgICAgIHZhbCBpbnRlbnQgPSBJbnRlbnQoSW50ZW50LkFDVElPTl9WSUVXKS5hcHBseSB7CiAgICAgICAgICAgICAgICBzZXREYXRhQW5kVHlwZSh1cmksIGdldE1pbWVUeXBlRnJvbU5hbWUoZmlsZS5maWxlLm5hbWUpKQogICAgICAgICAgICAgICAgYWRkRmxhZ3MoSW50ZW50LkZMQUdfR1JBTlRfUkVBRF9VUklfUEVSTUlTU0lPTikKICAgICAgICAgICAgfQogICAgICAgICAgICBzdGFydEFjdGl2aXR5KEludGVudC5jcmVhdGVDaG9vc2VyKGludGVudCwgJnF1b3Q72YHYqtitINin2YTZhdmE2YEmcXVvdDspKQogICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgIFRvYXN0Lm1ha2VUZXh0KHRoaXMsICZxdW90O9mB2LTZhCDZgdmDINin2YTYqti02YHZitixJnF1b3Q7LCBUb2FzdC5MRU5HVEhfU0hPUlQpLnNob3coKQogICAgICAgIH0KICAgIH0KICAgIAogICAgcHJpdmF0ZSBmdW4gc2hhcmVGaWxlKGZpbGU6IFZhdWx0RmlsZSkgewogICAgICAgIHZhbCBkZWNyeXB0ZWRGaWxlID0gRmlsZShjYWNoZURpciwgJnF1b3Q7c2hhcmVfJHtTeXN0ZW0uY3VycmVudFRpbWVNaWxsaXMoKX0mcXVvdDspCiAgICAgICAgaWYgKGVuY3J5cHRpb25NYW5hZ2VyLmRlY3J5cHRGaWxlKGZpbGUuZmlsZS5uYW1lLCBkZWNyeXB0ZWRGaWxlKSkgewogICAgICAgICAgICB2YWwgdXJpID0gRmlsZVByb3ZpZGVyLmdldFVyaUZvckZpbGUodGhpcywgJnF1b3Q7JHtwYWNrYWdlTmFtZX0ucHJvdmlkZXImcXVvdDssIGRlY3J5cHRlZEZpbGUpCiAgICAgICAgICAgIHZhbCBpbnRlbnQgPSBJbnRlbnQoSW50ZW50LkFDVElPTl9TRU5EKS5hcHBseSB7CiAgICAgICAgICAgICAgICB0eXBlID0gZ2V0TWltZVR5cGVGcm9tTmFtZShmaWxlLmZpbGUubmFtZSkKICAgICAgICAgICAgICAgIHB1dEV4dHJhKEludGVudC5FWFRSQV9TVFJFQU0sIHVyaSkKICAgICAgICAgICAgICAgIGFkZEZsYWdzKEludGVudC5GTEFHX0dSQU5UX1JFQURfVVJJX1BFUk1JU1NJT04pCiAgICAgICAgICAgIH0KICAgICAgICAgICAgc3RhcnRBY3Rpdml0eShJbnRlbnQuY3JlYXRlQ2hvb3NlcihpbnRlbnQsICZxdW90O9mF2LTYp9ix2YPYqSDYp9mE2YXZhNmBJnF1b3Q7KSkKICAgICAgICB9CiAgICB9CiAgICAKICAgIHByaXZhdGUgZnVuIGRlbGV0ZUZpbGUoZmlsZTogVmF1bHRGaWxlKSB7CiAgICAgICAgQWxlcnREaWFsb2cuQnVpbGRlcih0aGlzKQogICAgICAgICAgICAuc2V0VGl0bGUoJnF1b3Q72K3YsNmBINin2YTZhdmE2YEmcXVvdDspCiAgICAgICAgICAgIC5zZXRNZXNzYWdlKCZxdW90O9mH2YQg2KPZhtiqINmF2KrYo9mD2K8g2YXZhiDYrdiw2YEg2YfYsNinINin2YTZhdmE2YHYnyZxdW90OykKICAgICAgICAgICAgLnNldFBvc2l0aXZlQnV0dG9uKCZxdW90O9it2LDZgSZxdW90OykgeyBfLCBfIC0mZ3Q7CiAgICAgICAgICAgICAgICBpZiAoZmlsZS5maWxlLmRlbGV0ZSgpKSB7CiAgICAgICAgICAgICAgICAgICAgcmVmcmVzaEZpbGVMaXN0KCkKICAgICAgICAgICAgICAgICAgICBUb2FzdC5tYWtlVGV4dCh0aGlzLCAmcXVvdDvYqtmFINin2YTYrdiw2YEmcXVvdDssIFRvYXN0LkxFTkdUSF9TSE9SVCkuc2hvdygpCiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KICAgICAgICAgICAgLnNldE5lZ2F0aXZlQnV0dG9uKCZxdW90O9il2YTYutin2KEmcXVvdDssIG51bGwpCiAgICAgICAgICAgIC5zaG93KCkKICAgIH0KICAgIAogICAgcHJpdmF0ZSBmdW4gZXhwb3J0VG9EZXZpY2UoZmlsZTogVmF1bHRGaWxlKSB7CiAgICAgICAgdmFsIGRlY3J5cHRlZEZpbGUgPSBGaWxlKGNhY2hlRGlyLCAmcXVvdDtleHBvcnRfJHtTeXN0ZW0uY3VycmVudFRpbWVNaWxsaXMoKX0mcXVvdDspCiAgICAgICAgaWYgKGVuY3J5cHRpb25NYW5hZ2VyLmRlY3J5cHRGaWxlKGZpbGUuZmlsZS5uYW1lLCBkZWNyeXB0ZWRGaWxlKSkgewogICAgICAgICAgICB2YWwgb3JpZ2luYWxOYW1lID0gZmlsZS5maWxlLm5hbWUucmVwbGFjZShSZWdleCgmcXVvdDtcXC5lbmNfKGltYWdlfHZpZGVvfGF1ZGlvfGZpbGUpJCZxdW90OyksICZxdW90OyZxdW90OykKICAgICAgICAgICAgdmFsIG91dHB1dEZpbGVOYW1lID0gJnF1b3Q7JHtTeXN0ZW0uY3VycmVudFRpbWVNaWxsaXMoKX1fJG9yaWdpbmFsTmFtZSZxdW90OwogICAgICAgICAgICAKICAgICAgICAgICAgaWYgKEJ1aWxkLlZFUlNJT04uU0RLX0lOVCAmZ3Q7PSBCdWlsZC5WRVJTSU9OX0NPREVTLlEpIHsKICAgICAgICAgICAgICAgIHZhbCByZXNvbHZlciA9IGNvbnRlbnRSZXNvbHZlcgogICAgICAgICAgICAgICAgdmFsIGNvbnRlbnRWYWx1ZXMgPSBDb250ZW50VmFsdWVzKCkuYXBwbHkgewogICAgICAgICAgICAgICAgICAgIHB1dChNZWRpYVN0b3JlLk1lZGlhQ29sdW1ucy5ESVNQTEFZX05BTUUsIG91dHB1dEZpbGVOYW1lKQogICAgICAgICAgICAgICAgICAgIHB1dChNZWRpYVN0b3JlLk1lZGlhQ29sdW1ucy5NSU1FX1RZUEUsIGdldE1pbWVUeXBlRnJvbU5hbWUoZmlsZS5maWxlLm5hbWUpKQogICAgICAgICAgICAgICAgICAgIHB1dChNZWRpYVN0b3JlLk1lZGlhQ29sdW1ucy5SRUxBVElWRV9QQVRILCBFbnZpcm9ubWVudC5ESVJFQ1RPUllfRE9XTkxPQURTKQogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YWwgdXJpID0gcmVzb2x2ZXIuaW5zZXJ0KE1lZGlhU3RvcmUuRG93bmxvYWRzLkVYVEVSTkFMX0NPTlRFTlRfVVJJLCBjb250ZW50VmFsdWVzKQogICAgICAgICAgICAgICAgdXJpPy5sZXQgewogICAgICAgICAgICAgICAgICAgIHJlc29sdmVyLm9wZW5PdXRwdXRTdHJlYW0oaXQpPy51c2UgeyBvdXRwdXQgLSZndDsKICAgICAgICAgICAgICAgICAgICAgICAgRmlsZUlucHV0U3RyZWFtKGRlY3J5cHRlZEZpbGUpLnVzZSB7IGlucHV0IC0mZ3Q7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbnB1dC5jb3B5VG8ob3V0cHV0KQogICAgICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICAgIFRvYXN0Lm1ha2VUZXh0KHRoaXMsICZxdW90O9iq2YUg2KfZhNiq2LXYr9mK2LEg2KXZhNmJINmF2KzZhNivINin2YTYqtmG2LLZitmE2KfYqiZxdW90OywgVG9hc3QuTEVOR1RIX1NIT1JUKS5zaG93KCkKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgICAgIHZhbCBkb3dubG9hZHNEaXIgPSBFbnZpcm9ubWVudC5nZXRFeHRlcm5hbFN0b3JhZ2VQdWJsaWNEaXJlY3RvcnkoRW52aXJvbm1lbnQuRElSRUNUT1JZX0RPV05MT0FEUykKICAgICAgICAgICAgICAgIHZhbCBvdXRwdXRGaWxlID0gRmlsZShkb3dubG9hZHNEaXIsIG91dHB1dEZpbGVOYW1lKQogICAgICAgICAgICAgICAgZGVjcnlwdGVkRmlsZS5jb3B5VG8ob3V0cHV0RmlsZSwgdHJ1ZSkKICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgTWVkaWFTY2FubmVyQ29ubmVjdGlvbi5zY2FuRmlsZSh0aGlzLCBhcnJheU9mKG91dHB1dEZpbGUuYWJzb2x1dGVQYXRoKSwgbnVsbCwgbnVsbCkKICAgICAgICAgICAgICAgIFRvYXN0Lm1ha2VUZXh0KHRoaXMsICZxdW90O9iq2YUg2KfZhNiq2LXYr9mK2LEg2KXZhNmJICR7b3V0cHV0RmlsZS5hYnNvbHV0ZVBhdGh9JnF1b3Q7LCBUb2FzdC5MRU5HVEhfU0hPUlQpLnNob3coKQogICAgICAgICAgICB9CiAgICAgICAgICAgIGRlY3J5cHRlZEZpbGUuZGVsZXRlKCkKICAgICAgICB9CiAgICB9CiAgICAKICAgIHByaXZhdGUgZnVuIGdldE1pbWVUeXBlRnJvbU5hbWUoZmlsZU5hbWU6IFN0cmluZyk6IFN0cmluZyB7CiAgICAgICAgcmV0dXJuIHdoZW4gewogICAgICAgICAgICBmaWxlTmFtZS5jb250YWlucygmcXVvdDsuZW5jX2ltYWdlJnF1b3Q7KSB8fCBmaWxlTmFtZS5tYXRjaGVzKFJlZ2V4KCZxdW90Oy4qXFwuKGpwZ3xqcGVnfHBuZ3xnaWYpJCZxdW90OywgUmVnZXhPcHRpb24uSUdOT1JFX0NBU0UpKSAtJmd0OyAmcXVvdDtpbWFnZS8qJnF1b3Q7CiAgICAgICAgICAgIGZpbGVOYW1lLmNvbnRhaW5zKCZxdW90Oy5lbmNfdmlkZW8mcXVvdDspIHx8IGZpbGVOYW1lLm1hdGNoZXMoUmVnZXgoJnF1b3Q7LipcXC4obXA0fGF2aXxta3YpJCZxdW90OywgUmVnZXhPcHRpb24uSUdOT1JFX0NBU0UpKSAtJmd0OyAmcXVvdDt2aWRlby8qJnF1b3Q7CiAgICAgICAgICAgIGZpbGVOYW1lLmNvbnRhaW5zKCZxdW90Oy5lbmNfYXVkaW8mcXVvdDspIHx8IGZpbGVOYW1lLm1hdGNoZXMoUmVnZXgoJnF1b3Q7LipcXC4obXAzfHdhdnxvZ2cpJCZxdW90OywgUmVnZXhPcHRpb24uSUdOT1JFX0NBU0UpKSAtJmd0OyAmcXVvdDthdWRpby8qJnF1b3Q7CiAgICAgICAgICAgIGVsc2UgLSZndDsgJnF1b3Q7Ki8qJnF1b3Q7CiAgICAgICAgfQogICAgfQogICAgCiAgICBwcml2YXRlIGZ1biBvcGVuUVJTY2FubmVyKCkgewogICAgICAgIHZhbCBpbnRlbnQgPSBJbnRlbnQoTWVkaWFTdG9yZS5BQ1RJT05fSU1BR0VfQ0FQVFVSRSkKICAgICAgICBzdGFydEFjdGl2aXR5Rm9yUmVzdWx0KGludGVudCwgMjAwKQogICAgfQogICAgCiAgICBARGVwcmVjYXRlZCgmcXVvdDtEZXByZWNhdGVkIGluIEphdmEmcXVvdDspCiAgICBvdmVycmlkZSBmdW4gb25BY3Rpdml0eVJlc3VsdChyZXF1ZXN0Q29kZTogSW50LCByZXN1bHRDb2RlOiBJbnQsIGRhdGE6IEludGVudD8pIHsKICAgICAgICBzdXBlci5vbkFjdGl2aXR5UmVzdWx0KHJlcXVlc3RDb2RlLCByZXN1bHRDb2RlLCBkYXRhKQogICAgICAgIGlmIChyZXF1ZXN0Q29kZSA9PSAyMDAgJmFtcDsmYW1wOyByZXN1bHRDb2RlID09IFJFU1VMVF9PSykgewogICAgICAgICAgICB2YWwgaW1hZ2VCaXRtYXAgPSBkYXRhPy5leHRyYXM/LmdldCgmcXVvdDtkYXRhJnF1b3Q7KSBhcyBCaXRtYXAKICAgICAgICAgICAgc2NhblFSRnJvbUJpdG1hcChpbWFnZUJpdG1hcCkKICAgICAgICB9CiAgICB9CiAgICAKICAgIHByaXZhdGUgZnVuIHNjYW5RUkZyb21CaXRtYXAoYml0bWFwOiBCaXRtYXApIHsKICAgICAgICB2YWwgaW1hZ2UgPSBJbnB1dEltYWdlLmZyb21CaXRtYXAoYml0bWFwLCAwKQogICAgICAgIHZhbCBzY2FubmVyID0gQmFyY29kZVNjYW5uaW5nLmdldENsaWVudCgpCiAgICAgICAgCiAgICAgICAgc2Nhbm5lci5wcm9jZXNzKGltYWdlKQogICAgICAgICAgICAuYWRkT25TdWNjZXNzTGlzdGVuZXIgeyBiYXJjb2RlcyAtJmd0OwogICAgICAgICAgICAgICAgZm9yIChiYXJjb2RlIGluIGJhcmNvZGVzKSB7CiAgICAgICAgICAgICAgICAgICAgdmFsIHRleHQgPSBiYXJjb2RlLnJhd1ZhbHVlCiAgICAgICAgICAgICAgICAgICAgaWYgKCF0ZXh0LmlzTnVsbE9yRW1wdHkoKSkgewogICAgICAgICAgICAgICAgICAgICAgICBUb2FzdC5tYWtlVGV4dCh0aGlzLCAmcXVvdDvYqtmFINmF2LPYrSDYp9mE2LHZhdiyOiAkdGV4dCZxdW90OywgVG9hc3QuTEVOR1RIX0xPTkcpLnNob3coKQogICAgICAgICAgICAgICAgICAgICAgICBzYXZlUVJUZXh0QXNGaWxlKHRleHQpCiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgICAgIC5hZGRPbkZhaWx1cmVMaXN0ZW5lciB7CiAgICAgICAgICAgICAgICBUb2FzdC5tYWtlVGV4dCh0aGlzLCAmcXVvdDvZgdi02YQg2YXYs9itINin2YTYsdmF2LImcXVvdDssIFRvYXN0LkxFTkdUSF9TSE9SVCkuc2hvdygpCiAgICAgICAgICAgIH0KICAgIH0KICAgIAogICAgcHJpdmF0ZSBmdW4gc2F2ZVFSVGV4dEFzRmlsZSh0ZXh0OiBTdHJpbmcpIHsKICAgICAgICB2YWwgZmlsZU5hbWUgPSAmcXVvdDtxcl8ke1N5c3RlbS5jdXJyZW50VGltZU1pbGxpcygpfS50eHQmcXVvdDsKICAgICAgICB2YWwgdGVtcEZpbGUgPSBGaWxlKGNhY2hlRGlyLCBmaWxlTmFtZSkKICAgICAgICB0ZW1wRmlsZS53cml0ZVRleHQodGV4dCkKICAgICAgICBlbmNyeXB0aW9uTWFuYWdlci5lbmNyeXB0RmlsZSh0ZW1wRmlsZSwgJnF1b3Q7cXJfJHtTeXN0ZW0uY3VycmVudFRpbWVNaWxsaXMoKX0uZW5jX2ZpbGUmcXVvdDspCiAgICAgICAgdGVtcEZpbGUuZGVsZXRlKCkKICAgICAgICByZWZyZXNoRmlsZUxpc3QoKQogICAgICAgIFRvYXN0Lm1ha2VUZXh0KHRoaXMsICZxdW90O9iq2YUg2K3Zgdi4INmF2K3YqtmI2Ykg2KfZhNix2YXYsiZxdW90OywgVG9hc3QuTEVOR1RIX1NIT1JUKS5zaG93KCkKICAgIH0KICAgIAogICAgcHJpdmF0ZSBmdW4gc2hvd0dlbmVyYXRlUVJEaWFsb2coKSB7CiAgICAgICAgdmFsIGlucHV0ID0gRWRpdFRleHQodGhpcykKICAgICAgICBpbnB1dC5oaW50ID0gJnF1b3Q72KPYr9iu2YQg2KfZhNmG2LUg2YTYqtit2YjZitmE2Ycg2KXZhNmJINix2YXYsiBRUiZxdW90OwogICAgICAgIAogICAgICAgIEFsZXJ0RGlhbG9nLkJ1aWxkZXIodGhpcykKICAgICAgICAgICAgLnNldFRpdGxlKCZxdW90O9il2YbYtNin2KEg2LHZhdiyIFFSJnF1b3Q7KQogICAgICAgICAgICAuc2V0VmlldyhpbnB1dCkKICAgICAgICAgICAgLnNldFBvc2l0aXZlQnV0dG9uKCZxdW90O9il2YbYtNin2KEmcXVvdDspIHsgXywgXyAtJmd0OwogICAgICAgICAgICAgICAgdmFsIHRleHQgPSBpbnB1dC50ZXh0LnRvU3RyaW5nKCkKICAgICAgICAgICAgICAgIGlmICh0ZXh0LmlzTm90RW1wdHkoKSkgewogICAgICAgICAgICAgICAgICAgIGdlbmVyYXRlUVJDb2RlKHRleHQpCiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KICAgICAgICAgICAgLnNldE5lZ2F0aXZlQnV0dG9uKCZxdW90O9il2YTYutin2KEmcXVvdDssIG51bGwpCiAgICAgICAgICAgIC5zaG93KCkKICAgIH0KICAgIAogICAgcHJpdmF0ZSBmdW4gZ2VuZXJhdGVRUkNvZGUodGV4dDogU3RyaW5nKSB7CiAgICAgICAgdHJ5IHsKICAgICAgICAgICAgdmFsIHdyaXRlciA9IE11bHRpRm9ybWF0V3JpdGVyKCkKICAgICAgICAgICAgdmFsIG1hdHJpeDogQml0TWF0cml4ID0gd3JpdGVyLmVuY29kZSh0ZXh0LCBCYXJjb2RlRm9ybWF0LlFSX0NPREUsIDUwMCwgNTAwKQogICAgICAgICAgICB2YWwgYml0bWFwID0gbWF0cml4VG9CaXRtYXAobWF0cml4KQogICAgICAgICAgICAKICAgICAgICAgICAgdmFsIGZpbGVOYW1lID0gJnF1b3Q7cXJjb2RlXyR7U3lzdGVtLmN1cnJlbnRUaW1lTWlsbGlzKCl9LnBuZyZxdW90OwogICAgICAgICAgICB2YWwgdGVtcEZpbGUgPSBGaWxlKGNhY2hlRGlyLCBmaWxlTmFtZSkKICAgICAgICAgICAgRmlsZU91dHB1dFN0cmVhbSh0ZW1wRmlsZSkudXNlIHsgb3V0cHV0IC0mZ3Q7CiAgICAgICAgICAgICAgICBiaXRtYXAuY29tcHJlc3MoQml0bWFwLkNvbXByZXNzRm9ybWF0LlBORywgMTAwLCBvdXRwdXQpCiAgICAgICAgICAgIH0KICAgICAgICAgICAgCiAgICAgICAgICAgIGVuY3J5cHRpb25NYW5hZ2VyLmVuY3J5cHRGaWxlKHRlbXBGaWxlLCAmcXVvdDtxcl8ke1N5c3RlbS5jdXJyZW50VGltZU1pbGxpcygpfS5lbmNfaW1hZ2UmcXVvdDspCiAgICAgICAgICAgIHRlbXBGaWxlLmRlbGV0ZSgpCiAgICAgICAgICAgIHJlZnJlc2hGaWxlTGlzdCgpCiAgICAgICAgICAgIFRvYXN0Lm1ha2VUZXh0KHRoaXMsICZxdW90O9iq2YUg2K3Zgdi4INix2YXYsiBRUiDZgdmKINin2YTYrtiy2YbYqSZxdW90OywgVG9hc3QuTEVOR1RIX1NIT1JUKS5zaG93KCkKICAgICAgICB9IGNhdGNoIChlOiBFeGNlcHRpb24pIHsKICAgICAgICAgICAgVG9hc3QubWFrZVRleHQodGhpcywgJnF1b3Q72YHYtNmEINil2YbYtNin2KEg2KfZhNix2YXYsiZxdW90OywgVG9hc3QuTEVOR1RIX1NIT1JUKS5zaG93KCkKICAgICAgICB9CiAgICB9CiAgICAKICAgIHByaXZhdGUgZnVuIG1hdHJpeFRvQml0bWFwKG1hdHJpeDogQml0TWF0cml4KTogQml0bWFwIHsKICAgICAgICB2YWwgd2lkdGggPSBtYXRyaXgud2lkdGgKICAgICAgICB2YWwgaGVpZ2h0ID0gbWF0cml4LmhlaWdodAogICAgICAgIHZhbCBwaXhlbHMgPSBJbnRBcnJheSh3aWR0aCAqIGhlaWdodCkKICAgICAgICBmb3IgKHkgaW4gMCB1bnRpbCBoZWlnaHQpIHsKICAgICAgICAgICAgZm9yICh4IGluIDAgdW50aWwgd2lkdGgpIHsKICAgICAgICAgICAgICAgIHBpeGVsc1t5ICogd2lkdGggKyB4XSA9IGlmIChtYXRyaXhbeCwgeV0pIGFuZHJvaWQuZ3JhcGhpY3MuQ29sb3IuQkxBQ0sgZWxzZSBhbmRyb2lkLmdyYXBoaWNzLkNvbG9yLldISVRFCiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICAgICAgcmV0dXJuIEJpdG1hcC5jcmVhdGVCaXRtYXAocGl4ZWxzLCB3aWR0aCwgaGVpZ2h0LCBCaXRtYXAuQ29uZmlnLkFSR0JfODg4OCkKICAgIH0KfQoKLy8gPT09PT09PT09PT09PT09PT09PT09IENMQVNTRVMgPT09PT09PT09PT09PT09PT09PT09CgpkYXRhIGNsYXNzIFZhdWx0RmlsZSh2YWwgZmlsZTogRmlsZSwgdmFsIHR5cGU6IFN0cmluZykKCmNsYXNzIEVuY3J5cHRpb25NYW5hZ2VyKHByaXZhdGUgdmFsIGNvbnRleHQ6IENvbnRleHQpIHsKICAgIAogICAgcHJpdmF0ZSB2YWwgbWFzdGVyS2V5IGJ5IGxhenkgewogICAgICAgIE1hc3RlcktleS5CdWlsZGVyKGNvbnRleHQpCiAgICAgICAgICAgIC5zZXRLZXlTY2hlbWUoTWFzdGVyS2V5LktleVNjaGVtZS5BRVMyNTZfR0NNKQogICAgICAgICAgICAuYnVpbGQoKQogICAgfQogICAgCiAgICBmdW4gZW5jcnlwdEZpbGUoaW5wdXRGaWxlOiBGaWxlLCBvdXRwdXRGaWxlTmFtZTogU3RyaW5nKTogRmlsZT8gewogICAgICAgIHJldHVybiB0cnkgewogICAgICAgICAgICB2YWwgZW5jcnlwdGVkRmlsZURpciA9IEZpbGUoY29udGV4dC5maWxlc0RpciwgJnF1b3Q7dmF1bHQmcXVvdDspCiAgICAgICAgICAgIGlmICghZW5jcnlwdGVkRmlsZURpci5leGlzdHMoKSkgZW5jcnlwdGVkRmlsZURpci5ta2RpcnMoKQogICAgICAgICAgICAKICAgICAgICAgICAgdmFsIGVuY3J5cHRlZEZpbGUgPSBFbmNyeXB0ZWRGaWxlLkJ1aWxkZXIoCiAgICAgICAgICAgICAgICBjb250ZXh0LAogICAgICAgICAgICAgICAgRmlsZShlbmNyeXB0ZWRGaWxlRGlyLCBvdXRwdXRGaWxlTmFtZSksCiAgICAgICAgICAgICAgICBtYXN0ZXJLZXksCiAgICAgICAgICAgICAgICBFbmNyeXB0ZWRGaWxlLkZpbGVFbmNyeXB0aW9uU2NoZW1lLkFFUzI1Nl9HQ01fSEtERl80S0IKICAgICAgICAgICAgKS5idWlsZCgpCiAgICAgICAgICAgIAogICAgICAgICAgICBGaWxlSW5wdXRTdHJlYW0oaW5wdXRGaWxlKS51c2UgeyBpbnB1dCAtJmd0OwogICAgICAgICAgICAgICAgZW5jcnlwdGVkRmlsZS5vcGVuRmlsZU91dHB1dCgpLnVzZSB7IG91dHB1dCAtJmd0OwogICAgICAgICAgICAgICAgICAgIGlucHV0LmNvcHlUbyhvdXRwdXQpCiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KICAgICAgICAgICAgRmlsZShlbmNyeXB0ZWRGaWxlRGlyLCBvdXRwdXRGaWxlTmFtZSkKICAgICAgICB9IGNhdGNoIChlOiBFeGNlcHRpb24pIHsKICAgICAgICAgICAgZS5wcmludFN0YWNrVHJhY2UoKQogICAgICAgICAgICBudWxsCiAgICAgICAgfQogICAgfQogICAgCiAgICBmdW4gZGVjcnlwdEZpbGUoZW5jcnlwdGVkRmlsZU5hbWU6IFN0cmluZywgb3V0cHV0RmlsZTogRmlsZSk6IEJvb2xlYW4gewogICAgICAgIHJldHVybiB0cnkgewogICAgICAgICAgICB2YWwgZW5jcnlwdGVkRmlsZURpciA9IEZpbGUoY29udGV4dC5maWxlc0RpciwgJnF1b3Q7dmF1bHQmcXVvdDspCiAgICAgICAgICAgIHZhbCBlbmNyeXB0ZWRGaWxlID0gRW5jcnlwdGVkRmlsZS5CdWlsZGVyKAogICAgICAgICAgICAgICAgY29udGV4dCwKICAgICAgICAgICAgICAgIEZpbGUoZW5jcnlwdGVkRmlsZURpciwgZW5jcnlwdGVkRmlsZU5hbWUpLAogICAgICAgICAgICAgICAgbWFzdGVyS2V5LAogICAgICAgICAgICAgICAgRW5jcnlwdGVkRmlsZS5GaWxlRW5jcnlwdGlvblNjaGVtZS5BRVMyNTZfR0NNX0hLREZfNEtCCiAgICAgICAgICAgICkuYnVpbGQoKQogICAgICAgICAgICAKICAgICAgICAgICAgZW5jcnlwdGVkRmlsZS5vcGVuRmlsZUlucHV0KCkudXNlIHsgaW5wdXQgLSZndDsKICAgICAgICAgICAgICAgIEZpbGVPdXRwdXRTdHJlYW0ob3V0cHV0RmlsZSkudXNlIHsgb3V0cHV0IC0mZ3Q7CiAgICAgICAgICAgICAgICAgICAgaW5wdXQuY29weVRvKG91dHB1dCkKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQogICAgICAgICAgICB0cnVlCiAgICAgICAgfSBjYXRjaCAoZTogRXhjZXB0aW9uKSB7CiAgICAgICAgICAgIGUucHJpbnRTdGFja1RyYWNlKCkKICAgICAgICAgICAgZmFsc2UKICAgICAgICB9CiAgICB9Cn0KCmNsYXNzIFBhc3N3b3JkTWFuYWdlcihwcml2YXRlIHZhbCBjb250ZXh0OiBDb250ZXh0KSB7CiAgICAKICAgIHByaXZhdGUgdmFsIHByZWZzID0gY29udGV4dC5nZXRTaGFyZWRQcmVmZXJlbmNlcyhNYWluQWN0aXZpdHkuUFJFRlNfTkFNRSwgQ29udGV4dC5NT0RFX1BSSVZBVEUpCiAgICAKICAgIGZ1biByZWdpc3RlclBhc3N3b3JkKHBhc3N3b3JkOiBTdHJpbmcpOiBCb29sZWFuIHsKICAgICAgICB2YWwgc2FsdCA9IGdlbmVyYXRlU2FsdCgpCiAgICAgICAgdmFsIGhhc2ggPSBoYXNoUGFzc3dvcmQocGFzc3dvcmQsIHNhbHQpCiAgICAgICAgcHJlZnMuZWRpdCgpLnB1dFN0cmluZyhNYWluQWN0aXZpdHkuU0FMVF9LRVksIHNhbHQpLmFwcGx5KCkKICAgICAgICBwcmVmcy5lZGl0KCkucHV0U3RyaW5nKE1haW5BY3Rpdml0eS5QQVNTV09SRF9LRVksIGhhc2gpLmFwcGx5KCkKICAgICAgICByZXR1cm4gdHJ1ZQogICAgfQogICAgCiAgICBmdW4gdmVyaWZ5UGFzc3dvcmQocGFzc3dvcmQ6IFN0cmluZyk6IEJvb2xlYW4gewogICAgICAgIHZhbCBzYWx0ID0gcHJlZnMuZ2V0U3RyaW5nKE1haW5BY3Rpdml0eS5TQUxUX0tFWSwgbnVsbCkgPzogcmV0dXJuIGZhbHNlCiAgICAgICAgdmFsIHNhdmVkSGFzaCA9IHByZWZzLmdldFN0cmluZyhNYWluQWN0aXZpdHkuUEFTU1dPUkRfS0VZLCBudWxsKSA/OiByZXR1cm4gZmFsc2UKICAgICAgICByZXR1cm4gaGFzaFBhc3N3b3JkKHBhc3N3b3JkLCBzYWx0KSA9PSBzYXZlZEhhc2gKICAgIH0KICAgIAogICAgZnVuIGlzUGFzc3dvcmRTZXQoKTogQm9vbGVhbiA9IHByZWZzLmNvbnRhaW5zKE1haW5BY3Rpdml0eS5QQVNTV09SRF9LRVkpCiAgICAKICAgIHByaXZhdGUgZnVuIGdlbmVyYXRlU2FsdCgpOiBTdHJpbmcgewogICAgICAgIHZhbCBzYWx0ID0gQnl0ZUFycmF5KDE2KQogICAgICAgIFNlY3VyZVJhbmRvbSgpLm5leHRCeXRlcyhzYWx0KQogICAgICAgIHJldHVybiBCYXNlNjQuZ2V0RW5jb2RlcigpLmVuY29kZVRvU3RyaW5nKHNhbHQpCiAgICB9CiAgICAKICAgIHByaXZhdGUgZnVuIGhhc2hQYXNzd29yZChwYXNzd29yZDogU3RyaW5nLCBzYWx0OiBTdHJpbmcpOiBTdHJpbmcgewogICAgICAgIHZhbCBzcGVjID0gUEJFS2V5U3BlYyhwYXNzd29yZC50b0NoYXJBcnJheSgpLCBCYXNlNjQuZ2V0RGVjb2RlcigpLmRlY29kZShzYWx0KSwgMTAwMDAsIDI1NikKICAgICAgICB2YWwgZmFjdG9yeSA9IFNlY3JldEtleUZhY3RvcnkuZ2V0SW5zdGFuY2UoJnF1b3Q7UEJLREYyV2l0aEhtYWNTSEEyNTYmcXVvdDspCiAgICAgICAgdmFsIGhhc2ggPSBmYWN0b3J5LmdlbmVyYXRlU2VjcmV0KHNwZWMpLmVuY29kZWQKICAgICAgICByZXR1cm4gQmFzZTY0LmdldEVuY29kZXIoKS5lbmNvZGVUb1N0cmluZyhoYXNoKQogICAgfQp9CgpjbGFzcyBCaW9tZXRyaWNBdXRoKHByaXZhdGUgdmFsIGFjdGl2aXR5OiBBcHBDb21wYXRBY3Rpdml0eSkgewogICAgCiAgICBwcml2YXRlIHZhbCBleGVjdXRvciA9IGFuZHJvaWR4LmNvcmUuY29udGVudC5Db250ZXh0Q29tcGF0LmdldE1haW5FeGVjdXRvcihhY3Rpdml0eSkKICAgIAogICAgZnVuIGlzQmlvbWV0cmljQXZhaWxhYmxlKCk6IEJvb2xlYW4gewogICAgICAgIHZhbCBiaW9tZXRyaWNNYW5hZ2VyID0gQmlvbWV0cmljTWFuYWdlci5mcm9tKGFjdGl2aXR5KQogICAgICAgIHJldHVybiBiaW9tZXRyaWNNYW5hZ2VyLmNhbkF1dGhlbnRpY2F0ZShCaW9tZXRyaWNNYW5hZ2VyLkF1dGhlbnRpY2F0b3JzLkJJT01FVFJJQ19TVFJPTkcpID09IEJpb21ldHJpY01hbmFnZXIuQklPTUVUUklDX1NVQ0NFU1MKICAgIH0KICAgIAogICAgZnVuIGF1dGhlbnRpY2F0ZShvblN1Y2Nlc3M6ICgpIC0mZ3Q7IFVuaXQsIG9uRmFpbGVkOiAoKSAtJmd0OyBVbml0KSB7CiAgICAgICAgdmFsIGJpb21ldHJpY1Byb21wdCA9IEJpb21ldHJpY1Byb21wdChhY3Rpdml0eSwgZXhlY3V0b3IsCiAgICAgICAgICAgIG9iamVjdCA6IEJpb21ldHJpY1Byb21wdC5BdXRoZW50aWNhdGlvbkNhbGxiYWNrKCkgewogICAgICAgICAgICAgICAgb3ZlcnJpZGUgZnVuIG9uQXV0aGVudGljYXRpb25TdWNjZWVkZWQocmVzdWx0OiBCaW9tZXRyaWNQcm9tcHQuQXV0aGVudGljYXRpb25SZXN1bHQpIHsKICAgICAgICAgICAgICAgICAgICBvblN1Y2Nlc3MoKQogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgb3ZlcnJpZGUgZnVuIG9uQXV0aGVudGljYXRpb25GYWlsZWQoKSB7IG9uRmFpbGVkKCkgfQogICAgICAgICAgICAgICAgb3ZlcnJpZGUgZnVuIG9uQXV0aGVudGljYXRpb25FcnJvcihlcnJvckNvZGU6IEludCwgZXJyU3RyaW5nOiBDaGFyU2VxdWVuY2UpIHsgb25GYWlsZWQoKSB9CiAgICAgICAgICAgIH0KICAgICAgICApCiAgICAgICAgCiAgICAgICAgdmFsIHByb21wdEluZm8gPSBCaW9tZXRyaWNQcm9tcHQuUHJvbXB0SW5mby5CdWlsZGVyKCkKICAgICAgICAgICAgLnNldFRpdGxlKCZxdW90O9mB2KrYrSDYp9mE2K7YstmG2KkmcXVvdDspCiAgICAgICAgICAgIC5zZXRTdWJ0aXRsZSgmcXVvdDvYp9iz2KrYrtiv2YUg2KjYtdmF2KrZgyDZhNmB2KrYrSDYp9mE2KrYt9io2YrZgiZxdW90OykKICAgICAgICAgICAgLnNldE5lZ2F0aXZlQnV0dG9uVGV4dCgmcXVvdDvYpdmE2LrYp9ihJnF1b3Q7KQogICAgICAgICAgICAuYnVpbGQoKQogICAgICAgIAogICAgICAgIGJpb21ldHJpY1Byb21wdC5hdXRoZW50aWNhdGUocHJvbXB0SW5mbykKICAgIH0KfQoKY2xhc3MgRmlsZUFkYXB0ZXIocHJpdmF0ZSB2YWwgZmlsZXM6IExpc3QmbHQ7VmF1bHRGaWxlJmd0OywgcHJpdmF0ZSB2YWwgb25JdGVtQ2xpY2s6IChWYXVsdEZpbGUpIC0mZ3Q7IFVuaXQpIDoKICAgIFJlY3ljbGVyVmlldy5BZGFwdGVyJmx0O0ZpbGVBZGFwdGVyLlZpZXdIb2xkZXImZ3Q7KCkgewogICAgCiAgICBpbm5lciBjbGFzcyBWaWV3SG9sZGVyKGl0ZW1WaWV3OiBhbmRyb2lkLnZpZXcuVmlldykgOiBSZWN5Y2xlclZpZXcuVmlld0hvbGRlcihpdGVtVmlldykgewogICAgICAgIHZhbCBpY29uOiBJbWFnZVZpZXcgPSBpdGVtVmlldy5maW5kVmlld0J5SWQoUi5pZC5maWxlSWNvbikKICAgICAgICB2YWwgbmFtZTogVGV4dFZpZXcgPSBpdGVtVmlldy5maW5kVmlld0J5SWQoUi5pZC5maWxlTmFtZSkKICAgICAgICB2YWwgc2l6ZTogVGV4dFZpZXcgPSBpdGVtVmlldy5maW5kVmlld0J5SWQoUi5pZC5maWxlU2l6ZSkKICAgIH0KICAgIAogICAgb3ZlcnJpZGUgZnVuIG9uQ3JlYXRlVmlld0hvbGRlcihwYXJlbnQ6IGFuZHJvaWQudmlldy5WaWV3R3JvdXAsIHZpZXdUeXBlOiBJbnQpOiBWaWV3SG9sZGVyIHsKICAgICAgICB2YWwgdmlldyA9IGFuZHJvaWQudmlldy5MYXlvdXRJbmZsYXRlci5mcm9tKHBhcmVudC5jb250ZXh0KQogICAgICAgICAgICAuaW5mbGF0ZShSLmxheW91dC5pdGVtX2ZpbGUsIHBhcmVudCwgZmFsc2UpCiAgICAgICAgcmV0dXJuIFZpZXdIb2xkZXIodmlldykKICAgIH0KICAgIAogICAgb3ZlcnJpZGUgZnVuIG9uQmluZFZpZXdIb2xkZXIoaG9sZGVyOiBWaWV3SG9sZGVyLCBwb3NpdGlvbjogSW50KSB7CiAgICAgICAgdmFsIGZpbGUgPSBmaWxlc1twb3NpdGlvbl0KICAgICAgICBob2xkZXIubmFtZS50ZXh0ID0gZmlsZS5maWxlLm5hbWUucmVwbGFjZShSZWdleCgmcXVvdDtcXC5lbmNfLiokJnF1b3Q7KSwgJnF1b3Q7JnF1b3Q7KQogICAgICAgIGhvbGRlci5zaXplLnRleHQgPSBmb3JtYXRGaWxlU2l6ZShmaWxlLmZpbGUubGVuZ3RoKCkpCiAgICAgICAgaG9sZGVyLmljb24uc2V0SW1hZ2VSZXNvdXJjZShnZXRJY29uRm9yVHlwZShmaWxlLnR5cGUpKQogICAgICAgIGhvbGRlci5pdGVtVmlldy5zZXRPbkNsaWNrTGlzdGVuZXIgeyBvbkl0ZW1DbGljayhmaWxlKSB9CiAgICB9CiAgICAKICAgIG92ZXJyaWRlIGZ1biBnZXRJdGVtQ291bnQoKTogSW50ID0gZmlsZXMuc2l6ZQogICAgCiAgICBwcml2YXRlIGZ1biBnZXRJY29uRm9yVHlwZSh0eXBlOiBTdHJpbmcpOiBJbnQgewogICAgICAgIHJldHVybiB3aGVuICh0eXBlKSB7CiAgICAgICAgICAgICZxdW90O2ltYWdlJnF1b3Q7IC0mZ3Q7IGFuZHJvaWQuUi5kcmF3YWJsZS5pY19tZW51X2dhbGxlcnkKICAgICAgICAgICAgJnF1b3Q7dmlkZW8mcXVvdDsgLSZndDsgYW5kcm9pZC5SLmRyYXdhYmxlLmljX21lZGlhX3BsYXkKICAgICAgICAgICAgJnF1b3Q7YXVkaW8mcXVvdDsgLSZndDsgYW5kcm9pZC5SLmRyYXdhYmxlLmljX21lZGlhX3BsYXkKICAgICAgICAgICAgZWxzZSAtJmd0OyBhbmRyb2lkLlIuZHJhd2FibGUuaWNfbWVudV9zYXZlCiAgICAgICAgfQogICAgfQogICAgCiAgICBwcml2YXRlIGZ1biBmb3JtYXRGaWxlU2l6ZShzaXplOiBMb25nKTogU3RyaW5nIHsKICAgICAgICByZXR1cm4gd2hlbiB7CiAgICAgICAgICAgIHNpemUgJmx0OyAxMDI0IC0mZ3Q7ICZxdW90OyRzaXplIEImcXVvdDsKICAgICAgICAgICAgc2l6ZSAmbHQ7IDEwMjQgKiAxMDI0IC0mZ3Q7ICZxdW90OyR7c2l6ZSAvIDEwMjR9IEtCJnF1b3Q7CiAgICAgICAgICAgIHNpemUgJmx0OyAxMDI0ICogMTAyNCAqIDEwMjQgLSZndDsgJnF1b3Q7JHtzaXplIC8gKDEwMjQgKiAxMDI0KX0gTUImcXVvdDsKICAgICAgICAgICAgZWxzZSAtJmd0OyAmcXVvdDske3NpemUgLyAoMTAyNCAqIDEwMjQgKiAxMDI0KX0gR0ImcXVvdDsKICAgICAgICB9CiAgICB9Cn0=
package com.htbl.app
import android.Manifest
import android.annotation.SuppressLint
import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.database.Cursor
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.media.MediaScannerConnection
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.provider.MediaStore
import android.provider.OpenableColumns
import android.util.Log
import android.widget.*
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.security.crypto.EncryptedFile
import androidx.security.crypto.MasterKey
import com.google.mlkit.vision.barcode.BarcodeScanning
import com.google.mlkit.vision.barcode.common.Barcode
import com.google.mlkit.vision.common.InputImage
import com.google.zxing.BarcodeFormat
import com.google.zxing.MultiFormatWriter
import com.google.zxing.common.BitMatrix
import java.io.*
import java.security.MessageDigest
import java.text.SimpleDateFormat
import java.util.*
import javax.crypto.SecretKeyFactory
import javax.crypto.spec.PBEKeySpec
class MainActivity : AppCompatActivity() {
private lateinit var recyclerView: RecyclerView
private lateinit var adapter: FileAdapter
private val fileList = mutableListOf<VaultFile>()
private lateinit var encryptionManager: EncryptionManager
private lateinit var passwordManager: PasswordManager
private lateinit var biometricAuth: BiometricAuth
private var isUnlocked = false
private var currentFilter = "all" // all, image, video, audio
companion object {
private const val PREFS_NAME = "vault_prefs"
private const val PASSWORD_KEY = "password_hash"
private const val SALT_KEY = "salt"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
encryptionManager = EncryptionManager(this)
passwordManager = PasswordManager(this)
biometricAuth = BiometricAuth(this)
setupUI()
checkPermissions()
// إذا كانت أول مرة، نطلب إنشاء كلمة مرور
if (!passwordManager.isPasswordSet()) {
showSetupPasswordDialog()
} else {
authenticateUser()
}
}
private fun setupUI() {
recyclerView = findViewById(R.id.recyclerView)
recyclerView.layoutManager = LinearLayoutManager(this)
adapter = FileAdapter(fileList) { file ->
if (isUnlocked) {
showFileOptions(file)
}
}
recyclerView.adapter = adapter
findViewById<Button>(R.id.btnAddFile).setOnClickListener { openFilePicker() }
findViewById<Button>(R.id.btnAddQR).setOnClickListener { openQRScanner() }
findViewById<Button>(R.id.btnFilterAll).setOnClickListener { currentFilter = "all"; refreshFileList() }
findViewById<Button>(R.id.btnFilterImage).setOnClickListener { currentFilter = "image"; refreshFileList() }
findViewById<Button>(R.id.btnFilterVideo).setOnClickListener { currentFilter = "video"; refreshFileList() }
findViewById<Button>(R.id.btnFilterAudio).setOnClickListener { currentFilter = "audio"; refreshFileList() }
findViewById<Button>(R.id.btnLock).setOnClickListener { lockVault() }
findViewById<Button>(R.id.btnGenerateQRCode).setOnClickListener { showGenerateQRDialog() }
}
private fun checkPermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val permissions = arrayOf(
Manifest.permission.READ_MEDIA_IMAGES,
Manifest.permission.READ_MEDIA_VIDEO,
Manifest.permission.READ_MEDIA_AUDIO,
Manifest.permission.USE_BIOMETRIC
)
val needPermissions = permissions.filter {
ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED
}.toTypedArray()
if (needPermissions.isNotEmpty()) {
ActivityCompat.requestPermissions(this, needPermissions, 100)
}
} else {
val permissions = arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.USE_BIOMETRIC
)
val needPermissions = permissions.filter {
ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED
}.toTypedArray()
if (needPermissions.isNotEmpty()) {
ActivityCompat.requestPermissions(this, needPermissions, 100)
}
}
}
private fun authenticateUser() {
if (biometricAuth.isBiometricAvailable()) {
biometricAuth.authenticate(
onSuccess = {
isUnlocked = true
refreshFileList()
Toast.makeText(this, "تم فتح الخزنة 🔓", Toast.LENGTH_SHORT).show()
},
onFailed = { showPasswordDialog() }
)
} else {
showPasswordDialog()
}
}
private fun showPasswordDialog() {
val input = EditText(this)
input.inputType = android.text.InputType.TYPE_CLASS_TEXT or android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD
input.hint = "أدخل كلمة المرور"
AlertDialog.Builder(this)
.setTitle("فتح الخزنة")
.setMessage("الرجاء إدخال كلمة المرور")
.setView(input)
.setPositiveButton("فتح") { _, _ ->
if (passwordManager.verifyPassword(input.text.toString())) {
isUnlocked = true
refreshFileList()
Toast.makeText(this, "تم فتح الخزنة 🔓", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, "كلمة مرور خاطئة ❌", Toast.LENGTH_SHORT).show()
finish()
}
}
.setNegativeButton("إغلاق") { _, _ -> finish() }
.show()
}
private fun showSetupPasswordDialog() {
val passwordInput = EditText(this)
passwordInput.inputType = android.text.InputType.TYPE_CLASS_TEXT or android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD
passwordInput.hint = "كلمة المرور الجديدة"
val confirmInput = EditText(this)
confirmInput.inputType = android.text.InputType.TYPE_CLASS_TEXT or android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD
confirmInput.hint = "تأكيد كلمة المرور"
val layout = LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
addView(passwordInput)
addView(confirmInput)
}
AlertDialog.Builder(this)
.setTitle("إعداد الخزنة")
.setMessage("قم بإنشاء كلمة مرور لحماية ملفاتك")
.setView(layout)
.setPositiveButton("إنشاء") { _, _ ->
val pass = passwordInput.text.toString()
val confirm = confirmInput.text.toString()
if (pass.isNotEmpty() && pass == confirm) {
passwordManager.registerPassword(pass)
authenticateUser()
} else {
Toast.makeText(this, "كلمة المرور غير متطابقة أو فارغة", Toast.LENGTH_SHORT).show()
finish()
}
}
.setNegativeButton("إلغاء") { _, _ -> finish() }
.show()
}
private fun lockVault() {
isUnlocked = false
fileList.clear()
adapter.notifyDataSetChanged()
Toast.makeText(this, "تم قفل الخزنة 🔒", Toast.LENGTH_SHORT).show()
}
private fun refreshFileList() {
if (!isUnlocked) return
fileList.clear()
val vaultDir = File(filesDir, "vault")
if (vaultDir.exists()) {
scanFiles(vaultDir)
}
adapter.notifyDataSetChanged()
}
private fun scanFiles(dir: File) {
dir.listFiles()?.forEach { file ->
if (file.isDirectory) {
scanFiles(file)
} else {
val type = getFileType(file.name)
if (currentFilter == "all" || currentFilter == type) {
fileList.add(VaultFile(file, type))
}
}
}
}
private fun getFileType(fileName: String): String {
return when {
fileName.endsWith(".enc_image") -> "image"
fileName.endsWith(".enc_video") -> "video"
fileName.endsWith(".enc_audio") -> "audio"
fileName.matches(Regex(".*\\.(jpg|jpeg|png|gif|bmp)$", RegexOption.IGNORE_CASE)) -> "image"
fileName.matches(Regex(".*\\.(mp4|avi|mkv|mov|3gp)$", RegexOption.IGNORE_CASE)) -> "video"
fileName.matches(Regex(".*\\.(mp3|wav|ogg|m4a|flac)$", RegexOption.IGNORE_CASE)) -> "audio"
else -> "other"
}
}
private val filePickerLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == RESULT_OK && result.data != null) {
val uri = result.data?.data
uri?.let { saveFileToVault(it) }
}
}
private fun openFilePicker() {
val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
type = "*/*"
putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("image/*", "video/*", "audio/*"))
}
filePickerLauncher.launch(Intent.createChooser(intent, "اختر ملفاً للحفظ"))
}
private fun saveFileToVault(uri: Uri) {
try {
val fileName = getFileName(uri)
val type = getMimeType(uri)
val extension = when {
type.startsWith("image/") -> ".enc_image"
type.startsWith("video/") -> ".enc_video"
type.startsWith("audio/") -> ".enc_audio"
else -> ".enc_file"
}
val encryptedName = "${System.currentTimeMillis()}_${fileName.replace(" ", "_")}$extension"
val tempFile = File(cacheDir, "temp_${System.currentTimeMillis()}")
contentResolver.openInputStream(uri)?.use { input ->
FileOutputStream(tempFile).use { output ->
input.copyTo(output)
}
}
val encrypted = encryptionManager.encryptFile(tempFile, encryptedName)
if (encrypted != null) {
tempFile.delete()
Toast.makeText(this, "تم حفظ وتشفير الملف 🔒", Toast.LENGTH_SHORT).show()
refreshFileList()
} else {
Toast.makeText(this, "فشل التشفير", Toast.LENGTH_SHORT).show()
}
} catch (e: Exception) {
e.printStackTrace()
Toast.makeText(this, "حدث خطأ: ${e.message}", Toast.LENGTH_SHORT).show()
}
}
private fun getFileName(uri: Uri): String {
var fileName = "unknown"
contentResolver.query(uri, null, null, null, null)?.use { cursor ->
if (cursor.moveToFirst()) {
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
if (nameIndex != -1) {
fileName = cursor.getString(nameIndex)
}
}
}
return fileName
}
private fun getMimeType(uri: Uri): String {
return contentResolver.getType(uri) ?: "application/octet-stream"
}
private fun showFileOptions(file: VaultFile) {
val options = arrayOf("عرض", "مشاركة", "حذف", "تصدير إلى الجهاز")
AlertDialog.Builder(this)
.setTitle(file.file.name)
.setItems(options) { _, which ->
when (which) {
0 -> viewFile(file)
1 -> shareFile(file)
2 -> deleteFile(file)
3 -> exportToDevice(file)
}
}
.show()
}
private fun viewFile(file: VaultFile) {
val decryptedFile = File(cacheDir, "decrypt_${System.currentTimeMillis()}")
if (encryptionManager.decryptFile(file.file.name, decryptedFile)) {
val uri = FileProvider.getUriForFile(this, "${packageName}.provider", decryptedFile)
val intent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(uri, getMimeTypeFromName(file.file.name))
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
startActivity(Intent.createChooser(intent, "فتح الملف"))
} else {
Toast.makeText(this, "فشل فك التشفير", Toast.LENGTH_SHORT).show()
}
}
private fun shareFile(file: VaultFile) {
val decryptedFile = File(cacheDir, "share_${System.currentTimeMillis()}")
if (encryptionManager.decryptFile(file.file.name, decryptedFile)) {
val uri = FileProvider.getUriForFile(this, "${packageName}.provider", decryptedFile)
val intent = Intent(Intent.ACTION_SEND).apply {
type = getMimeTypeFromName(file.file.name)
putExtra(Intent.EXTRA_STREAM, uri)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
startActivity(Intent.createChooser(intent, "مشاركة الملف"))
}
}
private fun deleteFile(file: VaultFile) {
AlertDialog.Builder(this)
.setTitle("حذف الملف")
.setMessage("هل أنت متأكد من حذف هذا الملف؟")
.setPositiveButton("حذف") { _, _ ->
if (file.file.delete()) {
refreshFileList()
Toast.makeText(this, "تم الحذف", Toast.LENGTH_SHORT).show()
}
}
.setNegativeButton("إلغاء", null)
.show()
}
private fun exportToDevice(file: VaultFile) {
val decryptedFile = File(cacheDir, "export_${System.currentTimeMillis()}")
if (encryptionManager.decryptFile(file.file.name, decryptedFile)) {
val originalName = file.file.name.replace(Regex("\\.enc_(image|video|audio|file)$"), "")
val outputFileName = "${System.currentTimeMillis()}_$originalName"
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val resolver = contentResolver
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, outputFileName)
put(MediaStore.MediaColumns.MIME_TYPE, getMimeTypeFromName(file.file.name))
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
}
val uri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)
uri?.let {
resolver.openOutputStream(it)?.use { output ->
FileInputStream(decryptedFile).use { input ->
input.copyTo(output)
}
}
Toast.makeText(this, "تم التصدير إلى مجلد التنزيلات", Toast.LENGTH_SHORT).show()
}
} else {
val downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
val outputFile = File(downloadsDir, outputFileName)
decryptedFile.copyTo(outputFile, true)
MediaScannerConnection.scanFile(this, arrayOf(outputFile.absolutePath), null, null)
Toast.makeText(this, "تم التصدير إلى ${outputFile.absolutePath}", Toast.LENGTH_SHORT).show()
}
decryptedFile.delete()
}
}
private fun getMimeTypeFromName(fileName: String): String {
return when {
fileName.contains(".enc_image") || fileName.matches(Regex(".*\\.(jpg|jpeg|png|gif)$", RegexOption.IGNORE_CASE)) -> "image/*"
fileName.contains(".enc_video") || fileName.matches(Regex(".*\\.(mp4|avi|mkv)$", RegexOption.IGNORE_CASE)) -> "video/*"
fileName.contains(".enc_audio") || fileName.matches(Regex(".*\\.(mp3|wav|ogg)$", RegexOption.IGNORE_CASE)) -> "audio/*"
else -> "*/*"
}
}
private fun openQRScanner() {
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
startActivityForResult(intent, 200)
}
@Deprecated("Deprecated in Java")
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == 200 && resultCode == RESULT_OK) {
val imageBitmap = data?.extras?.get("data") as Bitmap
scanQRFromBitmap(imageBitmap)
}
}
private fun scanQRFromBitmap(bitmap: Bitmap) {
val image = InputImage.fromBitmap(bitmap, 0)
val scanner = BarcodeScanning.getClient()
scanner.process(image)
.addOnSuccessListener { barcodes ->
for (barcode in barcodes) {
val text = barcode.rawValue
if (!text.isNullOrEmpty()) {
Toast.makeText(this, "تم مسح الرمز: $text", Toast.LENGTH_LONG).show()
saveQRTextAsFile(text)
}
}
}
.addOnFailureListener {
Toast.makeText(this, "فشل مسح الرمز", Toast.LENGTH_SHORT).show()
}
}
private fun saveQRTextAsFile(text: String) {
val fileName = "qr_${System.currentTimeMillis()}.txt"
val tempFile = File(cacheDir, fileName)
tempFile.writeText(text)
encryptionManager.encryptFile(tempFile, "qr_${System.currentTimeMillis()}.enc_file")
tempFile.delete()
refreshFileList()
Toast.makeText(this, "تم حفظ محتوى الرمز", Toast.LENGTH_SHORT).show()
}
private fun showGenerateQRDialog() {
val input = EditText(this)
input.hint = "أدخل النص لتحويله إلى رمز QR"
AlertDialog.Builder(this)
.setTitle("إنشاء رمز QR")
.setView(input)
.setPositiveButton("إنشاء") { _, _ ->
val text = input.text.toString()
if (text.isNotEmpty()) {
generateQRCode(text)
}
}
.setNegativeButton("إلغاء", null)
.show()
}
private fun generateQRCode(text: String) {
try {
val writer = MultiFormatWriter()
val matrix: BitMatrix = writer.encode(text, BarcodeFormat.QR_CODE, 500, 500)
val bitmap = matrixToBitmap(matrix)
val fileName = "qrcode_${System.currentTimeMillis()}.png"
val tempFile = File(cacheDir, fileName)
FileOutputStream(tempFile).use { output ->
bitmap.compress(Bitmap.CompressFormat.PNG, 100, output)
}
encryptionManager.encryptFile(tempFile, "qr_${System.currentTimeMillis()}.enc_image")
tempFile.delete()
refreshFileList()
Toast.makeText(this, "تم حفظ رمز QR في الخزنة", Toast.LENGTH_SHORT).show()
} catch (e: Exception) {
Toast.makeText(this, "فشل إنشاء الرمز", Toast.LENGTH_SHORT).show()
}
}
private fun matrixToBitmap(matrix: BitMatrix): Bitmap {
val width = matrix.width
val height = matrix.height
val pixels = IntArray(width * height)
for (y in 0 until height) {
for (x in 0 until width) {
pixels[y * width + x] = if (matrix[x, y]) android.graphics.Color.BLACK else android.graphics.Color.WHITE
}
}
return Bitmap.createBitmap(pixels, width, height, Bitmap.Config.ARGB_8888)
}
}
// ===================== CLASSES =====================
data class VaultFile(val file: File, val type: String)
class EncryptionManager(private val context: Context) {
private val masterKey by lazy {
MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
}
fun encryptFile(inputFile: File, outputFileName: String): File? {
return try {
val encryptedFileDir = File(context.filesDir, "vault")
if (!encryptedFileDir.exists()) encryptedFileDir.mkdirs()
val encryptedFile = EncryptedFile.Builder(
context,
File(encryptedFileDir, outputFileName),
masterKey,
EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build()
FileInputStream(inputFile).use { input ->
encryptedFile.openFileOutput().use { output ->
input.copyTo(output)
}
}
File(encryptedFileDir, outputFileName)
} catch (e: Exception) {
e.printStackTrace()
null
}
}
fun decryptFile(encryptedFileName: String, outputFile: File): Boolean {
return try {
val encryptedFileDir = File(context.filesDir, "vault")
val encryptedFile = EncryptedFile.Builder(
context,
File(encryptedFileDir, encryptedFileName),
masterKey,
EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build()
encryptedFile.openFileInput().use { input ->
FileOutputStream(outputFile).use { output ->
input.copyTo(output)
}
}
true
} catch (e: Exception) {
e.printStackTrace()
false
}
}
}
class PasswordManager(private val context: Context) {
private val prefs = context.getSharedPreferences(MainActivity.PREFS_NAME, Context.MODE_PRIVATE)
fun registerPassword(password: String): Boolean {
val salt = generateSalt()
val hash = hashPassword(password, salt)
prefs.edit().putString(MainActivity.SALT_KEY, salt).apply()
prefs.edit().putString(MainActivity.PASSWORD_KEY, hash).apply()
return true
}
fun verifyPassword(password: String): Boolean {
val salt = prefs.getString(MainActivity.SALT_KEY, null) ?: return false
val savedHash = prefs.getString(MainActivity.PASSWORD_KEY, null) ?: return false
return hashPassword(password, salt) == savedHash
}
fun isPasswordSet(): Boolean = prefs.contains(MainActivity.PASSWORD_KEY)
private fun generateSalt(): String {
val salt = ByteArray(16)
SecureRandom().nextBytes(salt)
return Base64.getEncoder().encodeToString(salt)
}
private fun hashPassword(password: String, salt: String): String {
val spec = PBEKeySpec(password.toCharArray(), Base64.getDecoder().decode(salt), 10000, 256)
val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
val hash = factory.generateSecret(spec).encoded
return Base64.getEncoder().encodeToString(hash)
}
}
class BiometricAuth(private val activity: AppCompatActivity) {
private val executor = androidx.core.content.ContextCompat.getMainExecutor(activity)
fun isBiometricAvailable(): Boolean {
val biometricManager = BiometricManager.from(activity)
return biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG) == BiometricManager.BIOMETRIC_SUCCESS
}
fun authenticate(onSuccess: () -> Unit, onFailed: () -> Unit) {
val biometricPrompt = BiometricPrompt(activity, executor,
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
onSuccess()
}
override fun onAuthenticationFailed() { onFailed() }
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { onFailed() }
}
)
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle("فتح الخزنة")
.setSubtitle("استخدم بصمتك لفتح التطبيق")
.setNegativeButtonText("إلغاء")
.build()
biometricPrompt.authenticate(promptInfo)
}
}
class FileAdapter(private val files: List<VaultFile>, private val onItemClick: (VaultFile) -> Unit) :
RecyclerView.Adapter<FileAdapter.ViewHolder>() {
inner class ViewHolder(itemView: android.view.View) : RecyclerView.ViewHolder(itemView) {
val icon: ImageView = itemView.findViewById(R.id.fileIcon)
val name: TextView = itemView.findViewById(R.id.fileName)
val size: TextView = itemView.findViewById(R.id.fileSize)
}
override fun onCreateViewHolder(parent: android.view.ViewGroup, viewType: Int): ViewHolder {
val view = android.view.LayoutInflater.from(parent.context)
.inflate(R.layout.item_file, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val file = files[position]
holder.name.text = file.file.name.replace(Regex("\\.enc_.*$"), "")
holder.size.text = formatFileSize(file.file.length())
holder.icon.setImageResource(getIconForType(file.type))
holder.itemView.setOnClickListener { onItemClick(file) }
}
override fun getItemCount(): Int = files.size
private fun getIconForType(type: String): Int {
return when (type) {
"image" -> android.R.drawable.ic_menu_gallery
"video" -> android.R.drawable.ic_media_play
"audio" -> android.R.drawable.ic_media_play
else -> android.R.drawable.ic_menu_save
}
}
private fun formatFileSize(size: Long): String {
return when {
size < 1024 -> "$size B"
size < 1024 * 1024 -> "${size / 1024} KB"
size < 1024 * 1024 * 1024 -> "${size / (1024 * 1024)} MB"
else -> "${size / (1024 * 1024 * 1024)} GB"
}
}
}