[
  {
    "path": ".gitignore",
    "content": "##### Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm #####\n##### (thanks to https://github.com/github/gitignore/blob/master/Global/JetBrains.gitignore) #####\n\n/*.iml\n*.iml\n*/build\n\n## Directory-based project format:\n.idea/\n# if you remove the above rule, at least ignore the following:\n\n# User-specific stuff:\n# .idea/workspace.xml\n# .idea/tasks.xml\n# .idea/dictionaries\n\n# Sensitive or high-churn files:\n# .idea/dataSources.ids\n# .idea/dataSources.xml\n# .idea/sqlDataSources.xml\n# .idea/dynamic.xml\n# .idea/uiDesigner.xml\n\n# Gradle:\n# .idea/gradle.xml\n# .idea/libraries\n\n# Mongo Explorer plugin:\n# .idea/mongoSettings.xml\n\n## File-based project format:\n*.ipr\n*.iws\n\n## Plugin-specific files:\n\n# IntelliJ\nout/\n\n# mpeltonen/sbt-idea plugin\n.idea_modules/\n\n# JIRA plugin\natlassian-ide-plugin.xml\n\n# Crashlytics plugin (for Android Studio and IntelliJ)\ncom_crashlytics_export_strings.xml\n\n\n###### Android Studio generated #####\n.gradle\n/local.properties\n/.idea/workspace.xml\n.DS_Store\n/build\n\n# Built application files\n*.apk\n*.ap_\n\n# Files for the Dalvik VM\n*.dex\n\n# Java class files\n*.class\n\n# Generated files\nbin/\ngen/\n\n####### And standard github .gitignore for Android #####\n\n# Proguard folder generated by Eclipse\nproguard/\n\n# Log Files\n*.log\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: android\njdk: oraclejdk8\nsudo: false\n\nandroid:\n  components:\n      - platform-tools\n      - tools\n      - build-tools-24.0.2\n      - build-tools-25.0.2\n      - android-22\n      - android-24\n      - android-25\n      - sys-img-armeabi-v7a-android-22\n      - sys-img-armeabi-v7a-android-25\n      - extra-android-m2repository\n\n#    # Uncomment the lines below if you want to\n#    # use the latest revision of Android SDK Tools\n#    - platform-tools\n#    - tools\n#\n#    # The BuildTools version used by your project\n#    - build-tools-25.0.2\n#\n#    # The SDK version used to compile your project\n#    - android-25\n#\n#    # Additional components\n#    - extra-google-google_play_services\n#    - extra-google-m2repository\n#    - extra-android-m2repository\n#    - addon-google_apis-google-25\n#\n#    # Specify at least one system image,\n#    # if you need to run emulator(s) during your tests\n#    - sys-img-armeabi-v7a-android-22\n#    - sys-img-armeabi-v7a-android-16\n\nbefore_script:\n  # Create and start emulator\n  - echo no | android create avd --force -n test -t android-22 --abi armeabi-v7a\n  - emulator -avd test -no-skin -no-audio -no-window &\n  - android-wait-for-emulator\n  - adb shell input keyevent 82 &\n\nscript: ./gradlew  -PbintrayUser=blah -PbintrayApikey=blah connectedAndroidTest\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2014 Slava Egorenkov\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\n"
  },
  {
    "path": "MaskedEditText/build.gradle",
    "content": "plugins {\n    id \"com.jfrog.bintray\" version \"1.7.3\"\n}\n\napply plugin: 'com.android.library'\napply plugin: 'com.github.dcendents.android-maven'\n\nString projectVersion = \"1.0.5\"\nString projectGroup = \"ru.egslava\"\n\nversion = projectVersion\ngroup = projectGroup\n\nandroid {\n    compileSdkVersion 25\n    buildToolsVersion '25.0.2'\n\n\n    defaultConfig {\n        minSdkVersion 9\n        targetSdkVersion 25\n        versionCode 1\n        versionName projectVersion\n        testInstrumentationRunner \"android.support.test.runner.AndroidJUnitRunner\"\n    }\n}\n\ndependencies {\n    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {\n        exclude group: 'com.android.support', module: 'support-annotations'\n    })\n\n    compile fileTree(dir: 'libs', include: ['*.jar'])\n    compile 'com.android.support:appcompat-v7:25.2.0'\n\n    testCompile 'junit:junit:4.12'\n}\n\n\n// bintray deploy ....\nString projectName = \"edittext-mask\"\nString projectDescription = GFileUtils.readFile(new File(\"README.md\"))\nString webUrl = \"https://github.com/egslava/edittext-mask\"\nString gitUrl = \"https://github.com/egslava/edittext-mask.git\"\n\n\ninstall {\n    repositories.mavenInstaller {\n        pom {\n            project {\n                packaging 'aar'\n                \n                name projectName\n                description projectDescription\n                url webUrl\n\n                inceptionYear '2017' // HARDCODED\n\n                licenses {\n                    license {\n                        name 'MIT'\n                        url 'http://www.apache.org/licenses/LICENSE-2.0.txt'\n                        distribution 'repo'\n                    }\n                }\n\n                scm {\n                    connection gitUrl\n                    developerConnection gitUrl\n                    url webUrl\n                }\n                developers {\n                    developer {\n                        id 'egslava'\n                        name 'Slava Egorenkov'\n                        email 'egslava@gmail.com'\n                    }\n                }\n            }\n        }\n    }\n}\n\n// The end of the gradle file is for JCenter publication.\n// The original code was written with look at Alexander Matveychuk's code\n// and\n// https://www.virag.si/2015/01/publishing-gradle-android-library-to-jcenter/\n\ntask sourcesJar(type: Jar) {\n    from android.sourceSets.main.java.srcDirs\n    classifier = 'sources'\n}\n\ntask javadoc(type: Javadoc) {\n    source = android.sourceSets.main.java.srcDirs\n    classpath += project.files(android.getBootClasspath().join(File.pathSeparator))\n}\n\ntask javadocJar(type: Jar, dependsOn: javadoc) {\n    classifier = 'javadoc'\n    from javadoc.destinationDir\n}\nartifacts {\n    archives javadocJar\n    archives sourcesJar\n}\n\nbintray {\n    user = bintrayUser\n    key = bintrayApikey\n\n    configurations = ['archives']\n    pkg {\n        repo = \"maven\"\n        name = projectName\n        userOrg = user\n//        projectUrl = webUrl\n        vcsUrl = gitUrl\n        licenses = [\"MIT\"]\n        group = projectGroup\n        publish = true\n\n        version {\n            name = projectVersion\n//            desc = projectDescription\n            vcsTag = projectVersion\n        }\n    }\n}"
  },
  {
    "path": "MaskedEditText/src/androidTest/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"br.com.sapereaude.maskedEditText\"\n    android:versionCode=\"1\"\n    android:versionName=\"1.0\" >\n\n    <uses-sdk\n        android:minSdkVersion=\"10\"\n        android:targetSdkVersion=\"10\" />\n\n    <application >\n        <activity android:name=\"ru.egslava.lib_phone.TestActivity\"\n            android:theme=\"@style/AppTheme\"/>\n    </application>\n\n</manifest>"
  },
  {
    "path": "MaskedEditText/src/androidTest/java/ru/egslava/lib_phone/MainActivityTest.java",
    "content": "package ru.egslava.lib_phone;\n\n\nimport android.content.pm.ActivityInfo;\nimport android.support.test.espresso.action.ViewActions;\nimport android.support.test.filters.Suppress;\nimport android.support.test.rule.ActivityTestRule;\nimport android.support.test.runner.AndroidJUnit4;\nimport android.test.suitebuilder.annotation.LargeTest;\n\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.security.InvalidParameterException;\n\nimport ru.egslava.lib_phone.actions.HintViewAction;\n\nimport static android.support.test.espresso.Espresso.onView;\nimport static android.support.test.espresso.action.ViewActions.clearText;\nimport static android.support.test.espresso.action.ViewActions.click;\nimport static android.support.test.espresso.action.ViewActions.closeSoftKeyboard;\nimport static android.support.test.espresso.action.ViewActions.typeText;\nimport static android.support.test.espresso.assertion.ViewAssertions.matches;\nimport static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;\nimport static android.support.test.espresso.matcher.ViewMatchers.withId;\nimport static android.support.test.espresso.matcher.ViewMatchers.withText;\nimport static org.hamcrest.Matchers.allOf;\nimport static ru.egslava.lib_phone.actions.KeepHintViewAction.dontKeepHints;\nimport static ru.egslava.lib_phone.actions.KeepHintViewAction.keepHints;\n\n@LargeTest\n@RunWith(AndroidJUnit4.class)\npublic class MainActivityTest {\n\n    public static final int phone_input = br.com.sapereaude.maskedEditText.test.R.id.vControl;\n\n    @Rule\n    public ActivityTestRule<TestActivity> mActivityTestRule = new ActivityTestRule<>(TestActivity.class);\n\n    @Test\n    public void textTypingTest() {\n        onView(\n            allOf(\n                withId(phone_input),\n                isDisplayed())\n        ).perform(click());\n\n        // just in case, check again that it's the same view after the click\n        onView(\n            allOf(\n                withId(phone_input),\n                isDisplayed())\n        ).perform(typeText(\"9997055671\"));\n\n        onView(\n            allOf(\n                withId(phone_input),\n                withText(\"+7(999)705-56-71\"),\n                isDisplayed())\n        ).perform(closeSoftKeyboard());\n\n        onView( allOf(\n            withId(phone_input),\n            isDisplayed())\n        ).check(matches(withText(\"+7(999)705-56-71\")));\n    }\n\n    @Test\n    public void saveInstanceStateTest() {\n        onView(\n            allOf(\n                withId(phone_input),\n                isDisplayed())\n        ).perform(click());\n\n        // just in case, check again that it's the same view after the click\n        onView(\n            allOf(\n                withId(phone_input),\n                isDisplayed())\n        ).perform(typeText(\"9997055671\"));\n\n        onView(\n            allOf(\n                withId(phone_input),\n                withText(\"+7(999)705-56-71\"),\n                isDisplayed())\n        ).perform(closeSoftKeyboard());\n\n        mActivityTestRule.getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);\n        mActivityTestRule.getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);\n\n        onView( allOf(\n            withId(phone_input),\n            isDisplayed())\n        ).check(matches(withText(\"+7(999)705-56-71\")));\n    }\n\n\n    /**\n     * After merging with Alexander Matveychuk, the demo app started to crash\n     * if there's no any text and you rotate the phone\n     */\n    @Test\n    public void saveInstanceStateTest2() {\n        mActivityTestRule.getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);\n        mActivityTestRule.getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);\n    }\n\n    /**\n     * After setKeepHint(true) a hint should appeared.\n     * After setKeepHint(false) a hint should disappear.\n     */\n    @Test\n    public void setKeepHintTest() {\n        onView(withId(phone_input))\n                .perform(new HintViewAction(\"9997055671\"))\n                .perform(dontKeepHints)     // just to be sure\n\n                // the first check. After setKeepHint(true) the hint should appear immediate\n                .perform(ViewActions.typeText(\"999\"))\n                .perform(keepHints)\n                .check(matches(withText(\"+7(999)705-56-71\")))\n\n                // the second check. After setKeepHint(false) the hint should disappear immediate\n                .perform(dontKeepHints)\n                .check(matches(withText(\"+7(999)\")));\n    }\n\n\n    /**\n     * If the text is empty changing of keepHint can lead to a crash.\n     * It's the regression test\n     */\n    @Test\n    public void setEmptyTextTest() {\n\n        // given\n        onView(withId(phone_input))\n                .perform(new HintViewAction(\"9997055671\"))\n\n        // tests\n                .perform(dontKeepHints)\n                .check(matches(withText(\"+7(999)705-56-71\")))\n                .perform(keepHints)\n                .check(matches(withText(\"+7(999)705-56-71\")))\n                .perform(dontKeepHints)\n\n                // YES! Because the text is empty, user need to see a hint\n                .check(matches(withText(\"+7(999)705-56-71\")));\n    }\n    /**\n     * It should keep state of keepHint after activity recreation :-/\n     * It's the regression test\n     */\n    @Test\n    @Suppress   // TODO\n    public void keepHintAfterRotationTest() throws InterruptedException {\n\n        // ======================================\n        // if initial state was keepHint(false)\n\n        // given\n        onView(withId(phone_input))\n                .perform(new HintViewAction(\"9997055671\"))\n                .perform(keepHints)\n                .perform(typeText(\"999\"))\n                .check(matches(withText(\"+7(999)705-56-71\")))\n                .perform(dontKeepHints);\n\n        // rotating screen\n        final TestActivity a1 = mActivityTestRule.getActivity();\n        a1.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);\n\n        Thread.sleep(2500);\n        final TestActivity a2 = mActivityTestRule.getActivity();\n        a2.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);\n        Thread.sleep(2500);\n\n\n        if (a1 != a2) {\n            throw new InvalidParameterException(\"a1 != a2\");\n        }\n        // tests\n        onView(withId(phone_input))\n                .check(matches(withText(\"+7(999)\")));\n\n        // ======================================\n        // and if initial state was keepHint(true)\n\n        onView(withId(phone_input))\n                .perform(clearText())   // after previous test\n                .perform(new HintViewAction(\"1234567890\"))\n                .perform(keepHints)\n                .perform(typeText(\"999\"))\n                .check(matches(withText(\"+7(999)456-78-90\")))\n                ;\n\n        // rotating screen\n        mActivityTestRule.getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);\n        Thread.sleep(5000);\n        mActivityTestRule.getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);\n        Thread.sleep(2500);\n\n\n        // tests\n        onView(withId(phone_input))\n                .check(matches(withText(\"+7(999)456-78-90\")));\n    }\n}\n"
  },
  {
    "path": "MaskedEditText/src/androidTest/java/ru/egslava/lib_phone/TestActivity.java",
    "content": "package ru.egslava.lib_phone;\n\nimport android.os.Bundle;\nimport android.support.v7.app.AppCompatActivity;\n\n/**\n * Created by egslava on 04/03/2017.\n */\n\npublic class TestActivity extends AppCompatActivity {\n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(br.com.sapereaude.maskedEditText.test.R.layout.activity_main);\n    }\n}\n"
  },
  {
    "path": "MaskedEditText/src/androidTest/java/ru/egslava/lib_phone/actions/HintViewAction.java",
    "content": "package ru.egslava.lib_phone.actions;\n\nimport android.support.test.espresso.UiController;\nimport android.support.test.espresso.ViewAction;\nimport android.view.View;\nimport android.widget.TextView;\n\nimport org.hamcrest.Matcher;\nimport org.hamcrest.Matchers;\n\npublic class HintViewAction implements ViewAction {\n\n\n    String hint;\n\n    public HintViewAction(String hint) {\n        this.hint = hint;\n    }\n\n    @Override\n    public Matcher<View> getConstraints() {\n        return Matchers.instanceOf(TextView.class);\n    }\n\n    @Override\n    public String getDescription() {\n        return \"Set hints on view\";\n    }\n\n    @Override\n    public void perform(UiController uiController, View view) {\n        ((TextView)view).setHint(hint);\n    }\n}"
  },
  {
    "path": "MaskedEditText/src/androidTest/java/ru/egslava/lib_phone/actions/KeepHintViewAction.java",
    "content": "package ru.egslava.lib_phone.actions;\n\nimport android.support.test.espresso.UiController;\nimport android.support.test.espresso.ViewAction;\nimport android.view.View;\n\nimport org.hamcrest.Matcher;\nimport org.hamcrest.Matchers;\n\nimport br.com.sapereaude.maskedEditText.MaskedEditText;\n\npublic enum KeepHintViewAction implements ViewAction {\n    keepHints(true), dontKeepHints(false);\n\n\n    boolean keepHint;\n\n    KeepHintViewAction(boolean keepHint) {\n        this.keepHint = keepHint;\n    }\n\n    @Override\n    public Matcher<View> getConstraints() {\n        return Matchers.instanceOf(MaskedEditText.class);\n    }\n\n    @Override\n    public String getDescription() {\n        return \"Set keepHint on/off\";\n    }\n\n    @Override\n    public void perform(UiController uiController, View view) {\n        ((MaskedEditText)view).setKeepHint(keepHint);\n    }\n}"
  },
  {
    "path": "MaskedEditText/src/androidTest/java/ru/egslava/lib_phone/actions/SetTextViewAction.java",
    "content": "package ru.egslava.lib_phone.actions;\n\nimport android.support.test.espresso.UiController;\nimport android.support.test.espresso.ViewAction;\nimport android.view.View;\n\nimport org.hamcrest.Matcher;\n\nimport br.com.sapereaude.maskedEditText.MaskedEditText;\n\nimport static org.hamcrest.Matchers.allOf;\nimport static org.hamcrest.Matchers.instanceOf;\n\npublic class SetTextViewAction implements ViewAction {\n\n    private String value;\n\n    public SetTextViewAction(String value) {\n        this.value = value;\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    @Override\n    public Matcher<View> getConstraints() {\n        return allOf(instanceOf(MaskedEditText.class));\n    }\n\n    @Override\n    public void perform(UiController uiController, View view) {\n        MaskedEditText maskedEditText = (MaskedEditText) view;\n        maskedEditText.setText(maskedEditText.getRawText());\n    }\n\n    @Override\n    public String getDescription() {\n        return \"replace text\";\n    }\n};"
  },
  {
    "path": "MaskedEditText/src/androidTest/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:mask=\"http://schemas.android.com/apk/res-auto\"\n    android:orientation=\"vertical\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://schemas.android.com/apk/res/android\">\n\n    <br.com.sapereaude.maskedEditText.MaskedEditText\n        android:id=\"@+id/vControl\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:inputType=\"phone\"\n        android:typeface=\"monospace\"\n        mask:allowed_chars=\"1234567890\"\n        mask:mask=\"+7(###)###-##-##\"\n        />\n\n</LinearLayout>"
  },
  {
    "path": "MaskedEditText/src/androidTest/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light.DarkActionBar\">\n        <!-- Customize your theme here. -->\n    </style>\n\n</resources>\n"
  },
  {
    "path": "MaskedEditText/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"br.com.sapereaude.maskedEditText\" >\n\n</manifest>"
  },
  {
    "path": "MaskedEditText/src/main/java/br/com/sapereaude/maskedEditText/MaskedEditText.java",
    "content": "package br.com.sapereaude.maskedEditText;\n\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport android.os.Bundle;\nimport android.os.Parcelable;\nimport android.support.v4.text.TextUtilsCompat;\nimport android.support.v7.widget.AppCompatEditText;\nimport android.text.Editable;\nimport android.text.SpannableStringBuilder;\nimport android.text.TextWatcher;\nimport android.text.style.ForegroundColorSpan;\nimport android.util.AttributeSet;\nimport android.util.Log;\nimport android.view.KeyEvent;\nimport android.view.View;\nimport android.widget.TextView;\n\nimport static android.content.ContentValues.TAG;\n\npublic class MaskedEditText extends AppCompatEditText implements TextWatcher {\n\n    public static final String SPACE = \" \";\n\tprivate final OnEditorActionListener onEditorActionListener = new OnEditorActionListener() {\n\t\t@Override\n\t\tpublic boolean onEditorAction(TextView v, int actionId,KeyEvent event) {\n\t\t\tswitch (actionId) {\n//\t\t\t\tcase EditorInfo.IME_ACTION_NEXT:\n\t\t\t\t// fixing actionNext\n//\t\t\t\t\treturn false;\n\t\t\t\tdefault:\n\t\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t};\n\tprivate String mask;\n\tprivate char charRepresentation;\n\tprivate boolean keepHint;\n\tprivate int[] rawToMask;\n\tprivate RawText rawText;\n\tprivate boolean editingBefore;\n\tprivate boolean editingOnChanged;\n\tprivate boolean editingAfter;\n\tprivate int[] maskToRaw;\n\tprivate int selection;\n\tprivate boolean initialized;\n\tprivate boolean ignore;\n\tprotected int maxRawLength;\n\tprivate int lastValidMaskPosition;\n\tprivate boolean selectionChanged;\n\tprivate OnFocusChangeListener focusChangeListener;\n    private String allowedChars;\n    private String deniedChars;\n    private boolean shouldKeepText;\n\n    public MaskedEditText(Context context) {\n\t\tsuper(context);\n\t\tinit();\n\t}\n\n\tpublic MaskedEditText(Context context, AttributeSet attrs) {\n\t\tsuper(context, attrs);\n\t\tinit();\n\n\t\tTypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable.MaskedEditText);\n\t\tmask = attributes.getString(R.styleable.MaskedEditText_mask);\n\n        allowedChars = attributes.getString(R.styleable.MaskedEditText_allowed_chars);\n        deniedChars = attributes.getString(R.styleable.MaskedEditText_denied_chars);\n        boolean enableImeAction = attributes.getBoolean(R.styleable.MaskedEditText_enable_ime_action, false);\n\n\t\tString representation = attributes.getString(R.styleable.MaskedEditText_char_representation);\n\n\t\tif(representation == null) {\n\t\t\tcharRepresentation = '#';\n\t\t} else {\n\t\t\tcharRepresentation = representation.charAt(0);\n\t\t}\n\n\t\tkeepHint = attributes.getBoolean(R.styleable.MaskedEditText_keep_hint, false);\n\n\t\tcleanUp();\n\n\t\t// Ignoring enter key presses if needed\n\t\tif (!enableImeAction) {\n\t\t\tsetOnEditorActionListener(onEditorActionListener);\n\t\t} else {\n\t\t\tsetOnEditorActionListener(null);\n\t\t}\n\t\tattributes.recycle();\n\t}\n\n\t@Override\n\tpublic Parcelable onSaveInstanceState() {\n\t\tfinal Parcelable superParcellable = super.onSaveInstanceState();\n\t\tfinal Bundle state = new Bundle();\n\t\tstate.putParcelable(\"super\", superParcellable);\n\t\tstate.putString(\"text\", getRawText());\n\t\tstate.putBoolean(\"keepHint\", isKeepHint());\n\t\treturn state;\n\t}\n\n\t@Override\n\tpublic void onRestoreInstanceState(Parcelable state) {\n\t\tBundle bundle = (Bundle) state;\n\t\tkeepHint = bundle.getBoolean(\"keepHint\", false);\n\t\tsuper.onRestoreInstanceState(((Bundle) state).getParcelable(\"super\"));\n\t\tfinal String text = bundle.getString(\"text\");\n\n\t\tsetText(text);\n\t\tLog.d(TAG, \"onRestoreInstanceState: \" + text);\n\t}\n\n\t@Override\n\tpublic void setText(CharSequence text, BufferType type) {\n//\t\tif (text == null || text.equals(\"\")) return;\n\t\tsuper.setText(text, type);\n\t}\n\n\t/** @param listener - its onFocusChange() method will be called before performing MaskedEditText operations,\n\t * related to this event. */\n\t@Override\n\tpublic void setOnFocusChangeListener(OnFocusChangeListener listener) {\n\t\tfocusChangeListener = listener;\n\t}\n\n\tprivate void cleanUp() {\n\t\tinitialized = false;\n\t\tif(mask == null || mask.isEmpty()){\n                    return;\n                }\n\t\tgeneratePositionArrays();\n                if (!shouldKeepText || rawText == null) {\n                    rawText = new RawText();\n                    selection = rawToMask[0];\n                }\n\t\teditingBefore = true;\n\t\teditingOnChanged = true;\n\t\teditingAfter = true;\n\t\tif(hasHint() && rawText.length() == 0) {\n            this.setText(makeMaskedTextWithHint());\n\t\t} else {\n            this.setText(makeMaskedText());\n\t\t}\n\t\teditingBefore = false;\n\t\teditingOnChanged = false;\n\t\teditingAfter = false;\n\n\t\tmaxRawLength = maskToRaw[previousValidPosition(mask.length() - 1)] + 1;\n\t\tlastValidMaskPosition = findLastValidMaskPosition();\n\t\tinitialized = true;\n\n\t\tsuper.setOnFocusChangeListener(new OnFocusChangeListener() {\n\t\t\t@Override\n\t\t\tpublic void onFocusChange(View v, boolean hasFocus) {\n\t\t\t\tif (focusChangeListener != null) {\n\t\t\t\t\tfocusChangeListener.onFocusChange(v, hasFocus);\n\t\t\t\t}\n\n\t\t\t\tif (hasFocus()) {\n\t\t\t\t\tselectionChanged = false;\n\t\t\t\t\tMaskedEditText.this.setSelection(lastValidPosition());\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate int findLastValidMaskPosition() {\n\t\tfor(int i = maskToRaw.length - 1; i >= 0; i--) {\n\t\t\tif(maskToRaw[i] != -1) return i;\n\t\t}\n\t\tthrow new RuntimeException(\"Mask must contain at least one representation char\");\n\t}\n\n\tprivate boolean hasHint() {\n\t\treturn getHint() != null;\n\t}\n\n\tpublic MaskedEditText(Context context, AttributeSet attrs, int defStyle) {\n\t\tsuper(context, attrs, defStyle);\n\t\tinit();\n\t}\n\n        public void setShouldKeepText(boolean shouldKeepText) {\n            this.shouldKeepText = shouldKeepText;\n        }\n\n        public boolean isKeepingText() {\n            return shouldKeepText;\n        }\n\n\tpublic void setMask(String mask) {\n\t\tthis.mask = mask;\n\t\tcleanUp();\n\t}\n\n\tpublic String getMask() {\n\t\treturn this.mask;\n\t}\n\n\tpublic void setImeActionEnabled(boolean isEnabled) {\n\t\tif (isEnabled)\n\t\t\tsetOnEditorActionListener(onEditorActionListener);\n\t\telse\n\t\t\tsetOnEditorActionListener(null);\n\t}\n\n\tpublic String getRawText() {\n\t\treturn this.rawText.getText();\n\t}\n\n\tpublic void setCharRepresentation(char charRepresentation) {\n\t\tthis.charRepresentation = charRepresentation;\n\t\tcleanUp();\n\t}\n\n\tpublic char getCharRepresentation() {\n\t\treturn this.charRepresentation;\n\t}\n\n    /**\n     *  Generates positions for values characters. For instance:\n     *  Input data: mask = \"+7(###)###-##-##\n     *  After method execution:\n     *  rawToMask = [3, 4, 5, 6, 8, 9, 11, 12, 14, 15]\n     *  maskToRaw = [-1, -1, -1, 0, 1, 2, -1, 3, 4, 5, -1, 6, 7, -1, 8, 9]\n     *  charsInMask = \"+7()- \" (and space, yes)\n     */\n\tprivate void generatePositionArrays() {\n\t\tint[] aux = new int[mask.length()];\n\t\tmaskToRaw = new int[mask.length()];\n\t\tString charsInMaskAux = \"\";\n\n\t\tint charIndex = 0;\n\t\tfor(int i = 0; i < mask.length(); i++) {\n\t\t\tchar currentChar = mask.charAt(i);\n\t\t\tif(currentChar == charRepresentation) {\n\t\t\t\taux[charIndex] = i;\n\t\t\t\tmaskToRaw[i] = charIndex++;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tString charAsString = Character.toString(currentChar);\n\t\t\t\tif(!charsInMaskAux.contains(charAsString)) {\n\t\t\t\t\tcharsInMaskAux = charsInMaskAux.concat(charAsString);\n\t\t\t\t}\n\t\t\t\tmaskToRaw[i] = -1;\n\t\t\t}\n\t\t}\n\t\tif(charsInMaskAux.indexOf(' ') < 0) {\n\t\t\tcharsInMaskAux = charsInMaskAux + SPACE;\n\t\t}\n\n\t\tchar[] charsInMask = charsInMaskAux.toCharArray();\n\n\t\trawToMask = new int[charIndex];\n\t\tSystem.arraycopy(aux, 0, rawToMask, 0, charIndex);\n\t}\n\n\tprivate void init() {\n\t\taddTextChangedListener(this);\n\t}\n\n\t@Override\n\tpublic void beforeTextChanged(CharSequence s, int start, int count,\n\t\t\tint after) {\n\t\tif(!editingBefore) {\n\t\t\teditingBefore = true;\n\t\t\tif(start > lastValidMaskPosition) {\n\t\t\t\tignore = true;\n\t\t\t}\n\t\t\tint rangeStart = start;\n\t\t\tif(after == 0) {\n\t\t\t\trangeStart = erasingStart(start);\n\t\t\t}\n\t\t\tRange range = calculateRange(rangeStart, start + count);\n\t\t\tif(range.getStart() != -1) {\n\t\t\t\trawText.subtractFromString(range);\n\t\t\t}\n\t\t\tif(count > 0) {\n\t\t\t\tselection = previousValidPosition(start);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate int erasingStart(int start) {\n\t\twhile(start > 0 && maskToRaw[start] == -1) {\n\t\t\tstart--;\n\t\t}\n\t\treturn start;\n\t}\n\n\t@Override\n\tpublic void onTextChanged(CharSequence s, int start, int before, int count) {\n\t\tif(!editingOnChanged && editingBefore) {\n\t\t\teditingOnChanged = true;\n\t\t\tif(ignore) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif(count > 0) {\n\t\t\t\tint startingPosition = maskToRaw[nextValidPosition(start)];\n\t\t\t\tString addedString = s.subSequence(start, start + count).toString();\n\t\t\t\tcount = rawText.addToString(clear(addedString), startingPosition, maxRawLength);\n\t\t\t\tif(initialized) {\n\t\t\t\t\tint currentPosition;\n\t\t\t\t\tif(startingPosition + count < rawToMask.length)\n\t\t\t\t\t\tcurrentPosition = rawToMask[startingPosition + count];\n\t\t\t\t\telse\n\t\t\t\t\t\tcurrentPosition = lastValidMaskPosition + 1;\n\t\t\t\t\tselection = nextValidPosition(currentPosition);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t@Override\n\tpublic void afterTextChanged(Editable s) {\n\t\tif(!editingAfter && editingBefore && editingOnChanged) {\n\t\t\teditingAfter = true;\n            if (hasHint() && (keepHint || rawText.length() == 0)) {\n                setText(makeMaskedTextWithHint());\n\t\t\t} else {\n                setText(makeMaskedText());\n            }\n\n\t\t\tselectionChanged = false;\n\t\t\tsetSelection(selection);\n\n\t\t\teditingBefore = false;\n\t\t\teditingOnChanged = false;\n\t\t\teditingAfter = false;\n\t\t\tignore = false;\n\t\t}\n\t}\n\n\tpublic boolean isKeepHint() {\n\t\treturn keepHint;\n\t}\n\n\tpublic void setKeepHint(boolean keepHint) {\n\t\tthis.keepHint = keepHint;\n\t\tsetText(getRawText());\n\t}\n\n\t@Override\n\tprotected void onSelectionChanged(int selStart, int selEnd) {\n\t\t// On Android 4+ this method is being called more than 1 time if there is a hint in the EditText, what moves the cursor to left\n\t\t// Using the boolean var selectionChanged to limit to one execution\n\n\t\tif(initialized ){\n\t\t\tif(!selectionChanged) {\n                selStart = fixSelection(selStart);\n                selEnd = fixSelection(selEnd);\n\n\t\t\t\t// exactly in this order. If getText.length() == 0 then selStart will be -1\n\t\t\t\tif (selStart > getText().length()) selStart = getText().length();\n\t\t\t\tif (selStart < 0) selStart = 0;\n\n\t\t\t\t// exactly in this order. If getText.length() == 0 then selEnd will be -1\n\t\t\t\tif (selEnd > getText().length()) selEnd = getText().length();\n\t\t\t\tif (selEnd < 0) selEnd = 0;\n\n\t\t\t\tsetSelection(selStart, selEnd);\n\t\t\t\tselectionChanged = true;\n\t\t\t} else{\n\t\t\t    //check to see if the current selection is outside the already entered text\n\t\t\t\tif(selStart > rawText.length() - 1){\n\t\t\t\t\tfinal int start = fixSelection(selStart);\n\t\t\t\t\tfinal int end = fixSelection(selEnd);\n\t\t\t\t\tif (start >= 0 && end < getText().length()){\n\t\t\t\t\t\tsetSelection(start, end);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tsuper.onSelectionChanged(selStart, selEnd);\n\t}\n\n\tprivate int fixSelection(int selection) {\n\t\tif(selection > lastValidPosition()) {\n\t\t\treturn lastValidPosition();\n\t\t} else {\n\t\t\treturn nextValidPosition(selection);\n\t\t}\n\t}\n\n\tprivate int nextValidPosition(int currentPosition) {\n\t\twhile(currentPosition < lastValidMaskPosition && maskToRaw[currentPosition] == -1) {\n\t\t\tcurrentPosition++;\n\t\t}\n\t\tif(currentPosition > lastValidMaskPosition) return lastValidMaskPosition + 1;\n\t\treturn currentPosition;\n\t}\n\n\tprivate int previousValidPosition(int currentPosition) {\n\t\twhile(currentPosition >= 0 && maskToRaw[currentPosition] == -1) {\n\t\t\tcurrentPosition--;\n\t\t\tif(currentPosition < 0) {\n\t\t\t\treturn nextValidPosition(0);\n\t\t\t}\n\t\t}\n\t\treturn currentPosition;\n\t}\n\n\tprivate int lastValidPosition() {\n\t\tif(rawText.length() == maxRawLength) {\n\t\t\treturn rawToMask[rawText.length() - 1] + 1;\n\t\t}\n\t\treturn nextValidPosition(rawToMask[rawText.length()]);\n\t}\n\n\n\tprivate String makeMaskedText() {\n        int maskedTextLength;\n        if (rawText.length() < rawToMask.length) {\n            maskedTextLength = rawToMask[rawText.length()];\n        } else {\n            maskedTextLength = mask.length();\n        }\n\t\tchar[] maskedText = new char[maskedTextLength]; //mask.replace(charRepresentation, ' ').toCharArray();\n        for (int i = 0; i < maskedText.length; i++) {\n            int rawIndex = maskToRaw[i];\n            if (rawIndex == -1) {\n                maskedText[i] = mask.charAt(i);\n            } else {\n                maskedText[i] = rawText.charAt(rawIndex);\n            }\n        }\n\t\treturn new String(maskedText);\n\t}\n\n    private CharSequence makeMaskedTextWithHint() {\n        SpannableStringBuilder ssb = new SpannableStringBuilder();\n        int mtrv;\n        int maskFirstChunkEnd = rawToMask[0];\n        for(int i = 0; i < mask.length(); i++) {\n            mtrv = maskToRaw[i];\n            if (mtrv != -1) {\n                if (mtrv < rawText.length()) {\n                    ssb.append(rawText.charAt(mtrv));\n                } else {\n                    ssb.append(getHint().charAt(maskToRaw[i]));\n                }\n            } else {\n                ssb.append(mask.charAt(i));\n            }\n            if ((keepHint && rawText.length() < rawToMask.length && i >= rawToMask[rawText.length()])\n                    || (!keepHint && i >= maskFirstChunkEnd)) {\n                ssb.setSpan(new ForegroundColorSpan(getCurrentHintTextColor()), i, i + 1, 0);\n            }\n        }\n        return ssb;\n    }\n\n\tprivate Range calculateRange(int start, int end) {\n\t\tRange range = new Range();\n\t\tfor(int i = start; i <= end && i < mask.length(); i++) {\n\t\t\tif(maskToRaw[i] != -1) {\n\t\t\t\tif(range.getStart() == -1) {\n\t\t\t\t\trange.setStart(maskToRaw[i]);\n\t\t\t\t}\n\t\t\t\trange.setEnd(maskToRaw[i]);\n\t\t\t}\n\t\t}\n\t\tif(end == mask.length()) {\n\t\t\trange.setEnd(rawText.length());\n\t\t}\n\t\tif(range.getStart() == range.getEnd() && start < end) {\n\t\t\tint newStart = previousValidPosition(range.getStart() - 1);\n\t\t\tif(newStart < range.getStart()) {\n\t\t\t\trange.setStart(newStart);\n\t\t\t}\n\t\t}\n\t\treturn range;\n\t}\n\n\tprivate String clear(String string) {\n        if (deniedChars != null){\n            for(char c: deniedChars.toCharArray()){\n\t\t\t    string = string.replace(Character.toString(c), \"\");\n            }\n        }\n\n        if (allowedChars != null){\n            StringBuilder builder = new StringBuilder(string.length());\n\n            for(char c: string.toCharArray() ){\n                if (allowedChars.contains(String.valueOf(c) )){\n                    builder.append(c);\n                }\n            }\n\n            string = builder.toString();\n        }\n\n\t\treturn string;\n\t}\n}\n"
  },
  {
    "path": "MaskedEditText/src/main/java/br/com/sapereaude/maskedEditText/Range.java",
    "content": "package br.com.sapereaude.maskedEditText;\n\npublic class Range {\n\tprivate int start;\n\tprivate int end;\n\n\tRange() {\n\t\tstart = -1;\n\t\tend = -1;\n\t}\n\t\n\tpublic int getStart() {\n\t\treturn start;\n\t}\n\t\n\tpublic void setStart(int start) {\n\t\tthis.start = start;\n\t}\n\t\n\tpublic int getEnd() {\n\t\treturn end;\n\t}\n\t\n\tpublic void setEnd(int end) {\n\t\tthis.end = end;\n\t}\n\n\n}\n"
  },
  {
    "path": "MaskedEditText/src/main/java/br/com/sapereaude/maskedEditText/RawText.java",
    "content": "package br.com.sapereaude.maskedEditText;\n\n/**\n * Raw text, another words TextWithout mask characters\n */\npublic class RawText {\n\tprivate String text;\n\t\n\tpublic RawText() {\n\t\ttext = \"\";\n\t}\n\n    /**\n     * text = 012345678, range = 123 =&gt; text = 0456789\n     * @param range given range\n     */\n\tpublic void subtractFromString(Range range) {\n\t\tString firstPart = \"\";\n\t\tString lastPart = \"\";\n\t\t\n\t\tif(range.getStart() > 0 && range.getStart() <= text.length()) {\n\t\t\tfirstPart = text.substring(0, range.getStart());\n\t\t}\n\t\tif(range.getEnd() >= 0 && range.getEnd() < text.length()) {\n\t\t\tlastPart = text.substring(range.getEnd(), text.length());\n\t\t}\n\t\ttext = firstPart.concat(lastPart);\n\t}\n\n\t/**\n\t * \n\t * @param newString New String to be added\n\t * @param start Position to insert newString\n\t * @param maxLength Maximum raw text length\n\t * @return Number of added characters\n\t */\n\tpublic int addToString(String newString, int start, int maxLength) {\n\t\tString firstPart = \"\";\n\t\tString lastPart = \"\";\n\t\t\n\t\tif(newString == null || newString.equals(\"\")) {\n\t\t\treturn 0;\n\t\t}\n\t\telse if(start < 0) {\n\t\t\tthrow new IllegalArgumentException(\"Start position must be non-negative\");\n\t\t}\n\t\telse if(start > text.length()) {\n\t\t\tthrow new IllegalArgumentException(\"Start position must be less than the actual text length\");\n\t\t}\n\t\t\n\t\tint count = newString.length();\n\t\t\n\t\tif(start > 0) {\n\t\t\tfirstPart = text.substring(0, start);\n\t\t}\n\t\tif(start >= 0 && start < text.length()) {\n\t\t\tlastPart = text.substring(start, text.length());\n\t\t}\n\t\tif(text.length() + newString.length() > maxLength) {\n\t\t\tcount = maxLength - text.length();\n\t\t\tnewString = newString.substring(0, count);\n\t\t}\n\t\ttext = firstPart.concat(newString).concat(lastPart);\t\t\n\t\treturn count;\n\t}\n\n\tpublic String getText() {\n\t\treturn text;\n\t}\n\n\tpublic int length() {\n\t\treturn text.length();\n\t}\n\n\tpublic char charAt(int position) {\n\t\treturn text.charAt(position);\n\t}\n}\n"
  },
  {
    "path": "MaskedEditText/src/main/res/values/attrs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\t<declare-styleable name=\"MaskedEditText\">\n\t\t<attr name=\"mask\" format=\"string\" />\n        <attr name=\"allowed_chars\" format=\"string\" />\n        <attr name=\"enable_ime_action\" format=\"boolean\" />\n        <attr name=\"denied_chars\" format=\"string\" />\n\t\t<attr name=\"char_representation\" format=\"string\" />\n\t\t<attr name=\"keep_hint\" format=\"boolean\" />\n\t</declare-styleable>\n</resources>\n"
  },
  {
    "path": "README.md",
    "content": "**Announcement**: [let's travel the world](https://github.com/egslava/edittext-mask/issues/65)! or let's just be guests :)\n\n# MaskedEditText\n[![Download](https://api.bintray.com/packages/egorenkov/maven/edittext-mask/images/download.svg) ](https://bintray.com/egorenkov/maven/edittext-mask/_latestVersion) [![Build Status](https://travis-ci.org/egslava/edittext-mask.svg?branch=master)](https://travis-ci.org/egslava/edittext-mask)\n\n![MaskedEditText - the library for masked input of phone numbers, social security numbers and so on for Android](publish/README.gif)\n\n**Announcement**: [let's travel the world](https://github.com/egslava/edittext-mask/issues/65)! or let's just be guests :)\n\nThis project derives from [toshikurauchi/MaskedEditText](https://github.com/toshikurauchi/MaskedEditText), but it's been adapted for gradle build system and has additional features:\n\n1. filter allowed chars\n2. filter denied chars\n3. user can use chars from mask in his input (in original version of this library user couldn't use digit '7' in the '+7(XXX)XXX-XX-XX' pattern).\n4. You can keep hints even when user started typing.\n\nSo it allows you to use masks for phones, urls, etc.\n\nEnjoy!\n\n<a href='https://play.google.com/store/apps/details?id=ru.egslava.edittextmaskdemo&utm_source=github&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img width=200 alt='Get it on Google Play' src='https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png'/></a>\n\n*********************************\n## en_US\nMaskedEditText is a simple Android EditText with customizable input mask support.\n\nFor instance, you need user specified his phone in format +7(XXX)XXX-XX-XX. You also know user should have the only possibility to write digits but minuses, brackets and \"+7\" should appear automatically.\n\n### Usage\n\nAdd this to your `build.gradle` :\n```groovy\ncompile 'ru.egslava:MaskedEditText:1.0.5'\n```\nOr download project and plug it in as a library.\n\n**Announcement**: [let's travel the world](https://github.com/egslava/edittext-mask/issues/65)! or let's just be guests :)\n\nAdd _xmlns:mask=\"http://schemas.android.com/apk/res-auto\"_ to your layout xml root:\n\n      <br.com.sapereaude.maskedEditText.MaskedEditText\n        android:id=\"@+id/phone_input\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:inputType=\"phone\"\n        android:typeface=\"monospace\"\n        mask:allowed_chars=\"1234567890\"\n        mask:mask=\"+7(###)###-##-##\"\n        android:hint=\"1234567890\"\n        app:keep_hint=\"true\"\n        />    \nWhere _mask_ is the input mask you want and '#' is an editable position (will be replaced by a whitespace on screen).\n\nYou can optionally set the representation character (in case you don't want to use '#'):\n\n    <br.com.sapereaude.maskedEditText.MaskedEditText\n        android:layout_width=\"fill_parent\"\n        android:layout_height=\"wrap_content\"\n        mask:mask=\"ccc.ccc.ccc-cc\"\n        mask:char_representation=\"c\"\n    />\n\n**Announcement**: [let's travel the world](https://github.com/egslava/edittext-mask/issues/65)! or let's just be guests :)\n\nYou can also change the mask and the representation character programatically:\n\n\tMaskedEditText editText = (MaskedEditText) findViewById(R.id.my_edit_text)\n\t// Setting the representation character to '$'\n\teditText.setCharRepresentation('$');\n\t// Logging the representation character\n\tLog.i(\"Representation character\", editText.getCharRepresentation());\n\t// Setting the mask\n\teditText.setMask(\"##/##/####\");\n\t// Logging the mask\n\tLog.i(\"Mask\", editText.getMask());\n\t\nTo enable Enter softkey action (IME action):\n\n\t<br.com.sapereaude.maskedEditText.MaskedEditText\n\t    ...\n\t    mask:enable_ime_action=\"true\"\n\t    ...\n\t/>\n    \n**Announcement**: [let's travel the world](https://github.com/egslava/edittext-mask/issues/65)! or let's just be guests :)\n\nOr programmatically:\n\n\tMaskedEditText editText = (MaskedEditText) findViewById(R.id.my_edit_text)\n\teditText.setImeActionEnabled(true);\n\t\n*************************************************************************************************\n## ru_RU\n\nMaskedEditText - это всего лишь EditText, но с возможностью задавать произвольную маску.\n\nНапример, нужно ввести телефон в формате +7(XXX)XXX-XX-XX. Причём можно ввести только цифры, а скобочки, дефисы и \"+7\" должны подставляться самостоятельно.\n\n### Использование\n\nВписать в `build.gradle`:\n```groovy\ncompile 'ru.egslava:MaskedEditText:1.0.5'\n```\nили скачать проект и подключить как библиотеку.\n\nДобавить _xmlns:mask=\"http://schemas.android.com/apk/res-auto\"_ в корневой элемент файла разметки:\n\n      <br.com.sapereaude.maskedEditText.MaskedEditText\n        android:id=\"@+id/phone_input\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:inputType=\"phone\"\n        android:typeface=\"monospace\"\n        mask:allowed_chars=\"1234567890\"\n        mask:mask=\"+7(###)###-##-##\"\n        android:hint=\"1234567890\"\n        app:keep_hint=\"true\"\n        />\n\n_mask_ задаёт требуемую маску, символ '#' задаёт редактируемую позицию (и будет заменён на пробел на экране).\n\nЕсли использовать '#' нельзя, то можно попробовать использовать другой символ:\n\n    <br.com.sapereaude.maskedEditText.MaskedEditText\n        android:layout_width=\"fill_parent\"\n        android:layout_height=\"wrap_content\"\n        mask:mask=\"ccc.ccc.ccc-cc\"\n        mask:char_representation=\"c\"\n    />\n\nКроме того, всё тоже самое можно сделать и программно:\n\n\tMaskedEditText editText = (MaskedEditText) findViewById(R.id.my_edit_text)\n\t// Setting the representation character to '$'\n\teditText.setCharRepresentation('$');\n\t// Logging the representation character\n\tLog.i(\"Representation character\", editText.getCharRepresentation());\n\t// Setting the mask\n\teditText.setMask(\"##/##/####\");\n\t// Logging the mask\n\tLog.i(\"Mask\", editText.getMask());\n\t\nЧтобы включить обработку нажатия Enter (IME action):\n\n\t<br.com.sapereaude.maskedEditText.MaskedEditText\n\t    ...\n\t    mask:enable_ime_action=\"true\"\n\t    ...\n\t/>\n    \nИли программно:\n\n\tMaskedEditText editText = (MaskedEditText) findViewById(R.id.my_edit_text)\n\teditText.setImeActionEnabled(true);\n"
  },
  {
    "path": "app/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "app/build.gradle",
    "content": "apply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion 25\n    buildToolsVersion '25.0.2'\n\n    defaultConfig {\n        applicationId \"ru.egslava.edittextphonenumber\"\n        minSdkVersion 9\n        targetSdkVersion 25\n        versionCode 1\n        versionName \"1.0.0\"\n        testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner'\n    }\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n}\n\ndependencies {\n    compile fileTree(dir: 'libs', include: ['*.jar'])\n    compile 'com.android.support:appcompat-v7:25.2.0'\n    compile project(':MaskedEditText')\n    androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2', {\n        exclude group: 'com.android.support', module: 'support-annotations'\n    }\n}\n"
  },
  {
    "path": "app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /Users/egslava/apps/sdk/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n"
  },
  {
    "path": "app/src/androidTest/java/ru/egslava/edittextphonenumber/ApplicationTest.java",
    "content": "package ru.egslava.edittextphonenumber;\n\nimport android.app.Application;\nimport android.test.ApplicationTestCase;\n\n/**\n * <a href=\"http://d.android.com/tools/testing/testing_android.html\">Testing Fundamentals</a>\n */\npublic class ApplicationTest extends ApplicationTestCase<Application> {\n    public ApplicationTest() {\n        super(Application.class);\n    }\n}"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"ru.egslava.edittextphonenumber\" >\n\n    <application\n        android:allowBackup=\"true\"\n        android:icon=\"@drawable/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:theme=\"@style/AppTheme\" >\n        <activity\n            android:name=\".MainActivity\"\n            android:label=\"@string/app_name\" >\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "app/src/main/java/ru/egslava/edittextphonenumber/MainActivity.java",
    "content": "package ru.egslava.edittextphonenumber;\n\nimport android.support.v7.app.ActionBarActivity;\nimport android.os.Bundle;\nimport android.text.InputFilter;\nimport android.text.Spanned;\nimport android.view.Menu;\nimport android.view.MenuItem;\nimport android.view.View;\nimport android.widget.EditText;\n\n\nimport br.com.sapereaude.maskedEditText.MaskedEditText;\n\npublic class MainActivity extends ActionBarActivity {\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n        MaskedEditText phone = (MaskedEditText)findViewById(R.id.phone_input);\n    }\n\n    @Override\n    public boolean onCreateOptionsMenu(Menu menu) {\n        getMenuInflater().inflate(R.menu.main, menu);\n        return true;\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(MenuItem item) {\n        int id = item.getItemId();\n        if (id == R.id.action_settings) {\n            return true;\n        }\n        return super.onOptionsItemSelected(item);\n    }\n}\n"
  },
  {
    "path": "app/src/main/res/layout/activity_main.xml",
    "content": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    xmlns:mask=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:paddingLeft=\"@dimen/activity_horizontal_margin\"\n    android:paddingRight=\"@dimen/activity_horizontal_margin\"\n    android:paddingTop=\"@dimen/activity_vertical_margin\"\n    android:paddingBottom=\"@dimen/activity_vertical_margin\"\n    tools:context=\".MainActivity\"\n    android:orientation=\"vertical\">\n\n    <TextView\n        android:id=\"@+id/phone\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/please_provide_phone_number\"\n        />\n\n    <br.com.sapereaude.maskedEditText.MaskedEditText\n        android:id=\"@+id/phone_input\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:inputType=\"phone\"\n        android:typeface=\"monospace\"\n        mask:allowed_chars=\"1234567890\"\n        android:hint=\"9081234567\"\n        android:textColorHint=\"@android:color/darker_gray\"\n        mask:mask=\"+7(###)###-##-##\"\n        mask:keep_hint=\"false\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/menu/main.xml",
    "content": "<menu 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    tools:context=\".MainActivity\" >\n    <item android:id=\"@+id/action_settings\"\n        android:title=\"@string/action_settings\"\n        android:orderInCategory=\"100\"\n        app:showAsAction=\"never\" />\n</menu>\n"
  },
  {
    "path": "app/src/main/res/values/dimens.xml",
    "content": "<resources>\n    <!-- Default screen margins, per the Android Design guidelines. -->\n    <dimen name=\"activity_horizontal_margin\">16dp</dimen>\n    <dimen name=\"activity_vertical_margin\">16dp</dimen>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <string name=\"app_name\">EditTextPhoneNumber</string>\n    <string name=\"hello_world\">Hello world!</string>\n    <string name=\"action_settings\">Settings</string>\n    <string name=\"please_provide_phone_number\">Please, provide your phone number</string>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light.DarkActionBar\">\n        <!-- Customize your theme here. -->\n    </style>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-ru/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <string name=\"app_name\">EditTextPhoneNumber</string>\n    <string name=\"hello_world\">Hello world!</string>\n    <string name=\"action_settings\">Settings</string>\n    <string name=\"please_provide_phone_number\">Пожалуйста, укажите свой номер телефона</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-w820dp/dimens.xml",
    "content": "<resources>\n    <!-- Example customization of dimensions originally defined in res/values/dimens.xml\n         (such as screen margins) for screens with more than 820dp of available width. This\n         would include 7\" and 10\" devices in landscape (~960dp and ~1280dp respectively). -->\n    <dimen name=\"activity_horizontal_margin\">64dp</dimen>\n</resources>\n"
  },
  {
    "path": "build.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n    repositories {\n        jcenter()\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:2.2.3'\n//        classpath 'com.github.dcendents:android-maven-plugin:1.2'\n        // https://mvnrepository.com/artifact/com.github.dcendents/android-maven-gradle-plugin\n        classpath group: 'com.github.dcendents', name: 'android-maven-gradle-plugin', version: '1.5'\n//        classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.0'\n        // NOTE: Do not place your application dependencies here; they belong\n        // in the individual module build.gradle files\n    }\n}\nallprojects {\n    repositories {\n        jcenter()\n    }\n}"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "#Wed Apr 10 15:27:10 PDT 2013\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-3.3-all.zip"
  },
  {
    "path": "gradle.properties",
    "content": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Settings specified in this file will override any Gradle settings\n# configured through the IDE.\n\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\n# Default value: -Xmx10248m -XX:MaxPermSize=256m\n org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8\n\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\norg.gradle.parallel=true\norg.gradle.daemon=true"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn ( ) {\n    echo \"$*\"\n}\n\ndie ( ) {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\nesac\n\n# For Cygwin, ensure paths are in UNIX format before anything is touched.\nif $cygwin ; then\n    [ -n \"$JAVA_HOME\" ] && JAVA_HOME=`cygpath --unix \"$JAVA_HOME\"`\nfi\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >&-\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >&-\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules\nfunction splitJvmOpts() {\n    JVM_OPTS=(\"$@\")\n}\neval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\nJVM_OPTS[${#JVM_OPTS[*]}]=\"-Dorg.gradle.appname=$APP_BASE_NAME\"\n\nexec \"$JAVACMD\" \"${JVM_OPTS[@]}\" -classpath \"$CLASSPATH\" org.gradle.wrapper.GradleWrapperMain \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\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\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=\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:init\r\n@rem Get command-line arguments, handling Windowz variants\r\n\r\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\r\nif \"%@eval[2+2]\" == \"4\" goto 4NT_args\r\n\r\n:win9xME_args\r\n@rem Slurp the command line arguments.\r\nset CMD_LINE_ARGS=\r\nset _SKIP=2\r\n\r\n:win9xME_args_slurp\r\nif \"x%~1\" == \"x\" goto execute\r\n\r\nset CMD_LINE_ARGS=%*\r\ngoto execute\r\n\r\n:4NT_args\r\n@rem Get arguments from the 4NT Shell from JP Software\r\nset CMD_LINE_ARGS=%$\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "settings.gradle",
    "content": "include ':app', ':MaskedEditText'"
  }
]