[
  {
    "path": ".gitignore",
    "content": "syntax: glob\n.crashlytics_data/\n.DS_Store\n.gradle/\n.settings\n.idea\n*.iml\n*.apk\nbin/\nbuild/\ngen/\nlocal.properties\nHomeAssistantTaskerPlugin/release/\n"
  },
  {
    "path": "HomeAssistantTaskerPlugin/build.gradle",
    "content": "apply plugin: 'com.android.application'\napply plugin: 'findbugs'\n\ndependencies {\n    implementation group:'net.jcip', name:'jcip-annotations', version:\"${JCIP_ANNOTATION_VERSION_MATCHER}\"\n    implementation group:'com.android.support', name:'support-annotations', version:\"${ANDROID_SUPPORT_VERSION_MATCHER}\"\n    implementation group:'com.android.support', name:'appcompat-v7', version:\"${ANDROID_SUPPORT_VERSION_MATCHER}\"\n    implementation group:'com.twofortyfouram', name:'android-annotation', version:\"${TWOFORTYFOURAM_ANNOTATION_VERSION_MATCHER}\"\n    implementation group:'com.twofortyfouram', name:'android-assertion', version:\"${TWOFORTYFOURAM_ASSERTION_VERSION_MATCHER}\"\n    implementation group:'com.twofortyfouram', name:'android-plugin-api-for-locale', version:\"${TWOFORTYFOURAM_PLUGIN_API_VERSION_MATCHER}\"\n    implementation group:'com.twofortyfouram', name:'android-plugin-client-sdk-for-locale', version:\"${TWOFORTYFOURAM_PLUGIN_CLIENT_SDK_VERSION_MATCHER}\"\n    implementation group:'com.twofortyfouram', name:'android-spackle', version:\"${TWOFORTYFOURAM_SPACKLE_VERSION_MATCHER}\"\n\n    androidTestImplementation group:'com.twofortyfouram', name:'android-test', version:\"${TWOFORTYFOURAM_TEST_VERSION_MATCHER}\"\n}\n\nandroid {\n    compileSdkVersion Integer.parseInt(ANDROID_COMPILE_SDK_VERSION)\n    buildToolsVersion ANDROID_BUILD_TOOLS_VERSION\n\n    defaultConfig {\n        minSdkVersion Integer.parseInt(ANDROID_MIN_SDK_VERSION)\n        targetSdkVersion Integer.parseInt(ANDROID_TARGET_SDK_VERSION)\n        versionCode Integer.parseInt(ANDROID_VERSION_CODE)\n        versionName ANDROID_VERSION_NAME\n\n        // Keep only the default and English localizations; which reduces app size since\n        // dependencies contain localizations.\n        // The last two are for support of pseudolocales in debug builds.\n        resConfigs 'en', 'en-rUS', 'en_XA', 'ar_XB'\n\n        vectorDrawables.useSupportLibrary = true\n    }\n\n    if (RELEASE_KEYSTORE_PATH && RELEASE_KEYSTORE_PASSWORD && RELEASE_KEY_ALIAS && RELEASE_KEY_ALIAS_PASSWORD) {\n        signingConfigs {\n            release {\n                storeFile file(RELEASE_KEYSTORE_PATH)\n                storePassword RELEASE_KEYSTORE_PASSWORD\n                keyAlias RELEASE_KEY_ALIAS\n                keyPassword RELEASE_KEY_ALIAS_PASSWORD\n            }\n        }\n    }\n\n    buildTypes {\n        debug {\n            testCoverageEnabled Boolean.parseBoolean(IS_COVERAGE_ENABLED)\n            applicationIdSuffix '.debug'\n            pseudoLocalesEnabled true\n            zipAlignEnabled true\n        }\n        release {\n            minifyEnabled true\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-project.txt'\n\n            shrinkResources true\n\n            if (RELEASE_KEYSTORE_PATH && RELEASE_KEYSTORE_PASSWORD && RELEASE_KEY_ALIAS && RELEASE_KEY_ALIAS_PASSWORD) {\n                signingConfig signingConfigs.release\n            }\n        }\n    }\n}\n\ntask findbugs(type: FindBugs) {\n    ignoreFailures = true\n    classes = fileTree('build/intermediates/classes/release/')\n    source = fileTree('src/main/java/')\n    classpath = files()\n    excludeFilter = file(\"$rootProject.projectDir/tools/findbugs/android-filter.xml\")\n    effort = 'max'\n    reportLevel = 'low'\n    reports {\n        // Only one report type can be enabled at a time, apparently.\n        xml.enabled = false\n        html.enabled = true\n        html.destination = \"$project.buildDir/outputs/reports/findbugs/findbugs.html\"\n    }\n}\n"
  },
  {
    "path": "HomeAssistantTaskerPlugin/lint.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<lint>\n    <!-- App indexing for this app makes no sense. -->\n    <issue id=\"GoogleAppIndexingWarning\" severity=\"ignore\"/>\n\n    <!-- https://code.google.com/p/android/issues/detail?id=203407. -->\n    <issue id=\"ResourceCycle\" severity=\"ignore\"/>\n</lint>"
  },
  {
    "path": "HomeAssistantTaskerPlugin/proguard-project.txt",
    "content": "# This improves obfuscation and moves non-public classes to their own namespace.\n-repackageclasses 'com.markadamson.taskerplugin.homeassistant'\n\n# Ensure that stacktraces are reversible.\n-renamesourcefileattribute SourceFile\n-keepattributes SourceFile,LineNumberTable\n"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/androidTest/java/com/markadamson/taskerplugin/homeassistant/bundle/PluginBundleValuesTest.java",
    "content": "/*\n * home-assistant-plugin-for-tasker <https://github.com/MarkAdamson/home-assistant-plugin-for-tasker>\n * Copyright 2019 Mark Adamson\n *\n * Original author:\n * android-toast-setting-plugin-for-locale <https://github.com/twofortyfouram/android-toast-setting-plugin-for-locale>\n * Copyright 2014 two forty four a.m. LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except\n * in compliance with the License. You may obtain a copy of the License at\n *\n *  http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under the\n * License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either\n * express or implied. See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.markadamson.taskerplugin.homeassistant.bundle;\n\nimport android.os.Bundle;\nimport android.test.AndroidTestCase;\nimport android.test.suitebuilder.annotation.SmallTest;\n\nimport com.twofortyfouram.spackle.AppBuildInfo;\n\n/**\n * Tests {@link PluginBundleValues}.\n */\npublic final class PluginBundleValuesTest extends AndroidTestCase {\n\n    @SmallTest\n    public static void testExtraConstants() {\n        /*\n         * NOTE: This test is expected to fail initially when you are adapting this example to your\n         * own plug-in. Once you've settled on constant names for your Intent extras, go ahead and\n         * update this test case.\n         *\n         * The goal of this test case is to prevent accidental renaming of the Intent extras. If the\n         * extra is intentionally changed, then this unit test needs to be intentionally updated.\n         */\n        assertEquals(\n                \"com.markadamson.taskerplugin.homeassistant.extra.STRING_MESSAGE\",\n                PluginBundleValues.BUNDLE_EXTRA_STRING_MESSAGE); //$NON-NLS-1$\n        assertEquals(\n                \"com.markadamson.taskerplugin.homeassistant.extra.INT_VERSION_CODE\",\n                PluginBundleValues.BUNDLE_EXTRA_INT_VERSION_CODE); //$NON-NLS-1$\n    }\n\n    @SmallTest\n    public void testGenerateBundle() {\n        final Bundle bundle = PluginBundleValues.generateBundle(getContext(), \"Foo\"); //$NON-NLS-1$\n        assertNotNull(bundle);\n\n        assertEquals(2, bundle.keySet().size());\n\n        assertEquals(\"Foo\",\n                bundle.getString(PluginBundleValues.BUNDLE_EXTRA_STRING_MESSAGE)); //$NON-NLS-1$\n        assertEquals(AppBuildInfo.getVersionCode(getContext()),\n                bundle.getInt(PluginBundleValues.BUNDLE_EXTRA_INT_VERSION_CODE));\n    }\n\n    @SmallTest\n    public static void testVerifyBundle_correct() {\n        final Bundle bundle = new Bundle();\n        bundle.putString(PluginBundleValues.BUNDLE_EXTRA_STRING_MESSAGE,\n                \"I am a toast message!\"); //$NON-NLS-1$\n        bundle.putInt(PluginBundleValues.BUNDLE_EXTRA_INT_VERSION_CODE, 1);\n        assertTrue(PluginBundleValues.isBundleValid(bundle));\n    }\n\n    @SmallTest\n    public static void testVerifyBundle_null() {\n        assertFalse(PluginBundleValues.isBundleValid(null));\n    }\n\n    @SmallTest\n    public static void testVerifyBundle_missing_extra() {\n        assertFalse(PluginBundleValues.isBundleValid(new Bundle()));\n    }\n\n    @SmallTest\n    public static void testVerifyBundle_extra_items() {\n        final Bundle bundle = new Bundle();\n        bundle.putString(PluginBundleValues.BUNDLE_EXTRA_STRING_MESSAGE,\n                \"I am a toast message!\"); //$NON-NLS-1$\n        bundle.putInt(PluginBundleValues.BUNDLE_EXTRA_INT_VERSION_CODE, 1);\n        bundle.putString(\"test\", \"test\"); //$NON-NLS-1$//$NON-NLS-2$\n        assertFalse(PluginBundleValues.isBundleValid(bundle));\n    }\n\n    @SmallTest\n    public static void testVerifyBundle_null_message() {\n        final Bundle bundle = new Bundle();\n        bundle.putString(PluginBundleValues.BUNDLE_EXTRA_STRING_MESSAGE, null);\n        bundle.putInt(PluginBundleValues.BUNDLE_EXTRA_INT_VERSION_CODE, 1);\n        assertFalse(PluginBundleValues.isBundleValid(bundle));\n    }\n\n    @SmallTest\n    public static void testVerifyBundle_empty_message() {\n        final Bundle bundle = new Bundle();\n        bundle.putString(PluginBundleValues.BUNDLE_EXTRA_STRING_MESSAGE, \"\"); //$NON-NLS-1$\n        bundle.putInt(PluginBundleValues.BUNDLE_EXTRA_INT_VERSION_CODE, 1);\n        assertFalse(PluginBundleValues.isBundleValid(bundle));\n    }\n\n    @SmallTest\n    public static void testVerifyBundle_wrong_type() {\n        {\n            final Bundle bundle = new Bundle();\n            bundle.putInt(PluginBundleValues.BUNDLE_EXTRA_STRING_MESSAGE, 1);\n            bundle.putInt(PluginBundleValues.BUNDLE_EXTRA_INT_VERSION_CODE, 1);\n            assertFalse(PluginBundleValues.isBundleValid(bundle));\n        }\n\n        {\n            final Bundle bundle = new Bundle();\n            bundle.putString(PluginBundleValues.BUNDLE_EXTRA_STRING_MESSAGE,\n                    \"I am a toast message!\"); //$NON-NLS-1$\n            bundle.putString(PluginBundleValues.BUNDLE_EXTRA_INT_VERSION_CODE,\n                    \"test\"); //$NON-NLS-1$\n            assertFalse(PluginBundleValues.isBundleValid(bundle));\n        }\n    }\n}\n"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/androidTest/java/com/markadamson/taskerplugin/homeassistant/receiver/FireReceiverTest.java",
    "content": "/*\n * home-assistant-plugin-for-tasker <https://github.com/MarkAdamson/home-assistant-plugin-for-tasker>\n * Copyright 2019 Mark Adamson\n *\n * Original author:\n * android-toast-setting-plugin-for-locale <https://github.com/twofortyfouram/android-toast-setting-plugin-for-locale>\n * Copyright 2014 two forty four a.m. LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except\n * in compliance with the License. You may obtain a copy of the License at\n *\n *  http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under the\n * License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either\n * express or implied. See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.markadamson.taskerplugin.homeassistant.receiver;\n\nimport com.markadamson.taskerplugin.homeassistant.bundle.PluginBundleValues;\n\nimport android.content.BroadcastReceiver;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.test.AndroidTestCase;\nimport android.test.suitebuilder.annotation.SmallTest;\n\n/**\n * Tests the {@link FireReceiver}.\n */\npublic final class FireReceiverTest extends AndroidTestCase {\n    /*\n     * These test cases perform sanity checks. These tests are not very extensive and additional\n     * testing is required to verify the BroadcastReceiver works correctly. For example, a human\n     * would need to manually verify that a Toast message appears when a correct Intent is sent to\n     * the receiver. Depending on what your setting implements, you may be able to verify more\n     * easily that the setting triggered the desired result via unit tests than this sample setting\n     * can.\n     */\n\n    @SmallTest\n    public void testNullMessage() {\n        final BroadcastReceiver fireReceiver = new FireReceiver();\n\n        final Bundle bundle = PluginBundleValues\n                .generateBundle(getContext(), \"test_message\"); //$NON-NLS-1$\n        bundle.putString(PluginBundleValues.BUNDLE_EXTRA_STRING_MESSAGE, null);\n\n        /*\n         * The receiver shouldn't crash if the EXTRA_BUNDLE is incorrect\n         */\n        fireReceiver.onReceive(getContext(), new Intent(\n                com.twofortyfouram.locale.api.Intent.ACTION_FIRE_SETTING).putExtra(\n                com.twofortyfouram.locale.api.Intent.EXTRA_BUNDLE, bundle));\n    }\n\n    @SmallTest\n    public void testNormal() {\n        final BroadcastReceiver fireReceiver = new FireReceiver();\n\n        final Bundle bundle = PluginBundleValues\n                .generateBundle(getContext(), \"test_message\"); //$NON-NLS-1$\n\n        fireReceiver.onReceive(getContext(), new Intent(\n                com.twofortyfouram.locale.api.Intent.ACTION_FIRE_SETTING).putExtra(\n                com.twofortyfouram.locale.api.Intent.EXTRA_BUNDLE, bundle));\n    }\n}\n"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/androidTest/java/com/markadamson/taskerplugin/homeassistant/setting/toast/test/InstallLocation.java",
    "content": "/*\n * home-assistant-plugin-for-tasker <https://github.com/MarkAdamson/home-assistant-plugin-for-tasker>\n * Copyright 2019 Mark Adamson\n *\n * Original author:\n * android-toast-setting-plugin-for-locale <https://github.com/twofortyfouram/android-toast-setting-plugin-for-locale>\n * Copyright 2014 two forty four a.m. LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except\n * in compliance with the License. You may obtain a copy of the License at\n *\n *  http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under the\n * License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either\n * express or implied. See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.markadamson.taskerplugin.homeassistant.setting.toast.test;\n\nimport com.twofortyfouram.annotation.Slow;\nimport com.twofortyfouram.annotation.Slow.Speed;\nimport com.twofortyfouram.annotation.VisibleForTesting;\nimport com.twofortyfouram.annotation.VisibleForTesting.Visibility;\nimport com.twofortyfouram.assertion.Assertions;\n\nimport net.jcip.annotations.ThreadSafe;\n\nimport org.xmlpull.v1.XmlPullParser;\nimport org.xmlpull.v1.XmlPullParserException;\n\nimport android.content.Context;\nimport android.content.pm.PackageManager.NameNotFoundException;\nimport android.content.res.XmlResourceParser;\nimport android.support.annotation.NonNull;\n\nimport java.io.IOException;\n\n/**\n * Represents the Android Manifest's possible states for install location.\n */\n@ThreadSafe\npublic enum InstallLocation {\n    /**\n     * The application permits installation to either internal or external\n     * storage, with Android automatically deciding.\n     */\n    auto,\n\n    /**\n     * The application can only be installed to internal storage.\n     */\n    internalOnly,\n\n    /**\n     * The application permits installation to either internal or external\n     * storage, with preference for external storage.\n     */\n    preferExternal,\n\n    /**\n     * No install location was specified in the Android Manifest. In terms of\n     * how Android interprets this, it is basically the same as\n     * {@link #internalOnly}.\n     */\n    MISSING,\n\n    /**\n     * An unknown install location, such as a new install location added in\n     * newer versions of Android.\n     */\n    UNKNOWN;\n\n    /**\n     * The Android Manifest int value for auto install location.\n     */\n    /*\n     * Note: This value is a private API in Android and could change without\n     * warning\n     */\n    @VisibleForTesting(Visibility.PRIVATE)\n    /* package */ static final int MANIFEST_INSTALL_LOCATION_AUTO = 0;\n\n    /**\n     * The Android Manifest int value for internal only install location.\n     */\n    /*\n     * Note: This value is a private API in Android and could change without\n     * warning\n     */\n    @VisibleForTesting(Visibility.PRIVATE)\n    /* package */ static final int MANIFEST_INSTALL_LOCATION_INTERNAL_ONLY = 1;\n\n    /**\n     * The Android Manifest int value for internal or external storage, with\n     * preference for external storage.\n     */\n    /*\n     * Note: This value is a private API in Android and could change without\n     * warning\n     */\n    @VisibleForTesting(Visibility.PRIVATE)\n    /* package */ static final int MANIFEST_INSTALL_LOCATION_PREFER_EXTERNAL = 2;\n\n    /**\n     * Takes the integer value of install location from the Android Manifest and\n     * converts it to an enum value.\n     *\n     * @param location one of the Android Manifest install locations.\n     * @return The enum type for the install location.\n     */\n    @NonNull\n    /* package */ static InstallLocation getInstallLocation(final int location) {\n        switch (location) {\n            case MANIFEST_INSTALL_LOCATION_AUTO: {\n                return auto;\n            }\n            case MANIFEST_INSTALL_LOCATION_INTERNAL_ONLY: {\n                return internalOnly;\n            }\n            case MANIFEST_INSTALL_LOCATION_PREFER_EXTERNAL: {\n                return preferExternal;\n            }\n            default: {\n                return UNKNOWN;\n            }\n        }\n    }\n\n    /**\n     * Gets a package's install location, as per the Android Manifest.\n     *\n     * @param context     Application context.\n     * @param packageName Package whose install location is to be checked.\n     * @return the install location.\n     * @throws NameNotFoundException  if {@code packageName} isn't installed.\n     * @throws XmlPullParserException If the target package's manifest couldn't\n     *                                be parsed.\n     * @throws IOException            If an error occurred reading the target package.\n     */\n    @NonNull\n    @Slow(Speed.MILLISECONDS)\n    public static InstallLocation getManifestInstallLocation(@NonNull final Context context,\n            @NonNull final String packageName) throws NameNotFoundException,\n            XmlPullParserException, IOException {\n        Assertions.assertNotNull(context, \"context\"); //$NON-NLS-1$\n        Assertions.assertNotNull(packageName, \"packageName\"); //$NON-NLS-1$\n\n        /*\n         * There isn't a public API to check the installLocation of an APK, so\n         * this is a hacky implementation to read the value directly from the\n         * package's AndroidManifest.\n         */\n        final XmlResourceParser xml = context\n                .createPackageContext(packageName, Context.CONTEXT_RESTRICTED).getAssets()\n                .openXmlResourceParser(\"AndroidManifest.xml\"); //$NON-NLS-1$\n        try {\n            for (int eventType = xml.getEventType(); XmlPullParser.END_DOCUMENT != eventType;\n                    eventType = xml\n                            .nextToken()) {\n                switch (eventType) {\n                    case XmlPullParser.START_TAG: {\n                        if (xml.getName().matches(\"manifest\")) { //$NON-NLS-1$\n                            for (int x = 0; x < xml.getAttributeCount(); x++) {\n                                if (xml.getAttributeName(x)\n                                        .matches(\"installLocation\")) { //$NON-NLS-1$\n                                    return InstallLocation.getInstallLocation(Integer.parseInt(xml\n                                            .getAttributeValue(x)));\n                                }\n                            }\n                        }\n\n                        break;\n                    }\n                }\n            }\n\n            /*\n             * Once this point is reached, it can be assumed the installLocation\n             * didn't exist in the AndroidManifest\n             */\n            return InstallLocation.MISSING;\n        } finally {\n            xml.close();\n        }\n    }\n}\n"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/androidTest/java/com/markadamson/taskerplugin/homeassistant/setting/toast/test/ManifestTest.java",
    "content": "/*\n * home-assistant-plugin-for-tasker <https://github.com/MarkAdamson/home-assistant-plugin-for-tasker>\n * Copyright 2019 Mark Adamson\n *\n * Original author:\n * android-toast-setting-plugin-for-locale <https://github.com/twofortyfouram/android-toast-setting-plugin-for-locale>\n * Copyright 2014 two forty four a.m. LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except\n * in compliance with the License. You may obtain a copy of the License at\n *\n *  http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under the\n * License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either\n * express or implied. See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.markadamson.taskerplugin.homeassistant.setting.toast.test;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.pm.ResolveInfo;\nimport android.support.annotation.NonNull;\nimport android.test.AndroidTestCase;\nimport android.test.suitebuilder.annotation.SmallTest;\n\nimport java.util.LinkedList;\nimport java.util.List;\n\n/**\n * Tests to verify proper entries in the plug-in's Android Manifest.\n */\npublic final class ManifestTest extends AndroidTestCase {\n\n    @SmallTest\n    public void testApplicationEnabled() {\n        assertTrue(getContext().getApplicationInfo().enabled);\n    }\n\n    @SmallTest\n    public void testPluginActivityPresent() {\n        final List<ResolveInfo> activities = getPluginActivities(getContext());\n        assertFalse(activities.isEmpty());\n\n        for (final ResolveInfo x : activities) {\n            assertTrue(x.activityInfo.enabled);\n            assertTrue(x.activityInfo.exported);\n\n            /*\n             * Verify that the plug-in doesn't request permissions not available to the host\n             */\n            assertNull(x.activityInfo.permission);\n\n            /*\n             * Verify that the plug-in has a label attribute in the AndroidManifest\n             */\n            assertFalse(0 == x.activityInfo.labelRes);\n\n            /*\n             * Verify that the plug-in has a icon attribute in the AndroidManifest\n             */\n            assertFalse(0 == x.activityInfo.icon);\n        }\n    }\n\n    @SmallTest\n    public void testPluginReceiver() {\n        final List<ResolveInfo> receivers = getPluginReceivers(getContext());\n\n        assertEquals(1, receivers.size());\n\n        for (final ResolveInfo x : receivers) {\n            assertTrue(x.activityInfo.enabled);\n            assertTrue(x.activityInfo.exported);\n\n            /*\n             * Verify that the plug-in doesn't request permissions not available to the host\n             */\n            assertNull(x.activityInfo.permission);\n        }\n    }\n\n    /**\n     * Verifies the package is configured to be installed to internal memory\n     */\n    @SmallTest\n    public void testManifestInstallLocation() throws Exception {\n        /*\n         * Note that in addition to this test, Locale will also check that a plug-in is actually on\n         * internal memory at runtime. This primarily affects custom ROMs that permit moving apps to\n         * external memory even if the app specifies internalOnly.\n         */\n        assertEquals(InstallLocation.internalOnly, InstallLocation.getManifestInstallLocation(\n                getContext(), getContext().getPackageName()));\n    }\n\n    /**\n     * Gets a list of all Activities in {@code context}'s package that export\n     * {@link com.twofortyfouram.locale.api.Intent#ACTION_EDIT_SETTING}.\n     *\n     * @param context Application context.\n     */\n    private static List<ResolveInfo> getPluginActivities(@NonNull final Context context) {\n\n        final String packageName = context.getPackageName();\n\n        final List<ResolveInfo> result = new LinkedList<ResolveInfo>();\n\n        for (final ResolveInfo x : context.getPackageManager().queryIntentActivities(\n                new Intent(com.twofortyfouram.locale.api.Intent.ACTION_EDIT_SETTING),\n                0)) {\n            if (packageName.equals(x.activityInfo.packageName)) {\n                result.add(x);\n            }\n        }\n\n        return result;\n    }\n\n    /**\n     * Gets a list of all BroadcastReceivers in {@code context}'s package that export\n     * {@link com.twofortyfouram.locale.api.Intent#ACTION_FIRE_SETTING ACTION_FIRE_SETTING}.\n     *\n     * @param context Application context.\n     */\n    private static List<ResolveInfo> getPluginReceivers(@NonNull final Context context) {\n        final String packageName = context.getPackageName();\n\n        final List<ResolveInfo> result = new LinkedList<ResolveInfo>();\n\n        for (final ResolveInfo x : context.getPackageManager().queryBroadcastReceivers(\n                new Intent(com.twofortyfouram.locale.api.Intent.ACTION_FIRE_SETTING),\n                0)) {\n            if (packageName.equals(x.activityInfo.packageName)) {\n                result.add(x);\n            }\n\n        }\n\n        return result;\n    }\n}\n"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/androidTest/java/com/markadamson/taskerplugin/homeassistant/ui/activity/EditActivityTest.java",
    "content": "/*\n * home-assistant-plugin-for-tasker <https://github.com/MarkAdamson/home-assistant-plugin-for-tasker>\n * Copyright 2019 Mark Adamson\n *\n * Original author:\n * android-toast-setting-plugin-for-locale <https://github.com/twofortyfouram/android-toast-setting-plugin-for-locale>\n * Copyright 2014 two forty four a.m. LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except\n * in compliance with the License. You may obtain a copy of the License at\n *\n *  http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under the\n * License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either\n * express or implied. See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.markadamson.taskerplugin.homeassistant.ui.activity;\n\nimport android.app.Activity;\nimport android.app.Instrumentation;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.os.Looper;\nimport android.test.ActivityInstrumentationTestCase2;\nimport android.test.UiThreadTest;\nimport android.test.suitebuilder.annotation.MediumTest;\nimport android.text.TextUtils;\nimport android.widget.EditText;\n\nimport com.markadamson.taskerplugin.homeassistant.bundle.PluginBundleValues;\nimport com.markadamson.taskerplugin.homeassistant.R;\nimport com.twofortyfouram.test.ui.activity.ActivityTestUtil;\n\n/**\n * Tests the {@link EditActivity}.\n */\npublic final class EditActivityTest extends ActivityInstrumentationTestCase2<EditActivity> {\n\n    /**\n     * Context of the target application. This is initialized in {@link #setUp()}.\n     */\n    private Context mTargetContext;\n\n    /**\n     * Instrumentation for the test. This is initialized in {@link #setUp()}.\n     */\n    private Instrumentation mInstrumentation;\n\n    /**\n     * Constructor for the test class; required by Android.\n     */\n    public EditActivityTest() {\n        super(EditActivity.class);\n    }\n\n    /**\n     * Setup that executes before every test case\n     */\n    @Override\n    protected void setUp() throws Exception {\n        super.setUp();\n\n        mInstrumentation = getInstrumentation();\n        mTargetContext = mInstrumentation.getTargetContext();\n\n        /*\n         * Perform test case specific initialization. This is required to be set up here because\n         * setActivityIntent has no effect inside a method annotated with @UiThreadTest\n         */\n        if (\"testNewSettingCancel\".equals(getName())) { //$NON-NLS-1$\n            setActivityIntent(new Intent(com.twofortyfouram.locale.api.Intent.ACTION_EDIT_SETTING)\n                    .putExtra(com.twofortyfouram.locale.api.Intent.EXTRA_STRING_BREADCRUMB,\n                            \"Locale > Edit Situation\")); //$NON-NLS-1$\n        } else if (\"testNewSettingSave\".equals(getName())) { //$NON-NLS-1$\n            setActivityIntent(new Intent(com.twofortyfouram.locale.api.Intent.ACTION_EDIT_SETTING)\n                    .putExtra(com.twofortyfouram.locale.api.Intent.EXTRA_STRING_BREADCRUMB,\n                            \"Locale > Edit Situation\")); //$NON-NLS-1$\n        } else if (\"testOldSetting\".equals(getName())) {  //$NON-NLS-1$\n            final Bundle bundle = PluginBundleValues.generateBundle(mTargetContext,\n                    \"I am a toast message!\"); //$NON-NLS-1$\n\n            setActivityIntent(new Intent(com.twofortyfouram.locale.api.Intent.ACTION_EDIT_SETTING)\n                    .putExtra(com.twofortyfouram.locale.api.Intent.EXTRA_STRING_BREADCRUMB,\n                            \"Locale > Edit Situation\")\n                    .putExtra(com.twofortyfouram.locale.api.Intent.EXTRA_STRING_BLURB,\n                            \"I am a toast message!\")\n                    .putExtra(com.twofortyfouram.locale.api.Intent.EXTRA_BUNDLE,\n                            bundle)); //$NON-NLS-1$\n        } else if (\"testBadBundle\".equals(getName())) {  //$NON-NLS-1$\n            final Bundle bundle = PluginBundleValues.generateBundle(mTargetContext,\n                    \"I am a toast message!\"); //$NON-NLS-1$\n            bundle.putString(PluginBundleValues.BUNDLE_EXTRA_STRING_MESSAGE, null);\n\n            setActivityIntent(new Intent(com.twofortyfouram.locale.api.Intent.ACTION_EDIT_SETTING)\n                    .putExtra(com.twofortyfouram.locale.api.Intent.EXTRA_STRING_BREADCRUMB,\n                            \"Locale > Edit Situation\")\n                    .putExtra(com.twofortyfouram.locale.api.Intent.EXTRA_BUNDLE,\n                            bundle)); //$NON-NLS-1$\n        }\n    }\n\n    @MediumTest\n    @UiThreadTest\n    public void testNewSettingCancel() throws Throwable {\n        final Activity activity = getActivity();\n\n        assertMessageAutoSync(\"\"); //$NON-NLS-1$\n        assertHintAutoSync(mTargetContext.getString(R.string.message_hint));\n\n        activity.finish();\n\n        assertEquals(Activity.RESULT_CANCELED, ActivityTestUtil.getActivityResultCode(activity));\n    }\n\n    @MediumTest\n    @UiThreadTest\n    public void testNewSettingSave() throws Throwable {\n        final Activity activity = getActivity();\n\n        assertMessageAutoSync(\"\"); //$NON-NLS-1$\n        assertHintAutoSync(mTargetContext.getString(R.string.message_hint));\n\n        setMessageAutoSync(getName());\n\n        activity.finish();\n\n        assertActivityResultAutoSync(getName());\n    }\n\n    @MediumTest\n    @UiThreadTest\n    public void testOldSetting() throws Throwable {\n        final Activity activity = getActivity();\n\n        /*\n         * It is necessary to call this manually; the test case won't call\n         * onPostCreate() for us :-(\n         */\n        getActivity().onPostCreateWithPreviousResult(\n                getActivity().getIntent().getBundleExtra(\n                        com.twofortyfouram.locale.api.Intent.EXTRA_BUNDLE),\n                getActivity().getIntent().getStringExtra(\n                        com.twofortyfouram.locale.api.Intent.EXTRA_STRING_BLURB));\n\n        assertMessageAutoSync(\"I am a toast message!\"); //$NON-NLS-1$\n\n        activity.finish();\n\n        assertEquals(Activity.RESULT_CANCELED, ActivityTestUtil.getActivityResultCode\n                (activity));\n    }\n\n    /**\n     * Verifies the Activity properly handles a bundle with a bad value embedded in it.\n     */\n    @MediumTest\n    @UiThreadTest\n    public void testBadBundle() throws Throwable {\n        final Activity activity = getActivity();\n\n        assertMessageAutoSync(\"\"); //$NON-NLS-1$\n        assertHintAutoSync(mTargetContext.getString(R.string.message_hint));\n\n        activity.finish();\n        assertEquals(Activity.RESULT_CANCELED, ActivityTestUtil.getActivityResultCode\n                (activity));\n    }\n\n    /**\n     * Asserts the Activity result contains the expected values for the given display state.\n     *\n     * @param message The message the plug-in is supposed to show.\n     */\n    private void assertActivityResultAutoSync(final String message) throws Throwable {\n        final Activity activity = getActivity();\n\n        final Runnable runnable = new Runnable() {\n            public void run() {\n                activity.finish();\n\n                assertEquals(Activity.RESULT_OK, ActivityTestUtil.getActivityResultCode(activity));\n\n                final Intent result = ActivityTestUtil.getActivityResultData(activity);\n                assertNotNull(result);\n\n                final Bundle extras = result.getExtras();\n                assertNotNull(extras);\n                assertEquals(\n                        String.format(\n                                \"Extras should only contain %s and %s but actually contain %s\",\n                                com.twofortyfouram.locale.api.Intent.EXTRA_BUNDLE,\n                                com.twofortyfouram.locale.api.Intent.EXTRA_STRING_BLURB,\n                                extras.keySet()), 2, extras.keySet() //$NON-NLS-1$\n                                .size());\n\n                assertFalse(TextUtils.isEmpty(extras\n                        .getString(com.twofortyfouram.locale.api.Intent.EXTRA_STRING_BLURB)));\n\n                final Bundle pluginBundle = extras\n                        .getBundle(com.twofortyfouram.locale.api.Intent.EXTRA_BUNDLE);\n                assertNotNull(pluginBundle);\n\n                assertTrue(PluginBundleValues.isBundleValid(pluginBundle));\n                assertEquals(message,\n                        pluginBundle.getString(PluginBundleValues.BUNDLE_EXTRA_STRING_MESSAGE));\n            }\n        };\n\n        autoSyncRunnable(runnable);\n    }\n\n    /**\n     * Asserts provided message is what the UI shows.\n     *\n     * @param message Message to assert equals the EditText.\n     */\n    private void assertMessageAutoSync(final String message) throws Throwable {\n        final Runnable runnable = new Runnable() {\n            private final Activity mActivity = getActivity();\n\n            public void run() {\n                assertEquals(message, ((EditText) mActivity.findViewById(android.R.id.text1))\n                        .getText().toString());\n            }\n        };\n\n        autoSyncRunnable(runnable);\n    }\n\n    /**\n     * Asserts provided hint is what the UI shows.\n     *\n     * @param hint Hint to assert equals the EditText.\n     */\n    private void assertHintAutoSync(final String hint) throws Throwable {\n        final Runnable runnable = new Runnable() {\n            private final Activity mActivity = getActivity();\n\n            public void run() {\n                assertEquals(hint, ((EditText) mActivity.findViewById(android.R.id.text1))\n                        .getHint());\n            }\n        };\n\n        autoSyncRunnable(runnable);\n    }\n\n    /**\n     * Sets the message.\n     *\n     * @param message The message to set.\n     */\n    private void setMessageAutoSync(final String message) throws Throwable {\n        final Runnable runnable = new Runnable() {\n            private final Activity mActivity = getActivity();\n\n            public void run() {\n                final EditText editText = (EditText) mActivity.findViewById(android.R.id.text1);\n\n                editText.setText(message);\n            }\n        };\n\n        autoSyncRunnable(runnable);\n    }\n\n    /**\n     * Executes a runnable on the main thread. This method works even if the current thread is\n     * already the main thread.\n     *\n     * @param runnable to execute.\n     */\n    protected final void autoSyncRunnable(final Runnable runnable) {\n        //noinspection ObjectEquality\n        if (Looper.getMainLooper() == Looper.myLooper()) {\n            runnable.run();\n        } else {\n            getInstrumentation().runOnMainSync(runnable);\n            getInstrumentation().waitForIdleSync();\n        }\n    }\n}\n"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/debug/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n          xmlns:tools=\"http://schemas.android.com/tools\"\n          package=\"com.markadamson.taskerplugin.homeassistant\">\n\n    <!-- For code coverage -->\n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>\n\n    <application/>\n</manifest>\n"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Because plug-ins interact with the host in the background, plug-ins MUST be installed to\n     internal memory. This is consistent with Google's app install location guidelines:\n     <http://developer.android.com/guide/appendix/install-location.html#Should>.\n-->\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n          xmlns:tools=\"http://schemas.android.com/tools\"\n          package=\"com.markadamson.taskerplugin.homeassistant\"\n          android:installLocation=\"internalOnly\">\n\n    <!-- This allows the plug-in to appear on non-touchscreen devices like Google TV.\n    Be sure to test that the app works without a touchscreen. -->\n    <uses-feature\n            android:name=\"android.hardware.touchscreen\"\n            android:required=\"false\"/>\n\n    <uses-permission android:name=\"android.permission.INTERNET\"/>\n    <uses-permission android:name=\"android.permission.WAKE_LOCK\" />\n\n    <!-- Although many of these default to true, some users with modified ROMs have\n         trouble seeing apps unless these attributes are explicitly set. -->\n    <supports-screens\n            android:anyDensity=\"true\"\n            android:largeScreens=\"true\"\n            android:normalScreens=\"true\"\n            android:smallScreens=\"true\"\n            android:xlargeScreens=\"true\" tools:ignore=\"UnusedAttribute\"/>\n\n    <!-- When hardware acceleration is enabled, the RAM memory usage of the app is\n         significantly increased as soon as any UI is displayed. To reduce the memory\n         usage of a plug-in which is primarily a background app, either (1) hardware\n         acceleration should be disabled or (2) the plug-in's BroadcastReceiver (and any\n         applicable Service) should be put into a different process. This example plug-in\n         creates two processes. -->\n    <application\n            android:name=\"com.markadamson.taskerplugin.homeassistant.PluginApplication\"\n            android:allowBackup=\"true\"\n            android:fullBackupContent=\"true\"\n            android:hardwareAccelerated=\"true\"\n            android:icon=\"@mipmap/ic_launcher\"\n            android:label=\"@string/app_name\"\n            tools:ignore=\"UnusedAttribute\"\n            android:usesCleartextTraffic=\"true\">\n\n        <activity\n            android:name=\"com.markadamson.taskerplugin.homeassistant.ui.activity.EditActivity\"\n            android:configChanges=\"orientation|screenSize\"\n            android:exported=\"false\"\n            android:label=\"@string/call_service\"\n            android:theme=\"@style/Theme.AppCompat\"\n            android:uiOptions=\"splitActionBarWhenNarrow\"\n            android:windowSoftInputMode=\"adjustResize\"\n            tools:ignore=\"UnusedAttribute\">\n        </activity>\n\n        <activity\n            android:name=\"com.markadamson.taskerplugin.homeassistant.ui.activity.EditGetStateActivity\"\n            android:configChanges=\"orientation|screenSize\"\n            android:exported=\"false\"\n            android:label=\"@string/get_state\"\n            android:theme=\"@style/Theme.AppCompat\"\n            android:uiOptions=\"splitActionBarWhenNarrow\"\n            android:windowSoftInputMode=\"adjustResize\"\n            tools:ignore=\"UnusedAttribute\">\n        </activity>\n\n        <activity\n            android:name=\"com.markadamson.taskerplugin.homeassistant.ui.activity.EditRenderTemplateActivity\"\n            android:configChanges=\"orientation|screenSize\"\n            android:exported=\"false\"\n            android:label=\"@string/render_template\"\n            android:theme=\"@style/Theme.AppCompat\"\n            android:uiOptions=\"splitActionBarWhenNarrow\"\n            android:windowSoftInputMode=\"adjustResize\"\n            tools:ignore=\"UnusedAttribute\">\n        </activity>\n\n        <activity\n            android:name=\"com.markadamson.taskerplugin.homeassistant.ui.activity.EditServerActivity\"\n            android:exported=\"false\"\n            android:label=\"@string/edit_server\"\n            android:theme=\"@style/Theme.AppCompat\"\n            android:uiOptions=\"splitActionBarWhenNarrow\"\n            android:windowSoftInputMode=\"adjustResize\"\n            tools:ignore=\"UnusedAttribute\">\n        </activity>\n        <!--\n             This is the \"edit\" Activity. Note that the host will reject plug-in\n             Activities for the following reasons:\n                 - Missing \"android:label=[...]\"\n                 - Missing \"android:icon=[...]\"\n                 - The Activity isn't exported (e.g. android:exported=\"false\")\n                 - The Activity isn't enabled (e.g. android:enabled=\"false\")\n                 - The Activity requires permissions not available to the host\n        -->\n        <activity-alias\n            android:name=\"com.markadamson.taskerplugin.homeassistant.ui.activity.PluginActivity\"\n            android:exported=\"true\"\n            android:icon=\"@drawable/ic_plugin\"\n            android:label=\"@string/call_service\"\n            android:targetActivity=\"com.markadamson.taskerplugin.homeassistant.ui.activity.EditActivity\"\n            tools:ignore=\"ExportedActivity\">\n            <!-- this Intent filter allows the plug-in to be discovered by the host. -->\n            <intent-filter>\n                <action android:name=\"com.twofortyfouram.locale.intent.action.EDIT_SETTING\"/>\n            </intent-filter>\n        </activity-alias>\n        <activity-alias\n            android:name=\"com.markadamson.taskerplugin.homeassistant.ui.activity.GetStatePluginActivity\"\n            android:exported=\"true\"\n            android:icon=\"@drawable/ic_plugin\"\n            android:label=\"@string/get_state\"\n            android:targetActivity=\"com.markadamson.taskerplugin.homeassistant.ui.activity.EditGetStateActivity\"\n            tools:ignore=\"ExportedActivity\">\n            <!-- this Intent filter allows the plug-in to be discovered by the host. -->\n            <intent-filter>\n                <action android:name=\"com.twofortyfouram.locale.intent.action.EDIT_SETTING\"/>\n            </intent-filter>\n        </activity-alias>\n        <activity-alias\n            android:name=\"com.markadamson.taskerplugin.homeassistant.ui.activity.RenderTemplatePluginActivity\"\n            android:exported=\"true\"\n            android:icon=\"@drawable/ic_plugin\"\n            android:label=\"@string/render_template\"\n            android:targetActivity=\"com.markadamson.taskerplugin.homeassistant.ui.activity.EditRenderTemplateActivity\"\n            tools:ignore=\"ExportedActivity\">\n            <!-- this Intent filter allows the plug-in to be discovered by the host. -->\n            <intent-filter>\n                <action android:name=\"com.twofortyfouram.locale.intent.action.EDIT_SETTING\"/>\n            </intent-filter>\n        </activity-alias>\n\n        <!--\n             If this plug-in does not stand alone (e.g. it is only a plug-in for Locale and does not\n             have a Launcher Activity), including this in the manifest will help when users try to\n             open the app directly from the app store.\n        -->\n        <activity\n                android:name=\"com.twofortyfouram.locale.sdk.client.ui.activity.InfoActivity\"\n                android:exported=\"true\"\n                tools:ignore=\"ExportedActivity\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\"/>\n\n                <category android:name=\"android.intent.category.INFO\"/>\n            </intent-filter>\n        </activity>\n\n        <!--\n             This is the \"fire\" BroadcastReceiver. Note that the host will reject plug-in\n             BroadcastReceivers for the following reasons:\n                - The BroadcastReceiver isn't exported (e.g. android:exported=\"false\")\n                - The BroadcastReceiver isn't enabled (e.g. android:enabled=\"false\")\n                - The BroadcastReceiver requires permissions not available to the host\n                - There are multiple BroadcastReceivers for com.twofortyfouram.locale.intent.action.FIRE_SETTING\n        -->\n        <receiver\n                android:name=\"com.markadamson.taskerplugin.homeassistant.receiver.FireReceiver\"\n                android:exported=\"true\"\n                android:process=\":background\"\n                tools:ignore=\"ExportedReceiver\">\n\n            <!-- this Intent filter allows the plug-in to discovered by the host. -->\n            <intent-filter>\n                <action android:name=\"com.twofortyfouram.locale.intent.action.FIRE_SETTING\"/>\n            </intent-filter>\n        </receiver>\n\n        <service\n            android:name=\".service.ActionService\"\n            android:permission=\"android.permission.BIND_JOB_SERVICE\"\n            android:exported=\"true\"/>\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/main/java/com/markadamson/locale/sdk/client/internal/PluginActivityDelegate.java",
    "content": "package com.markadamson.locale.sdk.client.internal;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\n\nimport com.markadamson.taskerplugin.homeassistant.TaskerPlugin;\nimport com.twofortyfouram.assertion.BundleAssertions;\nimport com.markadamson.locale.sdk.client.ui.activity.IPluginActivity;\nimport com.twofortyfouram.log.Lumberjack;\nimport com.twofortyfouram.spackle.bundle.BundleComparer;\nimport com.twofortyfouram.spackle.bundle.BundleScrubber;\n\nimport net.jcip.annotations.Immutable;\n\nimport static com.twofortyfouram.assertion.Assertions.assertNotNull;\n\n/**\n * Activities that implement the {@link IPluginActivity} interface can delegate much of their\n * responsibility to this class.\n *\n * @param <T> Plug-in activity.\n */\n/*\n * This class is intended to make the implementation of various plug-in Activities DRY.\n *\n * This class has no state, so therefore is immutable.\n */\n@Immutable\npublic final class PluginActivityDelegate<T extends Activity & IPluginActivity> {\n    /**\n     * @param intent Intent to check.\n     * @return True if intent is a Locale plug-in edit Intent.\n     */\n    public static boolean isLocalePluginIntent(@NonNull final Intent intent) {\n        assertNotNull(intent, \"intent\"); //$NON-NLS-1$\n\n        final String action = intent.getAction();\n\n        return com.twofortyfouram.locale.api.Intent.ACTION_EDIT_CONDITION.equals(action)\n                || com.twofortyfouram.locale.api.Intent.ACTION_EDIT_SETTING.equals(action);\n    }\n\n    public void onCreate(@NonNull final T activity, @Nullable final Bundle savedInstanceState) {\n        assertNotNull(activity, \"activity\"); //$NON-NLS-1$\n\n        final Intent intent = activity.getIntent();\n\n        if (isLocalePluginIntent(intent)) {\n            if (BundleScrubber.scrub(intent)) {\n                return;\n            }\n\n            final Bundle previousBundle = activity.getPreviousBundle();\n            if (BundleScrubber.scrub(previousBundle)) {\n                return;\n            }\n\n            Lumberjack\n                    .v(\"Creating Activity with Intent=%s, savedInstanceState=%s, EXTRA_BUNDLE=%s\",\n                            intent, savedInstanceState, previousBundle); //$NON-NLS-1$\n        }\n    }\n\n    public void onPostCreate(@NonNull final T activity, @Nullable final Bundle savedInstanceState) {\n        assertNotNull(activity, \"activity\"); //$NON-NLS-1$\n\n        if (PluginActivityDelegate.isLocalePluginIntent(activity.getIntent())) {\n            if (null == savedInstanceState) {\n                final Bundle previousBundle = activity.getPreviousBundle();\n                final String previousBlurb = activity.getPreviousBlurb();\n                if (null != previousBundle && null != previousBlurb) {\n                    activity.onPostCreateWithPreviousResult(previousBundle, previousBlurb);\n                }\n            }\n        }\n    }\n\n    public void finish(@NonNull final T activity, final boolean isCancelled) {\n        if (PluginActivityDelegate.isLocalePluginIntent(activity.getIntent())) {\n            if (!isCancelled) {\n                final Bundle resultBundle = activity.getResultBundle();\n\n                if (null != resultBundle) {\n                    BundleAssertions.assertSerializable(resultBundle);\n\n                    final String blurb = activity.getResultBlurb(resultBundle);\n                    assertNotNull(blurb, \"blurb\"); //$NON-NLS-1$\n\n                    if (!BundleComparer.areBundlesEqual(resultBundle, activity.getPreviousBundle())\n                            || !blurb.equals(activity.getPreviousBlurb())) {\n                        final Intent resultIntent = new Intent();\n                        resultIntent.putExtra(com.twofortyfouram.locale.api.Intent.EXTRA_BUNDLE,\n                                resultBundle);\n                        resultIntent.putExtra(\n                                com.twofortyfouram.locale.api.Intent.EXTRA_STRING_BLURB,\n                                blurb);\n                        TaskerPlugin.addRelevantVariableList(resultIntent,\n                                activity.getRelevantVariableList());\n                        TaskerPlugin.Setting.requestTimeoutMS(resultIntent,\n                                activity.requestedTimeoutMS());\n\n                        activity.setResult(Activity.RESULT_OK, resultIntent);\n                    }\n                }\n            }\n        }\n    }\n\n    @Nullable\n    public final String getPreviousBlurb(@NonNull final T activity) {\n        final String blurb = activity.getIntent().getStringExtra(\n                com.twofortyfouram.locale.api.Intent.EXTRA_STRING_BLURB);\n\n        return blurb;\n    }\n\n    @Nullable\n    public Bundle getPreviousBundle(@NonNull final T activity) {\n        assertNotNull(activity, \"activity\"); //$NON-NLS-1$\n\n        final Bundle bundle = activity.getIntent().getBundleExtra(\n                com.twofortyfouram.locale.api.Intent.EXTRA_BUNDLE);\n\n        if (null != bundle) {\n            if (activity.isBundleValid(bundle)) {\n                return bundle;\n            }\n        }\n\n        return null;\n    }\n}"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/main/java/com/markadamson/locale/sdk/client/ui/activity/AbstractAppCompatPluginActivity.java",
    "content": "/*\n * android-plugin-client-sdk-for-locale https://github.com/twofortyfouram/android-plugin-client-sdk-for-locale\n * Copyright 2014 two forty four a.m. LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except\n * in compliance with the License. You may obtain a copy of the License at\n *\n *  http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under the\n * License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either\n * express or implied. See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.markadamson.locale.sdk.client.ui.activity;\n\nimport android.os.Bundle;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\nimport android.support.v7.app.AppCompatActivity;\n\nimport com.markadamson.locale.sdk.client.internal.PluginActivityDelegate;\n\n/**\n * <p>NOTE: This class is for compatibility with Material Design via the appcompat-v7 library.  To use this\n * class, appcompat-v7 must be on the application's build path.  Typically, this would involve adding\n * appcompat-v7 to the dependencies section of the application's build.gradle script.  For example,\n * the dependency might look something like this\n * {@code compile group:'com.android.support', name:'appcompat-v7', version:'[21,)'}</p>\n * <p>\n * Implements the basic behaviors of a \"Edit\" activity for a\n * plug-in, handling the Intent protocol for storing and retrieving the plug-in's data.\n * Recall that a plug-in Activity more or less saves a Bundle and a String blurb via the Intent\n * extras {@link com.twofortyfouram.locale.api.Intent#EXTRA_BUNDLE EXTRA_BUNDLE} and {@link\n * com.twofortyfouram.locale.api.Intent#EXTRA_STRING_BLURB EXTRA_STRING_BLURB}.\n * Those extras represent the configured plug-in, so this Activity helps plug-ins store and\n * retrieve\n * those\n * extras while abstracting the actual Intent protocol.\n * </p>\n * <p>\n * The Activity can be started in one of two states:\n * <ul>\n * <li>New plug-in instance: The Activity's Intent will not contain\n * {@link com.twofortyfouram.locale.api.Intent#EXTRA_BUNDLE EXTRA_BUNDLE}.</li>\n * <li>Old plug-in instance: The Activity's Intent will contain\n * {@link com.twofortyfouram.locale.api.Intent#EXTRA_BUNDLE EXTRA_BUNDLE} and {@link\n * com.twofortyfouram.locale.api.Intent#EXTRA_STRING_BLURB EXTRA_STRING_BLURB} from a previously\n * saved plug-in instance that the user is editing. The previously saved Bundle\n * and blurb can be retrieved at any time via {@link #getPreviousBundle()} and\n * {@link #getPreviousBlurb()}. These will also be delivered via\n * {@link #onPostCreateWithPreviousResult(android.os.Bundle, String)} during the\n * Activity's {@link #onPostCreate(android.os.Bundle)} phase when the Activity is first\n * created.</li>\n * </ul>\n * <p>During\n * the Activity's {@link #finish()} lifecycle callback, this class will call {@link\n * #getResultBundle()} and {@link #getResultBlurb(android.os.Bundle)}, which should return the\n * Bundle and blurb data the Activity would like to save back to the host.\n * </p>\n * <p>\n * Note that all of these behaviors only apply if the Intent\n * starting the Activity is one of the plug-in \"edit\" Intent actions.\n * </p>\n *\n * @see com.twofortyfouram.locale.api.Intent#ACTION_EDIT_CONDITION ACTION_EDIT_CONDITION\n * @see com.twofortyfouram.locale.api.Intent#ACTION_EDIT_SETTING ACTION_EDIT_SETTING\n */\npublic abstract class AbstractAppCompatPluginActivity extends AppCompatActivity implements IPluginActivity {\n\n    /**\n     * Flag boolean that can be set prior to calling {@link #finish()} to control whether the\n     * Activity\n     * attempts to save a result back to the host.  Typically this is only set to true after an\n     * explicit user interaction to abort editing the plug-in, such as tapping a \"cancel\" button.\n     */\n    /*\n     * There is no need to save/restore this field's state.\n     */\n    protected boolean mIsCancelled = false;\n\n    @NonNull\n    private final PluginActivityDelegate<AbstractAppCompatPluginActivity> mPluginActivityDelegate = new PluginActivityDelegate<>();\n\n    @Override\n    protected void onCreate(@Nullable final Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        mPluginActivityDelegate.onCreate(this, savedInstanceState);\n    }\n\n    @Override\n    public void onPostCreate(@Nullable final Bundle savedInstanceState) {\n        super.onPostCreate(savedInstanceState);\n\n        mPluginActivityDelegate.onPostCreate(this, savedInstanceState);\n    }\n\n    @Override\n    public void finish() {\n        mPluginActivityDelegate.finish(this, mIsCancelled);\n\n        /*\n         * Super call must come after the Activity result is set. If it comes\n         * first, then the Activity result will be lost.\n         */\n        super.finish();\n    }\n\n    /**\n     * @return The {@link com.twofortyfouram.locale.api.Intent#EXTRA_BUNDLE EXTRA_BUNDLE} that was\n     * previously saved to the host and subsequently passed back to this Activity for further\n     * editing.  Internally, this method relies on {@link #isBundleValid(android.os.Bundle)}.  If\n     * the bundle exists but is not valid, this method will return null.\n     */\n    @Nullable\n    public final Bundle getPreviousBundle() {\n        return mPluginActivityDelegate.getPreviousBundle(this);\n    }\n\n    /**\n     * @return The {@link com.twofortyfouram.locale.api.Intent#EXTRA_STRING_BLURB\n     * EXTRA_STRING_BLURB} that was\n     * previously saved to the host and subsequently passed back to this Activity for further\n     * editing.\n     */\n    @Nullable\n    public final String getPreviousBlurb() {\n        return mPluginActivityDelegate.getPreviousBlurb(this);\n    }\n}"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/main/java/com/markadamson/locale/sdk/client/ui/activity/IPluginActivity.java",
    "content": "package com.markadamson.locale.sdk.client.ui.activity;\n\nimport android.os.Bundle;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\n\n/**\n * Common interface for plug-in Activities.\n */\npublic interface IPluginActivity {\n    /**\n     * @return The {@link com.twofortyfouram.locale.api.Intent#EXTRA_BUNDLE EXTRA_BUNDLE} that was\n     * previously saved to the host and subsequently passed back to this Activity for further\n     * editing.  Internally, this method relies on {@link #isBundleValid(android.os.Bundle)}.  If\n     * the bundle exists but is not valid, this method will return null.\n     */\n    @Nullable\n    Bundle getPreviousBundle();\n\n    /**\n     * @return The {@link com.twofortyfouram.locale.api.Intent#EXTRA_STRING_BLURB\n     * EXTRA_STRING_BLURB} that was\n     * previously saved to the host and subsequently passed back to this Activity for further\n     * editing.\n     */\n    @Nullable\n    String getPreviousBlurb();\n\n    /**\n     * <p>Validates the Bundle, to ensure that a malicious application isn't attempting to pass\n     * an invalid Bundle.</p>\n     *\n     * @param bundle The plug-in's Bundle previously returned by the edit\n     *               Activity.  {@code bundle} should not be mutated by this method.\n     * @return true if {@code bundle} is valid for the plug-in.\n     */\n    boolean isBundleValid(@NonNull final Bundle bundle);\n\n    /**\n     * Plug-in Activity lifecycle callback to allow the Activity to restore\n     * state for editing a previously saved plug-in instance. This callback will\n     * occur during the onPostCreate() phase of the Activity lifecycle.\n     * <p>{@code bundle} will have been\n     * validated by {@link #isBundleValid(android.os.Bundle)} prior to this\n     * method being called.  If {@link #isBundleValid(android.os.Bundle)} returned false, then this\n     * method will not be called.  This helps ensure that plug-in Activity subclasses only have to\n     * worry about bundle validation once, in the {@link #isBundleValid(android.os.Bundle)}\n     * method.</p>\n     * <p>Note this callback only occurs the first time the Activity is created, so it will not be\n     * called\n     * when the Activity is recreated (e.g. {@code savedInstanceState != null}) such as after a\n     * configuration change like a screen rotation.</p>\n     *\n     * @param previousBundle Previous bundle that the Activity saved.\n     * @param previousBlurb  Previous blurb that the Activity saved\n     */\n    void onPostCreateWithPreviousResult(\n            @NonNull final Bundle previousBundle, @NonNull final String previousBlurb);\n\n    /**\n     * @return Bundle for the plug-in or {@code null} if a valid Bundle cannot\n     * be generated.\n     */\n    @Nullable\n    Bundle getResultBundle();\n\n    /**\n     * @param bundle Valid bundle for the component.\n     * @return Blurb for {@code bundle}.\n     */\n    @NonNull\n    String getResultBlurb(@NonNull final Bundle bundle);\n\n    /**\n     * @return Relevant variables.\n     */\n    @NonNull\n    String[] getRelevantVariableList();\n\n    int requestedTimeoutMS();\n\n}"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/main/java/com/markadamson/taskerplugin/homeassistant/Constants.java",
    "content": "package com.markadamson.taskerplugin.homeassistant;\n\nimport android.support.annotation.NonNull;\n\npublic class Constants {\n    @NonNull\n    public static final String BUNDLE_EXTRA_BUNDLE_TYPE\n            = \"com.markadamson.taskerplugin.homeassistant.extra.BUNDLE_TYPE\"; //$NON-NLS-1$\n\n    public static final int BUNDLE_CALL_SERVICE = 0;\n    public static final int BUNDLE_GET_STATE = 1;\n    public static final int BUNDLE_RENDER_TEMPLATE = 2;\n}\n"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/main/java/com/markadamson/taskerplugin/homeassistant/PluginApplication.java",
    "content": "/*\n * home-assistant-plugin-for-tasker <https://github.com/MarkAdamson/home-assistant-plugin-for-tasker>\n * Copyright 2019 Mark Adamson\n *\n * Original author:\n * android-toast-setting-plugin-for-locale <https://github.com/twofortyfouram/android-toast-setting-plugin-for-locale>\n * Copyright 2014 two forty four a.m. LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except\n * in compliance with the License. You may obtain a copy of the License at\n *\n *  http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under the\n * License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either\n * express or implied. See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.markadamson.taskerplugin.homeassistant;\n\nimport android.app.Application;\n\nimport com.twofortyfouram.log.Lumberjack;\n\n/**\n * Implements an application object for the plug-in.\n */\n/*\n * This application is non-essential for the plug-in's operation; it simply enables debugging\n * options globally for the app.\n */\npublic final class PluginApplication extends Application {\n    @Override\n    public void onCreate() {\n        super.onCreate();\n        Lumberjack.init(getApplicationContext());\n    }\n}\n"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/main/java/com/markadamson/taskerplugin/homeassistant/TaskerPlugin.java",
    "content": "//package com.yourcompany.yourcondition;\n//package com.yourcompany.yoursetting;\npackage com.markadamson.taskerplugin.homeassistant;\n\n// Constants and functions for Tasker *extensions* to the plugin protocol\n// See Also: http://tasker.dinglisch.net/plugins.html\n\n// Release Notes\n\n// v1.1 20140202\n// added function variableNameValid()\n// fixed some javadoc entries (thanks to David Stone)\n\n// v1.2 20140211\n// added ACTION_EDIT_EVENT\n\n// v1.3 20140227\n// added REQUESTED_TIMEOUT_MS_NONE, REQUESTED_TIMEOUT_MS_MAX and REQUESTED_TIMEOUT_MS_NEVER\n// requestTimeoutMS(): added range check\n\n// v1.4 20140516\n// support for data pass through in REQUEST_QUERY intent\n// some javadoc entries fixed (thanks again David :-))\n\n// v1.5 20141120\n// added RESULT_CODE_FAILED_PLUGIN_FIRST\n// added Setting.VARNAME_ERROR_MESSAGE\n\n// v1.6 20150213\n// added Setting.getHintTimeoutMS()\n// added Host.addHintTimeoutMS()\n\n// v1.7 20160619\n// null check for getCallingActivity() in hostSupportsOnFireVariableReplacement( Activity editActivity )\n\n// v1.8 20161002\n// added hostSupportsKeyEncoding(), setKeyEncoding() and Host.getKeysWithEncoding()\n\nimport java.net.URISyntaxException;\nimport java.security.SecureRandom;\nimport java.util.regex.Pattern;\n\nimport android.app.Activity;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.pm.PackageInfo;\nimport android.content.pm.PackageManager;\nimport android.net.Uri;\nimport android.os.Bundle;\nimport android.os.ResultReceiver;\nimport android.util.Log;\n\npublic class TaskerPlugin {\n\n    private final static String \tTAG = \"TaskerPlugin\";\n\n    private final static String \tBASE_KEY = \"net.dinglisch.android.tasker\";\n\n    private final static String \tEXTRAS_PREFIX = BASE_KEY + \".extras.\";\n\n    private final static int\t\tFIRST_ON_FIRE_VARIABLES_TASKER_VERSION = 80;\n\n    public final static String\t\tVARIABLE_PREFIX = \"%\";\n\n    // when generating non-repeating integers, look this far back for repeats\n    // see getPositiveNonRepeatingRandomInteger()\n    private final static int\t\tRANDOM_HISTORY_SIZE = 100;\n\n    /**\n     * \tAction that the EditActivity for an event plugin should be launched by\n     */\n    public final static String \t\tACTION_EDIT_EVENT = BASE_KEY + \".ACTION_EDIT_EVENT\";\n\n    private final static String\t\tVARIABLE_NAME_START_EXPRESSION =  \"[\\\\w&&[^_]]\";\n    private final static String\t\tVARIABLE_NAME_MID_EXPRESSION =  \"[\\\\w0-9]+\";\n    private final static String\t\tVARIABLE_NAME_END_EXPRESSION =  \"[\\\\w0-9&&[^_]]\";\n\n    public final static String\t\tVARIABLE_NAME_MAIN_PART_MATCH_EXPRESSION =\n            VARIABLE_NAME_START_EXPRESSION + VARIABLE_NAME_MID_EXPRESSION + VARIABLE_NAME_END_EXPRESSION\n            ;\n\n    public final static String\t\tVARIABLE_NAME_MATCH_EXPRESSION =\n            VARIABLE_PREFIX + \"+\" +\n                    VARIABLE_NAME_MAIN_PART_MATCH_EXPRESSION\n            ;\n\n    private static Pattern\t\t\tVARIABLE_NAME_MATCH_PATTERN = null;\n\n    /**\n     *\t@see #addVariableBundle(Bundle, Bundle)\n     *\t@see Host#getVariablesBundle(Bundle)\n     */\n    private final static String\t\tEXTRA_VARIABLES_BUNDLE = EXTRAS_PREFIX + \"VARIABLES\";\n\n    /**\n     * \tHost capabilities, passed to plugin with edit intents\n     */\n    private final static String\t\tEXTRA_HOST_CAPABILITIES = EXTRAS_PREFIX + \"HOST_CAPABILITIES\";\n\n    /**\n     *  @see Setting#hostSupportsVariableReturn(Bundle)\n     */\n    public final static int\t\t\tEXTRA_HOST_CAPABILITY_SETTING_RETURN_VARIABLES = 2;\n\n    /**\n     *\t@see Condition#hostSupportsVariableReturn(Bundle)\n     */\n    public final static int\t\t\tEXTRA_HOST_CAPABILITY_CONDITION_RETURN_VARIABLES = 4;\n\n    /**\n     * \t@see Setting#hostSupportsOnFireVariableReplacement(Bundle)\n     */\n    public final static int\t\t\tEXTRA_HOST_CAPABILITY_SETTING_FIRE_VARIABLE_REPLACEMENT = 8;\n\n    /**\n     * @see Setting#hostSupportsVariableReturn(Bundle)\n     */\n    private final static int\t\tEXTRA_HOST_CAPABILITY_RELEVANT_VARIABLES = 16;\n\n    public final static int\t\t\tEXTRA_HOST_CAPABILITY_SETTING_SYNCHRONOUS_EXECUTION = 32;\n\n    public final static int\t\t\tEXTRA_HOST_CAPABILITY_REQUEST_QUERY_DATA_PASS_THROUGH = 64;\n\n    public final static int\t\t\tEXTRA_HOST_CAPABILITY_ENCODING_JSON = 128;\n\n    public final static int\t\t\tEXTRA_HOST_CAPABILITY_ALL =\n            EXTRA_HOST_CAPABILITY_SETTING_RETURN_VARIABLES |\n                    EXTRA_HOST_CAPABILITY_CONDITION_RETURN_VARIABLES |\n                    EXTRA_HOST_CAPABILITY_SETTING_FIRE_VARIABLE_REPLACEMENT |\n                    EXTRA_HOST_CAPABILITY_RELEVANT_VARIABLES|\n                    EXTRA_HOST_CAPABILITY_SETTING_SYNCHRONOUS_EXECUTION |\n                    EXTRA_HOST_CAPABILITY_REQUEST_QUERY_DATA_PASS_THROUGH |\n                    EXTRA_HOST_CAPABILITY_ENCODING_JSON\n            ;\n\n    /**\n     * Possible encodings of text in bundle values\n     *\n     * @see #setKeyEncoding(Bundle,String[],Encoding)\n     */\n    public enum Encoding { JSON };\n\n    private final static String\t\tBUNDLE_KEY_ENCODING_JSON_KEYS = BASE_KEY + \".JSON_ENCODED_KEYS\";\n\n    public static boolean hostSupportsKeyEncoding( Bundle extrasFromHost, Encoding encoding ) {\n        switch ( encoding ) {\n            case JSON:\n                return hostSupports( extrasFromHost, EXTRA_HOST_CAPABILITY_ENCODING_JSON );\n            default:\n                return false;\n        }\n    }\n\n    /**\n     *\n     *  Miscellaneous operational hints going one way or the other\n     *  @see Setting#hostSupportsVariableReturn(Bundle)\n     */\n\n    private final static String\t\tEXTRA_HINTS_BUNDLE = EXTRAS_PREFIX + \"HINTS\";\n\n    private final static String\t\tBUNDLE_KEY_HINT_PREFIX = \".hints.\";\n\n    private final static String\t\tBUNDLE_KEY_HINT_TIMEOUT_MS = BUNDLE_KEY_HINT_PREFIX + \"TIMEOUT\";\n\n    /**\n     *\n     *\t@see #hostSupportsRelevantVariables(Bundle)\n     *  @see #addRelevantVariableList(Intent, String[])\n     *  @see #getRelevantVariableList(Bundle)\n     */\n    private final static String\tBUNDLE_KEY_RELEVANT_VARIABLES = BASE_KEY + \".RELEVANT_VARIABLES\";\n\n\n    public static boolean hostSupportsRelevantVariables( Bundle extrasFromHost ) {\n        return hostSupports( extrasFromHost,  EXTRA_HOST_CAPABILITY_RELEVANT_VARIABLES );\n    }\n\n    /**\n     * Specifies to host which variables might be used by the plugin.\n     *\n     * Used in EditActivity, before setResult().\n     *\n     * @param  intentToHost the intent being returned to the host\n     * @param  variableNames array of relevant variable names\n     */\n    public static void addRelevantVariableList( Intent intentToHost, String [] variableNames ) {\n        intentToHost.putExtra( BUNDLE_KEY_RELEVANT_VARIABLES, variableNames );\n    }\n\n    /**\n     * Validate a variable name.\n     *\n     * The basic requirement for variables from a plugin is that they must be all lower-case.\n     *\n     * @param  varName name to check\n     */\n    public static boolean variableNameValid( String varName ) {\n\n        boolean validFlag = false;\n\n        if ( varName == null )\n            Log.d( TAG, \"variableNameValid: null name\" );\n        else {\n            if ( VARIABLE_NAME_MATCH_PATTERN == null )\n                VARIABLE_NAME_MATCH_PATTERN = Pattern.compile( VARIABLE_NAME_MATCH_EXPRESSION, 0 );\n\n            if ( VARIABLE_NAME_MATCH_PATTERN.matcher( varName ).matches() ) {\n\n                if ( variableNameIsLocal( varName ) )\n                    validFlag = true;\n                else\n                    Log.d( TAG, \"variableNameValid: name not local: \" + varName );\n            }\n            else\n                Log.d( TAG, \"variableNameValid: invalid name: \" + varName );\n        }\n\n        return validFlag;\n    }\n\n    /**\n     * Allows the plugin/host to indicate to each other a set of variables which they are referencing.\n     * The host may use this to e.g. show a variable selection list in it's UI.\n     * The host should use this if it previously indicated to the plugin that it supports relevant vars\n     *\n     * @param  fromHostIntentExtras usually from getIntent().getExtras()\n     * @return variableNames an array of relevant variable names\n     */\n    public static String [] getRelevantVariableList( Bundle fromHostIntentExtras ) {\n\n        String [] relevantVars = (String []) getBundleValueSafe( fromHostIntentExtras, BUNDLE_KEY_RELEVANT_VARIABLES, String [].class, \"getRelevantVariableList\" );\n\n        if ( relevantVars == null )\n            relevantVars = new String [0];\n\n        return relevantVars;\n    }\n\n    /**\n     * Used by: plugin QueryReceiver, FireReceiver\n     *\n     * Add a bundle of variable name/value pairs.\n     *\n     * Names must be valid Tasker local variable names.\n     * Values must be String, String [] or ArrayList<String>\n     * Null values cause deletion of possible already-existing variables\n     * A null value where the variable does not already exist results in attempted deletion\n     * of any existing array indices (%arr1, %arr2 etc)\n     *\n     * @param resultExtras the result extras from the receiver onReceive (from a call to getResultExtras())\n     * @param variables the variables to send\n     * @see Setting#hostSupportsVariableReturn(Bundle)\n     * @see #variableNameValid(String)\n     */\n    public static void addVariableBundle( Bundle resultExtras, Bundle variables ) {\n        resultExtras.putBundle( EXTRA_VARIABLES_BUNDLE, variables );\n    }\n\n    /**\n     * Used by: plugin EditActivity\n     *\n     * Specify the encoding for a set of bundle keys.\n     *\n     * This is completely optional and currently only necessary if using Setting#setVariableReplaceKeys\n     * where the corresponding values of some of the keys specified are JSON encoded.\n     *\n     * @param  resultBundleToHost the bundle being returned to the host\n     * @param keys the keys being returned to the host which are encoded in some way\n     * @param encoding the encoding of the values corresponding to the specified keys\n     * @see #setVariableReplaceKeys(Bundle,String[])\n     * @see #hostSupportsKeyEncoding(Bundle, Encoding)\n     */\n    public static void setKeyEncoding( Bundle resultBundleToHost, String [] keys, Encoding encoding ) {\n        if ( Encoding.JSON.equals( encoding ) )\n            addStringArrayToBundleAsString(\n                    keys, resultBundleToHost, BUNDLE_KEY_ENCODING_JSON_KEYS, \"setValueEncoding\"\n            );\n        else\n            Log.e( TAG, \"unknown encoding: \" + encoding );\n    }\n\n    // ----------------------------- SETTING PLUGIN ONLY --------------------------------- //\n\n    public static class Setting {\n\n        /**\n         * \tVariable name into which a description of any error that occurred can be placed\n         *  for the user to process.\n         *\n         *  Should *only* be set when the BroadcastReceiver result code indicates a failure.\n         *\n         *  Note that the user needs to have configured the task to continue after failure of the plugin\n         *  action otherwise they will not be able to make use of the error message.\n         *\n         *  For use with #addRelevantVariableList(Intent, String[]) and #addVariableBundle(Bundle, Bundle)\n         *\n         */\n        public final static String\t\tVARNAME_ERROR_MESSAGE = VARIABLE_PREFIX + \"errmsg\";\n\n        /**\n         *\t@see #setVariableReplaceKeys(Bundle, String[])\n         */\n        private final static String\t\tBUNDLE_KEY_VARIABLE_REPLACE_STRINGS = EXTRAS_PREFIX + \"VARIABLE_REPLACE_KEYS\";\n\n        /**\n         *\t@see #requestTimeoutMS(android.content.Intent, int)\n         */\n        private final static String \tEXTRA_REQUESTED_TIMEOUT = EXTRAS_PREFIX + \"REQUESTED_TIMEOUT\";\n\n        /**\n         *\t@see #requestTimeoutMS(android.content.Intent, int)\n         */\n\n        public final static int \t\tREQUESTED_TIMEOUT_MS_NONE = 0;\n\n        /**\n         *\t@see #requestTimeoutMS(android.content.Intent, int)\n         */\n\n        public final static int \t\tREQUESTED_TIMEOUT_MS_MAX = 3599000;\n\n        /**\n         *\t@see #requestTimeoutMS(android.content.Intent, int)\n         */\n\n        public final static int \t\tREQUESTED_TIMEOUT_MS_NEVER = REQUESTED_TIMEOUT_MS_MAX + 1000;\n\n        /**\n         *  @see #signalFinish(Context, Intent, int, Bundle)\n         *  @see #addCompletionIntent(Intent, Intent,ComponentName, boolean)\n         */\n        private final static String \tEXTRA_PLUGIN_COMPLETION_INTENT = EXTRAS_PREFIX + \"COMPLETION_INTENT\";\n\n        /**\n         *  @see #signalFinish(Context, Intent, int, Bundle)\n         *  @see Host#getSettingResultCode(Intent)\n         */\n        public final static String \t\tEXTRA_RESULT_CODE = EXTRAS_PREFIX + \"RESULT_CODE\";\n\n        /**\n         *\n         *\t@see #signalFinish(Context, Intent, int, Bundle)\n         *  @see #addCompletionIntent(Intent, Intent,ComponentName, boolean)\n         */\n        public final static String EXTRA_CALL_SERVICE_PACKAGE = BASE_KEY + \".EXTRA_CALL_SERVICE_PACKAGE\";\n        public final static String EXTRA_CALL_SERVICE = BASE_KEY + \".EXTRA_CALL_SERVICE\";\n        public final static String EXTRA_CALL_SERVICE_FOREGROUND = BASE_KEY + \".EXTRA_CALL_SERVICE_FOREGROUND\";\n        /**\n         *  @see #signalFinish(Context, Intent, int, Bundle)\n         *  @see Host#getSettingResultCode(Intent)\n         */\n\n        public final static int\tRESULT_CODE_OK = Activity.RESULT_OK;\n        public final static int\tRESULT_CODE_OK_MINOR_FAILURES = Activity.RESULT_FIRST_USER;\n        public final static int\tRESULT_CODE_FAILED = Activity.RESULT_FIRST_USER + 1;\n        public final static int\tRESULT_CODE_PENDING = Activity.RESULT_FIRST_USER + 2;\n        public final static int\tRESULT_CODE_UNKNOWN = Activity.RESULT_FIRST_USER + 3;\n\n        /**\n         * If a plugin wants to define it's own error codes, start numbering them here.\n         * The code will be placed in an error variable (%err in the case of Tasker) for\n         * the user to process after the plugin action.\n         */\n\n        public final static int\tRESULT_CODE_FAILED_PLUGIN_FIRST = Activity.RESULT_FIRST_USER + 9;\n\n        /**\n         * Used by: plugin EditActivity.\n         *\n         * Indicates to plugin that host will replace variables in specified bundle keys.\n         *\n         * Replacement takes place every time the setting is fired, before the bundle is\n         * passed to the plugin FireReceiver.\n         *\n         * @param  extrasFromHost intent extras from the intent received by the edit activity\n         * @see #setVariableReplaceKeys(Bundle, String[])\n         */\n        public static boolean hostSupportsOnFireVariableReplacement( Bundle extrasFromHost ) {\n            return hostSupports( extrasFromHost, EXTRA_HOST_CAPABILITY_SETTING_FIRE_VARIABLE_REPLACEMENT );\n        }\n\n        /**\n         * Used by: plugin EditActivity.\n         *\n         * Description as above.\n         *\n         * This version also includes backwards compatibility with pre 4.2 Tasker versions.\n         * At some point this function will be deprecated.\n         *\n         * @param  editActivity the plugin edit activity, needed to test calling Tasker version\n         * @see #setVariableReplaceKeys(Bundle, String[])\n         */\n\n        public static boolean hostSupportsOnFireVariableReplacement( Activity editActivity ) {\n\n            boolean supportedFlag = hostSupportsOnFireVariableReplacement( editActivity.getIntent().getExtras() );\n\n            if ( ! supportedFlag ) {\n\n                ComponentName callingActivity = editActivity.getCallingActivity();\n\n                if ( callingActivity == null )\n                    Log.w( TAG, \"hostSupportsOnFireVariableReplacement: null callingActivity, defaulting to false\" );\n                else {\n                    String callerPackage = callingActivity.getPackageName();\n\n                    // Tasker only supporteed this from 1.0.10\n                    supportedFlag =\n                            ( callerPackage.startsWith( BASE_KEY ) ) &&\n                                    ( getPackageVersionCode( editActivity.getPackageManager(), callerPackage ) > FIRST_ON_FIRE_VARIABLES_TASKER_VERSION )\n                    ;\n                }\n            }\n\n            return supportedFlag;\n        }\n\n        public static boolean hostSupportsSynchronousExecution( Bundle extrasFromHost ) {\n            return hostSupports( extrasFromHost, EXTRA_HOST_CAPABILITY_SETTING_SYNCHRONOUS_EXECUTION );\n        }\n\n        /**\n         * Request the host to wait the specified number of milliseconds before continuing.\n         * Note that the host may choose to ignore the request.\n         *\n         * Maximum value is REQUESTED_TIMEOUT_MS_MAX.\n         * Also available are REQUESTED_TIMEOUT_MS_NONE (continue immediately without waiting\n         * for the plugin to finish) and REQUESTED_TIMEOUT_MS_NEVER (wait forever for\n         * a result).\n         *\n         * Used in EditActivity, before setResult().\n         *\n         * @param  intentToHost the intent being returned to the host\n         * @param  timeoutMS\n         */\n        public static void requestTimeoutMS( Intent intentToHost, int timeoutMS ) {\n            if ( timeoutMS < 0 )\n                Log.w( TAG, \"requestTimeoutMS: ignoring negative timeout (\" + timeoutMS + \")\" );\n            else {\n                if (\n                        ( timeoutMS > REQUESTED_TIMEOUT_MS_MAX ) &&\n                                ( timeoutMS != REQUESTED_TIMEOUT_MS_NEVER )\n                        ) {\n                    Log.w( TAG, \"requestTimeoutMS: requested timeout \" + timeoutMS + \" exceeds maximum, setting to max (\" + REQUESTED_TIMEOUT_MS_MAX + \")\" );\n                    timeoutMS = REQUESTED_TIMEOUT_MS_MAX;\n                }\n                intentToHost.putExtra( EXTRA_REQUESTED_TIMEOUT, timeoutMS );\n            }\n        }\n\n        /**\n         * Used by: plugin EditActivity\n         *\n         * Indicates to host which bundle keys should be replaced.\n         *\n         * @param  resultBundleToHost the bundle being returned to the host\n         * @param  listOfKeyNames which bundle keys to replace variables in when setting fires\n         * @see #hostSupportsOnFireVariableReplacement(Bundle)\n         * @see #setKeyEncoding(Bundle,String[],Encoding)\n         */\n        public static void setVariableReplaceKeys( Bundle resultBundleToHost, String [] listOfKeyNames ) {\n            addStringArrayToBundleAsString(\n                    listOfKeyNames, resultBundleToHost, BUNDLE_KEY_VARIABLE_REPLACE_STRINGS,\n                    \"setVariableReplaceKeys\"\n            );\n        }\n\n        public static boolean hasVariableReplaceKeys(Bundle resultBundleToHost) {\n            return resultBundleToHost.containsKey(BUNDLE_KEY_VARIABLE_REPLACE_STRINGS);\n        }\n\n        /**\n         * Used by: plugin FireReceiver\n         *\n         * Indicates to plugin whether the host will process variables which it passes back\n         *\n         * @param  extrasFromHost intent extras from the intent received by the FireReceiver\n         * @see #signalFinish(Context, Intent, int, Bundle)\n         */\n        public static boolean hostSupportsVariableReturn( Bundle extrasFromHost ) {\n            return hostSupports( extrasFromHost, EXTRA_HOST_CAPABILITY_SETTING_RETURN_VARIABLES );\n        }\n\n        /**\n         * Used by: plugin FireReceiver\n         *\n         * Tell the host that the plugin has finished execution.\n         *\n         * This should only be used if RESULT_CODE_PENDING was returned by FireReceiver.onReceive().\n         *\n         * @param originalFireIntent the intent received from the host (via onReceive())\n         * @param resultCode level of success in performing the settings\n         * @param vars any variables that the plugin wants to set in the host\n         * @see #hostSupportsSynchronousExecution(Bundle)\n         */\n        public static boolean signalFinish( Context context, Intent originalFireIntent, int resultCode, Bundle vars ) {\n\n            String errorPrefix = \"signalFinish: \";\n\n            boolean okFlag = false;\n\n            String completionIntentString = (String) getExtraValueSafe( originalFireIntent, Setting.EXTRA_PLUGIN_COMPLETION_INTENT, String.class, \"signalFinish\" );\n\n            if ( completionIntentString != null ) {\n\n                Uri completionIntentUri = null;\n                try {\n                    completionIntentUri = Uri.parse( completionIntentString );\n                }\n                // \tshould only throw NullPointer but don't particularly trust it\n                catch ( Exception e ) {\n                    Log.w( TAG, errorPrefix + \"couldn't parse \" + completionIntentString );\n                }\n\n                if ( completionIntentUri != null ) {\n                    try {\n                        Intent completionIntent = Intent.parseUri( completionIntentString, Intent.URI_INTENT_SCHEME );\n\n                        completionIntent.putExtra( EXTRA_RESULT_CODE, resultCode );\n\n                        if ( vars != null )\n                            completionIntent.putExtra( EXTRA_VARIABLES_BUNDLE, vars );\n\n                        String callServicePackage = (String) getExtraValueSafe(completionIntent, Setting.EXTRA_CALL_SERVICE_PACKAGE, String.class, \"signalFinish\");\n                        String callService = (String) getExtraValueSafe(completionIntent, Setting.EXTRA_CALL_SERVICE, String.class, \"signalFinish\");\n                        Boolean foreground = (Boolean) getExtraValueSafe(completionIntent, Setting.EXTRA_CALL_SERVICE_FOREGROUND, Boolean.class, \"signalFinish\");\n                        if (callServicePackage != null && callService != null && foreground != null) {\n                            completionIntent.setComponent(new ComponentName(callServicePackage, callService));\n                            if (foreground && android.os.Build.VERSION.SDK_INT >= 26) {\n                                context.startForegroundService(completionIntent);\n                            } else {\n                                context.startService(completionIntent);\n                            }\n                        } else {\n                            context.sendBroadcast(completionIntent);\n                        }\n\n                        okFlag = true;\n                    }\n                    catch ( URISyntaxException e ) {\n                        Log.w( TAG, errorPrefix + \"bad URI: \" + completionIntentUri );\n                    }\n                }\n            }\n\n            return okFlag;\n        }\n\n        /**\n         * Check for a hint on the timeout value the host is using.\n         * Used by: plugin FireReceiver.\n         * Requires Tasker 4.7+\n         *\n         * @param  extrasFromHost intent extras from the intent received by the FireReceiver\n         * @return timeoutMS the hosts timeout setting for the action or -1 if no hint is available.\n         *\n         * @see #REQUESTED_TIMEOUT_MS_NONE, REQUESTED_TIMEOUT_MS_MAX, REQUESTED_TIMEOUT_MS_NEVER\n         */\n        public static int getHintTimeoutMS( Bundle extrasFromHost ) {\n\n            int timeoutMS = -1;\n\n            Bundle hintsBundle = (Bundle) TaskerPlugin.getBundleValueSafe( extrasFromHost, EXTRA_HINTS_BUNDLE, Bundle.class, \"getHintTimeoutMS\" );\n\n            if ( hintsBundle != null ) {\n\n                Integer val = (Integer) getBundleValueSafe( hintsBundle, BUNDLE_KEY_HINT_TIMEOUT_MS, Integer.class, \"getHintTimeoutMS\" );\n\n                if ( val != null )\n                    timeoutMS = val;\n            }\n\n            return timeoutMS;\n        }\n    }\n\n    // ----------------------------- CONDITION/EVENT PLUGIN ONLY --------------------------------- //\n\n    public static class Condition {\n\n        /**\n         * @see #getResultReceiver(Intent)\n         */\n        public final static String EXTRA_RESULT_RECEIVER = BASE_KEY + \".EXTRA_RESULT_RECEIVER\";\n        /**\n         * Used by: plugin QueryReceiver\n         *\n         * Indicates to plugin whether the host will process variables which it passes back\n         *\n         * @param  extrasFromHost intent extras from the intent received by the QueryReceiver\n         * @see #addVariableBundle(Bundle, Bundle)\n         */\n        public static boolean hostSupportsVariableReturn( Bundle extrasFromHost ) {\n            return hostSupports( extrasFromHost,  EXTRA_HOST_CAPABILITY_CONDITION_RETURN_VARIABLES );\n        }\n\n        public static ResultReceiver getResultReceiver(Intent intentFromHost) {\n            if (intentFromHost == null) {\n                return null;\n            }\n            return (ResultReceiver) getExtraValueSafe(intentFromHost, EXTRA_RESULT_RECEIVER, ResultReceiver.class, \"getResultReceiver\");\n\n        }\n    }\n\n    // ----------------------------- EVENT PLUGIN ONLY --------------------------------- //\n\n    public static class Event {\n\n        public final static String\tPASS_THROUGH_BUNDLE_MESSAGE_ID_KEY = BASE_KEY + \".MESSAGE_ID\";\n\n        private final static String\tEXTRA_REQUEST_QUERY_PASS_THROUGH_DATA = EXTRAS_PREFIX + \"PASS_THROUGH_DATA\";\n\n        /**\n         * @param  extrasFromHost intent extras from the intent received by the QueryReceiver\n         * @see #addPassThroughData(Intent, Bundle)\n         */\n        public static boolean hostSupportsRequestQueryDataPassThrough( Bundle extrasFromHost ) {\n            return hostSupports( extrasFromHost,  EXTRA_HOST_CAPABILITY_REQUEST_QUERY_DATA_PASS_THROUGH );\n        }\n\n        /**\n         * Specify a bundle of data (probably representing whatever change happened in the condition)\n         * which will be included in the QUERY_CONDITION broadcast sent by the host for each\n         * event instance of the plugin.\n         *\n         * The minimal purpose is to enable the plugin to associate a QUERY_CONDITION to the\n         * with the REQUEST_QUERY that caused it.\n         *\n         * Note that for security reasons it is advisable to also store a message ID with the bundle\n         * which can be compared to known IDs on receipt. The host cannot validate the source of\n         * REQUEST_QUERY intents so fake data may be passed. Replay attacks are also possible.\n         * addPassThroughMesssageID() can be used to add an ID if the plugin doesn't wish to add it's\n         * own ID to the pass through bundle.\n         *\n         * Note also that there are several situations where REQUEST_QUERY will not result in a\n         * QUERY_CONDITION intent (e.g. event throttling by the host), so plugin-local data\n         * indexed with a message ID needs to be timestamped and eventually timed-out.\n         *\n         * This function can be called multiple times, each time all keys in data will be added to\n         * that of previous calls.\n         *\n         * @param requestQueryIntent intent being sent to the host\n         * @param data the data to be passed-through\n         * @see #hostSupportsRequestQueryDataPassThrough(Bundle)\n         * @see #retrievePassThroughData(Intent)\n         * @see #addPassThroughMessageID\n         *\n         */\n        public static void addPassThroughData( Intent requestQueryIntent, Bundle data ) {\n\n            Bundle passThroughBundle = retrieveOrCreatePassThroughBundle( requestQueryIntent );\n\n            passThroughBundle.putAll( data );\n        }\n\n        /**\n         * Retrieve the pass through data from a QUERY_REQUEST from the host which was generated\n         * by a REQUEST_QUERY from the plugin.\n         *\n         * Note that if addPassThroughMessageID() was previously called, the data will contain an extra\n         * key TaskerPlugin.Event.PASS_THOUGH_BUNDLE_MESSAGE_ID_KEY.\n         *\n         * @param queryConditionIntent QUERY_REQUEST sent from host\n         * @return data previously added to the REQUEST_QUERY intent\n         * @see #hostSupportsRequestQueryDataPassThrough(Bundle)\n         * @see #addPassThroughData(Intent,Bundle)\n         */\n        public static Bundle retrievePassThroughData( Intent queryConditionIntent ) {\n            return (Bundle) getExtraValueSafe(\n                    queryConditionIntent,\n                    EXTRA_REQUEST_QUERY_PASS_THROUGH_DATA,\n                    Bundle.class,\n                    \"retrievePassThroughData\"\n            );\n        }\n\n        /**\n         * Add a message ID to a REQUEST_QUERY intent which will then be included in the corresponding\n         * QUERY_CONDITION broadcast sent by the host for each event instance of the plugin.\n         *\n         * The minimal purpose is to enable the plugin to associate a QUERY_CONDITION to the\n         * with the REQUEST_QUERY that caused it. It also allows the message to be verified\n         * by the plugin to prevent e.g. replay attacks\n         *\n         * @param requestQueryIntent intent being sent to the host\n         * @return a guaranteed non-repeating within 100 calls message ID\n         * @see #hostSupportsRequestQueryDataPassThrough(Bundle)\n         * @see #retrievePassThroughData(Intent)\n         * @return an ID for the bundle so it can be identified and the caller verified when it is again received by the plugin\n         *\n         */\n        public static int addPassThroughMessageID( Intent requestQueryIntent ) {\n\n            Bundle passThroughBundle = retrieveOrCreatePassThroughBundle( requestQueryIntent );\n\n            int id = getPositiveNonRepeatingRandomInteger();\n\n            passThroughBundle.putInt( PASS_THROUGH_BUNDLE_MESSAGE_ID_KEY, id );\n\n            return id;\n        }\n\n        /*\n         * Retrieve the pass through data from a QUERY_REQUEST from the host which was generated\n         * by a REQUEST_QUERY from the plugin.\n         *\n         * @param queryConditionIntent QUERY_REQUEST sent from host\n         * @return the ID which was passed through by the host, or -1 if no ID was found\n         * @see #hostSupportsRequestQueryDataPassThrough(Bundle)\n         * @see #addPassThroughData(Intent,Bundle)\n         */\n        public static int retrievePassThroughMessageID( Intent queryConditionIntent ) {\n\n            int toReturn = -1;\n\n            Bundle passThroughData = Event.retrievePassThroughData( queryConditionIntent );\n\n            if ( passThroughData != null ) {\n                Integer id = (Integer) getBundleValueSafe(\n                        passThroughData,\n                        PASS_THROUGH_BUNDLE_MESSAGE_ID_KEY,\n                        Integer.class,\n                        \"retrievePassThroughMessageID\"\n                );\n\n                if ( id != null )\n                    toReturn = id;\n            }\n\n            return toReturn;\n        }\n\n        // internal use\n        private static Bundle retrieveOrCreatePassThroughBundle( Intent requestQueryIntent ) {\n\n            Bundle passThroughBundle;\n\n            if ( requestQueryIntent.hasExtra( EXTRA_REQUEST_QUERY_PASS_THROUGH_DATA ) )\n                passThroughBundle = requestQueryIntent.getBundleExtra( EXTRA_REQUEST_QUERY_PASS_THROUGH_DATA );\n            else {\n                passThroughBundle = new Bundle();\n                requestQueryIntent.putExtra( EXTRA_REQUEST_QUERY_PASS_THROUGH_DATA, passThroughBundle );\n            }\n\n            return passThroughBundle;\n        }\n    }\n    // ---------------------------------- HOST  ----------------------------------------- //\n\n    public static class Host {\n\n        /**\n         * Tell the plugin what capabilities the host support. This should be called when sending\n         * intents to any EditActivity, FireReceiver or QueryReceiver.\n         *\n         * @param  toPlugin the intent we're sending\n         * @return capabilities one or more of the EXTRA_HOST_CAPABILITY_XXX flags\n         */\n        public static Intent addCapabilities( Intent toPlugin, int capabilities ) {\n            return toPlugin.putExtra( EXTRA_HOST_CAPABILITIES, capabilities  );\n        }\n\n        /**\n         * Add an intent to the fire intent before it goes to the plugin FireReceiver, which the plugin\n         * can use to signal when it is finished. Only use if @code{pluginWantsSychronousExecution} is true.\n         *\n         * @param fireIntent fire intent going to the plugin\n         * @param completionIntent intent which will signal the host that the plugin is finished.\n         * Implementation is host-dependent.\n         */\n        public static void addCompletionIntent(Intent fireIntent, Intent completionIntent, ComponentName callService, boolean foreground) {\n            if (callService != null) {\n                completionIntent.putExtra(Setting.EXTRA_CALL_SERVICE_PACKAGE, callService.getPackageName());\n                completionIntent.putExtra(Setting.EXTRA_CALL_SERVICE, callService.getClassName());\n                completionIntent.putExtra(Setting.EXTRA_CALL_SERVICE_FOREGROUND, foreground);\n            }\n            fireIntent.putExtra(\n                    Setting.EXTRA_PLUGIN_COMPLETION_INTENT,\n                    completionIntent.toUri(Intent.URI_INTENT_SCHEME)\n            );\n        }\n\n        /**\n         * When a setting plugin is finished, it sends the host the intent which was passed to it\n         * via @code{addCompletionIntent}.\n         *\n         * @param completionIntent intent returned from the plugin when it finished.\n         * @return resultCode measure of plugin success, defaults to UNKNOWN\n         */\n        public static int getSettingResultCode( Intent completionIntent ) {\n\n            Integer val = (Integer) getExtraValueSafe( completionIntent, Setting.EXTRA_RESULT_CODE, Integer.class, \"getSettingResultCode\" );\n\n            return ( val == null ) ? Setting.RESULT_CODE_UNKNOWN : val;\n        }\n\n        /**\n         * Extract a bundle of variables from an intent received from the FireReceiver. This\n         * should be called if the host previously indicated to the plugin\n         * that it supports setting variable return.\n         *\n         * @param resultExtras getResultExtras() from BroadcastReceiver:onReceive()\n         * @return variables a bundle of variable name/value pairs\n         * @see #addCapabilities(Intent, int)\n         */\n\n        public static Bundle getVariablesBundle( Bundle resultExtras ) {\n            return (Bundle) getBundleValueSafe(\n                    resultExtras, EXTRA_VARIABLES_BUNDLE, Bundle.class, \"getVariablesBundle\"\n            );\n        }\n\n        /**\n         * Inform a setting plugin of the timeout value the host is using.\n         *\n         * @param toPlugin the intent we're sending\n         * @param timeoutMS the hosts timeout setting for the action. Note that this may differ from\n         * that which the plugin requests.\n         * @see #REQUESTED_TIMEOUT_MS_NONE, REQUESTED_TIMEOUT_MS_MAX, REQUESTED_TIMEOUT_MS_NEVER\n         */\n        public static void addHintTimeoutMS( Intent toPlugin, int timeoutMS ) {\n            getHintsBundle( toPlugin, \"addHintTimeoutMS\" ).putInt( BUNDLE_KEY_HINT_TIMEOUT_MS, timeoutMS );\n        }\n\n        private static Bundle getHintsBundle( Intent intent, String funcName ) {\n\n            Bundle hintsBundle = (Bundle) getExtraValueSafe( intent, EXTRA_HINTS_BUNDLE, Bundle.class, funcName );\n\n            if ( hintsBundle == null ) {\n                hintsBundle = new Bundle();\n                intent.putExtra( EXTRA_HINTS_BUNDLE, hintsBundle );\n            }\n\n            return hintsBundle;\n        }\n\n        public static boolean haveRequestedTimeout( Bundle extrasFromPluginEditActivity ) {\n            return extrasFromPluginEditActivity.containsKey( Setting.EXTRA_REQUESTED_TIMEOUT );\n        }\n\n        public static int getRequestedTimeoutMS( Bundle extrasFromPluginEditActivity ) {\n            return\n                    (Integer) getBundleValueSafe(\n                            extrasFromPluginEditActivity, Setting.EXTRA_REQUESTED_TIMEOUT,\tInteger.class, \"getRequestedTimeout\"\n                    )\n                    ;\n        }\n\n        public static String [] getSettingVariableReplaceKeys( Bundle fromPluginEditActivity ) {\n            return getStringArrayFromBundleString(\n                    fromPluginEditActivity, Setting.BUNDLE_KEY_VARIABLE_REPLACE_STRINGS,\n                    \"getSettingVariableReplaceKeys\"\n            );\n        }\n\n        public static String [] getKeysWithEncoding( Bundle fromPluginEditActivity, Encoding encoding ) {\n\n            String [] toReturn = null;\n\n            if ( Encoding.JSON.equals( encoding ) )\n                toReturn = getStringArrayFromBundleString(\n                        fromPluginEditActivity, TaskerPlugin.BUNDLE_KEY_ENCODING_JSON_KEYS,\n                        \"getKeyEncoding:JSON\"\n                );\n            else\n                Log.w( TAG, \"Host.getKeyEncoding: unknown encoding \" + encoding );\n\n            return toReturn;\n        }\n\n        public static boolean haveRelevantVariables( Bundle b ) {\n            return b.containsKey( BUNDLE_KEY_RELEVANT_VARIABLES );\n        }\n\n        public static void cleanRelevantVariables( Bundle b ) {\n            b.remove( BUNDLE_KEY_RELEVANT_VARIABLES );\n        }\n\n        public static void cleanHints( Bundle extras ) {\n            extras.remove( TaskerPlugin.EXTRA_HINTS_BUNDLE );\n        }\n\n        public static void cleanRequestedTimeout( Bundle extras ) {\n            extras.remove( Setting.EXTRA_REQUESTED_TIMEOUT );\n        }\n\n        public static void cleanSettingReplaceVariables( Bundle b ) {\n            b.remove( Setting.BUNDLE_KEY_VARIABLE_REPLACE_STRINGS );\n        }\n    }\n\n    // ---------------------------------- HELPER FUNCTIONS -------------------------------- //\n\n    private static Object getBundleValueSafe( Bundle b, String key, Class<?> expectedClass, String funcName ) {\n        Object value = null;\n\n        if ( b != null ) {\n            if ( b.containsKey( key ) ) {\n                Object obj = b.get( key );\n                if ( obj == null )\n                    Log.w( TAG, funcName + \": \" + key + \": null value\" );\n                else if ( obj.getClass() != expectedClass )\n                    Log.w( TAG, funcName + \": \" + key + \": expected \" + expectedClass.getClass().getName() + \", got \" + obj.getClass().getName() );\n                else\n                    value = obj;\n            }\n        }\n        return value;\n    }\n\n    private static Object getExtraValueSafe( Intent i, String key, Class<?> expectedClass, String funcName ) {\n        return ( i.hasExtra( key ) ) ?\n                getBundleValueSafe( i.getExtras(), key, expectedClass, funcName ) :\n                null;\n    }\n\n    private static boolean hostSupports( Bundle extrasFromHost, int capabilityFlag ) {\n        Integer flags = (Integer) getBundleValueSafe( extrasFromHost, EXTRA_HOST_CAPABILITIES, Integer.class, \"hostSupports\" );\n        return\n                ( flags != null ) &&\n                        ( ( flags & capabilityFlag ) > 0 )\n                ;\n    }\n\n    public static int getPackageVersionCode( PackageManager pm, String packageName ) {\n\n        int code = -1;\n\n        if ( pm != null ) {\n            try {\n                PackageInfo pi = pm.getPackageInfo( packageName, 0 );\n                if ( pi != null )\n                    code = pi.versionCode;\n            }\n            catch ( Exception e ) {\n                Log.e( TAG, \"getPackageVersionCode: exception getting package info\" );\n            }\n        }\n\n        return code;\n    }\n\n    private static boolean variableNameIsLocal( String varName ) {\n\n        int digitCount = 0;\n        int length = varName.length();\n\n        for ( int x = 0; x < length; x++ ) {\n            char ch = varName.charAt( x );\n\n            if ( Character.isUpperCase( ch ) )\n                return false;\n            else if ( Character.isDigit( ch ) )\n                digitCount++;\n        }\n\n        if ( digitCount == ( varName.length() - 1 ) )\n            return false;\n\n        return true;\n    }\n\n    private static String [] getStringArrayFromBundleString( Bundle bundle, String key, String funcName ) {\n\n        String spec = (String) getBundleValueSafe( bundle, key, String.class, funcName );\n\n        String [] toReturn = null;\n\n        if ( spec != null )\n            toReturn = spec.split( \" \" );\n\n        return toReturn;\n    }\n\n    private static void addStringArrayToBundleAsString( String [] toAdd, Bundle bundle, String key, String callerName ) {\n\n        StringBuilder builder = new StringBuilder();\n\n        if ( toAdd != null ) {\n\n            for ( String keyName : toAdd ) {\n\n                if ( keyName.contains( \" \" ) )\n                    Log.w( TAG, callerName + \": ignoring bad keyName containing space: \" + keyName );\n                else {\n                    if ( builder.length() > 0 )\n                        builder.append( ' ' );\n\n                    builder.append( keyName );\n                }\n\n                if ( builder.length() > 0 )\n                    bundle.putString( key, builder.toString() );\n            }\n        }\n    }\n\n    // state tracking for random number sequence\n    private static int [] \t\tlastRandomsSeen = null;\n    private static int \t\t\trandomInsertPointer = 0;\n    private static SecureRandom sr = null;\n\n    /**\n     * Generate a sequence of secure random positive integers which is guaranteed not to repeat\n     * in the last 100 calls to this function.\n     *\n     * @return a random positive integer\n     */\n    public static int getPositiveNonRepeatingRandomInteger() {\n\n        // initialize on first call\n        if ( sr == null ) {\n            sr = new SecureRandom();\n            lastRandomsSeen = new int[RANDOM_HISTORY_SIZE];\n\n            for ( int x = 0; x < lastRandomsSeen.length; x++ )\n                lastRandomsSeen[x] = -1;\n        }\n\n        int toReturn;\n        do {\n            // pick a number\n            toReturn = sr.nextInt( Integer.MAX_VALUE );\n\n            // check we havn't see it recently\n            for ( int seen : lastRandomsSeen ) {\n                if ( seen == toReturn ) {\n                    toReturn = -1;\n                    break;\n                }\n            }\n        }\n        while ( toReturn == -1 );\n\n        // update history\n        lastRandomsSeen[randomInsertPointer] = toReturn;\n        randomInsertPointer = ( randomInsertPointer + 1 ) % lastRandomsSeen.length;\n\n        return toReturn;\n    }\n\n}"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/main/java/com/markadamson/taskerplugin/homeassistant/Utils.java",
    "content": "package com.markadamson.taskerplugin.homeassistant;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.DialogInterface;\nimport android.content.Intent;\nimport android.os.Build;\nimport android.os.PowerManager;\nimport android.provider.Settings;\nimport android.support.v7.app.AlertDialog;\nimport android.view.View;\nimport android.widget.ArrayAdapter;\nimport android.widget.EditText;\n\npublic class Utils {\n\n    public static void initVariableSelectUI(String[] variables, View button, final EditText destination) {\n        final ArrayAdapter<String> adapter = new ArrayAdapter<>(button.getContext(), android.R.layout.select_dialog_item, variables);\n\n        button.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                AlertDialog.Builder builderSingle = new AlertDialog.Builder(v.getContext());\n                builderSingle.setTitle(\"Variable Select\");\n                builderSingle.setNegativeButton(\"cancel\", new DialogInterface.OnClickListener() {\n                    @Override\n                    public void onClick(DialogInterface dialog, int which) {\n                        dialog.dismiss();\n                    }\n                });\n\n                builderSingle.setAdapter(adapter, new DialogInterface.OnClickListener() {\n                    @Override\n                    public void onClick(DialogInterface dialog, int which) {\n                        String variable = adapter.getItem(which);\n                        assert variable != null;\n\n                        int start = Math.max(destination.getSelectionStart(), 0);\n                        int end = Math.max(destination.getSelectionEnd(), 0);\n                        destination.getText().replace(Math.min(start, end), Math.max(start, end),\n                                variable, 0, variable.length());\n                    }\n                });\n                builderSingle.show();\n            }\n        });\n    }\n\n    public static void checkBatteryOptimisation(final Activity activity) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n            String packageName = activity.getPackageName();\n            PowerManager pm = (PowerManager) activity.getSystemService(Context.POWER_SERVICE);\n            if (!pm.isIgnoringBatteryOptimizations(packageName)) {\n                new android.app.AlertDialog.Builder(activity)\n                        .setTitle(R.string.disable_battery_optimization)\n                        .setMessage(R.string.battery_optimization_dialog)\n                        .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {\n                            @Override\n                            public void onClick(DialogInterface dialog, int which) {\n                                activity.startActivity(new Intent()\n                                        .setAction(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS));\n                            }\n                        })\n                        .setNegativeButton(android.R.string.no, null)\n                        .show();\n            }\n        }\n    }\n\n    private Utils() {\n        throw new UnsupportedOperationException(\"This class is non-instantiable\"); //$NON-NLS-1$\n    }\n}\n"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/main/java/com/markadamson/taskerplugin/homeassistant/bundle/GetStatePluginBundleValues.java",
    "content": "/*\n * home-assistant-plugin-for-tasker <https://github.com/MarkAdamson/home-assistant-plugin-for-tasker>\n * Copyright 2019 Mark Adamson\n *\n * Original author:\n * android-toast-setting-plugin-for-locale <https://github.com/twofortyfouram/android-toast-setting-plugin-for-locale>\n * Copyright 2014 two forty four a.m. LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except\n * in compliance with the License. You may obtain a copy of the License at\n *\n *  http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under the\n * License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either\n * express or implied. See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.markadamson.taskerplugin.homeassistant.bundle;\n\nimport android.content.Context;\nimport android.os.Bundle;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\n\nimport com.markadamson.taskerplugin.homeassistant.Constants;\nimport com.markadamson.taskerplugin.homeassistant.TaskerPlugin;\nimport com.twofortyfouram.assertion.BundleAssertions;\nimport com.twofortyfouram.log.Lumberjack;\nimport com.twofortyfouram.spackle.AppBuildInfo;\n\nimport net.jcip.annotations.ThreadSafe;\n\nimport java.util.UUID;\n\nimport static com.twofortyfouram.assertion.Assertions.assertNotEmpty;\nimport static com.twofortyfouram.assertion.Assertions.assertNotNull;\n\n/**\n * Manages the {@link com.twofortyfouram.locale.api.Intent#EXTRA_BUNDLE EXTRA_BUNDLE} for this\n * plug-in.\n */\n@ThreadSafe\npublic final class GetStatePluginBundleValues {\n\n    /**\n     * Type: {@code String}.\n     * <p>\n     * Server UUID as string.\n     */\n    @NonNull\n    public static final String BUNDLE_EXTRA_STRING_SERVER\n            = \"com.markadamson.taskerplugin.homeassistant.extra.STRING_SERVER\"; //$NON-NLS-1$\n\n    /**\n     * Type: {@code String}.\n     * <p>\n     * Domain/service to call.\n     */\n    @NonNull\n    public static final String BUNDLE_EXTRA_STRING_ENTITY\n            = \"com.markadamson.taskerplugin.homeassistant.extra.STRING_ENTITY\"; //$NON-NLS-1$\n\n    /**\n     * Type: {@code String}.\n     * <p>\n     * State output variable.\n     */\n    @NonNull\n    public static final String BUNDLE_EXTRA_STRING_VARIABLE\n            = \"com.markadamson.taskerplugin.homeassistant.extra.STRING_VARIABLE\"; //$NON-NLS-1$\n\n    /**\n     * Type: {@code String}.\n     * <p>\n     * Attributes output variable.\n     */\n    @NonNull\n    public static final String BUNDLE_EXTRA_STRING_ATTRS_VARIABLE\n            = \"com.markadamson.taskerplugin.homeassistant.extra.STRING_ATTRS_VARIABLE\"; //$NON-NLS-1$\n\n    /**\n     * Type: {@code int}.\n     * <p>\n     * versionCode of the plug-in that saved the Bundle.\n     */\n    /*\n     * This extra is not strictly required, however it makes backward and forward compatibility\n     * significantly easier. For example, suppose a bug is found in how some version of the plug-in\n     * stored its Bundle. By having the version, the plug-in can better detect when such bugs occur.\n     */\n    @NonNull\n    public static final String BUNDLE_EXTRA_INT_VERSION_CODE\n            = \"com.markadamson.taskerplugin.homeassistant.extra.INT_VERSION_CODE\"; //$NON-NLS-1$\n\n    /**\n     * Method to verify the content of the bundle are correct.\n     * <p>\n     * This method will not mutate {@code bundle}.\n     *\n     * @param bundle bundle to verify. May be null, which will always return false.\n     * @return true if the Bundle is valid, false if the bundle is invalid.\n     */\n    public static boolean isBundleValid(@Nullable final Bundle bundle) {\n        if (null == bundle) {\n            return false;\n        }\n\n        try {\n            BundleAssertions.assertHasString(bundle, BUNDLE_EXTRA_STRING_SERVER, false, false);\n            BundleAssertions.assertHasString(bundle, BUNDLE_EXTRA_STRING_ENTITY, false, false);\n            BundleAssertions.assertHasString(bundle, BUNDLE_EXTRA_STRING_VARIABLE, false, true);\n            BundleAssertions.assertHasInt(bundle, BUNDLE_EXTRA_INT_VERSION_CODE);\n            BundleAssertions.assertHasInt(bundle, Constants.BUNDLE_EXTRA_BUNDLE_TYPE,\n                    Constants.BUNDLE_GET_STATE, Constants.BUNDLE_GET_STATE);\n\n            int bundleVer = bundle.getInt(BUNDLE_EXTRA_INT_VERSION_CODE), expectedCount = 5;\n\n            // Bundle may now have replacement vars key:\n            if (bundleVer >= 5 && TaskerPlugin.Setting.hasVariableReplaceKeys(bundle))\n                expectedCount++;\n\n            if (bundleVer >= 6) {\n                BundleAssertions.assertHasString(bundle, BUNDLE_EXTRA_STRING_ATTRS_VARIABLE, false, true);\n                expectedCount++;\n            }\n\n            BundleAssertions.assertKeyCount(bundle, expectedCount);\n        } catch (final AssertionError e) {\n            Lumberjack.e(\"Bundle failed verification%s\", e); //$NON-NLS-1$\n            return false;\n        }\n\n        return true;\n    }\n\n    /**\n     * @param context Application context.\n     * @param server The server UUID.\n     * @param entity The domain/service to call.\n     * @param stateVariable The state output variable.\n     * @param attrsVariable The attributes output variable.\n     * @return A plug-in bundle.\n     */\n    @NonNull\n    public static Bundle generateBundle(@NonNull final Context context,\n                                        @NonNull final UUID server,\n                                        @NonNull final String entity,\n                                        @NonNull final String stateVariable,\n                                        @NonNull final String attrsVariable) {\n        assertNotNull(context, \"context\"); //$NON-NLS-1$\n        assertNotNull(server, \"server\"); //$NON-NLS-1$\n        assertNotEmpty(entity, \"service\"); //$NON-NLS-1$\n\n        final Bundle result = new Bundle();\n        result.putInt(BUNDLE_EXTRA_INT_VERSION_CODE, AppBuildInfo.getVersionCode(context));\n        result.putInt(Constants.BUNDLE_EXTRA_BUNDLE_TYPE, Constants.BUNDLE_GET_STATE);\n        result.putString(BUNDLE_EXTRA_STRING_SERVER, server.toString());\n        result.putString(BUNDLE_EXTRA_STRING_ENTITY, entity);\n        result.putString(BUNDLE_EXTRA_STRING_VARIABLE, stateVariable);\n        result.putString(BUNDLE_EXTRA_STRING_ATTRS_VARIABLE, attrsVariable);\n        TaskerPlugin.Setting.setVariableReplaceKeys(result, new String[] {BUNDLE_EXTRA_STRING_ENTITY});\n\n        return result;\n    }\n\n    /**\n     * @param bundle A valid plug-in bundle.\n     * @return The message inside the plug-in bundle.\n     */\n    @NonNull\n    public static UUID getServer(@NonNull final Bundle bundle) {\n        return UUID.fromString(bundle.getString(BUNDLE_EXTRA_STRING_SERVER));\n    }\n\n    @NonNull\n    public static String getEntity(@NonNull final Bundle bundle) {\n        return bundle.getString(BUNDLE_EXTRA_STRING_ENTITY);\n    }\n\n    @NonNull\n    public static String getStateVariable(@NonNull final Bundle bundle) {\n        return bundle.getString(BUNDLE_EXTRA_STRING_VARIABLE);\n    }\n\n    @NonNull\n    public static String getAttrsVariable(@NonNull final Bundle bundle) {\n        return bundle.getString(BUNDLE_EXTRA_STRING_ATTRS_VARIABLE, \"\");\n    }\n\n    /**\n     * Private constructor prevents instantiation\n     *\n     * @throws UnsupportedOperationException because this class cannot be instantiated.\n     */\n    private GetStatePluginBundleValues() {\n        throw new UnsupportedOperationException(\"This class is non-instantiable\"); //$NON-NLS-1$\n    }\n}\n"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/main/java/com/markadamson/taskerplugin/homeassistant/bundle/PluginBundleValues.java",
    "content": "/*\n * home-assistant-plugin-for-tasker <https://github.com/MarkAdamson/home-assistant-plugin-for-tasker>\n * Copyright 2019 Mark Adamson\n *\n * Original author:\n * android-toast-setting-plugin-for-locale <https://github.com/twofortyfouram/android-toast-setting-plugin-for-locale>\n * Copyright 2014 two forty four a.m. LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except\n * in compliance with the License. You may obtain a copy of the License at\n *\n *  http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under the\n * License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either\n * express or implied. See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.markadamson.taskerplugin.homeassistant.bundle;\n\nimport android.content.Context;\nimport android.os.Bundle;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\n\nimport com.markadamson.taskerplugin.homeassistant.Constants;\nimport com.markadamson.taskerplugin.homeassistant.TaskerPlugin;\nimport com.twofortyfouram.assertion.BundleAssertions;\nimport com.twofortyfouram.log.Lumberjack;\nimport com.twofortyfouram.spackle.AppBuildInfo;\n\nimport net.jcip.annotations.ThreadSafe;\n\nimport java.util.UUID;\n\nimport static com.twofortyfouram.assertion.Assertions.assertNotEmpty;\nimport static com.twofortyfouram.assertion.Assertions.assertNotNull;\n\n/**\n * Manages the {@link com.twofortyfouram.locale.api.Intent#EXTRA_BUNDLE EXTRA_BUNDLE} for this\n * plug-in.\n */\n@ThreadSafe\npublic final class PluginBundleValues {\n\n    /**\n     * Type: {@code String}.\n     * <p>\n     * Server UUID as string.\n     */\n    @NonNull\n    public static final String BUNDLE_EXTRA_STRING_SERVER\n            = \"com.markadamson.taskerplugin.homeassistant.extra.STRING_SERVER\"; //$NON-NLS-1$\n\n    /**\n     * Type: {@code String}.\n     * <p>\n     * Domain/service to call.\n     */\n    @NonNull\n    public static final String BUNDLE_EXTRA_STRING_SERVICE\n            = \"com.markadamson.taskerplugin.homeassistant.extra.STRING_SERVICE\"; //$NON-NLS-1$\n\n    /**\n     * Type: {@code String}.\n     * <p>\n     * Service data (JSON, optional).\n     */\n    @NonNull\n    public static final String BUNDLE_EXTRA_STRING_DATA\n            = \"com.markadamson.taskerplugin.homeassistant.extra.STRING_DATA\"; //$NON-NLS-1$\n\n    /**\n     * Type: {@code int}.\n     * <p>\n     * versionCode of the plug-in that saved the Bundle.\n     */\n    /*\n     * This extra is not strictly required, however it makes backward and forward compatibility\n     * significantly easier. For example, suppose a bug is found in how some version of the plug-in\n     * stored its Bundle. By having the version, the plug-in can better detect when such bugs occur.\n     */\n    @NonNull\n    public static final String BUNDLE_EXTRA_INT_VERSION_CODE\n            = \"com.markadamson.taskerplugin.homeassistant.extra.INT_VERSION_CODE\"; //$NON-NLS-1$\n\n    /**\n     * Method to verify the content of the bundle are correct.\n     * <p>\n     * This method will not mutate {@code bundle}.\n     *\n     * @param bundle bundle to verify. May be null, which will always return false.\n     * @return true if the Bundle is valid, false if the bundle is invalid.\n     */\n    public static boolean isBundleValid(@Nullable final Bundle bundle) {\n        if (null == bundle) {\n            return false;\n        }\n\n        try {\n            BundleAssertions.assertHasString(bundle, BUNDLE_EXTRA_STRING_SERVER, false, false);\n            BundleAssertions.assertHasString(bundle, BUNDLE_EXTRA_STRING_SERVICE, false, false);\n            BundleAssertions.assertHasString(bundle, BUNDLE_EXTRA_STRING_DATA, false, true);\n            BundleAssertions.assertHasInt(bundle, BUNDLE_EXTRA_INT_VERSION_CODE);\n\n            int bundleVer = bundle.getInt(BUNDLE_EXTRA_INT_VERSION_CODE), expectedCount = 4;\n\n            if (bundleVer >= 3) {\n                BundleAssertions.assertHasInt(bundle, Constants.BUNDLE_EXTRA_BUNDLE_TYPE,\n                        Constants.BUNDLE_CALL_SERVICE, Constants.BUNDLE_CALL_SERVICE);\n                expectedCount++;\n            }\n\n            // Bundle may now have replacement vars key:\n            if (bundleVer >= 5 && TaskerPlugin.Setting.hasVariableReplaceKeys(bundle))\n                expectedCount++;\n\n            BundleAssertions.assertKeyCount(bundle, expectedCount);\n\n        } catch (final AssertionError e) {\n            Lumberjack.e(\"Bundle failed verification%s\", e); //$NON-NLS-1$\n            return false;\n        }\n\n        return true;\n    }\n\n    /**\n     * @param context Application context.\n     * @param server The server UUID.\n     * @param service The domain/service to call.\n     * @param data The service data to send.\n     * @return A plug-in bundle.\n     */\n    @NonNull\n    public static Bundle generateBundle(@NonNull final Context context,\n                                        @NonNull final UUID server,\n                                        @NonNull final String service,\n                                        @NonNull final String data) {\n        assertNotNull(context, \"context\"); //$NON-NLS-1$\n        assertNotNull(server, \"server\"); //$NON-NLS-1$\n        assertNotEmpty(service, \"service\"); //$NON-NLS-1$\n\n        final Bundle result = new Bundle();\n        result.putInt(BUNDLE_EXTRA_INT_VERSION_CODE, AppBuildInfo.getVersionCode(context));\n        result.putInt(Constants.BUNDLE_EXTRA_BUNDLE_TYPE, Constants.BUNDLE_CALL_SERVICE);\n        result.putString(BUNDLE_EXTRA_STRING_SERVER, server.toString());\n        result.putString(BUNDLE_EXTRA_STRING_SERVICE, service);\n        result.putString(BUNDLE_EXTRA_STRING_DATA, data);\n        TaskerPlugin.Setting.setVariableReplaceKeys(result, new String[] {BUNDLE_EXTRA_STRING_SERVICE, BUNDLE_EXTRA_STRING_DATA});\n\n        return result;\n    }\n\n    /**\n     * @param bundle A valid plug-in bundle.\n     * @return The message inside the plug-in bundle.\n     */\n    @NonNull\n    public static UUID getServer(@NonNull final Bundle bundle) {\n        return UUID.fromString(bundle.getString(BUNDLE_EXTRA_STRING_SERVER));\n    }\n\n    @NonNull\n    public static String getService(@NonNull final Bundle bundle) {\n        return bundle.getString(BUNDLE_EXTRA_STRING_SERVICE);\n    }\n\n    @NonNull\n    public static String getData(@NonNull final Bundle bundle) {\n        return bundle.getString(BUNDLE_EXTRA_STRING_DATA);\n    }\n\n    /**\n     * Private constructor prevents instantiation\n     *\n     * @throws UnsupportedOperationException because this class cannot be instantiated.\n     */\n    private PluginBundleValues() {\n        throw new UnsupportedOperationException(\"This class is non-instantiable\"); //$NON-NLS-1$\n    }\n}\n"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/main/java/com/markadamson/taskerplugin/homeassistant/bundle/RenderTemplatePluginBundleValues.java",
    "content": "/*\n * home-assistant-plugin-for-tasker <https://github.com/MarkAdamson/home-assistant-plugin-for-tasker>\n * Copyright 2019 Mark Adamson\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except\n * in compliance with the License. You may obtain a copy of the License at\n *\n *  http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under the\n * License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either\n * express or implied. See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.markadamson.taskerplugin.homeassistant.bundle;\n\nimport android.content.Context;\nimport android.os.Bundle;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\n\nimport com.markadamson.taskerplugin.homeassistant.Constants;\nimport com.markadamson.taskerplugin.homeassistant.TaskerPlugin;\nimport com.twofortyfouram.assertion.BundleAssertions;\nimport com.twofortyfouram.log.Lumberjack;\nimport com.twofortyfouram.spackle.AppBuildInfo;\n\nimport net.jcip.annotations.ThreadSafe;\n\nimport java.util.UUID;\n\nimport static com.twofortyfouram.assertion.Assertions.assertNotEmpty;\nimport static com.twofortyfouram.assertion.Assertions.assertNotNull;\n\n/**\n * Manages the {@link com.twofortyfouram.locale.api.Intent#EXTRA_BUNDLE EXTRA_BUNDLE} for this\n * plug-in.\n */\n@ThreadSafe\npublic final class RenderTemplatePluginBundleValues {\n\n    /**\n     * Type: {@code String}.\n     * <p>\n     * Server UUID as string.\n     */\n    @NonNull\n    public static final String BUNDLE_EXTRA_STRING_SERVER\n            = \"com.markadamson.taskerplugin.homeassistant.extra.STRING_SERVER\"; //$NON-NLS-1$\n\n    /**\n     * Type: {@code String}.\n     * <p>\n     * Template to render.\n     */\n    @NonNull\n    public static final String BUNDLE_EXTRA_STRING_TEMPLATE\n            = \"com.markadamson.taskerplugin.homeassistant.extra.STRING_TEMPLATE\"; //$NON-NLS-1$\n\n    /**\n     * Type: {@code String}.\n     * <p>\n     * Variable to store result in.\n     */\n    @NonNull\n    public static final String BUNDLE_EXTRA_STRING_VARIABLE\n            = \"com.markadamson.taskerplugin.homeassistant.extra.STRING_VARIABLE\"; //$NON-NLS-1$\n\n    /**\n     * Type: {@code int}.\n     * <p>\n     * versionCode of the plug-in that saved the Bundle.\n     */\n    /*\n     * This extra is not strictly required, however it makes backward and forward compatibility\n     * significantly easier. For example, suppose a bug is found in how some version of the plug-in\n     * stored its Bundle. By having the version, the plug-in can better detect when such bugs occur.\n     */\n    @NonNull\n    public static final String BUNDLE_EXTRA_INT_VERSION_CODE\n            = \"com.markadamson.taskerplugin.homeassistant.extra.INT_VERSION_CODE\"; //$NON-NLS-1$\n\n    /**\n     * Method to verify the content of the bundle are correct.\n     * <p>\n     * This method will not mutate {@code bundle}.\n     *\n     * @param bundle bundle to verify. May be null, which will always return false.\n     * @return true if the Bundle is valid, false if the bundle is invalid.\n     */\n    public static boolean isBundleValid(@Nullable final Bundle bundle) {\n        if (null == bundle) {\n            return false;\n        }\n\n        try {\n            BundleAssertions.assertHasInt(bundle, Constants.BUNDLE_EXTRA_BUNDLE_TYPE,\n                    Constants.BUNDLE_RENDER_TEMPLATE, Constants.BUNDLE_RENDER_TEMPLATE);\n            BundleAssertions.assertHasString(bundle, BUNDLE_EXTRA_STRING_SERVER, false, false);\n            BundleAssertions.assertHasString(bundle, BUNDLE_EXTRA_STRING_TEMPLATE, false, true);\n            BundleAssertions.assertHasString(bundle, BUNDLE_EXTRA_STRING_VARIABLE, false, false);\n            BundleAssertions.assertHasInt(bundle, BUNDLE_EXTRA_INT_VERSION_CODE);\n\n            int bundleVer = bundle.getInt(BUNDLE_EXTRA_INT_VERSION_CODE), expectedCount = 5;\n\n            // Bundle may now have replacement vars key:\n            if (TaskerPlugin.Setting.hasVariableReplaceKeys(bundle))\n                expectedCount++;\n\n            BundleAssertions.assertKeyCount(bundle, expectedCount);\n\n        } catch (final AssertionError e) {\n            Lumberjack.e(\"Bundle failed verification%s\", e); //$NON-NLS-1$\n            return false;\n        }\n\n        return true;\n    }\n\n    /**\n     * @param context Application context.\n     * @param server The server UUID.\n     * @param template The domain/template to call.\n     * @param variable The template variable to send.\n     * @return A plug-in bundle.\n     */\n    @NonNull\n    public static Bundle generateBundle(@NonNull final Context context,\n                                        @NonNull final UUID server,\n                                        @NonNull final String template,\n                                        @NonNull final String variable) {\n        assertNotNull(context, \"context\"); //$NON-NLS-1$\n        assertNotNull(server, \"server\"); //$NON-NLS-1$\n        assertNotNull(template, \"template\"); //$NON-NLS-1$\n        assertNotNull(variable, \"variable\"); //$NON-NLS-1$\n        assertNotEmpty(variable, \"variable\"); //$NON-NLS-1$\n\n        final Bundle result = new Bundle();\n        result.putInt(BUNDLE_EXTRA_INT_VERSION_CODE, AppBuildInfo.getVersionCode(context));\n        result.putInt(Constants.BUNDLE_EXTRA_BUNDLE_TYPE, Constants.BUNDLE_RENDER_TEMPLATE);\n        result.putString(BUNDLE_EXTRA_STRING_SERVER, server.toString());\n        result.putString(BUNDLE_EXTRA_STRING_TEMPLATE, template);\n        result.putString(BUNDLE_EXTRA_STRING_VARIABLE, variable);\n        TaskerPlugin.Setting.setVariableReplaceKeys(result, new String[] {BUNDLE_EXTRA_STRING_TEMPLATE});\n\n        return result;\n    }\n\n    /**\n     * @param bundle A valid plug-in bundle.\n     * @return The message inside the plug-in bundle.\n     */\n    @NonNull\n    public static UUID getServer(@NonNull final Bundle bundle) {\n        return UUID.fromString(bundle.getString(BUNDLE_EXTRA_STRING_SERVER));\n    }\n\n    @NonNull\n    public static String getTemplate(@NonNull final Bundle bundle) {\n        return bundle.getString(BUNDLE_EXTRA_STRING_TEMPLATE);\n    }\n\n    @NonNull\n    public static String getVariable(@NonNull final Bundle bundle) {\n        return bundle.getString(BUNDLE_EXTRA_STRING_VARIABLE);\n    }\n\n    /**\n     * Private constructor prevents instantiation\n     *\n     * @throws UnsupportedOperationException because this class cannot be instantiated.\n     */\n    private RenderTemplatePluginBundleValues() {\n        throw new UnsupportedOperationException(\"This class is non-instantiable\"); //$NON-NLS-1$\n    }\n}\n"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/main/java/com/markadamson/taskerplugin/homeassistant/model/HAAPI.java",
    "content": "package com.markadamson.taskerplugin.homeassistant.model;\n\nimport org.json.JSONArray;\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.io.OutputStream;\nimport java.net.HttpURLConnection;\nimport java.net.URL;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Iterator;\nimport java.util.List;\n\npublic class HAAPI {\n    private final HAServer mServer;\n\n    public HAAPI(HAServer mServer) {\n        this.mServer = mServer;\n    }\n\n    public boolean testServer() throws HAAPIException {\n        HttpURLConnection httpConn = null;\n        try {\n            URL url = new URL(mServer.getBaseURL() + \"/api/\");\n            httpConn = (HttpURLConnection) url.openConnection();\n            httpConn.setRequestMethod(\"GET\");\n            httpConn.setRequestProperty(\"Authorization\", \"Bearer \" + mServer.getAccessToken());\n\n            InputStream inputStream = httpConn.getInputStream();\n            InputStreamReader inputStreamReader = new InputStreamReader(inputStream);\n            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);\n            String line = bufferedReader.readLine();\n\n            JSONObject apiResult = new JSONObject(line);\n            return apiResult.has(\"message\") && \"API running.\".equals(apiResult.getString(\"message\"));\n        } catch (IOException e) {\n            if (httpConn != null)\n                try {\n                    throw new HAAPIException(\"Network Error: \".concat(httpConn.getResponseMessage()), e);\n                } catch (IOException e1) {\n                    throw new HAAPIException(\"Network Error\", e);\n                }\n            else\n                throw new HAAPIException(\"IO Error\", e);\n        } catch (JSONException e) {\n            throw new HAAPIException(\"JSON Error\", e);\n        }\n    }\n\n    public List<String> getServices() throws HAAPIException {\n        HttpURLConnection httpConn = null;\n        try {\n            URL url = new URL(mServer.getBaseURL() + \"/api/services\");\n            httpConn = (HttpURLConnection) url.openConnection();\n            httpConn.setRequestMethod(\"GET\");\n            httpConn.setRequestProperty(\"Authorization\", \"Bearer \" + mServer.getAccessToken());\n\n            InputStream inputStream = httpConn.getInputStream();\n            InputStreamReader inputStreamReader = new InputStreamReader(inputStream);\n            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);\n            String line = bufferedReader.readLine();\n\n            List<String> result = new ArrayList<>();\n            JSONArray apiResults = new JSONArray(line);\n            for (int d = 0; d < apiResults.length(); d++) {\n                JSONObject jsonDomain = apiResults.getJSONObject(d);\n                String strDomain = jsonDomain.getString(\"domain\");\n\n                JSONObject jsonServices = jsonDomain.getJSONObject(\"services\");\n                Iterator<String> keys = jsonServices.keys();\n                while (keys.hasNext())\n                    result.add(strDomain + \".\" + keys.next());\n            }\n\n            Collections.sort(result);\n            return result;\n        } catch (IOException e) {\n            if (httpConn != null)\n                try {\n                    throw new HAAPIException(\"Network Error: \".concat(httpConn.getResponseMessage()), e);\n                } catch (IOException e1) {\n                    throw new HAAPIException(\"Network Error\", e);\n                }\n            else\n                throw new HAAPIException(\"IO Error\", e);\n        } catch (JSONException e) {\n            throw new HAAPIException(\"JSON Error\", e);\n        }\n    }\n\n    public void callService(String domain, String service, String data) throws HAAPIException {\n        HttpURLConnection httpConn = null;\n        try {\n            URL url = new URL(mServer.getBaseURL() + \"/api/services/\" + domain + \"/\" + service);\n            httpConn = (HttpURLConnection)url.openConnection();\n            httpConn.setRequestMethod(\"POST\");\n            httpConn.setRequestProperty(\"Authorization\", \"Bearer \" + mServer.getAccessToken());\n            httpConn.setRequestProperty(\"Content-Type\", \"application/json\");\n            httpConn.setDoOutput(true);\n\n            if (data != null &! data.isEmpty()) {\n                OutputStream os = httpConn.getOutputStream();\n                os.write(data.getBytes(\"UTF-8\"));\n                os.close();\n            }\n\n            if (httpConn.getResponseCode() != 200) {\n                InputStream errorStream = httpConn.getErrorStream();\n                InputStreamReader errorStreamReader = new InputStreamReader(errorStream);\n                BufferedReader bufferedReader = new BufferedReader(errorStreamReader);\n                JSONObject jsonResult = new JSONObject(bufferedReader.readLine());\n                throw new HAAPIException(jsonResult.getString(\"message\"));\n            }\n\n        } catch (IOException e) {\n            if (httpConn != null)\n                try {\n                    throw new HAAPIException(\"Network Error: \".concat(httpConn.getResponseMessage()), e);\n                } catch (IOException e1) {\n                    throw new HAAPIException(\"Network Error\", e);\n                }\n            else\n                throw new HAAPIException(\"IO Error\", e);\n        } catch (JSONException e) {\n            throw new HAAPIException(\"JSON Error\", e);\n        }\n    }\n\n    public List<String> getEntities() throws HAAPIException {\n        HttpURLConnection httpConn = null;\n        try {\n            URL url = new URL(mServer.getBaseURL() + \"/api/states\");\n            httpConn = (HttpURLConnection)url.openConnection();\n            httpConn.setRequestMethod(\"GET\");\n            httpConn.setRequestProperty(\"Authorization\", \"Bearer \" + mServer.getAccessToken());\n\n            InputStream inputStream = httpConn.getInputStream();\n            InputStreamReader inputStreamReader = new InputStreamReader(inputStream);\n            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);\n            String line = bufferedReader.readLine();\n\n            List<String> result = new ArrayList<>();\n            JSONArray apiResults = new JSONArray(line);\n            for (int e = 0; e < apiResults.length(); e++)\n                result.add(apiResults.getJSONObject(e).getString(\"entity_id\"));\n\n            Collections.sort(result);\n            return result;\n        } catch (IOException e) {\n            if (httpConn != null)\n                try {\n                    throw new HAAPIException(\"Network Error: \".concat(httpConn.getResponseMessage()), e);\n                } catch (IOException e1) {\n                    throw new HAAPIException(\"Network Error\", e);\n                }\n            else\n                throw new HAAPIException(\"IO Error\", e);\n        } catch (JSONException e) {\n            throw new HAAPIException(\"JSON Error\", e);\n        }\n    }\n\n    public HAEntity getEntity(String entityId) throws HAAPIException {\n        HttpURLConnection httpConn = null;\n        try {\n            URL url = new URL(mServer.getBaseURL() + \"/api/states/\" + entityId);\n            httpConn = (HttpURLConnection)url.openConnection();\n            httpConn.setRequestMethod(\"GET\");\n            httpConn.setRequestProperty(\"Authorization\", \"Bearer \" + mServer.getAccessToken());\n\n            InputStream inputStream = httpConn.getInputStream();\n            InputStreamReader inputStreamReader = new InputStreamReader(inputStream);\n            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);\n            String line = bufferedReader.readLine();\n\n            JSONObject json = new JSONObject(line);\n\n            return new HAEntity(\n                    json.getString(\"state\"),\n                    json.getString(\"attributes\"));\n        } catch (IOException e) {\n            if (httpConn != null)\n                try {\n                    throw new HAAPIException(\"Network Error: \".concat(httpConn.getResponseMessage()), e);\n                } catch (IOException e1) {\n                    throw new HAAPIException(\"Network Error\", e);\n                }\n            else\n                throw new HAAPIException(\"IO Error\", e);\n        } catch (JSONException e) {\n            throw new HAAPIException(\"JSON Error\", e);\n        }\n    }\n\n    public String renderTemplate(String template) throws HAAPIException {\n        HttpURLConnection httpConn = null;\n        try {\n            URL url = new URL(mServer.getBaseURL() + \"/api/template\");\n            httpConn = (HttpURLConnection)url.openConnection();\n            httpConn.setRequestMethod(\"POST\");\n            httpConn.setRequestProperty(\"Authorization\", \"Bearer \" + mServer.getAccessToken());\n            httpConn.setRequestProperty(\"Content-Type\", \"application/json\");\n            httpConn.setDoOutput(true);\n\n            JSONObject jsonBody = new JSONObject();\n            jsonBody.put(\"template\", template);\n            byte[] outputBytes = jsonBody.toString().getBytes(\"UTF-8\");\n            OutputStream os = httpConn.getOutputStream();\n            os.write(outputBytes);\n            os.close();\n\n            StringBuilder sb = new StringBuilder();\n            BufferedReader reader = new BufferedReader(new InputStreamReader(httpConn.getInputStream()));\n            int c;\n            while ((c = reader.read()) != -1)\n                sb.append((char) c);\n            return sb.toString();\n        } catch (IOException e) {\n            if (httpConn != null)\n                try {\n                    throw new HAAPIException(\"Network Error: \".concat(httpConn.getResponseMessage()), e);\n                } catch (IOException e1) {\n                    throw new HAAPIException(\"Network Error\", e);\n                }\n            else\n                throw new HAAPIException(\"IO Error\", e);\n        } catch (JSONException e) {\n            throw new HAAPIException(\"JSON Error\", e);\n        }\n    }\n}\n"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/main/java/com/markadamson/taskerplugin/homeassistant/model/HAAPIException.java",
    "content": "package com.markadamson.taskerplugin.homeassistant.model;\n\nimport java.lang.Exception;\n\npublic class HAAPIException extends Exception {\n    private static final long serialVersionUID = 7395216398898021862L;\n\n    public HAAPIException() { super(); }\n    public HAAPIException(String message) { super(message); }\n    public HAAPIException(String message, Throwable cause) { super(message, cause); }\n    public HAAPIException(Throwable cause) { super(cause); }\n}\n"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/main/java/com/markadamson/taskerplugin/homeassistant/model/HAAPIResult.java",
    "content": "package com.markadamson.taskerplugin.homeassistant.model;\n\npublic class HAAPIResult<T> {\n    private final HAAPIException mException ;\n    private final T mResult;\n\n    public HAAPIResult(T result) {\n        mResult = result;\n        mException = null;\n    }\n\n    public HAAPIResult(HAAPIException exception) {\n        mException = exception;\n        mResult = null;\n    }\n\n    public HAAPIException getException() {\n        return mException;\n    }\n\n    public T getResult() {\n        return mResult;\n    }\n}\n"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/main/java/com/markadamson/taskerplugin/homeassistant/model/HAAPITask.java",
    "content": "package com.markadamson.taskerplugin.homeassistant.model;\n\nimport android.os.AsyncTask;\n\npublic abstract class HAAPITask<Params,Progress,Result> extends AsyncTask<Params,Progress,HAAPIResult<Result>> {\n    private final HAAPI mAPI;\n\n    protected HAAPITask(HAServer server) {\n        this.mAPI = new HAAPI(server);\n    }\n\n    @Override @SafeVarargs\n    protected final HAAPIResult<Result> doInBackground(Params... params) {\n        try {\n            return new HAAPIResult<>(doAPIInBackground(mAPI, params));\n        } catch (HAAPIException e) {\n            return new HAAPIResult<>(e);\n        }\n    }\n\n    protected abstract Result doAPIInBackground(HAAPI api, Params... params) throws HAAPIException;\n}\n"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/main/java/com/markadamson/taskerplugin/homeassistant/model/HAEntity.java",
    "content": "package com.markadamson.taskerplugin.homeassistant.model;\n\npublic class HAEntity {\n    private String mState, mAttributes;\n\n    public HAEntity(String mState, String mAttributes) {\n        this.mState = mState;\n        this.mAttributes = mAttributes;\n    }\n\n    public String getState() {\n        return mState;\n    }\n\n    public String getAttributes() {\n        return mAttributes;\n    }\n}\n"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/main/java/com/markadamson/taskerplugin/homeassistant/model/HAServer.java",
    "content": "package com.markadamson.taskerplugin.homeassistant.model;\n\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\npublic class HAServer {\n    private String name, baseURL, accessToken;\n\n    public HAServer(String name, String baseURL, String accessToken) {\n        this.name = name;\n        this.baseURL = baseURL;\n        this.accessToken = accessToken;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public String getBaseURL() {\n        return baseURL;\n    }\n\n    public void setBaseURL(String baseURL) {\n        this.baseURL = baseURL;\n    }\n\n    public String getAccessToken() {\n        return accessToken;\n    }\n\n    public void setAccessToken(String accessToken) {\n        this.accessToken = accessToken;\n    }\n\n    @Override\n    public String toString() {\n        return getName();\n    }\n\n    private static String NAME = \"Name\";\n    private static String BASE_URL = \"BaseURL\";\n    private static String ACCESS_TOKEN = \"AccessToken\";\n\n    public static HAServer fromJSON(JSONObject json) throws JSONException {\n        return new HAServer(\n                json.getString(NAME), json.getString(BASE_URL), json.getString(ACCESS_TOKEN)\n        );\n    }\n\n    public JSONObject toJSON() {\n        JSONObject result = new JSONObject();\n        try {\n            result.put(NAME, name);\n            result.put(BASE_URL, baseURL);\n            result.put(ACCESS_TOKEN, accessToken);\n        } catch (JSONException e) {\n            e.printStackTrace();\n        }\n        return result;\n    }\n}\n"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/main/java/com/markadamson/taskerplugin/homeassistant/model/HAServerStore.java",
    "content": "package com.markadamson.taskerplugin.homeassistant.model;\n\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.preference.PreferenceManager;\n\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.Map;\nimport java.util.UUID;\n\npublic class HAServerStore {\n    private final Context mContext;\n\n    public HAServerStore(final Context context) {\n        mContext = context;\n    }\n\n    private static String PREFS_KEY = \"com.markadamson.taskerplugin.homeassistant.model.HAServerStore.PREFS_KEY\";\n\n    public Map<UUID, HAServer> getServers() {\n        Map<UUID,HAServer> result = new HashMap<>();\n\n        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);\n        if (prefs.contains(PREFS_KEY)) {\n            try {\n                JSONObject jsonServers = new JSONObject(prefs.getString(PREFS_KEY, \"\"));\n                Iterator<String> keys = jsonServers.keys();\n\n                while (keys.hasNext()) {\n                    String uuid = keys.next();\n                    result.put(UUID.fromString(uuid), HAServer.fromJSON((JSONObject) jsonServers.get(uuid)));\n                }\n            } catch (JSONException e) {\n                e.printStackTrace();\n                prefs.edit().remove(PREFS_KEY).apply();\n            }\n        }\n\n        return result;\n    }\n\n    public UUID addServer(HAServer server) {\n        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);\n\n        JSONObject jsonServers;\n        if (prefs.contains(PREFS_KEY)) {\n            try {\n                jsonServers = new JSONObject(prefs.getString(PREFS_KEY, \"\"));\n            } catch (JSONException e) {\n                jsonServers = new JSONObject();\n            }\n        } else\n            jsonServers = new JSONObject();\n\n        UUID result = UUID.randomUUID();\n\n        try {\n            jsonServers.put(result.toString(), server.toJSON());\n        } catch (JSONException e) {\n            e.printStackTrace();\n        }\n\n        prefs.edit().putString(PREFS_KEY, jsonServers.toString()).apply();\n\n        return result;\n    }\n\n    public void deleteServer(UUID serverID) {\n        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);\n\n        if (prefs.contains(PREFS_KEY)) {\n            JSONObject jsonServers;\n            try {\n                jsonServers = new JSONObject(prefs.getString(PREFS_KEY, \"\"));\n            } catch (JSONException e) {\n                e.printStackTrace();\n                prefs.edit().remove(PREFS_KEY).apply();\n                return;\n            }\n\n            if (jsonServers.has(serverID.toString())) {\n                jsonServers.remove(serverID.toString());\n                prefs.edit().putString(PREFS_KEY, jsonServers.toString()).apply();\n            }\n        }\n    }\n\n    public void updateServer(UUID serverID, HAServer server) {\n        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);\n\n        if (!prefs.contains(PREFS_KEY))\n            throw new RuntimeException(\"No such server in storage!\");\n\n        JSONObject jsonServers;\n\n        try {\n            jsonServers = new JSONObject(prefs.getString(PREFS_KEY, \"\"));\n        } catch (JSONException e) {\n            e.printStackTrace();\n            prefs.edit().remove(PREFS_KEY).apply();\n            throw new RuntimeException(\"No such server in storage!\", e);\n        }\n\n        if (!jsonServers.has(serverID.toString()))\n            throw new RuntimeException(\"No such server in storage!\");\n\n        jsonServers.remove(serverID.toString());\n        try {\n            jsonServers.put(serverID.toString(), server.toJSON());\n        } catch (JSONException e) {\n            e.printStackTrace();\n        }\n        prefs.edit().putString(PREFS_KEY, jsonServers.toString()).apply();\n    }\n}\n"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/main/java/com/markadamson/taskerplugin/homeassistant/receiver/AbstractAsyncReceiver.java",
    "content": "/*\n * home-assistant-plugin-for-tasker <https://github.com/MarkAdamson/home-assistant-plugin-for-tasker>\n * Copyright 2019 Mark Adamson\n *\n * Original author:\n * android-plugin-client-sdk-for-locale https://github.com/twofortyfouram/android-plugin-client-sdk-for-locale\n * Copyright 2014 two forty four a.m. LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except\n * in compliance with the License. You may obtain a copy of the License at\n *\n *  http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under the\n * License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either\n * express or implied. See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.markadamson.taskerplugin.homeassistant.receiver;\n\nimport android.annotation.TargetApi;\nimport android.content.BroadcastReceiver;\nimport android.os.Build.VERSION_CODES;\nimport android.os.Handler;\nimport android.os.HandlerThread;\nimport android.os.Looper;\nimport android.os.Message;\nimport android.support.annotation.NonNull;\nimport android.util.Pair;\n\nimport com.twofortyfouram.spackle.AndroidSdkVersion;\nimport com.twofortyfouram.spackle.ThreadUtil;\nimport com.twofortyfouram.spackle.ThreadUtil.ThreadPriority;\n\nimport net.jcip.annotations.ThreadSafe;\n\nimport static com.twofortyfouram.assertion.Assertions.assertInRangeInclusive;\nimport static com.twofortyfouram.assertion.Assertions.assertNotNull;\n\n/**\n * Simplifies asynchronous broadcast handling. Subclasses call\n * {@link #goAsyncWithCallback(AsyncCallback, boolean)}, and the abstract class takes\n * care of executing the callback on a background thread.\n */\n@ThreadSafe\n/* package */ abstract class AbstractAsyncReceiver extends BroadcastReceiver {\n\n    /*\n     * This method is package visible rather than protected so that it will be\n     * obfuscated by ProGuard.\n     *\n     * @param callback Callback to execute on a background thread.\n     * @param isOrdered Indicates whether an ordered broadcast is being processed.\n     */\n    @TargetApi(VERSION_CODES.HONEYCOMB)\n    /* package */ final void goAsyncWithCallback(@NonNull final AsyncCallback callback,\n                                                 final boolean isOrdered) {\n        assertNotNull(callback, \"callback\"); //$NON-NLS-1$\n\n        final PendingResult pendingResult = goAsync();\n        if (null == pendingResult) {\n            throw new AssertionError(\n                    \"PendingResult was null.  Was goAsync() called previously?\"); //$NON-NLS-1$\n        }\n\n        final Handler.Callback handlerCallback = new AsyncHandlerCallback();\n        final HandlerThread thread = ThreadUtil.newHandlerThread(getClass().getName(),\n                ThreadPriority.BACKGROUND);\n        final Handler handler = new Handler(thread.getLooper(), handlerCallback);\n\n        final Object obj = new Pair<PendingResult, AsyncCallback>(pendingResult, callback);\n        final int isOrderedInt = isOrdered ? 1 : 0;\n        final Message msg = handler\n                .obtainMessage(AsyncHandlerCallback.MESSAGE_HANDLE_CALLBACK, isOrderedInt, 0, obj);\n\n        final boolean isMessageSent = handler.sendMessage(msg);\n        if (!isMessageSent) {\n            throw new AssertionError();\n        }\n    }\n\n    @TargetApi(VERSION_CODES.HONEYCOMB)\n    private static final class AsyncHandlerCallback implements Handler.Callback {\n\n        /**\n         * Message MUST contain a {@code Pair<PendingResult, AsyncCallback>} as the {@code msg.obj}\n         * and a boolean encoded in the {@code msg.arg1} to indicate whether the broadcast was\n         * ordered.\n         */\n        public static final int MESSAGE_HANDLE_CALLBACK = 0;\n\n        @Override\n        public boolean handleMessage(final Message msg) {\n            assertNotNull(msg, \"msg\"); //$NON-NLS-1$\n            switch (msg.what) {\n                case MESSAGE_HANDLE_CALLBACK: {\n                    assertNotNull(msg.obj, \"msg.obj\"); //$NON-NLS-1$\n                    assertInRangeInclusive(msg.arg1, 0, 1, \"msg.arg1\");  //$NON-NLS-1$\n\n                    final Pair<PendingResult, AsyncCallback> pair = castObj(msg.obj);\n                    final boolean isOrdered = 0 != msg.arg1;\n\n                    final PendingResult pendingResult = pair.first;\n                    final AsyncCallback asyncCallback = pair.second;\n\n                    try {\n                        final int resultCode = asyncCallback.runAsync();\n\n                        if (isOrdered) {\n                            pendingResult.setResultCode(resultCode);\n                        }\n                    } finally {\n                        pendingResult.finish();\n                    }\n\n                    quit();\n\n                    break;\n                }\n            }\n            return true;\n        }\n\n        @NonNull\n        @SuppressWarnings(\"unchecked\")\n        private static Pair<PendingResult, AsyncCallback> castObj(@NonNull final Object o) {\n            return (Pair<PendingResult, AsyncCallback>) o;\n        }\n\n        private static void quit() {\n            if (AndroidSdkVersion.isAtLeastSdk(VERSION_CODES.JELLY_BEAN_MR2)) {\n                quitJellybeanMr2();\n            } else {\n                Looper.myLooper().quit();\n            }\n        }\n\n        @TargetApi(VERSION_CODES.JELLY_BEAN_MR2)\n        private static void quitJellybeanMr2() {\n            Looper.myLooper().quitSafely();\n        }\n    }\n\n    /* package */static interface AsyncCallback {\n\n        /**\n         * @return The result code to be set if this is an ordered broadcast.\n         */\n        public int runAsync();\n    }\n}"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/main/java/com/markadamson/taskerplugin/homeassistant/receiver/AbstractPluginSettingReceiver.java",
    "content": "/*\n * home-assistant-plugin-for-tasker <https://github.com/MarkAdamson/home-assistant-plugin-for-tasker>\n * Copyright 2019 Mark Adamson\n *\n * Original author:\n * android-plugin-client-sdk-for-locale https://github.com/twofortyfouram/android-plugin-client-sdk-for-locale\n * Copyright 2014 two forty four a.m. LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except\n * in compliance with the License. You may obtain a copy of the License at\n *\n *  http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under the\n * License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either\n * express or implied. See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.markadamson.taskerplugin.homeassistant.receiver;\n\n\nimport android.app.Activity;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.support.annotation.NonNull;\n\nimport com.twofortyfouram.log.Lumberjack;\nimport com.twofortyfouram.spackle.AndroidSdkVersion;\nimport com.twofortyfouram.spackle.bundle.BundleScrubber;\n\n/**\n * <p>Abstract superclass for a plug-in setting BroadcastReceiver implementation.</p>\n * <p>The plug-in receiver lifecycle is as follows:</p>\n * <ol>\n * <li>{@link #onReceive(android.content.Context, android.content.Intent)} is called by the Android\n * frameworks.\n * onReceive() will verify that the Intent is valid.  If the Intent is invalid, the receiver\n * returns\n * immediately.  If the Intent appears to be valid, then the lifecycle continues.</li>\n * <li>{@link #isBundleValid(android.os.Bundle)} is called to determine whether {@link\n * com.twofortyfouram.locale.api.Intent#EXTRA_BUNDLE EXTRA_BUNDLE} is valid. If the Bundle is\n * invalid, then the\n * receiver returns immediately.  If the bundle is valid, then the lifecycle continues.</li>\n * <li>{@link #isAsync()} is called to determine whether the remaining work should be performed on\n * a\n * background thread.</li>\n * <li>{@link #firePluginSetting(android.content.Context, android.os.Bundle)} is called to trigger\n * the plug-in setting's action.</li>\n * </ol>\n * <p>\n * Implementations of this BroadcastReceiver must be registered in the Android\n * Manifest with an Intent filter for\n * {@link com.twofortyfouram.locale.api.Intent#ACTION_FIRE_SETTING ACTION_FIRE_SETTING}. The\n * BroadcastReceiver must be exported, enabled, and cannot have permissions\n * enforced on it.\n * </p>\n */\npublic abstract class AbstractPluginSettingReceiver extends AbstractAsyncReceiver {\n\n    /*\n     * The multiple return statements in this method are a little gross, but the\n     * alternative of nested if statements is even worse :/\n     */\n    @Override\n    public final void onReceive(final Context context, final Intent intent) {\n        if (BundleScrubber.scrub(intent)) {\n            return;\n        }\n        Lumberjack.v(\"Received %s\", intent); //$NON-NLS-1$\n\n        /*\n         * Note: It is OK if a host sends an ordered broadcast for plug-in\n         * settings. Such a behavior would allow the host to optionally block until the\n         * plug-in setting finishes.\n         */\n\n        if (!com.twofortyfouram.locale.api.Intent.ACTION_FIRE_SETTING.equals(intent.getAction())) {\n            Lumberjack\n                    .e(\"Intent action is not %s\",\n                            com.twofortyfouram.locale.api.Intent.ACTION_FIRE_SETTING); //$NON-NLS-1$\n            return;\n        }\n\n        /*\n         * Ignore implicit intents, because they are not valid. It would be\n         * meaningless if ALL plug-in setting BroadcastReceivers installed were\n         * asked to handle queries not intended for them. Ideally this\n         * implementation here would also explicitly assert the class name as\n         * well, but then the unit tests would have trouble. In the end,\n         * asserting the package is probably good enough.\n         */\n        if (!context.getPackageName().equals(intent.getPackage())\n                && !new ComponentName(context, this.getClass().getName()).equals(intent\n                .getComponent())) {\n            Lumberjack.e(\"Intent is not explicit\"); //$NON-NLS-1$\n            return;\n        }\n\n        final Bundle bundle = intent\n                .getBundleExtra(com.twofortyfouram.locale.api.Intent.EXTRA_BUNDLE);\n        if (BundleScrubber.scrub(intent)) {\n            return;\n        }\n\n        if (null == bundle) {\n            Lumberjack.e(\"%s is missing\",\n                    com.twofortyfouram.locale.api.Intent.EXTRA_BUNDLE); //$NON-NLS-1$\n            return;\n        }\n\n        if (!isBundleValid(bundle)) {\n            Lumberjack.e(\"%s is invalid\",\n                    com.twofortyfouram.locale.api.Intent.EXTRA_BUNDLE); //$NON-NLS-1$\n            return;\n        }\n\n        if (isAsync() && AndroidSdkVersion.isAtLeastSdk(Build.VERSION_CODES.HONEYCOMB)) {\n            final AsyncCallback callback = new AsyncCallback() {\n\n                @NonNull\n                private final Context mContext = context;\n\n                @NonNull\n                private final Bundle mBundle = bundle;\n\n                @NonNull\n                private final Intent mIntent = intent;\n\n                @Override\n                public int runAsync() {\n                    firePluginSetting(mContext, mIntent, mBundle);\n                    return Activity.RESULT_OK;\n                }\n\n            };\n\n            goAsyncWithCallback(callback, isOrderedBroadcast());\n        } else {\n            firePluginSetting(context, intent, bundle);\n        }\n    }\n\n    /**\n     * <p>Gives the plug-in receiver an opportunity to validate the Bundle, to\n     * ensure that a malicious application isn't attempting to pass\n     * an invalid Bundle.</p>\n     * <p>\n     * This method will be called on the BroadcastReceiver's Looper (normatively the main thread)\n     * </p>\n     *\n     * @param bundle The plug-in's Bundle previously returned by the edit\n     *               Activity.  {@code bundle} should not be mutated by this method.\n     * @return true if {@code bundle} appears to be valid.  false if {@code bundle} appears to be\n     * invalid.\n     */\n    protected abstract boolean isBundleValid(@NonNull final Bundle bundle);\n\n    /**\n     * Configures the receiver whether it should process the Intent in a\n     * background thread. Plug-ins should return true if their\n     * {@link #firePluginSetting(android.content.Context, android.os.Bundle)} method performs any\n     * sort of disk IO (ContentProvider query, reading SharedPreferences, etc.).\n     * or other work that may be slow.\n     * <p>\n     * Asynchronous BroadcastReceivers are not supported prior to Honeycomb, so\n     * with older platforms broadcasts will always be processed on the BroadcastReceiver's Looper\n     * (which for Manifest registered receivers will be the main thread).\n     *\n     * @return True if the receiver should process the Intent in a background\n     * thread. False if the plug-in should process the Intent on the\n     * BroadcastReceiver's Looper (normatively the main thread).\n     */\n    protected abstract boolean isAsync();\n\n    /**\n     * If {@link #isAsync()} returns true, this method will be called on a\n     * background thread. If {@link #isAsync()} returns false, this method will\n     * be called on the main thread. Regardless of which thread this method is\n     * called on, this method MUST return within 10 seconds per the requirements\n     * for BroadcastReceivers.\n     *\n     * @param context BroadcastReceiver context.\n     * @param bundle  The plug-in's Bundle previously returned by the edit\n     *                Activity.\n     */\n    protected abstract void firePluginSetting(@NonNull final Context context,\n                                              @NonNull final Intent intent,\n                                              @NonNull final Bundle bundle);\n}"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/main/java/com/markadamson/taskerplugin/homeassistant/receiver/FireReceiver.java",
    "content": "/*\n * home-assistant-plugin-for-tasker <https://github.com/MarkAdamson/home-assistant-plugin-for-tasker>\n * Copyright 2019 Mark Adamson\n *\n * Original author:\n * android-toast-setting-plugin-for-locale <https://github.com/twofortyfouram/android-toast-setting-plugin-for-locale>\n * Copyright 2014 two forty four a.m. LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except\n * in compliance with the License. You may obtain a copy of the License at\n *\n *  http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under the\n * License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either\n * express or implied. See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.markadamson.taskerplugin.homeassistant.receiver;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.support.annotation.NonNull;\n\nimport com.markadamson.taskerplugin.homeassistant.Constants;\nimport com.markadamson.taskerplugin.homeassistant.TaskerPlugin;\nimport com.markadamson.taskerplugin.homeassistant.bundle.GetStatePluginBundleValues;\nimport com.markadamson.taskerplugin.homeassistant.bundle.PluginBundleValues;\nimport com.markadamson.taskerplugin.homeassistant.bundle.RenderTemplatePluginBundleValues;\nimport com.markadamson.taskerplugin.homeassistant.service.ActionService;\nimport com.twofortyfouram.log.Lumberjack;\n\npublic final class FireReceiver extends AbstractPluginSettingReceiver {\n\n    @Override\n    protected boolean isBundleValid(@NonNull final Bundle bundle) {\n        if (bundle.getInt(PluginBundleValues.BUNDLE_EXTRA_INT_VERSION_CODE) < 3 || bundle.getInt(Constants.BUNDLE_EXTRA_BUNDLE_TYPE) == Constants.BUNDLE_CALL_SERVICE)\n            return PluginBundleValues.isBundleValid(bundle);\n        else if (bundle.getInt(Constants.BUNDLE_EXTRA_BUNDLE_TYPE) == Constants.BUNDLE_GET_STATE)\n            return GetStatePluginBundleValues.isBundleValid(bundle);\n        else\n            return RenderTemplatePluginBundleValues.isBundleValid(bundle);\n    }\n\n    @Override\n    protected boolean isAsync() {\n        return false;\n    }\n\n    @Override\n    protected void firePluginSetting(@NonNull final Context context, @NonNull final Intent intent, @NonNull final Bundle bundle) {\n        Lumberjack.d(\"FireReceiver.firePluginSetting\");\n        intent.putExtra(ActionService.EXT_BUNDLE, bundle);\n        ActionService.enqueueWork(context, intent);\n        Lumberjack.d(\"Set result code \\\" Pending\\\"\");\n        if (isOrderedBroadcast())\n            setResultCode(TaskerPlugin.Setting.RESULT_CODE_PENDING);\n    }\n\n\n}\n"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/main/java/com/markadamson/taskerplugin/homeassistant/service/ActionService.java",
    "content": "package com.markadamson.taskerplugin.homeassistant.service;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.support.annotation.NonNull;\nimport android.support.v4.app.JobIntentService;\n\nimport com.markadamson.taskerplugin.homeassistant.Constants;\nimport com.markadamson.taskerplugin.homeassistant.TaskerPlugin;\nimport com.markadamson.taskerplugin.homeassistant.bundle.GetStatePluginBundleValues;\nimport com.markadamson.taskerplugin.homeassistant.bundle.PluginBundleValues;\nimport com.markadamson.taskerplugin.homeassistant.bundle.RenderTemplatePluginBundleValues;\nimport com.markadamson.taskerplugin.homeassistant.model.HAAPI;\nimport com.markadamson.taskerplugin.homeassistant.model.HAAPIException;\nimport com.markadamson.taskerplugin.homeassistant.model.HAEntity;\nimport com.markadamson.taskerplugin.homeassistant.model.HAServerStore;\nimport com.twofortyfouram.log.Lumberjack;\n\nimport java.util.UUID;\n\n/**\n * Created by marka on 14/06/2019.\n */\n\npublic class ActionService extends JobIntentService {\n    public static final String EXT_BUNDLE = \"com.markadamson.taskerplugin.homeassistant.service.ActionService.EXT_BUNDLE\";\n\n    /**\n     * Unique job ID for this service.\n     */\n    static final int JOB_ID = 1000;\n\n    /**\n     * Convenience method for enqueuing work in to this service.\n     */\n    public static void enqueueWork(Context context, Intent work) {\n        Lumberjack.d(\"ActionService.enqueueWork\");\n        enqueueWork(context, ActionService.class, JOB_ID, work);\n    }\n\n    private void signalUnknownServer(Intent intent) {\n        Bundle vars = new Bundle();\n        vars.putString(TaskerPlugin.Setting.VARNAME_ERROR_MESSAGE, \"Unknown Server - Has it been deleted?\");\n        TaskerPlugin.Setting.signalFinish(this, intent, TaskerPlugin.Setting.RESULT_CODE_FAILED, vars);\n    }\n\n    @Override\n    protected void onHandleWork(@NonNull Intent intent) {\n        Lumberjack.d(\"ActionService.onHandleWork\");\n        Bundle bundle = intent.getBundleExtra(EXT_BUNDLE);\n        HAServerStore servers = new HAServerStore(this);\n\n        try {\n            if (bundle.getInt(PluginBundleValues.BUNDLE_EXTRA_INT_VERSION_CODE) < 3 || bundle.getInt(Constants.BUNDLE_EXTRA_BUNDLE_TYPE) == Constants.BUNDLE_CALL_SERVICE) {\n                Lumberjack.d(\"Call Service\", bundle);\n                UUID serverId = PluginBundleValues.getServer(bundle);\n                if (!servers.getServers().containsKey(serverId)) {\n                    signalUnknownServer(intent);\n                    return;\n                }\n\n                String[] service = PluginBundleValues.getService(bundle).split(\"\\\\.\");\n                Lumberjack.d(\"Calling api...\");\n                new HAAPI(servers.getServers().get(serverId))\n                        .callService(service[0], service[1], PluginBundleValues.getData(bundle));\n                Lumberjack.d(\"Signalling finish...\");\n                TaskerPlugin.Setting.signalFinish(this, intent, TaskerPlugin.Setting.RESULT_CODE_OK, null);\n            } else if (bundle.getInt(Constants.BUNDLE_EXTRA_BUNDLE_TYPE) == Constants.BUNDLE_GET_STATE) {\n                Lumberjack.d(\"Get State\", bundle);\n                UUID serverId = GetStatePluginBundleValues.getServer(bundle);\n                if (!servers.getServers().containsKey(serverId)) {\n                    signalUnknownServer(intent);\n                    return;\n                }\n\n                String entityId = GetStatePluginBundleValues.getEntity(bundle);\n                Lumberjack.d(\"Calling api...\");\n                HAEntity entity = new HAAPI(servers.getServers().get(serverId))\n                        .getEntity(entityId);\n                Bundle vars = new Bundle();\n                vars.putString(GetStatePluginBundleValues.getStateVariable(bundle), entity.getState());\n\n                String attrsVar = GetStatePluginBundleValues.getAttrsVariable(bundle);\n                if (!attrsVar.isEmpty())\n                    vars.putString(attrsVar, entity.getAttributes());\n\n                Lumberjack.d(\"Signalling finish...\");\n                TaskerPlugin.Setting.signalFinish(this, intent, TaskerPlugin.Setting.RESULT_CODE_OK, vars);\n            } else {\n                Lumberjack.d(\"Render Template\", bundle);\n                UUID serverId = RenderTemplatePluginBundleValues.getServer(bundle);\n                if (!servers.getServers().containsKey(serverId)) {\n                    signalUnknownServer(intent);\n                    return;\n                }\n\n                String template = RenderTemplatePluginBundleValues.getTemplate(bundle);\n                Lumberjack.d(\"Calling api...\");\n                String result = new HAAPI(servers.getServers().get(serverId))\n                        .renderTemplate(template);\n                Bundle vars = new Bundle();\n                vars.putString(RenderTemplatePluginBundleValues.getVariable(bundle), result);\n\n                Lumberjack.d(\"Signalling finish...\");\n                TaskerPlugin.Setting.signalFinish(this, intent, TaskerPlugin.Setting.RESULT_CODE_OK, vars);\n            }\n        } catch (HAAPIException e) {\n            Lumberjack.e(e.getMessage());\n            e.printStackTrace();\n            Bundle vars = new Bundle();\n            vars.putString(TaskerPlugin.Setting.VARNAME_ERROR_MESSAGE, e.getMessage());\n            TaskerPlugin.Setting.signalFinish(this, intent, TaskerPlugin.Setting.RESULT_CODE_FAILED, vars);\n        }\n    }\n}\n"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/main/java/com/markadamson/taskerplugin/homeassistant/ui/ServerSelectionUI.java",
    "content": "package com.markadamson.taskerplugin.homeassistant.ui;\n\nimport android.app.Activity;\nimport android.content.DialogInterface;\nimport android.content.Intent;\nimport android.support.v7.app.AlertDialog;\nimport android.view.View;\nimport android.widget.AdapterView;\nimport android.widget.ArrayAdapter;\nimport android.widget.Spinner;\n\nimport com.markadamson.taskerplugin.homeassistant.R;\nimport com.markadamson.taskerplugin.homeassistant.model.HAServer;\nimport com.markadamson.taskerplugin.homeassistant.model.HAServerStore;\nimport com.markadamson.taskerplugin.homeassistant.ui.activity.EditServerActivity;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\n\npublic class ServerSelectionUI {\n    private final CharSequence mHostName;\n    private final HAServerStore mServerStore;\n    private final Spinner spnServers;\n\n    private List<UUID> mIds;\n    private List<HAServer> mServers;\n    private ArrayAdapter<HAServer> mServerAdapter;\n\n    public ServerSelectionUI(final Activity activity, final CharSequence hostName, final OnServerSelectedListener serverListener) {\n        mHostName = hostName;\n        mServerStore = new HAServerStore(activity);\n\n        Map<UUID,HAServer> serverMap = mServerStore.getServers();\n        mIds = new ArrayList<>(serverMap.keySet());\n        mServers = new ArrayList<>();\n        for(UUID id : mIds)\n            mServers.add(serverMap.get(id));\n        mServerAdapter = new ArrayAdapter<>(activity, android.R.layout.simple_spinner_dropdown_item, mServers);\n\n        spnServers = activity.findViewById(R.id.spn_server);\n        spnServers.setAdapter(mServerAdapter);\n\n        spnServers.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {\n            @Override\n            public void onItemSelected(AdapterView<?> adapterView, View view, int position, long id) {\n                if (serverListener != null)\n                    serverListener.onServerSelected(mServers.get(position));\n            }\n\n            @Override\n            public void onNothingSelected(AdapterView<?> adapterView) {\n                if (serverListener != null)\n                    serverListener.onNothingSelected();\n            }\n        });\n\n        activity.findViewById(R.id.btn_add_server).setOnClickListener(\n                new View.OnClickListener() {\n                    @Override\n                    public void onClick(View view) {\n                        activity.startActivityForResult(\n                                new Intent(activity, EditServerActivity.class)\n                                        .putExtra(EditServerActivity.EXT_HOST_NAME, mHostName)\n                                        .putExtra(EditServerActivity.EXT_REQUEST_CODE, EditServerActivity.REQ_NEW_SERVER),\n                                EditServerActivity.REQ_NEW_SERVER);\n                    }\n                }\n        );\n\n        activity.findViewById(R.id.btn_edit_server).setOnClickListener(\n                new View.OnClickListener() {\n                    @Override\n                    public void onClick(View view) {\n                        if (mServerAdapter.getCount() > 0) {\n                            HAServer server = mServerAdapter.getItem(spnServers.getSelectedItemPosition());\n                            activity.startActivityForResult(\n                                    new Intent(activity, EditServerActivity.class)\n                                            .putExtra(EditServerActivity.EXT_HOST_NAME, mHostName)\n                                            .putExtra(EditServerActivity.EXT_REQUEST_CODE, EditServerActivity.REQ_EDIT_SERVER)\n                                            .putExtra(EditServerActivity.EXT_SERVER_NAME, server.getName())\n                                            .putExtra(EditServerActivity.EXT_BASE_URL, server.getBaseURL())\n                                            .putExtra(EditServerActivity.EXT_ACCESS_TOKEN, server.getAccessToken()),\n                                    EditServerActivity.REQ_EDIT_SERVER\n                            );\n                        }\n                    }\n                }\n        );\n\n        activity.findViewById(R.id.btn_delete_server).setOnClickListener(\n                new View.OnClickListener() {\n                    @Override\n                    public void onClick(View view) {\n                        if (mServers.size() > 0)\n                            new AlertDialog.Builder(activity)\n                                    .setTitle(R.string.are_you_sure)\n                                    .setMessage(R.string.delete_server_warning)\n                                    .setPositiveButton(R.string.yes_delete_the_server, new DialogInterface.OnClickListener() {\n                                        @Override\n                                        public void onClick(DialogInterface dialog, int which) {\n                                            int idx = spnServers.getSelectedItemPosition();\n                                            mServerStore.deleteServer(mIds.get(idx));\n                                            mIds.remove(idx);\n                                            mServers.remove(idx);\n                                            mServerAdapter.notifyDataSetChanged();\n                                        }\n                                    })\n                                    .setNegativeButton(android.R.string.no, null)\n                                    .show();\n                    }\n                }\n        );\n    }\n\n    public int serverCount() {\n        return mServers.size();\n    }\n\n    public HAServer currentServer() {\n        return mServers.get(spnServers.getSelectedItemPosition());\n    }\n\n    public UUID currentId() {\n        return mIds.get(spnServers.getSelectedItemPosition());\n    }\n\n    public void setSelection(UUID id) {\n        spnServers.setSelection(mIds.indexOf(id));\n    }\n\n    public boolean onActivityResult(int requestCode, int resultCode, Intent data) {\n        if (!Arrays.asList(new Integer[]{EditServerActivity.REQ_NEW_SERVER,EditServerActivity.REQ_EDIT_SERVER}).contains(requestCode))\n            return false;\n\n        if (resultCode == Activity.RESULT_OK) {\n            HAServer server = new HAServer(\n                    data.getStringExtra(EditServerActivity.EXT_SERVER_NAME),\n                    data.getStringExtra(EditServerActivity.EXT_BASE_URL),\n                    data.getStringExtra(EditServerActivity.EXT_ACCESS_TOKEN)\n            );\n\n            switch (requestCode) {\n                case EditServerActivity.REQ_NEW_SERVER:\n                    mIds.add(mServerStore.addServer(server));\n                    mServerAdapter.add(server);\n                    break;\n                case EditServerActivity.REQ_EDIT_SERVER:\n                    int idx = spnServers.getSelectedItemPosition();\n                    mServerStore.updateServer(mIds.get(idx), server);\n                    mServers.set(idx, server);\n                    mServerAdapter.notifyDataSetChanged();\n                    break;\n            }\n        }\n\n        return true;\n    }\n\n    public interface OnServerSelectedListener {\n        void onServerSelected(HAServer server);\n        void onNothingSelected();\n    }\n}\n"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/main/java/com/markadamson/taskerplugin/homeassistant/ui/activity/EditActivity.java",
    "content": "/*\n * home-assistant-plugin-for-tasker <https://github.com/MarkAdamson/home-assistant-plugin-for-tasker>\n * Copyright 2019 Mark Adamson\n *\n * Original author:\n * android-toast-setting-plugin-for-locale <https://github.com/twofortyfouram/android-toast-setting-plugin-for-locale>\n * Copyright 2014 two forty four a.m. LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except\n * in compliance with the License. You may obtain a copy of the License at\n *\n *  http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under the\n * License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either\n * express or implied. See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.markadamson.taskerplugin.homeassistant.ui.activity;\n\nimport android.content.Intent;\nimport android.content.pm.PackageManager;\nimport android.content.res.Resources;\nimport android.os.Bundle;\nimport android.support.annotation.NonNull;\nimport android.view.Menu;\nimport android.view.MenuItem;\nimport android.view.View;\nimport android.widget.ArrayAdapter;\nimport android.widget.AutoCompleteTextView;\nimport android.widget.EditText;\nimport android.widget.Toast;\n\nimport com.markadamson.locale.sdk.client.ui.activity.AbstractAppCompatPluginActivity;\nimport com.markadamson.taskerplugin.homeassistant.R;\nimport com.markadamson.taskerplugin.homeassistant.TaskerPlugin;\nimport com.markadamson.taskerplugin.homeassistant.Utils;\nimport com.markadamson.taskerplugin.homeassistant.bundle.PluginBundleValues;\nimport com.markadamson.taskerplugin.homeassistant.model.HAAPI;\nimport com.markadamson.taskerplugin.homeassistant.model.HAAPIException;\nimport com.markadamson.taskerplugin.homeassistant.model.HAAPIResult;\nimport com.markadamson.taskerplugin.homeassistant.model.HAAPITask;\nimport com.markadamson.taskerplugin.homeassistant.model.HAServer;\nimport com.markadamson.taskerplugin.homeassistant.ui.ServerSelectionUI;\nimport com.twofortyfouram.log.Lumberjack;\n\nimport net.jcip.annotations.NotThreadSafe;\n\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\nimport java.lang.ref.WeakReference;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.UUID;\n\n@NotThreadSafe\npublic final class EditActivity extends AbstractAppCompatPluginActivity {\n    private ServerSelectionUI mServerUI;\n\n    private ArrayAdapter<String> mServiceAdapter;\n    private AutoCompleteTextView atvService;\n    private EditText etServiceData;\n\n    private abstract static class MyAPITask<Params,Progress,Result> extends HAAPITask<Params,Progress,Result> {\n        WeakReference<EditActivity> activityReference;\n\n        MyAPITask(EditActivity activity, HAServer server) {\n            super(server);\n            activityReference = new WeakReference<>(activity);\n        }\n    }\n\n    private static class GetServicesTask extends MyAPITask<Void,Void,List<String>> {\n        GetServicesTask(EditActivity activity, HAServer server) {\n            super(activity, server);\n            activity.mServiceAdapter.clear();\n        }\n\n        @Override\n        protected List<String> doAPIInBackground(HAAPI api, Void... voids) throws HAAPIException {\n            return api.getServices();\n        }\n\n        @Override\n        protected void onPostExecute(HAAPIResult<List<String>> services) {\n            EditActivity activity = activityReference.get();\n            if (activity == null || activity.isFinishing()) return;\n\n            if (services.getException() != null) {\n                services.getException().printStackTrace();\n                Toast.makeText(activity, services.getException().getMessage(), Toast.LENGTH_SHORT).show();\n                return;\n            }\n\n            activity.mServiceAdapter.clear();\n            activity.mServiceAdapter.addAll(services.getResult());\n        }\n    }\n\n    private static class TestServiceTask extends MyAPITask<String,Void,Void> {\n\n        TestServiceTask(EditActivity context, HAServer server) {\n            super(context, server);\n        }\n\n        @Override\n        protected Void doAPIInBackground(HAAPI api, String... strings) throws HAAPIException {\n            api.callService(strings[0], strings[1], strings[2]);\n            return null;\n        }\n\n        @Override\n        protected void onPostExecute(HAAPIResult<Void> result) {\n            EditActivity activity = activityReference.get();\n            if (activity == null || activity.isFinishing()) return;\n\n            if (result.getException() != null) {\n                result.getException().printStackTrace();\n                Toast.makeText(activity, result.getException().getMessage(), Toast.LENGTH_SHORT).show();\n                return;\n            }\n\n            Toast.makeText(activity, \"Success!\", Toast.LENGTH_SHORT).show();\n        }\n    }\n\n    @Override\n    protected void onCreate(final Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        setContentView(R.layout.main);\n\n        /*\n         * To help the user keep context, the title shows the host's name and the subtitle\n         * shows the plug-in's name.\n         */\n        CharSequence callingApplicationLabel = null;\n        try {\n            callingApplicationLabel =\n                    getPackageManager().getApplicationLabel(\n                            getPackageManager().getApplicationInfo(getCallingPackage(),\n                                    0));\n        } catch (final PackageManager.NameNotFoundException e) {\n            Lumberjack.e(\"Calling package couldn't be found%s\", e); //$NON-NLS-1$\n        }\n        if (null != callingApplicationLabel) {\n            setTitle(callingApplicationLabel);\n        }\n\n        Resources r = getResources();\n        getSupportActionBar().setSubtitle(r.getString(R.string.activity_subtitle, r.getString(R.string.call_service)));\n\n        getSupportActionBar().setDisplayHomeAsUpEnabled(true);\n\n        mServerUI = new ServerSelectionUI(this, callingApplicationLabel, new ServerSelectionUI.OnServerSelectedListener() {\n            @Override\n            public void onServerSelected(HAServer server) {\n                new GetServicesTask(EditActivity.this, server).execute();\n            }\n\n            @Override\n            public void onNothingSelected() {\n                mServiceAdapter.clear();\n            }\n        });\n\n\n        String[] variablesFromHost = TaskerPlugin.getRelevantVariableList(getIntent().getExtras());\n\n        mServiceAdapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_dropdown_item, new ArrayList<String>());\n        atvService = findViewById(R.id.atv_service);\n        atvService.setAdapter(mServiceAdapter);\n        Utils.initVariableSelectUI(variablesFromHost, findViewById(R.id.btn_service_variable), atvService);\n\n        etServiceData = findViewById(R.id.et_service_data);\n        Utils.initVariableSelectUI(variablesFromHost, findViewById(R.id.btn_service_data_variable), etServiceData);\n\n        findViewById(R.id.btn_test_service).setOnClickListener(\n                new View.OnClickListener() {\n                    @Override\n                    public void onClick(View view) {\n                        if (atvService.getText().toString().contains(\"%\")) {\n                            Toast.makeText(EditActivity.this, \"Cannot test using variables!\", Toast.LENGTH_SHORT).show();\n                            return;\n                        }\n\n                        String[] service = atvService.getText().toString().split(\"\\\\.\");\n                        if (service.length != 2) {\n                            Toast.makeText(EditActivity.this, \"Invalid service!\", Toast.LENGTH_SHORT).show();\n                            return;\n                        }\n\n                        new TestServiceTask(EditActivity.this, mServerUI.currentServer())\n                                .execute(service[0], service[1], etServiceData.getText().toString());\n                    }\n                }\n        );\n\n        Utils.checkBatteryOptimisation(this);\n    }\n\n    @Override\n    protected void onActivityResult(int requestCode, int resultCode, Intent data) {\n        super.onActivityResult(requestCode, resultCode, data);\n        mServerUI.onActivityResult(requestCode, resultCode, data);\n    }\n\n    private void restoreState(UUID id, String service, String data) {\n        mServerUI.setSelection(id);\n        atvService.setText(service);\n        etServiceData.setText(data);\n    }\n\n    @Override\n    public void onPostCreateWithPreviousResult(@NonNull final Bundle previousBundle,\n            @NonNull final String previousBlurb) {\n        restoreState(\n                PluginBundleValues.getServer(previousBundle),\n                PluginBundleValues.getService(previousBundle),\n                PluginBundleValues.getData(previousBundle)\n        );\n    }\n\n    @Override\n    public boolean isBundleValid(@NonNull final Bundle bundle) {\n        return PluginBundleValues.isBundleValid(bundle);\n    }\n\n    @Override\n    public Bundle getResultBundle() {\n        return PluginBundleValues.generateBundle(getApplicationContext(),\n                mServerUI.currentId(),\n                atvService.getText().toString(),\n                etServiceData.getText().toString());\n    }\n\n    @NonNull\n    @Override\n    public String getResultBlurb(@NonNull final Bundle bundle) {\n        final String message = PluginBundleValues.getService(bundle);\n\n        final int maxBlurbLength = getResources().getInteger(\n                R.integer.com_twofortyfouram_locale_sdk_client_maximum_blurb_length);\n\n        if (message.length() > maxBlurbLength) {\n            return message.substring(0, maxBlurbLength - 1).concat(\"…\");\n        }\n\n        return message;\n    }\n\n    @NonNull\n    @Override\n    public String[] getRelevantVariableList() {\n        return new String[0];\n    }\n\n    @Override\n    public int requestedTimeoutMS() {\n        return 10000;\n    }\n\n    @Override\n    public boolean onCreateOptionsMenu(final Menu menu) {\n        getMenuInflater().inflate(R.menu.menu, menu);\n\n        return true;\n    }\n\n    private boolean canSave() {\n        if (mServerUI.serverCount() == 0) {\n            Toast.makeText(this, \"Please select a Server\", Toast.LENGTH_SHORT).show();\n            return false;\n        }\n\n        if (atvService.getText().toString().isEmpty()) {\n            Toast.makeText(this, \"Please select a Service\", Toast.LENGTH_SHORT).show();\n            return false;\n        }\n\n        return true;\n    }\n\n    @Override\n    public void onBackPressed() {\n        if (canSave())\n            super.onBackPressed();\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(final MenuItem item) {\n        if (android.R.id.home == item.getItemId()) {\n            if (canSave())\n                finish();\n        }\n        else if (R.id.menu_cancel == item.getItemId()) {\n            mIsCancelled = true;\n\n            finish();\n\n            return true;\n        }\n\n        return super.onOptionsItemSelected(item);\n    }\n}\n"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/main/java/com/markadamson/taskerplugin/homeassistant/ui/activity/EditGetStateActivity.java",
    "content": "/*\n * home-assistant-plugin-for-tasker <https://github.com/MarkAdamson/home-assistant-plugin-for-tasker>\n * Copyright 2019 Mark Adamson\n *\n * Original author:\n * android-toast-setting-plugin-for-locale <https://github.com/twofortyfouram/android-toast-setting-plugin-for-locale>\n * Copyright 2014 two forty four a.m. LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except\n * in compliance with the License. You may obtain a copy of the License at\n *\n *  http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under the\n * License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either\n * express or implied. See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.markadamson.taskerplugin.homeassistant.ui.activity;\n\nimport android.content.Intent;\nimport android.content.pm.PackageManager;\nimport android.content.res.Resources;\nimport android.os.Bundle;\nimport android.support.annotation.NonNull;\nimport android.view.Menu;\nimport android.view.MenuItem;\nimport android.view.View;\nimport android.widget.ArrayAdapter;\nimport android.widget.AutoCompleteTextView;\nimport android.widget.EditText;\nimport android.widget.Toast;\n\nimport com.markadamson.locale.sdk.client.ui.activity.AbstractAppCompatPluginActivity;\nimport com.markadamson.taskerplugin.homeassistant.R;\nimport com.markadamson.taskerplugin.homeassistant.TaskerPlugin;\nimport com.markadamson.taskerplugin.homeassistant.Utils;\nimport com.markadamson.taskerplugin.homeassistant.bundle.GetStatePluginBundleValues;\nimport com.markadamson.taskerplugin.homeassistant.model.HAAPI;\nimport com.markadamson.taskerplugin.homeassistant.model.HAAPIException;\nimport com.markadamson.taskerplugin.homeassistant.model.HAAPIResult;\nimport com.markadamson.taskerplugin.homeassistant.model.HAAPITask;\nimport com.markadamson.taskerplugin.homeassistant.model.HAEntity;\nimport com.markadamson.taskerplugin.homeassistant.model.HAServer;\nimport com.markadamson.taskerplugin.homeassistant.ui.ServerSelectionUI;\nimport com.twofortyfouram.log.Lumberjack;\n\nimport net.jcip.annotations.NotThreadSafe;\n\nimport java.lang.ref.WeakReference;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.UUID;\n\n@NotThreadSafe\npublic final class EditGetStateActivity extends AbstractAppCompatPluginActivity {\n    private ServerSelectionUI mServerUI;\n\n    private ArrayAdapter<String> mEntityAdapter;\n    private AutoCompleteTextView atvEntity;\n    private EditText etStateVariable, etAttrsVariable;\n\n    private abstract static class MyAPITask<Params,Progress,Result> extends HAAPITask<Params,Progress,Result> {\n        WeakReference<EditGetStateActivity> activityReference;\n\n        MyAPITask(EditGetStateActivity context, HAServer server) {\n            super(server);\n            activityReference = new WeakReference<>(context);\n        }\n    }\n\n    private static class GetEntitiesTask extends MyAPITask<Void,Void,List<String>> {\n        GetEntitiesTask(EditGetStateActivity activity, HAServer server) {\n            super(activity, server);\n            activity.mEntityAdapter.clear();\n        }\n\n        @Override\n        protected List<String> doAPIInBackground(HAAPI api, Void... voids) throws HAAPIException {\n            return api.getEntities();\n        }\n\n        @Override\n        protected void onPostExecute(HAAPIResult<List<String>> entities) {\n            EditGetStateActivity activity = activityReference.get();\n            if (activity == null || activity.isFinishing()) return;\n\n            if (entities.getException() != null) {\n                entities.getException().printStackTrace();\n                Toast.makeText(activity, entities.getException().getMessage(), Toast.LENGTH_SHORT).show();\n                return;\n            }\n\n            activity.mEntityAdapter.clear();\n            activity.mEntityAdapter.addAll(entities.getResult());\n        }\n    }\n\n    private static class TestEntityTask extends MyAPITask<String,Void,HAEntity> {\n\n        TestEntityTask(EditGetStateActivity context, HAServer server) {\n            super(context, server);\n        }\n\n        @Override\n        protected HAEntity doAPIInBackground(HAAPI api, String... strings) throws HAAPIException {\n            return api.getEntity(strings[0]);\n        }\n\n        @Override\n        protected void onPostExecute(HAAPIResult<HAEntity> state) {\n            EditGetStateActivity activity = activityReference.get();\n            if (activity == null || activity.isFinishing()) return;\n\n            if (state.getException() != null) {\n                state.getException().printStackTrace();\n                Toast.makeText(activity, state.getException().getMessage(), Toast.LENGTH_SHORT).show();\n                return;\n            }\n\n            Toast.makeText(activity, state.getResult().getState(), Toast.LENGTH_SHORT).show();\n        }\n    }\n\n    @Override\n    protected void onCreate(final Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        setContentView(R.layout.edit_get_state);\n\n        /*\n         * To help the user keep context, the title shows the host's name and the subtitle\n         * shows the plug-in's name.\n         */\n        CharSequence callingApplicationLabel = null;\n        try {\n            callingApplicationLabel =\n                    getPackageManager().getApplicationLabel(\n                            getPackageManager().getApplicationInfo(getCallingPackage(),\n                                    0));\n        } catch (final PackageManager.NameNotFoundException e) {\n            Lumberjack.e(\"Calling package couldn't be found%s\", e); //$NON-NLS-1$\n        }\n        if (null != callingApplicationLabel) {\n            setTitle(callingApplicationLabel);\n        }\n\n        Resources r = getResources();\n        getSupportActionBar().setSubtitle(r.getString(R.string.activity_subtitle, r.getString(R.string.get_state)));\n\n        getSupportActionBar().setDisplayHomeAsUpEnabled(true);\n\n        mServerUI = new ServerSelectionUI(this, callingApplicationLabel, new ServerSelectionUI.OnServerSelectedListener() {\n            @Override\n            public void onServerSelected(HAServer server) {\n                new GetEntitiesTask(EditGetStateActivity.this, server).execute();\n            }\n\n            @Override\n            public void onNothingSelected() {\n                mEntityAdapter.clear();\n            }\n        });\n\n        String[] variablesFromHost = TaskerPlugin.getRelevantVariableList(getIntent().getExtras());\n\n        mEntityAdapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_dropdown_item, new ArrayList<String>());\n        atvEntity = findViewById(R.id.atv_entity);\n        atvEntity.setAdapter(mEntityAdapter);\n        Utils.initVariableSelectUI(variablesFromHost, findViewById(R.id.btn_entity_variable), atvEntity);\n\n        etStateVariable = findViewById(R.id.et_state_variable);\n        Utils.initVariableSelectUI(variablesFromHost, findViewById(R.id.btn_state_variable), etStateVariable);\n\n        etAttrsVariable = findViewById(R.id.et_attrs_variable);\n        Utils.initVariableSelectUI(variablesFromHost, findViewById(R.id.btn_attrs_variable), etAttrsVariable);\n\n        findViewById(R.id.btn_test_entity).setOnClickListener(\n                new View.OnClickListener() {\n                    @Override\n                    public void onClick(View view) {\n                        if (atvEntity.getText().toString().contains(\"%\")) {\n                            Toast.makeText(EditGetStateActivity.this, \"Cannot test using variables in entity id!\", Toast.LENGTH_SHORT).show();\n                            return;\n                        }\n\n                        new TestEntityTask(EditGetStateActivity.this, mServerUI.currentServer())\n                                .execute(atvEntity.getText().toString());\n                    }\n                }\n        );\n\n        Utils.checkBatteryOptimisation(this);\n    }\n\n    @Override\n    protected void onActivityResult(int requestCode, int resultCode, Intent data) {\n        super.onActivityResult(requestCode, resultCode, data);\n        mServerUI.onActivityResult(requestCode, resultCode, data);\n    }\n\n    private void restoreState(UUID id, String entity, String stateVariable, String attrsVariable) {\n        mServerUI.setSelection(id);\n        atvEntity.setText(entity);\n        etStateVariable.setText(stateVariable);\n        etAttrsVariable.setText(attrsVariable);\n    }\n\n\n\n    @Override\n    public void onPostCreateWithPreviousResult(@NonNull final Bundle previousBundle,\n                                               @NonNull final String previousBlurb) {\n        restoreState(\n                GetStatePluginBundleValues.getServer(previousBundle),\n                GetStatePluginBundleValues.getEntity(previousBundle),\n                GetStatePluginBundleValues.getStateVariable(previousBundle),\n                GetStatePluginBundleValues.getAttrsVariable(previousBundle)\n        );\n    }\n\n    @Override\n    public boolean isBundleValid(@NonNull final Bundle bundle) {\n        return GetStatePluginBundleValues.isBundleValid(bundle);\n    }\n\n    @Override\n    public Bundle getResultBundle() {\n        return GetStatePluginBundleValues.generateBundle(getApplicationContext(),\n                mServerUI.currentId(),\n                atvEntity.getText().toString(),\n                etStateVariable.getText().toString(),\n                etAttrsVariable.getText().toString());\n    }\n\n    @NonNull\n    @Override\n    public String getResultBlurb(@NonNull final Bundle bundle) {\n        final String message = GetStatePluginBundleValues.getEntity(bundle);\n\n        final int maxBlurbLength = getResources().getInteger(\n                R.integer.com_twofortyfouram_locale_sdk_client_maximum_blurb_length);\n\n        if (message.length() > maxBlurbLength) {\n            return message.substring(0, maxBlurbLength - 1).concat(\"…\");\n        }\n\n        return message;\n    }\n\n    @NonNull\n    @Override\n    public String[] getRelevantVariableList() {\n        ArrayList<String> vars = new ArrayList<>();\n        vars.add(String.format(\"%s\\nEntity State\\nThe entity state retrieved from %s\", etStateVariable.getText().toString(), mServerUI.currentServer().getName()));\n        if (!etAttrsVariable.getText().toString().isEmpty())\n            vars.add(String.format(\"%s\\nEntity Attributes\\nThe entity attributes retrieved from %s\", etAttrsVariable.getText().toString(), mServerUI.currentServer().getName()));\n        return vars.toArray(new String[vars.size()]);\n    }\n\n    @Override\n    public int requestedTimeoutMS() {\n        return 10000;\n    }\n\n    @Override\n    public boolean onCreateOptionsMenu(final Menu menu) {\n        getMenuInflater().inflate(R.menu.menu, menu);\n\n        return true;\n    }\n\n    private boolean canSave() {\n        boolean result = false;\n\n        if (mServerUI.serverCount() == 0)\n            Toast.makeText(this, \"Please select a Server\", Toast.LENGTH_SHORT).show();\n        else if (atvEntity.getText().toString().isEmpty())\n            Toast.makeText(this, \"Please select an Entity\", Toast.LENGTH_SHORT).show();\n        else if (!TaskerPlugin.variableNameValid(etStateVariable.getText().toString()))\n            Toast.makeText(this, \"State: Not a valid variable name\", Toast.LENGTH_SHORT).show();\n        else if (!(etAttrsVariable.getText().toString().isEmpty() || TaskerPlugin.variableNameValid(etStateVariable.getText().toString())))\n            Toast.makeText(this, \"Attributes: Not a valid variable name\", Toast.LENGTH_SHORT).show();\n        else\n            result = true;\n\n        return result;\n    }\n\n    @Override\n    public void onBackPressed() {\n        if (canSave())\n            super.onBackPressed();\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(final MenuItem item) {\n        if (android.R.id.home == item.getItemId()) {\n            if (canSave())\n                finish();\n        }\n        else if (R.id.menu_cancel == item.getItemId()) {\n            mIsCancelled = true;\n\n            finish();\n\n            return true;\n        }\n\n        return super.onOptionsItemSelected(item);\n    }\n}\n"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/main/java/com/markadamson/taskerplugin/homeassistant/ui/activity/EditRenderTemplateActivity.java",
    "content": "/*\n * home-assistant-plugin-for-tasker <https://github.com/MarkAdamson/home-assistant-plugin-for-tasker>\n * Copyright 2019 Mark Adamson\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except\n * in compliance with the License. You may obtain a copy of the License at\n *\n *  http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under the\n * License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either\n * express or implied. See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.markadamson.taskerplugin.homeassistant.ui.activity;\n\nimport android.content.Intent;\nimport android.content.pm.PackageManager;\nimport android.content.res.Resources;\nimport android.os.Bundle;\nimport android.support.annotation.NonNull;\nimport android.view.Menu;\nimport android.view.MenuItem;\nimport android.view.View;\nimport android.widget.EditText;\nimport android.widget.Toast;\n\nimport com.markadamson.locale.sdk.client.ui.activity.AbstractAppCompatPluginActivity;\nimport com.markadamson.taskerplugin.homeassistant.R;\nimport com.markadamson.taskerplugin.homeassistant.TaskerPlugin;\nimport com.markadamson.taskerplugin.homeassistant.Utils;\nimport com.markadamson.taskerplugin.homeassistant.bundle.RenderTemplatePluginBundleValues;\nimport com.markadamson.taskerplugin.homeassistant.model.HAAPI;\nimport com.markadamson.taskerplugin.homeassistant.model.HAAPIException;\nimport com.markadamson.taskerplugin.homeassistant.model.HAAPIResult;\nimport com.markadamson.taskerplugin.homeassistant.model.HAAPITask;\nimport com.markadamson.taskerplugin.homeassistant.model.HAServer;\nimport com.markadamson.taskerplugin.homeassistant.ui.ServerSelectionUI;\nimport com.twofortyfouram.log.Lumberjack;\n\nimport net.jcip.annotations.NotThreadSafe;\n\nimport java.lang.ref.WeakReference;\nimport java.util.UUID;\n\n@NotThreadSafe\npublic final class EditRenderTemplateActivity extends AbstractAppCompatPluginActivity {\n    private ServerSelectionUI mServerUI;\n\n    private EditText etTemplate, etVariable;\n\n    private abstract static class MyAPITask<Params,Progress,Result> extends HAAPITask<Params,Progress,Result> {\n        WeakReference<EditRenderTemplateActivity> activityReference;\n\n        MyAPITask(EditRenderTemplateActivity activity, HAServer server) {\n            super(server);\n            activityReference = new WeakReference<>(activity);\n        }\n    }\n\n    private static class TestTemplateTask extends MyAPITask<String,Void,String> {\n\n        TestTemplateTask(EditRenderTemplateActivity context, HAServer server) {\n            super(context, server);\n        }\n\n        @Override\n        protected String doAPIInBackground(HAAPI api, String... strings) throws HAAPIException {\n            return api.renderTemplate(strings[0]);\n        }\n\n        @Override\n        protected void onPostExecute(HAAPIResult<String> result) {\n            EditRenderTemplateActivity activity = activityReference.get();\n            if (activity == null || activity.isFinishing()) return;\n\n            if (result.getException() != null) {\n                result.getException().printStackTrace();\n                Toast.makeText(activity, result.getException().getMessage(), Toast.LENGTH_SHORT).show();\n                return;\n            }\n\n            Toast.makeText(activity, result.getResult(), Toast.LENGTH_SHORT).show();\n        }\n    }\n\n    @Override\n    protected void onCreate(final Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        setContentView(R.layout.edit_render_template);\n\n        /*\n         * To help the user keep context, the title shows the host's name and the subtitle\n         * shows the plug-in's name.\n         */\n        CharSequence callingApplicationLabel = null;\n        try {\n            callingApplicationLabel =\n                    getPackageManager().getApplicationLabel(\n                            getPackageManager().getApplicationInfo(getCallingPackage(),\n                                    0));\n        } catch (final PackageManager.NameNotFoundException e) {\n            Lumberjack.e(\"Calling package couldn't be found%s\", e); //$NON-NLS-1$\n        }\n        if (null != callingApplicationLabel) {\n            setTitle(callingApplicationLabel);\n        }\n\n        Resources r = getResources();\n        getSupportActionBar().setSubtitle(r.getString(R.string.activity_subtitle, r.getString(R.string.render_template)));\n\n        getSupportActionBar().setDisplayHomeAsUpEnabled(true);\n\n        mServerUI = new ServerSelectionUI(this, callingApplicationLabel, new ServerSelectionUI.OnServerSelectedListener() {\n            @Override\n            public void onServerSelected(HAServer server) {\n            }\n\n            @Override\n            public void onNothingSelected() {\n            }\n        });\n\n        String[] variablesFromHost = TaskerPlugin.getRelevantVariableList(getIntent().getExtras());\n\n        etTemplate = findViewById(R.id.et_template);\n        Utils.initVariableSelectUI(variablesFromHost, findViewById(R.id.btn_template_variable), etTemplate);\n\n        etVariable = findViewById(R.id.et_variable);\n        Utils.initVariableSelectUI(variablesFromHost, findViewById(R.id.btn_variable), etVariable);\n\n        findViewById(R.id.btn_test_template).setOnClickListener(\n                new View.OnClickListener() {\n                    @Override\n                    public void onClick(View view) {\n                        if (etTemplate.getText().toString().contains(\"%\")) {\n                            Toast.makeText(EditRenderTemplateActivity.this, \"Cannot test using variables!\", Toast.LENGTH_SHORT).show();\n                            return;\n                        }\n\n                        new TestTemplateTask(EditRenderTemplateActivity.this, mServerUI.currentServer())\n                                .execute(etTemplate.getText().toString());\n                    }\n                }\n        );\n\n        Utils.checkBatteryOptimisation(this);\n    }\n\n    @Override\n    protected void onActivityResult(int requestCode, int resultCode, Intent data) {\n        super.onActivityResult(requestCode, resultCode, data);\n        mServerUI.onActivityResult(requestCode, resultCode, data);\n    }\n\n    private void restoreState(UUID id, String template, String variable) {\n        mServerUI.setSelection(id);\n        etTemplate.setText(template);\n        etVariable.setText(variable);\n    }\n\n    @Override\n    public void onPostCreateWithPreviousResult(@NonNull final Bundle previousBundle,\n                                               @NonNull final String previousBlurb) {\n        restoreState(\n                RenderTemplatePluginBundleValues.getServer(previousBundle),\n                RenderTemplatePluginBundleValues.getTemplate(previousBundle),\n                RenderTemplatePluginBundleValues.getVariable(previousBundle)\n        );\n    }\n\n    @Override\n    public boolean isBundleValid(@NonNull final Bundle bundle) {\n        return RenderTemplatePluginBundleValues.isBundleValid(bundle);\n    }\n\n    @Override\n    public Bundle getResultBundle() {\n        return RenderTemplatePluginBundleValues.generateBundle(getApplicationContext(),\n                mServerUI.currentId(),\n                etTemplate.getText().toString(),\n                etVariable.getText().toString());\n    }\n\n    @NonNull\n    @Override\n    public String getResultBlurb(@NonNull final Bundle bundle) {\n        final String message = RenderTemplatePluginBundleValues.getTemplate(bundle);\n\n        final int maxBlurbLength = getResources().getInteger(\n                R.integer.com_twofortyfouram_locale_sdk_client_maximum_blurb_length);\n\n        if (message.length() > maxBlurbLength) {\n            return message.substring(0, maxBlurbLength - 1).concat(\"…\");\n        }\n\n        return message;\n    }\n\n    @NonNull\n    @Override\n    public String[] getRelevantVariableList() {\n        return new String[] {String.format(\"%s\\nRendered Template\\nThe rendered template from %s\", etVariable.getText().toString(), mServerUI.currentServer().getName())};\n    }\n\n    @Override\n    public int requestedTimeoutMS() {\n        return 10000;\n    }\n\n    @Override\n    public boolean onCreateOptionsMenu(final Menu menu) {\n        getMenuInflater().inflate(R.menu.menu, menu);\n\n        return true;\n    }\n\n    private boolean canSave() {\n        boolean result = false;\n\n        if (mServerUI.serverCount() == 0)\n            Toast.makeText(this, \"Please select a Server\", Toast.LENGTH_SHORT).show();\n        else if (!TaskerPlugin.variableNameValid(etVariable.getText().toString()))\n            Toast.makeText(this, \"Not a valid variable name\", Toast.LENGTH_SHORT).show();\n        else\n            result = true;\n\n        return result;\n    }\n\n    @Override\n    public void onBackPressed() {\n        if (canSave())\n            super.onBackPressed();\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(final MenuItem item) {\n        if (android.R.id.home == item.getItemId()) {\n            if (canSave())\n                finish();\n        }\n        else if (R.id.menu_cancel == item.getItemId()) {\n            mIsCancelled = true;\n\n            finish();\n\n            return true;\n        }\n\n        return super.onOptionsItemSelected(item);\n    }\n}\n"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/main/java/com/markadamson/taskerplugin/homeassistant/ui/activity/EditServerActivity.java",
    "content": "package com.markadamson.taskerplugin.homeassistant.ui.activity;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.content.res.Resources;\nimport android.os.Bundle;\nimport android.support.v7.app.AppCompatActivity;\nimport android.view.Menu;\nimport android.view.MenuItem;\nimport android.view.View;\nimport android.widget.EditText;\nimport android.widget.Toast;\n\nimport com.markadamson.taskerplugin.homeassistant.R;\nimport com.markadamson.taskerplugin.homeassistant.model.HAAPI;\nimport com.markadamson.taskerplugin.homeassistant.model.HAAPIException;\nimport com.markadamson.taskerplugin.homeassistant.model.HAAPIResult;\nimport com.markadamson.taskerplugin.homeassistant.model.HAAPITask;\nimport com.markadamson.taskerplugin.homeassistant.model.HAServer;\n\nimport java.lang.ref.WeakReference;\n\npublic class EditServerActivity extends AppCompatActivity {\n\n    public static final int REQ_NEW_SERVER = 0;\n    public static final int REQ_EDIT_SERVER = 1;\n\n    private static final int[] MODE_TITLE = {R.string.add_a_server, R.string.edit_server};\n\n    public static final String EXT_HOST_NAME = \"com.markadamson.taskerplugin.homeassistant.ui.activity.EditServerActivity.EXT_HOST_NAME\";\n    public static final String EXT_REQUEST_CODE = \"com.markadamson.taskerplugin.homeassistant.ui.activity.EditServerActivity.EXT_REQUEST CODE\";\n    public static final String EXT_SERVER_NAME = \"com.markadamson.taskerplugin.homeassistant.ui.activity.EditServerActivity.EXT_SERVER_NAME\";\n    public static final String EXT_BASE_URL = \"com.markadamson.taskerplugin.homeassistant.ui.activity.EditServerActivity.EXT_BASE_URL\";\n    public static final String EXT_ACCESS_TOKEN = \"com.markadamson.taskerplugin.homeassistant.ui.activity.EditServerActivity.EXT_ACCESS_TOKEN\";\n\n    EditText etServerName, etBaseURL, etAccessToken;\n\n    private abstract static class MyAPITask<Params,Progress,Result> extends HAAPITask<Params,Progress,Result> {\n        WeakReference<EditServerActivity> activityReference;\n\n        MyAPITask(EditServerActivity context, HAServer server) {\n            super(server);\n            activityReference = new WeakReference<>(context);\n        }\n    }\n\n    private static class TestServerTask extends MyAPITask<Void,Void,Boolean> {\n        TestServerTask(EditServerActivity context, HAServer server) {\n            super(context, server);\n        }\n\n        @Override\n        protected Boolean doAPIInBackground(HAAPI api, Void... voids) throws HAAPIException {\n            return api.testServer();\n        }\n\n        @Override\n        protected void onPostExecute(HAAPIResult<Boolean> result) {\n            EditServerActivity activity = activityReference.get();\n            if (activity == null || activity.isFinishing()) return;\n\n            if (result.getException() != null) {\n                result.getException().printStackTrace();\n                Toast.makeText(activity, result.getException().getMessage(), Toast.LENGTH_SHORT).show();\n                return;\n            }\n\n            Toast.makeText(activity, result.getResult() ? R.string.connection_successful : R.string.connection_failed, Toast.LENGTH_SHORT).show();\n        }\n    }\n\n    @Override\n    protected void onCreate(final Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        setContentView(R.layout.edit_server);\n\n        etServerName = findViewById(R.id.et_server_name);\n        etBaseURL = findViewById(R.id.et_base_url);\n        etAccessToken = findViewById(R.id.et_access_token);\n\n        Intent i = getIntent();\n        if (i.hasExtra(EXT_SERVER_NAME))\n            etServerName.setText(i.getStringExtra(EXT_SERVER_NAME));\n        if (i.hasExtra(EXT_BASE_URL))\n            etBaseURL.setText(i.getStringExtra(EXT_BASE_URL));\n        if (i.hasExtra(EXT_ACCESS_TOKEN))\n            etAccessToken.setText(i.getStringExtra(EXT_ACCESS_TOKEN));\n\n        if (i.hasExtra(EXT_HOST_NAME))\n            setTitle(i.getCharSequenceExtra(EXT_HOST_NAME));\n\n        Resources r = getResources();\n        String mode = r.getString(MODE_TITLE[i.getIntExtra(EXT_REQUEST_CODE, REQ_EDIT_SERVER)]);\n        getSupportActionBar().setSubtitle(r.getString(R.string.activity_subtitle, mode));\n\n        getSupportActionBar().setDisplayHomeAsUpEnabled(true);\n\n        findViewById(R.id.btn_test_server).setOnClickListener(\n                new View.OnClickListener() {\n                    @Override\n                    public void onClick(View view) {\n                        new TestServerTask(EditServerActivity.this,\n                                new HAServer(\n                                        etServerName.getText().toString(),\n                                        etBaseURL.getText().toString(),\n                                        etAccessToken.getText().toString()\n                                )).execute();\n                    }\n                }\n        );\n    }\n\n    @Override\n    public boolean onCreateOptionsMenu(final Menu menu) {\n        getMenuInflater().inflate(R.menu.menu, menu);\n\n        return true;\n    }\n\n    @Override\n    public void onBackPressed() {\n        setResult(Activity.RESULT_OK,\n                new Intent()\n                        .putExtra(EXT_SERVER_NAME, etServerName.getText().toString())\n                        .putExtra(EXT_BASE_URL, etBaseURL.getText().toString())\n                        .putExtra(EXT_ACCESS_TOKEN, etAccessToken.getText().toString()));\n        super.onBackPressed();\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(final MenuItem item) {\n        if (android.R.id.home == item.getItemId()) {\n            setResult(Activity.RESULT_OK,\n                    new Intent()\n                            .putExtra(EXT_SERVER_NAME, etServerName.getText().toString())\n                            .putExtra(EXT_BASE_URL, etBaseURL.getText().toString())\n                            .putExtra(EXT_ACCESS_TOKEN, etAccessToken.getText().toString()));\n            finish();\n        }\n        else if (R.id.menu_cancel == item.getItemId()) {\n            finish();\n\n            return true;\n        }\n\n        return super.onOptionsItemSelected(item);\n    }\n}\n"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/main/res/drawable/baseline_add_24.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"24dp\"\n        android:height=\"24dp\"\n        android:viewportWidth=\"24.0\"\n        android:viewportHeight=\"24.0\"\n        android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z\"/>\n</vector>\n"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/main/res/drawable/baseline_cancel_24.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"24dp\"\n        android:height=\"24dp\"\n        android:viewportWidth=\"24.0\"\n        android:viewportHeight=\"24.0\"\n        android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2zM17,15.59L15.59,17 12,13.41 8.41,17 7,15.59 10.59,12 7,8.41 8.41,7 12,10.59 15.59,7 17,8.41 13.41,12 17,15.59z\"/>\n</vector>\n"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/main/res/drawable/baseline_delete_24.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"24dp\"\n        android:height=\"24dp\"\n        android:viewportWidth=\"24.0\"\n        android:viewportHeight=\"24.0\"\n        android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z\"/>\n</vector>\n"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/main/res/drawable/baseline_edit_24.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"24dp\"\n        android:height=\"24dp\"\n        android:viewportWidth=\"24.0\"\n        android:viewportHeight=\"24.0\"\n        android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z\"/>\n</vector>\n"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/main/res/drawable/outline_label_24.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"24dp\"\n        android:height=\"24dp\"\n        android:viewportWidth=\"24.0\"\n        android:viewportHeight=\"24.0\"\n        android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M17.63,5.84C17.27,5.33 16.67,5 16,5L5,5.01C3.9,5.01 3,5.9 3,7v10c0,1.1 0.9,1.99 2,1.99L16,19c0.67,0 1.27,-0.33 1.63,-0.84L22,12l-4.37,-6.16zM16,17H5V7h11l3.55,5L16,17z\"/>\n</vector>\n"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/main/res/layout/edit_get_state.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n** Copyright 2019 Mark Adamson\n**\n** Original Author:\n** Copyright 2013 two forty four a.m. LLC <http://www.twofortyfouram.com>\n**\n** Licensed under the Apache License, Version 2.0 (the \"License\");\n** you may not use this file except in compliance with the License.\n** You may obtain a copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>\n**\n** Unless required by applicable law or agreed to in writing, software\n** distributed under the License is distributed on an \"AS IS\" BASIS,\n** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n** See the License for the specific language governing permissions and\n** limitations under the License.\n-->\n<ScrollView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\" >\n\n        <include\n            layout=\"@layout/select_server\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"6dip\"\n            android:layout_marginRight=\"6dip\"\n            android:layout_marginTop=\"8dip\"/>\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"horizontal\"\n            android:gravity=\"center_vertical\"\n            android:layout_marginLeft=\"6dip\"\n            android:layout_marginRight=\"6dip\"\n            android:layout_marginTop=\"8dip\">\n\n            <AutoCompleteTextView\n                android:id=\"@+id/atv_entity\"\n                android:hint=\"@string/entity_id\"\n                android:completionThreshold=\"1\"\n                android:singleLine=\"true\"\n                android:maxLines=\"1\"\n                android:imeOptions=\"flagNoExtractUi|actionNext\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"/>\n\n            <ImageButton\n                android:id=\"@+id/btn_entity_variable\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginLeft=\"2dip\"\n                android:layout_marginStart=\"2dip\"\n                android:contentDescription=\"@string/select_variable\"\n                android:src=\"@drawable/outline_label_white_18\" />\n\n        </LinearLayout>\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"horizontal\"\n            android:gravity=\"center_vertical\"\n            android:layout_marginLeft=\"6dip\"\n            android:layout_marginRight=\"6dip\"\n            android:layout_marginTop=\"8dip\">\n\n            <EditText\n                android:id=\"@+id/et_state_variable\"\n                android:hint=\"@string/state_variable\"\n                android:imeOptions=\"flagNoExtractUi\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"/>\n\n            <ImageButton\n                android:id=\"@+id/btn_state_variable\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginLeft=\"2dip\"\n                android:layout_marginStart=\"2dip\"\n                android:contentDescription=\"@string/select_variable\"\n                android:src=\"@drawable/outline_label_white_18\" />\n\n        </LinearLayout>\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"horizontal\"\n            android:gravity=\"center_vertical\"\n            android:layout_marginLeft=\"6dip\"\n            android:layout_marginRight=\"6dip\"\n            android:layout_marginTop=\"8dip\">\n\n            <EditText\n                android:id=\"@+id/et_attrs_variable\"\n                android:hint=\"@string/attrs_variable\"\n                android:imeOptions=\"flagNoExtractUi\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"/>\n\n            <ImageButton\n                android:id=\"@+id/btn_attrs_variable\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginLeft=\"2dip\"\n                android:layout_marginStart=\"2dip\"\n                android:contentDescription=\"@string/select_variable\"\n                android:src=\"@drawable/outline_label_white_18\" />\n\n        </LinearLayout>\n\n        <Button\n            android:id=\"@+id/btn_test_entity\"\n            android:text=\"@string/test_entity\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"6dip\"\n            android:layout_marginRight=\"6dip\"\n            android:layout_marginTop=\"8dip\"\n            android:layout_gravity=\"end\"/>\n\n    </LinearLayout>\n</ScrollView>"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/main/res/layout/edit_render_template.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n** Copyright 2019 Mark Adamson\n**\n** Licensed under the Apache License, Version 2.0 (the \"License\");\n** you may not use this file except in compliance with the License.\n** You may obtain a copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>\n**\n** Unless required by applicable law or agreed to in writing, software\n** distributed under the License is distributed on an \"AS IS\" BASIS,\n** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n** See the License for the specific language governing permissions and\n** limitations under the License.\n-->\n<ScrollView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\" >\n\n        <include\n            layout=\"@layout/select_server\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"6dip\"\n            android:layout_marginRight=\"6dip\"\n            android:layout_marginTop=\"8dip\"/>\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"horizontal\"\n            android:gravity=\"center_vertical\"\n            android:layout_marginLeft=\"6dip\"\n            android:layout_marginRight=\"6dip\"\n            android:layout_marginTop=\"8dip\">\n\n            <EditText\n                android:id=\"@+id/et_template\"\n                android:hint=\"@string/template\"\n                android:imeOptions=\"flagNoExtractUi|actionNext\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"/>\n\n            <ImageButton\n                android:id=\"@+id/btn_template_variable\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginLeft=\"2dip\"\n                android:layout_marginStart=\"2dip\"\n                android:contentDescription=\"@string/select_variable\"\n                android:src=\"@drawable/outline_label_white_18\" />\n\n        </LinearLayout>\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"horizontal\"\n            android:gravity=\"center_vertical\"\n            android:layout_marginLeft=\"6dip\"\n            android:layout_marginRight=\"6dip\"\n            android:layout_marginTop=\"8dip\">\n\n            <EditText\n                android:id=\"@+id/et_variable\"\n                android:hint=\"@string/variable\"\n                android:imeOptions=\"flagNoExtractUi\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"/>\n\n            <ImageButton\n                android:id=\"@+id/btn_variable\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginLeft=\"2dip\"\n                android:layout_marginStart=\"2dip\"\n                android:contentDescription=\"@string/select_variable\"\n                android:src=\"@drawable/outline_label_white_18\" />\n\n        </LinearLayout>\n\n        <Button\n            android:id=\"@+id/btn_test_template\"\n            android:text=\"@string/test_template\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"6dip\"\n            android:layout_marginRight=\"6dip\"\n            android:layout_marginTop=\"8dip\"\n            android:layout_gravity=\"end\"/>\n\n    </LinearLayout>\n</ScrollView>"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/main/res/layout/edit_server.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ScrollView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\" >\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"6dip\"\n            android:layout_marginRight=\"6dip\"\n            android:layout_marginTop=\"8dip\"\n            android:text=\"Server Name\"/>\n\n        <EditText\n            android:id=\"@+id/et_server_name\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"6dip\"\n            android:layout_marginRight=\"6dip\"\n            android:layout_marginTop=\"8dip\" />\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"6dip\"\n            android:layout_marginRight=\"6dip\"\n            android:layout_marginTop=\"8dip\"\n            android:text=\"Base URL\"/>\n\n        <EditText\n            android:id=\"@+id/et_base_url\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"6dip\"\n            android:layout_marginRight=\"6dip\"\n            android:layout_marginTop=\"8dip\" />\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"6dip\"\n            android:layout_marginRight=\"6dip\"\n            android:layout_marginTop=\"8dip\"\n            android:text=\"Access Token\"/>\n\n        <EditText\n            android:id=\"@+id/et_access_token\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"6dip\"\n            android:layout_marginRight=\"6dip\"\n            android:layout_marginTop=\"8dip\" />\n\n        <Button\n            android:id=\"@+id/btn_test_server\"\n            android:text=\"@string/test_server\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"6dip\"\n            android:layout_marginRight=\"6dip\"\n            android:layout_marginTop=\"8dip\"\n            android:layout_gravity=\"end\"/>\n\n    </LinearLayout>\n</ScrollView>"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/main/res/layout/main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n** Copyright 2019 Mark Adamson\n**\n** Original Author:\n** Copyright 2013 two forty four a.m. LLC <http://www.twofortyfouram.com>\n**\n** Licensed under the Apache License, Version 2.0 (the \"License\");\n** you may not use this file except in compliance with the License.\n** You may obtain a copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>\n**\n** Unless required by applicable law or agreed to in writing, software\n** distributed under the License is distributed on an \"AS IS\" BASIS,\n** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n** See the License for the specific language governing permissions and\n** limitations under the License.\n-->\n<ScrollView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\" >\n\n        <include\n            layout=\"@layout/select_server\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"6dip\"\n            android:layout_marginRight=\"6dip\"\n            android:layout_marginTop=\"8dip\"/>\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"horizontal\"\n            android:gravity=\"center_vertical\"\n            android:layout_marginLeft=\"6dip\"\n            android:layout_marginRight=\"6dip\"\n            android:layout_marginTop=\"8dip\">\n\n            <AutoCompleteTextView\n                android:id=\"@+id/atv_service\"\n                android:hint=\"@string/service\"\n                android:completionThreshold=\"1\"\n                android:singleLine=\"true\"\n                android:maxLines=\"1\"\n                android:imeOptions=\"flagNoExtractUi|actionNext\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"/>\n\n            <ImageButton\n                android:id=\"@+id/btn_service_variable\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginLeft=\"2dip\"\n                android:layout_marginStart=\"2dip\"\n                android:contentDescription=\"@string/select_variable\"\n                android:src=\"@drawable/outline_label_white_18\" />\n\n        </LinearLayout>\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"horizontal\"\n            android:gravity=\"center_vertical\"\n            android:layout_marginLeft=\"6dip\"\n            android:layout_marginRight=\"6dip\"\n            android:layout_marginTop=\"8dip\">\n\n            <EditText\n                android:id=\"@+id/et_service_data\"\n                android:hint=\"@string/service_data_hint\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"/>\n\n            <ImageButton\n                android:id=\"@+id/btn_service_data_variable\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginLeft=\"2dip\"\n                android:layout_marginStart=\"2dip\"\n                android:contentDescription=\"@string/select_variable\"\n                android:src=\"@drawable/outline_label_white_18\" />\n\n        </LinearLayout>\n\n        <Button\n            android:id=\"@+id/btn_test_service\"\n            android:text=\"@string/test_service\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"6dip\"\n            android:layout_marginRight=\"6dip\"\n            android:layout_marginTop=\"8dip\"\n            android:layout_gravity=\"end\"/>\n\n    </LinearLayout>\n</ScrollView>"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/main/res/layout/select_server.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:orientation=\"horizontal\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:gravity=\"center_vertical\">\n\n    <Spinner\n        android:id=\"@+id/spn_server\"\n        android:layout_weight=\"1\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"/>\n\n    <ImageButton\n        android:id=\"@+id/btn_add_server\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginLeft=\"2dip\"\n        android:layout_marginStart=\"2dip\"\n        android:contentDescription=\"@string/add_a_server\"\n        android:src=\"@drawable/baseline_add_white_18\" />\n\n    <ImageButton\n        android:id=\"@+id/btn_edit_server\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginLeft=\"2dip\"\n        android:layout_marginStart=\"2dip\"\n        android:contentDescription=\"@string/edit_server\"\n        android:src=\"@drawable/baseline_edit_white_18\" />\n\n    <ImageButton\n        android:id=\"@+id/btn_delete_server\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginLeft=\"2dip\"\n        android:layout_marginStart=\"2dip\"\n        android:contentDescription=\"@string/delete_server\"\n        android:src=\"@drawable/baseline_delete_white_18\" />\n\n</LinearLayout>"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/main/res/menu/menu.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n      xmlns:tools=\"http://schemas.android.com/tools\"\n      xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    >\n\n    <item\n        android:id=\"@+id/menu_cancel\"\n        android:icon=\"@drawable/baseline_cancel_white_36\"\n        app:showAsAction=\"ifRoom\"\n        android:title=\"@string/menu_cancel\"/>\n\n</menu>\n"
  },
  {
    "path": "HomeAssistantTaskerPlugin/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <!-- App name -->\n    <string name=\"app_name\">Home Assistant Plug-in for Tasker</string>\n\n    <!-- Name of the plug-in that appears in Locale -->\n    <string name=\"plugin_name\">Home Assistant</string>\n\n    <!-- Subtitle that appears in config activities -->\n    <string name=\"activity_subtitle\">Home Assistant - %1$s</string>\n\n    <!-- Discard menu item -->\n    <string name=\"menu_cancel\">Cancel</string>\n    <string name=\"add_a_server\">Add a Server</string>\n    <string name=\"delete_server\">Delete Server</string>\n\n    <string name=\"server_name\">Server Name</string>\n    <string name=\"base_url\">Base URL</string>\n    <string name=\"access_token\">Access Token</string>\n    <string name=\"edit_server\">Edit Server</string>\n    <string name=\"test_server\">Test Server</string>\n    <string name=\"connection_successful\">Connection Successful!</string>\n    <string name=\"connection_failed\">Connection Failed :(</string>\n    <string name=\"test_service\">Test Service</string>\n    <string name=\"service_data_hint\">Service Data (JSON, optional)</string>\n    <string name=\"call_service\">Call Service</string>\n    <string name=\"get_state\">Get State</string>\n    <string name=\"state_variable\">Variable</string>\n    <string name=\"test_entity\">Test Entity</string>\n    <string name=\"service\">Service</string>\n    <string name=\"entity_id\">Entity ID</string>\n    <string name=\"attrs_variable\">Attributes (Optional)</string>\n    <string name=\"template\">Template</string>\n    <string name=\"variable\">Variable</string>\n    <string name=\"test_template\">Test Template</string>\n    <string name=\"render_template\">Render Template</string>\n    <string name=\"select_variable\">Select Variable</string>\n    <string name=\"disable_battery_optimization\">Disable Battery Optimization?</string>\n    <string name=\"battery_optimization_dialog\">\n        Home Assistant Plug-In for Tasker is currently being optimized for battery. This means that\n        it may be killed by the system and stop working unexpectedly.\\n\\nTo prevent this, it\\'s\n        recommended that you change the battery optimization setting for the plug-in to \\'Don\\'t\n        Optimize\\'.\\n\\nWould you like to go to settings to change this now?\n    </string>\n    <string name=\"are_you_sure\">Are You Sure?</string>\n    <string name=\"delete_server_warning\">\n        Are you sure you want to delete this server?\\n\\nIf it is in use by any other actions, those\n        actions will stop working until they are reconfigured to use a different server!\n    </string>\n    <string name=\"yes_delete_the_server\">DELETE THE SERVER!</string>\n\n</resources>\n"
  },
  {
    "path": "LICENSE",
    "content": "   home-assistant-plugin-for-tasker\n   https://github.com/MarkAdamson/home-assistant-plugin-for-tasker\n   Copyright 2019 Mark Adamson\n   \n   Original Author:\n   android-toast-setting-plugin-for-locale\n   https://github.com/twofortyfouram/android-toast-setting-plugin-for-locale\n   Copyright 2009-2017 two forty four a.m. LLC\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n\n\n\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n"
  },
  {
    "path": "README.md",
    "content": "# Home Assistant Plug-In for Tasker\nThis is a Tasker plug-in to allow calling services on and getting entity states from a Home Assistant installation.\n\n<a href=\"https://play.google.com/store/apps/details?id=com.markadamson.taskerplugin.homeassistant\"><img alt=\"Get it on Google Play\" src=\"https://play.google.com/intl/en_gb/badges/images/generic/en_badge_web_generic.png\" height=96px /></a>\n\n## Requirements\n- Android 4.0 (API 14)\n- [Tasker](https://tasker.joaoapps.com/)\n- [Home Assistant 0.78](https://www.home-assistant.io/)\n\n## Usage\n\n**PLEASE NOTE: If your device is on Android 6.0 or higher, you should disable Battery Optimization for this plugin (and Tasker) - otherwise you may find that it doesn't work properly!**\n\n### Add A Server\n- Generate a [Long-Lived Access Token](https://www.home-assistant.io/docs/authentication/) in Home Assistant.\n- Create a new Task in Tasker.\n- Add an Action: 'Plugin' > 'Home Assistant Plug-In for Tasker' to the Task.\n- Tap the edit button by 'Configuration'.\n- Tap the '+' button near the top right to add a new Home Assistant server.\n- Enter the details for your Home Assistant server. The Base URL must include the protocol, and **not** a trailing backslash (eg: `https://my.home-assistant.com`).\n- Test the server, then click 'Save'.\n\n### Call A Service\n- Create a new Task in Tasker.\n- Add the Action 'Plugin' > 'Home Assistant Plug-In for Tasker' > 'Call Service' to the Task.\n- Select a Server, or add a new one as above.\n- Select a Service, and optionally enter Service Data in JSON format.\n- Test the Service call, then click 'Save'.\n\n### Get An Entity's State\n- Create a new Task in Tasker.\n- Add the Action 'Plugin' > 'Home Assistant Plug-In for Tasker' > 'Get State' to the Task.\n- Select a Server, or add a new one as above.\n- Select an Entity.\n- Enter a Tasker variable name, including the leading `%`.\n- Click 'Save'.\n- **Make sure to set a timeout - this tells the action to await a result, otherwise the task continues instantly and the variable will not be populated!**\n\n### Variables\nVariables are supported in the following fields:\n- 'Call Service' > 'Service'\n- 'Call Service' > 'Service Data'\n- 'Get State' > 'Entity ID'\n\nRemember to always include the leading `%`\n\n## To Do\n- [x] Actions\n- [x] Tasker Variables\n"
  },
  {
    "path": "build.gradle",
    "content": "apply plugin: 'com.github.ben-manes.versions'\n\nbuildscript {\n    repositories {\n        maven { url 'https://maven.google.com' }\n        jcenter()\n    }\n    dependencies {\n        classpath group:'com.android.tools.build', name:'gradle', version:\"${ANDROID_GRADLE_PLUGIN_VERSION_MATCHER}\"\n        classpath group:'com.github.ben-manes', name:'gradle-versions-plugin', version:\"${GRADLE_VERSIONS_PLUGIN_VERSION_MATCHER}\"\n    }\n}\n\nallprojects {\n    repositories {\n        maven { url 'https://maven.google.com' }\n        jcenter()\n    }\n\n    // Forces all dependencies to be resolved.  Useful in continuous integration\n    // to populate the dependency cache.\n    // https://discuss.gradle.org/t/download-all-dependencies/6294\n    task resolveAllDependencies {\n        doLast {\n            configurations.all { it.resolve() }\n        }\n    }\n\n    gradle.projectsEvaluated {\n        tasks.withType(JavaCompile) {\n            options.compilerArgs << '-Xlint:all'\n        }\n    }\n}\n"
  },
  {
    "path": "circle.yml",
    "content": "# Many CI systems start with the intention of having convention over configuration, and trying to make things look nice and easy to get started.  This can fall apart pretty quickly, if the convention isn't flexible enough or isn't keeping up with the latest developments.  That is what happened here, and this CI configuration is gross.\n# There are a number of custom things happening in this script that deviate from CircleCI's Android documentation.  The reasons are 1. performance and 2. portability between this and some other CI systems we use for other projects.\n# To speed up builds, our own versions of gcloud and the Android SDK are downloaded to a separate directory and cached.  This saves about 5 minutes per build.  Updating the preinstalled components is a pain, because we don't want to cache directories that might get overwritten with a new container image later.\n# Note: when forking this repo, environment variables GCLOUD_SERVICE_KEY_BASE_64, GCLOUD_PROJECT_ID, and GCLOUD_DEFAULT_BUCKET need to be defined for CI builds to work.\n# Note that this doesn't work to prefix the path.  CircleCI adds more crap to the beginning of PATH after we try to define it, including an old version of glcoud.  So each many invocations are prefixed with an override of PATH.\n# machine:\n#  environment:\n#    PATH: \"/home/ubuntu/mybin/gcloud/bin:${PATH}\"\n\ngeneral:\n  branches:\n    ignore:\n      - gh-pages\n\nmachine:\n  java:\n    version: oraclejdk8\n  environment:\n    ANDROID_HOME: \"/home/ubuntu/mybin/android-sdk-linux\"\n\ndependencies:\n  pre:\n    - mkdir -p ~/flags\n    - mkdir -p ~/mybin\n    - ./tools/ci/android-sdk-setup.sh ${ANDROID_HOME} 3859397 ~/flags/android-sdk \"tools\" \"platforms;android-25\" \"build-tools;25.0.3\" \"platform-tools\" \"docs\"\n    - PATH=\"/home/ubuntu/mybin/gcloud/bin:${PATH}\" && ./tools/ci/google-cloud-test-lab-setup.sh /home/ubuntu/mybin/gcloud 155.0.0 ~/flags/gcloud-setup ${GCLOUD_SERVICE_KEY_BASE_64} ${GCLOUD_PROJECT_ID}\n    - sudo apt-get update; sudo apt-get install parallel\n  override:\n    - ./gradlew resolveAllDependencies\n  cache_directories:\n    - \".gradle\"\n    - \"/home/ubuntu/flags\"\n    - \"/home/ubuntu/mybin\"\n    - \"/home/ubuntu/.android\"\n\ntest:\n  override:\n    - ./gradlew assemble assembleDebugAndroidTest lint findbugs dependencyUpdates --profile\n\n    - PATH=\"/home/ubuntu/mybin/gcloud/bin:${PATH}\" && parallel tools/ci/firebase-test-lab-test-module.sh toastPluginSettingApp ${GCLOUD_DEFAULT_BUCKET} {1} ':::' \"model=Nexus4,version=19,orientation=portrait\" \"model=NexusLowRes,version=25,orientation=portrait\"\n\n\n    # Convert the coverage.ec files into readable reports\n    - ./gradlew createDebugCoverageReport -x connectedDebugAndroidTest\n\n\n    - cp -r build/reports/profile $CIRCLE_ARTIFACTS\n    - cp -r build/dependencyUpdates $CIRCLE_ARTIFACTS\n    - cp -r toastPluginSettingApp/build/outputs $CIRCLE_ARTIFACTS\n    - cp -r toastPluginSettingApp/build/reports $CIRCLE_ARTIFACTS\n    - cp -r toastPluginSettingApp/build/outputs/androidTest-results/* $CIRCLE_TEST_REPORTS"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "#Mon Jul 08 14:35:42 BST 2019\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-4.6-all.zip\n"
  },
  {
    "path": "gradle.properties",
    "content": "// Moar memory is required for in-process dex.\norg.gradle.jvmargs=-Xmx2g\n\n// Enable automatic download of Android SDK components.\nandroid.builder.sdkDownload=true\n\n\n// Android build details\nANDROID_BUILD_TOOLS_VERSION=28.0.3\nANDROID_MIN_SDK_VERSION=14\nANDROID_TARGET_SDK_VERSION=28\nANDROID_COMPILE_SDK_VERSION=26\n\n\n// App version details\nANDROID_VERSION_CODE=13\nANDROID_VERSION_NAME=1.1.5\n\n// Signing configuration\nRELEASE_KEYSTORE_PATH=\nRELEASE_KEYSTORE_PASSWORD=\nRELEASE_KEY_ALIAS=\nRELEASE_KEY_ALIAS_PASSWORD=\n\n\n// Version matchers for dependencies\nANDROID_GRADLE_PLUGIN_VERSION_MATCHER=3.2.1\nANDROID_SUPPORT_VERSION_MATCHER=]27.0.0,28[\nJCIP_ANNOTATION_VERSION_MATCHER=1.0\nGRADLE_VERSIONS_PLUGIN_VERSION_MATCHER=0.14.0\nTWOFORTYFOURAM_ANNOTATION_VERSION_MATCHER=[2.0.1,3.0[\nTWOFORTYFOURAM_ASSERTION_VERSION_MATCHER=[1.1.1,2.0[\nTWOFORTYFOURAM_PLUGIN_API_VERSION_MATCHER=[1.0.1,2.0.0[\nTWOFORTYFOURAM_PLUGIN_CLIENT_SDK_VERSION_MATCHER=[4.0.2,5.0[\nTWOFORTYFOURAM_SPACKLE_VERSION_MATCHER=[2.0.0,3.0[\nTWOFORTYFOURAM_TEST_VERSION_MATCHER=[1.0.0,2.0[\n\n\n// Toggle JaCoCo code coverage reports.\nIS_COVERAGE_ENABLED=true"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env sh\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn ( ) {\n    echo \"$*\"\n}\n\ndie ( ) {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave ( ) {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=$(save \"$@\")\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\n# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong\nif [ \"$(uname)\" = \"Darwin\" ] && [ \"$HOME\" = \"$PWD\" ]; then\n  cd \"$(dirname \"$0\")\"\nfi\n\nexec \"$JAVACMD\" \"$@\"\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\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:init\r\n@rem Get command-line arguments, handling Windows variants\r\n\r\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\r\n\r\n:win9xME_args\r\n@rem Slurp the command line arguments.\r\nset CMD_LINE_ARGS=\r\nset _SKIP=2\r\n\r\n:win9xME_args_slurp\r\nif \"x%~1\" == \"x\" goto execute\r\n\r\nset CMD_LINE_ARGS=%*\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "settings.gradle",
    "content": "include ':HomeAssistantTaskerPlugin'\n"
  },
  {
    "path": "tools/ci/android-sdk-license",
    "content": "8933bad161af4178b1185d1a37fbf41ea5269c55"
  },
  {
    "path": "tools/ci/android-sdk-setup.sh",
    "content": "#!/bin/bash\n\n# Sets up the Android SDK.\n# Marks a flag to remember whether the SDK has been installed previously\n# and will bypass downloading if the flag exists. Note that if you change the version or\n# components passed in, you should delete the flag and the installation so that the script will download\n# the new version or components.\n#\n# The script expects several parameters in this order:\n# 1. android home directory, e.g. ~/android-sdk\n# 2. Android tools version, e.g. 3859397\n# 3. flag path, e.g. ~/flags/android-sdk-setup\n# 4. android sdk components, as used by `sdkmanager`.  e.g. \"platforms;android-25\" \"build-tools;25.0.2\" \"platform-tools\" \"docs\" \"extras;android;m2repository\"\n\nset +e\n\nandroid_sdk_installation_dir=\"$1\"\nandroid_tools_version=\"$2\"\nandroid_sdk_flag_file=\"$3\"\nshift 3\nandroid_sdk_components_to_install=(\"$@\")\n\nif ! test -f ${android_sdk_flag_file}; then\n  echo \"Install Android SDK ${android_tools_version} to ${android_sdk_installation_dir} with ${android_sdk_components_to_install[@]}\"\n\n  # Optionally download the SDK, as it might already exist in some environments.\n  if ! test -d $android_sdk_installation_dir; then\n    mkdir -p $android_sdk_installation_dir\n    curl -o $HOME/android-sdk-temp.zip \"https://dl.google.com/android/repository/sdk-tools-linux-${android_tools_version}.zip\"\n    unzip $HOME/android-sdk-temp.zip -d $android_sdk_installation_dir\n    rm $HOME/android-sdk-temp.zip\n  fi\n\n  mkdir -p ${android_sdk_installation_dir}/licenses/\n  cp tools/ci/android-sdk-license ${android_sdk_installation_dir}/licenses/android-sdk-license\n\n  if ! test -d ~/.android; then\n    mkdir ~/.android\n  fi\n\n  cp tools/ci/repositories.cfg ~/.android/repositories.cfg\n\n  for component in ${android_sdk_components_to_install[@]}; do\n      echo \"Installing ${component}\"\n      ${android_sdk_installation_dir}/tools/bin/sdkmanager ${component}\n  done\n\n  touch ${android_sdk_flag_file}\nfi"
  },
  {
    "path": "tools/ci/firebase-test-lab-test-module.sh",
    "content": "#!/bin/bash\n\n# Executes tests on a specified module and extracts the results into the \n# standard Android test directories.  This uses the default Firebase Test Lab\n# bucket with a custom results directory, in order to be able to copy the\n# results back programmatically.\n#\n# This script only runs a test on a single device configuration at a time, so it\n# does not support matrices.  In order to create a matrix, invoke this script\n# multiple times with additional arguments.\n#\n# When files are copied off of gcloud, they are given unique names to ensure\n# that multiple invocations of this script will not cause naming collisions.\n# \n# This script expects the following parameters in order:\n\n# 1. module tested\n# 2. Default Firebase test lab bucket.  You'll need to invoke the gcloud command manually and find the bucket name to pass for this parameter.\n# 3. Firebase Test Lab device as a quoted string.  E.g. \"model=NexusLowRes,version=25,orientation=portrait\"\n# 4. Additional Firebase Test Lab arguments, as a quoted string.\n\n# Example usage:\n# ./tools/ci/firebase-test-lab-test-module.sh spackleLib test-lab-5fjjivmbih0ck-i7fd9i4fw50ym \"model=NexusLowRes,version=25,orientation=portrait\"\n\nset -e\n\nmodule=\"$1\"\ndefault_bucket=\"$2\"\ndevice=\"$3\"\ngcloud_args=\"$4\"\n\nuuid=`uuidgen`\n\n# If a test case fails, exit code 10 will be returned. The script should continue if that occurs.\nset +e\ngcloud firebase test android run --results-dir=${uuid} tools/gcloud.yml:${module} --device ${device} ${gcloud_args}\ngcloud_exit_code=$?\nset -e\n\nif [[ $gcloud_exit_code != 0 && $gcloud_exit_code != 10 ]]; then exit $gcloud_exit_code; fi\n\n\n# Copy test results.\necho \"Fetching test results\"\nmkdir -p ${module}/build/outputs/androidTest-results/connected/\ngsutil -m cp -r \"gs://${default_bucket}/${uuid}/**test_result_*.xml\" \"${module}/build/outputs/androidTest-results/connected/${uuid}-test-result.xml\"\n\n# Copy coverage.\necho \"Fetching coverage\"\nmkdir -p ${module}/build/outputs/code-coverage/connected/\ngsutil -m cp -r \"gs://${default_bucket}/${uuid}/**/artifacts/coverage.ec\" \"${module}/build/outputs/code-coverage/connected/${uuid}-coverage.ec\"\n"
  },
  {
    "path": "tools/ci/google-cloud-test-lab-setup.sh",
    "content": "#!/bin/bash\n\n# Sets up gcloud.  Marks a flag to remember whether the SDK has been installed previously\n# and will bypass downloading if the flag exists. Note that if you change the version,\n# you should delete the flag and the installation so that the script will download\n# the new version.  The script expects several parameters in this order:\n# 1. gcloud installation directory — the directory must exist and should be empty (if the flag doesn't exist).  e.g. ~/gcloud\n# 2. gcloud version, e.g. 143.0.1\n# 3. flag path, e.g. ~/flags/gcloud-setup\n# 4. gcloud service key — base64 encoded gcloud service key\n# 5. gcloud project name — name of the gcloud project.\n\n# usage:\n# google-cloud-test-lab-setup.sh ~/gcloud 143.0.1 ~/flags/gcloud-setup <service_key_base64> <project>\n\nset +e\n\ngcloud_installation_dir=\"$1\"\ngcloud_version=\"$2\"\ngcloud_flag_file=\"$3\"\ngcloud_service_key_base64=\"$4\"\ngcloud_project=\"$5\"\n\nif ! test -f ${gcloud_flag_file}; then\n  echo \"Install gcloud SDK ${gcloud_version} to ${gcloud_installation_dir}\"\n\n  (cd ~ && curl \"https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-${gcloud_version}-linux-x86_64.tar.gz\" | tar zx && mv google-cloud-sdk ${gcloud_installation_dir})\n\n  ${gcloud_installation_dir}/bin/gcloud --quiet components install beta\n\n  touch ${gcloud_flag_file}\nfi\n\ngcloud_service_account_key_path=~/gcloud_service_account_key_temp.json\necho $gcloud_service_key_base64 | base64 --decode > ${gcloud_service_account_key_path}\n${gcloud_installation_dir}/bin/gcloud --quiet auth activate-service-account --key-file ${gcloud_service_account_key_path}\nrm ${gcloud_service_account_key_path}\n\n${gcloud_installation_dir}/bin/gcloud --quiet config set project $gcloud_project\n"
  },
  {
    "path": "tools/ci/repositories.cfg",
    "content": "### User Sources for Android SDK Manager\n#Mon Jan 30 14:33:04 PST 2017\ncount=0\n"
  },
  {
    "path": "tools/findbugs/android-filter.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<FindBugsFilter>\n    <Match>\n        <Class name=\"~.*\\.R\\$.*\"/>\n    </Match>\n    <Match>\n    <Class name=\"~.*\\.Manifest\\$.*\"/>\n    </Match>\n</FindBugsFilter>\n"
  },
  {
    "path": "tools/gcloud.yml",
    "content": "toastPluginSettingApp:\n  type: instrumentation\n  app: toastPluginSettingApp/build/outputs/apk/toastPluginSettingApp-debug.apk\n  test: toastPluginSettingApp/build/outputs/apk/toastPluginSettingApp-debug-androidTest.apk\n  timeout: 10m\n  results-history-name: toastPluginSettingApp\n  include: [coverage-environment-variables]\n\ncoverage-environment-variables:\n  environment-variables:\n    coverage: true\n    coverageFile: \"/sdcard/coverage.ec\"\n  directories-to-pull: [\"/sdcard\"]\n"
  }
]