├── mobile
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── drawable
│ │ │ │ ├── mycroft_big.png
│ │ │ │ ├── mycroft_logo_24dp.png
│ │ │ │ ├── kb_to_mic.xml
│ │ │ │ ├── ic_send_black_24dp.xml
│ │ │ │ ├── volume_toggle.xml
│ │ │ │ ├── ic_baseline_person_24.xml
│ │ │ │ ├── ic_mic_black_24dp.xml
│ │ │ │ ├── ic_volume_up_black_24dp.xml
│ │ │ │ ├── ic_keyboard_black_24dp.xml
│ │ │ │ ├── ic_volume_off_black_24dp.xml
│ │ │ │ ├── ic_info_black_24dp.xml
│ │ │ │ ├── ic_notifications_black_24dp.xml
│ │ │ │ └── ic_sync_black_24dp.xml
│ │ │ ├── drawable-hdpi
│ │ │ │ └── ic_mycroft.png
│ │ │ ├── drawable-mdpi
│ │ │ │ └── ic_mycroft.png
│ │ │ ├── mipmap-hdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── drawable-xhdpi
│ │ │ │ └── ic_mycroft.png
│ │ │ ├── drawable-xxhdpi
│ │ │ │ └── ic_mycroft.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ └── ic_mycroft.png
│ │ │ ├── values
│ │ │ │ ├── wear.xml
│ │ │ │ ├── colors.xml
│ │ │ │ ├── dimens.xml
│ │ │ │ ├── styles.xml
│ │ │ │ └── strings.xml
│ │ │ ├── values-v14
│ │ │ │ └── dimens.xml
│ │ │ ├── values-v21
│ │ │ │ └── styles.xml
│ │ │ ├── values-w820dp
│ │ │ │ └── dimens.xml
│ │ │ ├── menu
│ │ │ │ ├── menu_user_utterance_context.xml
│ │ │ │ ├── menu_mycroft_utterance_context.xml
│ │ │ │ └── menu_setup.xml
│ │ │ ├── xml
│ │ │ │ ├── mycroft_app_widget_info.xml
│ │ │ │ ├── pref_about.xml
│ │ │ │ ├── pref_general.xml
│ │ │ │ ├── pref_data_sync.xml
│ │ │ │ ├── pref_headers.xml
│ │ │ │ └── pref_notification.xml
│ │ │ └── layout
│ │ │ │ ├── activity_display.xml
│ │ │ │ ├── mycroft_app_widget.xml
│ │ │ │ ├── activity_discovery.xml
│ │ │ │ ├── activity_main.xml
│ │ │ │ ├── mycroft_card_layout.xml
│ │ │ │ ├── user_card_layout.xml
│ │ │ │ └── content_main.xml
│ │ ├── java
│ │ │ ├── mycroft
│ │ │ │ └── ai
│ │ │ │ │ ├── UtterenceFrom.kt
│ │ │ │ │ ├── Utterance.kt
│ │ │ │ │ ├── Constants.kt
│ │ │ │ │ ├── SafeCallback.kt
│ │ │ │ │ ├── utils
│ │ │ │ │ ├── PlayServicesUtil.kt
│ │ │ │ │ ├── NetworkUtil.kt
│ │ │ │ │ └── NetworkAutoDiscoveryUtil.kt
│ │ │ │ │ ├── receivers
│ │ │ │ │ └── NetworkChangeReceiver.kt
│ │ │ │ │ ├── MessageParser.kt
│ │ │ │ │ ├── services
│ │ │ │ │ └── MycroftWearListenerService.kt
│ │ │ │ │ ├── adapters
│ │ │ │ │ └── MycroftAdapter.kt
│ │ │ │ │ ├── AppCompatPreferenceActivity.kt
│ │ │ │ │ ├── TTSManager.kt
│ │ │ │ │ ├── DiscoveryActivity.kt
│ │ │ │ │ ├── SettingsActivity.kt
│ │ │ │ │ └── MainActivity.kt
│ │ │ └── layout
│ │ │ │ └── MycroftAppWidget.kt
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── mycroft
│ │ │ └── ai
│ │ │ ├── ExampleUnitTest.java
│ │ │ ├── LogAnswer.java
│ │ │ └── TTSManagerTest.java
│ └── androidTest
│ │ └── java
│ │ └── mycroft
│ │ └── ai
│ │ └── ExampleInstrumentationTest.java
├── proguard-rules.pro
└── build.gradle.kts
├── shared
├── .gitignore
├── proguard-rules.pro
├── src
│ ├── main
│ │ ├── res
│ │ │ └── values
│ │ │ │ └── strings.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── mycroft
│ │ │ └── ai
│ │ │ └── shared
│ │ │ ├── wear
│ │ │ └── Constants.kt
│ │ │ └── utilities
│ │ │ └── GuiUtilities.kt
│ ├── test
│ │ └── java
│ │ │ └── mycroft
│ │ │ └── ai
│ │ │ └── shared
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── mycroft
│ │ └── ai
│ │ └── shared
│ │ └── ExampleInstrumentedTest.java
└── build.gradle.kts
├── wear
├── .gitignore
├── src
│ └── main
│ │ ├── res
│ │ ├── drawable
│ │ │ └── mycroft_logo.png
│ │ ├── drawable-hdpi
│ │ │ └── ic_mycroft.png
│ │ ├── drawable-mdpi
│ │ │ └── ic_mycroft.png
│ │ ├── mipmap-xxhdpi
│ │ │ └── ic_mycroft.png
│ │ ├── drawable-xhdpi
│ │ │ └── ic_mycroft.png
│ │ ├── drawable-xxhdpi
│ │ │ └── ic_mycroft.png
│ │ ├── values
│ │ │ ├── wear.xml
│ │ │ └── strings.xml
│ │ └── layout
│ │ │ └── activity_main.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── mycroft
│ │ └── ai
│ │ └── MainActivity.kt
├── proguard-rules.pro
└── build.gradle.kts
├── settings.gradle.kts
├── .gitignore
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew.bat
├── README.md
└── gradlew
/mobile/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/shared/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/wear/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | include(":mobile", ":wear", ":shared")
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | .idea
4 | local.properties
5 | .DS_Store
6 | .gradle/
7 | build/
8 | /captures
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MycroftAI/Mycroft-Android/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/wear/src/main/res/drawable/mycroft_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MycroftAI/Mycroft-Android/HEAD/wear/src/main/res/drawable/mycroft_logo.png
--------------------------------------------------------------------------------
/mobile/src/main/res/drawable/mycroft_big.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MycroftAI/Mycroft-Android/HEAD/mobile/src/main/res/drawable/mycroft_big.png
--------------------------------------------------------------------------------
/wear/src/main/res/drawable-hdpi/ic_mycroft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MycroftAI/Mycroft-Android/HEAD/wear/src/main/res/drawable-hdpi/ic_mycroft.png
--------------------------------------------------------------------------------
/wear/src/main/res/drawable-mdpi/ic_mycroft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MycroftAI/Mycroft-Android/HEAD/wear/src/main/res/drawable-mdpi/ic_mycroft.png
--------------------------------------------------------------------------------
/wear/src/main/res/mipmap-xxhdpi/ic_mycroft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MycroftAI/Mycroft-Android/HEAD/wear/src/main/res/mipmap-xxhdpi/ic_mycroft.png
--------------------------------------------------------------------------------
/mobile/src/main/java/mycroft/ai/UtterenceFrom.kt:
--------------------------------------------------------------------------------
1 | package mycroft.ai
2 |
3 | enum class UtteranceFrom(val id: Int) {
4 | USER(0),
5 | MYCROFT(1)
6 | }
--------------------------------------------------------------------------------
/mobile/src/main/res/drawable-hdpi/ic_mycroft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MycroftAI/Mycroft-Android/HEAD/mobile/src/main/res/drawable-hdpi/ic_mycroft.png
--------------------------------------------------------------------------------
/mobile/src/main/res/drawable-mdpi/ic_mycroft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MycroftAI/Mycroft-Android/HEAD/mobile/src/main/res/drawable-mdpi/ic_mycroft.png
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MycroftAI/Mycroft-Android/HEAD/mobile/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MycroftAI/Mycroft-Android/HEAD/mobile/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MycroftAI/Mycroft-Android/HEAD/mobile/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/wear/src/main/res/drawable-xhdpi/ic_mycroft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MycroftAI/Mycroft-Android/HEAD/wear/src/main/res/drawable-xhdpi/ic_mycroft.png
--------------------------------------------------------------------------------
/wear/src/main/res/drawable-xxhdpi/ic_mycroft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MycroftAI/Mycroft-Android/HEAD/wear/src/main/res/drawable-xxhdpi/ic_mycroft.png
--------------------------------------------------------------------------------
/mobile/src/main/res/drawable-xhdpi/ic_mycroft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MycroftAI/Mycroft-Android/HEAD/mobile/src/main/res/drawable-xhdpi/ic_mycroft.png
--------------------------------------------------------------------------------
/mobile/src/main/res/drawable-xxhdpi/ic_mycroft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MycroftAI/Mycroft-Android/HEAD/mobile/src/main/res/drawable-xxhdpi/ic_mycroft.png
--------------------------------------------------------------------------------
/mobile/src/main/res/drawable/mycroft_logo_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MycroftAI/Mycroft-Android/HEAD/mobile/src/main/res/drawable/mycroft_logo_24dp.png
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MycroftAI/Mycroft-Android/HEAD/mobile/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-xxxhdpi/ic_mycroft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MycroftAI/Mycroft-Android/HEAD/mobile/src/main/res/mipmap-xxxhdpi/ic_mycroft.png
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun Jun 14 22:20:10 PDT 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip
7 |
--------------------------------------------------------------------------------
/mobile/src/main/res/drawable/kb_to_mic.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
9 |
--------------------------------------------------------------------------------
/mobile/src/main/res/drawable/ic_send_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/mobile/src/main/res/drawable/volume_toggle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
11 |
--------------------------------------------------------------------------------
/mobile/src/main/res/drawable/ic_baseline_person_24.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/mobile/src/main/res/drawable/ic_mic_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/mobile/src/main/res/drawable/ic_volume_up_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/mobile/src/main/res/drawable/ic_keyboard_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/mobile/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /opt/android-sdks/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/shared/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /home/joe/Android/Sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/wear/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in C:\Users\jpoff\Android\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/mobile/src/main/res/drawable/ic_volume_off_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 | android.useAndroidX=true
19 |
20 | android.enableJetifier=true
21 |
--------------------------------------------------------------------------------
/shared/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
20 |
21 |
22 | Shared
23 |
24 |
--------------------------------------------------------------------------------
/mobile/src/main/java/mycroft/ai/Utterance.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017. Mycroft AI, Inc.
3 | *
4 | * This file is part of Mycroft-Android a client for Mycroft Core.
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | package mycroft.ai
22 |
23 | /**
24 | * Data class representing a response from Mycroft or command from the user
25 | */
26 | data class Utterance(val utterance: String, val from: UtteranceFrom)
--------------------------------------------------------------------------------
/wear/src/main/res/values/wear.xml:
--------------------------------------------------------------------------------
1 |
2 |
21 |
22 |
23 |
24 | query_mycroft_capability
25 |
26 |
--------------------------------------------------------------------------------
/mobile/src/main/res/values/wear.xml:
--------------------------------------------------------------------------------
1 |
2 |
21 |
22 |
23 |
24 | query_mycroft_capability
25 |
26 |
--------------------------------------------------------------------------------
/wear/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
20 |
21 |
22 | Mycroft-Android
23 | Say something to Mycroft…
24 | Sorry! Your device doesn\'t support speech input
25 |
26 |
--------------------------------------------------------------------------------
/shared/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
20 |
21 |
22 |
23 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/mobile/src/main/java/mycroft/ai/Constants.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017. Mycroft AI, Inc.
3 | *
4 | * This file is part of Mycroft-Android a client for Mycroft Core.
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | package mycroft.ai
22 |
23 | class Constants {
24 | object MycroftMobileConstants {
25 | //how to properly call constants in kotlin?
26 | const val VERSION_NAME_PREFERENCE_KEY = "versionName"
27 | const val VERSION_CODE_PREFERENCE_KEY = "versionCode"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/mobile/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
21 |
22 |
23 | #22A7F0
24 | #4990e2
25 | #80c3f3
26 | #E1F5FE
27 |
28 |
--------------------------------------------------------------------------------
/mobile/src/main/res/values-v14/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
21 |
22 |
23 |
24 |
28 | 0dp
29 |
30 |
--------------------------------------------------------------------------------
/mobile/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
20 |
21 |
22 |
23 |
29 |
30 |
--------------------------------------------------------------------------------
/mobile/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
20 |
21 |
22 |
25 | 64dp
26 |
27 |
--------------------------------------------------------------------------------
/mobile/src/main/res/drawable/ic_info_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
20 |
21 |
26 |
29 |
30 |
--------------------------------------------------------------------------------
/mobile/src/test/java/mycroft/ai/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017. Mycroft AI, Inc.
3 | *
4 | * This file is part of Mycroft-Android a client for Mycroft Core.
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | package mycroft.ai;
22 |
23 | import org.junit.Test;
24 |
25 | import static org.junit.Assert.*;
26 |
27 | /**
28 | * Example local unit test, which will execute on the development machine (host).
29 | *
30 | * @see Testing documentation
31 | */
32 | public class ExampleUnitTest {
33 | @Test
34 | public void addition_isCorrect() throws Exception {
35 | assertEquals(4, 2 + 2);
36 | }
37 | }
--------------------------------------------------------------------------------
/mobile/src/main/res/menu/menu_user_utterance_context.xml:
--------------------------------------------------------------------------------
1 |
20 |
21 |
--------------------------------------------------------------------------------
/shared/src/test/java/mycroft/ai/shared/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017. Mycroft AI, Inc.
3 | *
4 | * This file is part of Mycroft-Android a client for Mycroft Core.
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | package mycroft.ai.shared;
22 |
23 | import org.junit.Test;
24 |
25 | import static org.junit.Assert.*;
26 |
27 | /**
28 | * Example local unit test, which will execute on the development machine (host).
29 | *
30 | * @see Testing documentation
31 | */
32 | public class ExampleUnitTest {
33 | @Test
34 | public void addition_isCorrect() throws Exception {
35 | assertEquals(4, 2 + 2);
36 | }
37 | }
--------------------------------------------------------------------------------
/mobile/src/main/res/menu/menu_mycroft_utterance_context.xml:
--------------------------------------------------------------------------------
1 |
20 |
21 |
--------------------------------------------------------------------------------
/mobile/src/main/java/mycroft/ai/SafeCallback.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017. Mycroft AI, Inc.
3 | *
4 | * This file is part of Mycroft-Android a client for Mycroft Core.
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | package mycroft.ai
22 |
23 | import java.util.concurrent.Callable
24 |
25 | /**
26 | * Inversion of the [java.util.concurrent.Callable] interface.
27 | *
28 | *
29 | * Note that the [.call] method in this class is
30 | * not allowed to throw exceptions.
31 | *
32 | *
33 | * @author Philip Cohn-Cort
34 | */
35 | interface SafeCallback {
36 | /**
37 | * Variant of [Callable.call]
38 | * @param param any value. May be null.
39 | */
40 | fun call(param: T)
41 | }
42 |
--------------------------------------------------------------------------------
/shared/src/main/java/mycroft/ai/shared/wear/Constants.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017. Mycroft AI, Inc.
3 | *
4 | * This file is part of Mycroft-Android a client for Mycroft Core.
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | package mycroft.ai.shared.wear
22 |
23 | /**
24 | * Created by joe on 12/21/16.
25 | */
26 |
27 | class Constants {
28 | object MycroftSharedConstants {
29 | const val MYCROFT_QUERY_MESSAGE_PATH = "/mycroft_query"
30 | const val MYCROFT_WEAR_REQUEST = "mycroft.ai.wear.request"
31 | const val MYCROFT_WEAR_REQUEST_KEY_NAME = "MYCROFT_WEAR_REQUEST"
32 | const val MYCROFT_WEAR_REQUEST_MESSAGE = "mycroft.ai.wear.request.message"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/mobile/src/main/res/drawable/ic_notifications_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
20 |
21 |
26 |
29 |
30 |
--------------------------------------------------------------------------------
/mobile/src/main/res/drawable/ic_sync_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
20 |
21 |
26 |
29 |
--------------------------------------------------------------------------------
/mobile/src/main/res/xml/mycroft_app_widget_info.xml:
--------------------------------------------------------------------------------
1 |
2 |
21 |
22 |
31 |
32 |
--------------------------------------------------------------------------------
/mobile/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
20 |
21 |
22 |
23 | 16dp
24 | 16dp
25 | 12dp
26 |
27 |
31 | 8dp
32 | 40dp
33 | 60dp
34 |
35 |
--------------------------------------------------------------------------------
/mobile/src/main/res/layout/activity_display.xml:
--------------------------------------------------------------------------------
1 |
2 |
21 |
22 |
29 |
30 |
35 |
--------------------------------------------------------------------------------
/mobile/src/main/res/xml/pref_about.xml:
--------------------------------------------------------------------------------
1 |
20 |
21 |
22 |
23 |
27 |
28 |
32 |
33 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/mobile/src/main/res/layout/mycroft_app_widget.xml:
--------------------------------------------------------------------------------
1 |
20 |
21 |
26 |
27 |
28 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/mobile/src/main/res/menu/menu_setup.xml:
--------------------------------------------------------------------------------
1 |
20 |
21 |
39 |
--------------------------------------------------------------------------------
/shared/src/main/java/mycroft/ai/shared/utilities/GuiUtilities.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017. Mycroft AI, Inc.
3 | *
4 | * This file is part of Mycroft-Android a client for Mycroft Core.
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | package mycroft.ai.shared.utilities
22 |
23 | import android.content.Context
24 | import android.view.Gravity
25 | import android.widget.LinearLayout
26 | import android.widget.TextView
27 | import android.widget.Toast
28 |
29 | /**
30 | * Created by joe on 12/21/16.
31 | */
32 |
33 | object GuiUtilities {
34 | fun showToast(context: Context, message: String) {
35 | val toast = Toast.makeText(context, message, Toast.LENGTH_SHORT)
36 |
37 | val layout = toast.view as LinearLayout
38 | if (layout.childCount > 0) {
39 | val tv = layout.getChildAt(0) as TextView
40 | tv.gravity = Gravity.CENTER_VERTICAL or Gravity.CENTER_HORIZONTAL
41 | }
42 |
43 | toast.show()
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/mobile/src/androidTest/java/mycroft/ai/ExampleInstrumentationTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017. Mycroft AI, Inc.
3 | *
4 | * This file is part of Mycroft-Android a client for Mycroft Core.
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | package mycroft.ai;
22 |
23 | import androidx.test.ext.junit.runners.AndroidJUnit4;
24 |
25 | import org.junit.Test;
26 | import org.junit.runner.RunWith;
27 |
28 | /**
29 | * Instrumentation test, which will execute on an Android device.
30 | *
31 | * @see Testing documentation
32 | */
33 | //@MediumTest
34 | @RunWith(AndroidJUnit4.class)
35 | public class ExampleInstrumentationTest {
36 | @Test
37 | public void useAppContext() throws Exception {
38 | // TODO Fix below
39 | // Context of the app under test.
40 | // Context appContext = InstrumentationRegistry.getTargetContext();
41 | //assertEquals("mycroft.ai", appContext.getPackageName());
42 | }
43 | }
--------------------------------------------------------------------------------
/shared/src/androidTest/java/mycroft/ai/shared/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017. Mycroft AI, Inc.
3 | *
4 | * This file is part of Mycroft-Android a client for Mycroft Core.
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | package mycroft.ai.shared;
22 |
23 | import androidx.test.runner.AndroidJUnit4;
24 |
25 | import org.junit.Test;
26 | import org.junit.runner.RunWith;
27 |
28 | /**
29 | * Instrumentation test, which will execute on an Android device.
30 | *
31 | * @see Testing documentation
32 | */
33 | @RunWith(AndroidJUnit4.class)
34 | public class ExampleInstrumentedTest {
35 | @Test
36 | public void useAppContext() throws Exception {
37 | // TODO Fix below
38 | // Context of the app under test.
39 | //Context appContext = InstrumentationRegistry.getTargetContext();
40 |
41 | //assertEquals("mycroft.ai.shared.test", appContext.getPackageName());
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/wear/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
21 |
22 |
28 |
29 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/mobile/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
20 |
21 |
22 |
23 |
24 |
32 |
33 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/mobile/src/main/res/xml/pref_general.xml:
--------------------------------------------------------------------------------
1 |
20 |
21 |
22 |
23 |
24 |
25 |
34 |
35 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/mobile/src/main/res/layout/activity_discovery.xml:
--------------------------------------------------------------------------------
1 |
2 |
21 |
22 |
26 |
31 |
37 |
38 |
43 |
44 |
--------------------------------------------------------------------------------
/wear/build.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017. Mycroft AI, Inc.
3 | *
4 | * This file is part of Mycroft-Android a client for Mycroft Core.
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | plugins {
22 | id("com.android.application")
23 | kotlin("android")
24 | }
25 |
26 | android {
27 |
28 | compileSdk = 29
29 | buildToolsVersion = "31.0.0"
30 | defaultConfig {
31 | applicationId = "mycroft.ai"
32 | minSdk = 19
33 | targetSdk = 29
34 | versionCode = project.ext.get("versionCode") as Int
35 | versionName = project.ext.get("versionName") as String
36 | }
37 | buildTypes {
38 | named("release") {
39 | isMinifyEnabled = false
40 | proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
41 | }
42 | }
43 | productFlavors {
44 | }
45 | }
46 |
47 | dependencies {
48 | implementation(fileTree("include" to arrayOf("*.jar"), "dir" to "libs"))
49 | compileOnly("com.google.android.wearable:wearable:2.8.1")
50 | implementation("com.google.android.support:wearable:2.8.1")
51 | implementation("com.google.android.gms:play-services-wearable:17.1.0")
52 | implementation(project(":shared"))
53 | implementation("org.jetbrains.kotlin:kotlin-stdlib:1.5.31")
54 | }
55 |
--------------------------------------------------------------------------------
/mobile/src/main/res/xml/pref_data_sync.xml:
--------------------------------------------------------------------------------
1 |
20 |
21 |
22 |
23 |
25 |
26 |
34 |
35 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/shared/build.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017. Mycroft AI, Inc.
3 | *
4 | * This file is part of Mycroft-Android a client for Mycroft Core.
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | plugins {
22 | id("com.android.library")
23 | kotlin("android")
24 | }
25 |
26 | android {
27 | compileSdk = 29
28 | buildToolsVersion = "31.0.0"
29 |
30 | defaultConfig {
31 | minSdk = 19
32 | targetSdk = 29
33 |
34 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
35 | }
36 |
37 | buildFeatures {
38 | dataBinding = true
39 | }
40 |
41 | buildTypes {
42 | named("release") {
43 | isMinifyEnabled = false
44 |
45 | proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
46 | }
47 | }
48 | }
49 |
50 | dependencies {
51 | implementation(fileTree("dir" to "libs", "include" to arrayOf("*.jar")))
52 | androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0")
53 | implementation("androidx.appcompat:appcompat:1.3.1")
54 | testImplementation("junit:junit:4.13.2")
55 | implementation("org.jetbrains.kotlin:kotlin-stdlib:1.5.31")
56 | implementation("androidx.annotation:annotation:1.2.0")
57 |
58 | implementation(group = "com.google.android.material", name = "material", version = "1.4.0")
59 | }
60 |
--------------------------------------------------------------------------------
/wear/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
31 |
32 |
33 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/mobile/src/main/res/xml/pref_headers.xml:
--------------------------------------------------------------------------------
1 |
20 |
21 |
22 |
23 |
24 |
28 |
29 |
33 |
34 |
38 |
39 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/mobile/src/test/java/mycroft/ai/LogAnswer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017. Mycroft AI, Inc.
3 | *
4 | * This file is part of Mycroft-Android a client for Mycroft Core.
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | package mycroft.ai;
22 |
23 | import org.mockito.invocation.InvocationOnMock;
24 | import org.mockito.stubbing.Answer;
25 |
26 | import java.io.PrintStream;
27 | import java.nio.charset.Charset;
28 | import java.util.Locale;
29 |
30 | /**
31 | * Useful for mocking {@link android.util.Log} static methods.
32 | *
33 | * Use it like so:
34 | * {@code
35 | * Mockito.when(Log.v(anyString(), anyString())).then(new LogAnswer(System.out));
36 | * }
37 | *
38 | *
39 | * @author Philip Cohn-Cort
40 | */
41 | public class LogAnswer implements Answer {
42 |
43 | protected final PrintStream stream;
44 |
45 | public LogAnswer(PrintStream stream) {
46 | this.stream = stream;
47 | }
48 |
49 | @Override
50 | public Integer answer(InvocationOnMock invocation) throws Throwable {
51 | String tag = invocation.getArgument(0, String.class);
52 | String msg = invocation.getArgument(1, String.class);
53 |
54 | String name = invocation.getMethod().getName();
55 |
56 | String format = String.format(Locale.US, "[Log.%s] %s: %s", name, tag, msg);
57 | stream.println(format);
58 |
59 | return format.getBytes(Charset.forName("UTF8")).length;
60 | }
61 | }
--------------------------------------------------------------------------------
/mobile/src/main/java/mycroft/ai/utils/PlayServicesUtil.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017. Mycroft AI, Inc.
3 | *
4 | * This file is part of Mycroft-Android a client for Mycroft Core.
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | package mycroft.ai.utils
22 |
23 | import android.content.Context
24 | import com.google.android.gms.common.ConnectionResult
25 | import com.google.android.gms.common.GoogleApiAvailability
26 |
27 |
28 | /**
29 | * Created by pscot on 6/25/2016.
30 | */
31 | class PlayServicesUtil(private val context: Context) {
32 |
33 | val isPsInstalled: Boolean
34 |
35 | init {
36 | isPsInstalled = checkPlayServices()
37 | }
38 |
39 | /**
40 | * Check the device to make sure it has the Google Play Services APK. If
41 | * it doesn't, display a dialog that allows users to download the APK from
42 | * the Google Play Store or enable it in the device's system settings.
43 | */
44 | private fun checkPlayServices(): Boolean {
45 | val apiAvailability = GoogleApiAvailability.getInstance()
46 | val resultCode = apiAvailability.isGooglePlayServicesAvailable(context.applicationContext)
47 | return if (resultCode != ConnectionResult.SUCCESS) {
48 | return false
49 | } else true //orig return true. Need to test. Might be an issue.
50 | }
51 |
52 | companion object {
53 |
54 | private val PLAY_SERVICES_RESOLUTION_REQUEST = 9000
55 | private val TAG = "PlayServicesUtil"
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/mobile/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
20 |
21 |
31 |
32 |
36 |
37 |
43 |
44 |
45 |
46 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/mobile/src/main/res/xml/pref_notification.xml:
--------------------------------------------------------------------------------
1 |
20 |
21 |
22 |
23 |
25 |
29 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/mobile/src/main/java/mycroft/ai/receivers/NetworkChangeReceiver.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017. Mycroft AI, Inc.
3 | *
4 | * This file is part of Mycroft-Android a client for Mycroft Core.
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | package mycroft.ai.receivers
22 |
23 | import android.content.BroadcastReceiver
24 | import android.content.Context
25 | import android.content.Intent
26 |
27 | import mycroft.ai.MainActivity
28 | import mycroft.ai.utils.NetworkUtil
29 |
30 | /**
31 | * Simple class to detect changes in network connectivity.
32 | *
33 | *
34 | * It should trigger connection and disconnection actions
35 | * on the appropriate handler, which for now is [MainActivity].
36 | *
37 | *
38 | * @see .setMainActivityHandler
39 | * @author Paul Scott
40 | */
41 | class NetworkChangeReceiver : BroadcastReceiver() {
42 |
43 | private var main: MainActivity? = null
44 |
45 | fun setMainActivityHandler(main: MainActivity?) {
46 | this.main = main
47 | }
48 |
49 | override fun onReceive(context: Context, intent: Intent) {
50 | val status = NetworkUtil.getConnectivityStatusString(context)
51 | if ("android.net.conn.CONNECTIVITY_CHANGE" != intent.action) {
52 | if (status == NetworkUtil.NETWORK_STATUS_NOT_CONNECTED) {
53 | // do something about it.. IDK
54 | } else if (main != null) {
55 | // reconnect websocket
56 | if (main!!.webSocketClient == null || main!!.webSocketClient!!.connection.isClosed) {
57 | main!!.connectWebSocket()
58 | }
59 | }
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/mobile/src/main/java/mycroft/ai/MessageParser.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017. Mycroft AI, Inc.
3 | *
4 | * This file is part of Mycroft-Android a client for Mycroft Core.
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | package mycroft.ai
22 |
23 | import android.util.Log
24 |
25 | import org.json.JSONException
26 | import org.json.JSONObject
27 |
28 | /**
29 | * Specialised Runnable that parses the [JSONObject] in [.message]
30 | * when run. If it contains a [Utterance] object, the callback
31 | * defined in [the constructor][.MessageParser] will
32 | * be [called][SafeCallback.call] with that object as a parameter.
33 | *
34 | *
35 | * TODO: Add error-aware callback for cases where the message is malformed.
36 | *
37 | *
38 | * @author Philip Cohn-Cort
39 | */
40 | internal class MessageParser(private val message: String,
41 | private val callback: SafeCallback) : Runnable {
42 | private val logTag = "MessageParser"
43 |
44 | override fun run() {
45 | Log.i(logTag, message)
46 | // new format
47 | // {"data": {"utterance": "There are only two hard problems in Computer Science: cache invalidation, naming things and off-by-one-errors."}, "type": "speak", "context": null}
48 | try {
49 | val obj = JSONObject(message)
50 | if (obj.optString("type") == "speak") {
51 | val ret = Utterance(obj.getJSONObject("data").getString("utterance"), UtteranceFrom.MYCROFT)
52 | callback.call(ret)
53 | }
54 | } catch (e: JSONException) {
55 | Log.e(logTag, "The response received did not conform to our expected JSON format.", e)
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/mobile/src/main/java/mycroft/ai/utils/NetworkUtil.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017. Mycroft AI, Inc.
3 | *
4 | * This file is part of Mycroft-Android a client for Mycroft Core.
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | package mycroft.ai.utils
22 |
23 | import android.content.Context
24 | import android.net.ConnectivityManager
25 |
26 | /**
27 | * Created by paul on 2016/06/22.
28 | */
29 |
30 | object NetworkUtil {
31 |
32 | private var TYPE_WIFI = 1
33 | private var TYPE_MOBILE = 2
34 | private var TYPE_NOT_CONNECTED = 0
35 | var NETWORK_STATUS_NOT_CONNECTED = 0
36 | var NETWORK_STATUS_WIFI = 1
37 | private var NETWORK_STATUS_MOBILE = 2
38 |
39 | fun getConnectivityStatus(context: Context): Int {
40 | val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
41 |
42 | // TODO Deprecated connection api below, update please
43 | val activeNetwork = cm.activeNetworkInfo
44 | if (null != activeNetwork) {
45 | if (activeNetwork.type == ConnectivityManager.TYPE_WIFI)
46 | return TYPE_WIFI
47 |
48 | if (activeNetwork.type == ConnectivityManager.TYPE_MOBILE)
49 | return TYPE_MOBILE
50 | }
51 | return TYPE_NOT_CONNECTED
52 | }
53 |
54 | fun getConnectivityStatusString(context: Context): Int {
55 | val conn = NetworkUtil.getConnectivityStatus(context)
56 | var status = 0
57 |
58 | if (conn == NetworkUtil.TYPE_WIFI) {
59 | status = NETWORK_STATUS_WIFI
60 | } else if (conn == NetworkUtil.TYPE_MOBILE) {
61 | status = NETWORK_STATUS_MOBILE
62 | } else if (conn == NetworkUtil.TYPE_NOT_CONNECTED) {
63 | status = NETWORK_STATUS_NOT_CONNECTED
64 | }
65 | return status
66 | }
67 | }
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://www.gnu.org/licenses/gpl-3.0) [](https://mycroft.ai/cla)  
2 |
3 | [](http://makeapullrequest.com)
4 | [](https://chat.mycroft.ai/community/channels/android)
5 |
6 |
7 |
8 | # Mycroft-Android
9 |
10 | This is the Android companion app to Mycroft-core. It works by opening a websocket connection to the Mycroft-core messagebus
11 | and sending and receiving messages from there.
12 |
13 | It implements voice recognition and Text To Speech (TTS) via Google API's at the moment, but that may change soon.
14 |
15 | ## To Install
16 |
17 | Import the repo into Android Studio, or your IDE of choice.
18 | Build and deploy to a device
19 |
20 | Once the app is running on a device (Lollipop or later SDK 24), you will need to set the IP address of your Mycroft-core instance
21 | in the Settings -> General Options menu. That will then create a websocket connection to your Mycroft and off you go!
22 |
23 | ## To help out
24 | If you would like to help out on this project, please join Mattermost at https://chat.mycroft.ai/login and
25 | ask where you can contribute! Currently, design and UI/UX is most needed, but any and all help is greatly appreciated!
26 |
27 | ## Submission Notes
28 | Want to submit a fix, feature or...? Here is everything, we think you will need to know.
29 |
30 | Mycroft.ai is a collaborative, open source project. That means we encourage and expect people to participate. But to make things a bit more clear here are some kind lines if you would like to submit a fix.
31 |
32 | ### Passthrough (component app)
33 | 1. Pull your own fork, work there
34 | 2. make a branch of whatever you are working on, makes sure your fork is the latest.
35 | 3. Test!!!!
36 | 4. merge into your master.
37 | 5. make pull request into project master
38 | 6. assign a reviewer.
39 | 7. check on it, if not reviewed after a week find a new reviewer, we are mostly volunteers so find one that has time.
40 | 8. sit back and enjoy your handy work.
41 |
42 | #### Coding style...
43 | We have moved now to Kotlin and therefore will be following the standard coding practices. Also please use descriptive method/function names. And use comments to back up that name when complicated, like a calculation or similar. Remember, you want to come back 6 months from now and be able to read your code.
44 |
45 | Most of all have fun. Ask questions and don't worry about breaking anything, that is why we have a versioning system.
46 |
--------------------------------------------------------------------------------
/mobile/src/main/res/layout/mycroft_card_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
21 |
22 |
38 |
46 |
47 |
48 |
54 |
66 |
67 |
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/mobile/src/main/res/layout/user_card_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
21 |
22 |
29 |
30 |
43 |
51 |
57 |
58 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/mobile/src/main/java/layout/MycroftAppWidget.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017. Mycroft AI, Inc.
3 | *
4 | * This file is part of Mycroft-Android a client for Mycroft Core.
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | package layout
22 |
23 | import android.app.PendingIntent
24 | import android.appwidget.AppWidgetManager
25 | import android.appwidget.AppWidgetProvider
26 | import android.content.ActivityNotFoundException
27 | import android.content.Context
28 | import android.content.Intent
29 | import android.speech.RecognizerIntent
30 | import android.widget.RemoteViews
31 | import android.widget.Toast
32 |
33 | import java.util.Locale
34 |
35 | import mycroft.ai.MainActivity
36 | import mycroft.ai.R
37 |
38 | /**
39 | * Implementation of App Widget functionality.
40 | */
41 | class MycroftAppWidget : AppWidgetProvider() {
42 |
43 | override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
44 | // There may be multiple widgets active, so update all of them
45 | for (appWidgetId in appWidgetIds) {
46 | updateAppWidget(context, appWidgetManager, appWidgetId)
47 | }
48 | }
49 |
50 | override fun onEnabled(context: Context) {
51 | // Enter relevant functionality for when the first widget is created
52 | }
53 |
54 | override fun onDisabled(context: Context) {
55 | // Enter relevant functionality for when the last widget is disabled
56 | }
57 |
58 | companion object {
59 |
60 | internal fun updateAppWidget(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int) {
61 |
62 | // Create an Intent to launch MainActivity
63 | val intent = Intent(context, MainActivity::class.java)
64 | intent.putExtra("launchedFromWidget", true)
65 | intent.putExtra("autopromptForSpeech", true)
66 |
67 | // TODO Errors will occur in android 12 and above due to below
68 | val pendingIntent = PendingIntent.getActivity(context, appWidgetId, intent, PendingIntent.FLAG_UPDATE_CURRENT)
69 |
70 | // Construct the RemoteViews object
71 | val views = RemoteViews(context.packageName, R.layout.mycroft_app_widget)
72 | views.setOnClickPendingIntent(R.id.appwidget_mycroft, pendingIntent)
73 |
74 | // Instruct the widget manager to update the widget
75 | appWidgetManager.updateAppWidget(appWidgetId, views)
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/mobile/src/main/java/mycroft/ai/services/MycroftWearListenerService.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017. Mycroft AI, Inc.
3 | *
4 | * This file is part of Mycroft-Android a client for Mycroft Core.
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | package mycroft.ai.services
22 |
23 | import android.app.Service
24 | import android.content.Intent
25 | import android.util.Log
26 | import androidx.localbroadcastmanager.content.LocalBroadcastManager
27 |
28 | import com.google.android.gms.wearable.MessageEvent
29 | import com.google.android.gms.wearable.Node
30 | import com.google.android.gms.wearable.WearableListenerService
31 |
32 | import mycroft.ai.MainActivity
33 | import mycroft.ai.shared.wear.Constants
34 | import mycroft.ai.shared.wear.Constants.MycroftSharedConstants.MYCROFT_QUERY_MESSAGE_PATH
35 | import mycroft.ai.shared.wear.Constants.MycroftSharedConstants.MYCROFT_WEAR_REQUEST
36 | import mycroft.ai.shared.wear.Constants.MycroftSharedConstants.MYCROFT_WEAR_REQUEST_MESSAGE
37 |
38 | /**
39 | * Created by jpoff on 9/7/2016.
40 | */
41 | class MycroftWearListenerService : WearableListenerService() {
42 |
43 | private var localBroadcastManager: LocalBroadcastManager? = null
44 | override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
45 | return Service.START_STICKY
46 | }
47 |
48 | override fun onCreate() {
49 | super.onCreate()
50 | localBroadcastManager = LocalBroadcastManager.getInstance(this)
51 | }
52 |
53 | override fun onMessageReceived(messageEvent: MessageEvent?) {
54 |
55 | val message = String(messageEvent!!.data)
56 |
57 | if (messageEvent.path == MYCROFT_QUERY_MESSAGE_PATH) {
58 | Log.d(TAG, "MycroftWearRequest Message: $message")
59 |
60 | val startIntent = Intent(this, MainActivity::class.java)
61 | startIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
62 | startIntent.putExtra("MYCROFT_WEAR_REQUEST", message)
63 | startActivity(startIntent)
64 |
65 | handoffWearRequest(message)
66 | }
67 | }
68 |
69 | override fun onPeerConnected(node: Node?) {
70 | Log.d(TAG, "onPeerConnected")
71 | }
72 |
73 | private fun handoffWearRequest(message: String?) {
74 | Log.d(TAG, "Hand Off Wear Request")
75 |
76 | if (message != null) {
77 | val intent = Intent(MYCROFT_WEAR_REQUEST)
78 | intent.putExtra(MYCROFT_WEAR_REQUEST_MESSAGE, message)
79 | localBroadcastManager!!.sendBroadcast(intent)
80 | }
81 | }
82 |
83 | companion object {
84 |
85 | private val TAG = "Mycroft"
86 | }
87 | }
88 |
89 |
90 |
--------------------------------------------------------------------------------
/mobile/src/main/java/mycroft/ai/adapters/MycroftAdapter.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017. Mycroft AI, Inc.
3 | *
4 | * This file is part of Mycroft-Android a client for Mycroft Core.
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | package mycroft.ai.adapters
22 |
23 | import android.content.Context
24 | import android.view.*
25 | import android.widget.TextView
26 | import androidx.recyclerview.widget.RecyclerView
27 | import mycroft.ai.R
28 | import mycroft.ai.Utterance
29 | import mycroft.ai.UtteranceFrom
30 |
31 | /**
32 | * Created by paul on 2016/06/22.
33 | */
34 | class MycroftAdapter(
35 | private val utteranceList: List,
36 | private val ctx: Context,
37 | private val menuInflater: MenuInflater
38 | ) : RecyclerView.Adapter() {
39 | var onLongClickListener: OnLongItemClickListener? = null
40 |
41 | interface OnLongItemClickListener {
42 | fun itemLongClicked(v: View, position: Int)
43 | }
44 |
45 | fun setOnLongItemClickListener(listener: OnLongItemClickListener) {
46 | onLongClickListener = listener
47 | }
48 |
49 | override fun getItemCount(): Int {
50 | return utteranceList.size
51 | }
52 |
53 | override fun onBindViewHolder(utteranceViewHolder: UtteranceViewHolder, i: Int) {
54 | utteranceViewHolder.vUtterance.text = utteranceList[i].utterance
55 | utteranceViewHolder.itemView.setOnLongClickListener { v ->
56 | onLongClickListener?.itemLongClicked(v, i)
57 | true
58 | }
59 | }
60 |
61 | @Throws(IndexOutOfBoundsException::class)
62 | override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): UtteranceViewHolder {
63 | val itemView = when (i) {
64 | UtteranceFrom.MYCROFT.id -> LayoutInflater.from(viewGroup.context)
65 | .inflate(R.layout.mycroft_card_layout, viewGroup, false)
66 | UtteranceFrom.USER.id -> LayoutInflater.from(viewGroup.context)
67 | .inflate(R.layout.user_card_layout, viewGroup, false)
68 | else -> throw IndexOutOfBoundsException("No such view id $i")
69 | }
70 |
71 | return UtteranceViewHolder(itemView, menuInflater, i)
72 | }
73 |
74 | override fun getItemViewType(position: Int): Int {
75 | val message = utteranceList[position]
76 | return message.from.id
77 | }
78 |
79 | class UtteranceViewHolder(v: View, private val menuInflater: MenuInflater, private val i: Int) :
80 | RecyclerView.ViewHolder(v), View.OnCreateContextMenuListener {
81 | val vUtterance = v.findViewById(R.id.utterance)
82 |
83 | init {
84 | v.setOnCreateContextMenuListener(this)
85 | }
86 |
87 | override fun onCreateContextMenu(
88 | menu: ContextMenu,
89 | v: View,
90 | menuInfo: ContextMenu.ContextMenuInfo?
91 | ) {
92 | when (i) {
93 | UtteranceFrom.USER.id -> menuInflater.inflate(
94 | R.menu.menu_user_utterance_context,
95 | menu
96 | )
97 | UtteranceFrom.MYCROFT.id -> menuInflater.inflate(
98 | R.menu.menu_mycroft_utterance_context,
99 | menu
100 | )
101 | }
102 | }
103 | }
104 | }
--------------------------------------------------------------------------------
/mobile/build.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017. Mycroft AI, Inc.
3 | *
4 | * This file is part of Mycroft-Android a client for Mycroft Core.
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | plugins {
22 | id("com.android.application")
23 | id("com.google.firebase.crashlytics")
24 | kotlin("android")
25 | id("com.google.gms.google-services")
26 | }
27 | android {
28 |
29 | compileSdk = 29
30 | buildToolsVersion = "31.0.0"
31 | defaultConfig {
32 | applicationId = "mycroft.ai"
33 | minSdk = 19
34 | targetSdk = 29
35 | versionCode = project.ext.get("versionCode") as Int
36 | versionName = project.ext.get("versionName") as String
37 |
38 | testInstrumentationRunner = ("androidx.test.runner.AndroidJUnitRunner")
39 | }
40 | buildTypes {
41 | named("release") {
42 | isMinifyEnabled = false
43 | proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
44 | }
45 | }
46 |
47 | buildFeatures {
48 | dataBinding = true
49 | viewBinding = true
50 | }
51 | }
52 |
53 | repositories {
54 | google()
55 | mavenCentral()
56 | maven("https://maven.fabric.io/public")
57 | }
58 |
59 | dependencies {
60 | implementation(fileTree("include" to arrayOf("*.jar"), "dir" to "libs"))
61 | implementation("androidx.appcompat:appcompat:1.3.1")
62 | implementation("com.google.android.material:material:1.4.0")
63 |
64 | implementation("androidx.legacy:legacy-support-v4:1.0.0")
65 |
66 | // layout deps
67 | implementation("androidx.cardview:cardview:1.0.0")
68 | implementation("androidx.recyclerview:recyclerview:1.2.1")
69 |
70 | implementation("androidx.palette:palette:1.0.0")
71 | implementation("androidx.coordinatorlayout:coordinatorlayout:1.1.0")
72 | implementation("com.google.firebase:firebase-crashlytics:18.2.4")
73 | implementation("com.google.firebase:firebase-analytics:20.0.0")
74 |
75 | // Unit test dependencies
76 | testImplementation("org.mockito:mockito-core:4.0.0")
77 | testImplementation("org.powermock:powermock-api-mockito:1.7.4")
78 | testImplementation("org.powermock:powermock-module-junit4-rule-agent:2.0.9")
79 | testImplementation("org.powermock:powermock-module-junit4-rule:2.0.9")
80 | testImplementation("org.powermock:powermock-module-junit4:2.0.9")
81 | testImplementation("junit:junit:4.13.2")
82 | // Instrumentation dependencies
83 | androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0")
84 | androidTestImplementation("androidx.test.ext:junit:1.1.3")
85 | androidTestImplementation("androidx.annotation:annotation:1.2.0")
86 |
87 | implementation("com.google.android.gms:play-services-wearable:17.1.0")
88 | wearApp(project(":wear"))
89 | implementation(project(":shared"))
90 | //kotlin
91 | implementation("org.jetbrains.kotlin:kotlin-stdlib:1.5.31")
92 | //rxjava,rxandroid
93 | implementation("io.reactivex.rxjava2:rxjava:2.2.21")
94 | implementation("io.reactivex.rxjava2:rxandroid:2.1.1")
95 |
96 | implementation("org.java-websocket:Java-WebSocket:1.5.2")
97 |
98 | implementation("androidx.legacy:legacy-support-core-utils:1.0.0")
99 | }
100 |
--------------------------------------------------------------------------------
/mobile/src/main/java/mycroft/ai/AppCompatPreferenceActivity.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017. Mycroft AI, Inc.
3 | *
4 | * This file is part of Mycroft-Android a client for Mycroft Core.
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | package mycroft.ai
22 |
23 | import android.content.res.Configuration
24 | import android.os.Bundle
25 | import android.preference.PreferenceActivity
26 | import android.view.MenuInflater
27 | import android.view.View
28 | import android.view.ViewGroup
29 | import androidx.annotation.LayoutRes
30 | import androidx.appcompat.app.ActionBar
31 | import androidx.appcompat.app.AppCompatDelegate
32 | import androidx.appcompat.widget.Toolbar
33 |
34 | /**
35 | * A [android.preference.PreferenceActivity] which implements and proxies the necessary calls
36 | * to be used with AppCompat.
37 | */
38 | abstract class AppCompatPreferenceActivity : PreferenceActivity() {
39 |
40 | private lateinit var mDelegate: AppCompatDelegate
41 |
42 | val supportActionBar: ActionBar?
43 | get() = delegate.supportActionBar
44 |
45 | private val delegate: AppCompatDelegate
46 | get() = if (::mDelegate.isInitialized) {
47 | mDelegate;
48 |
49 | } else {
50 | mDelegate = AppCompatDelegate.create(this, null)
51 |
52 | mDelegate
53 | }
54 |
55 | override fun onCreate(savedInstanceState: Bundle?) {
56 | delegate.installViewFactory()
57 | delegate.onCreate(savedInstanceState)
58 | super.onCreate(savedInstanceState)
59 | }
60 |
61 | override fun onPostCreate(savedInstanceState: Bundle?) {
62 | super.onPostCreate(savedInstanceState)
63 | delegate.onPostCreate(savedInstanceState)
64 | }
65 |
66 | fun setSupportActionBar(toolbar: Toolbar?) {
67 | delegate.setSupportActionBar(toolbar)
68 | }
69 |
70 | override fun getMenuInflater(): MenuInflater {
71 | return delegate.menuInflater
72 | }
73 |
74 | override fun setContentView(@LayoutRes layoutResID: Int) {
75 | delegate.setContentView(layoutResID)
76 | }
77 |
78 | override fun setContentView(view: View) {
79 | delegate.setContentView(view)
80 | }
81 |
82 | override fun setContentView(view: View, params: ViewGroup.LayoutParams) {
83 | delegate.setContentView(view, params)
84 | }
85 |
86 | override fun addContentView(view: View, params: ViewGroup.LayoutParams) {
87 | delegate.addContentView(view, params)
88 | }
89 |
90 | override fun onPostResume() {
91 | super.onPostResume()
92 | delegate.onPostResume()
93 | }
94 |
95 | override fun onTitleChanged(title: CharSequence, color: Int) {
96 | super.onTitleChanged(title, color)
97 | delegate.setTitle(title)
98 | }
99 |
100 | override fun onConfigurationChanged(newConfig: Configuration) {
101 | super.onConfigurationChanged(newConfig)
102 | delegate.onConfigurationChanged(newConfig)
103 | }
104 |
105 | override fun onStop() {
106 | super.onStop()
107 | delegate.onStop()
108 | }
109 |
110 | override fun onDestroy() {
111 | super.onDestroy()
112 | delegate.onDestroy()
113 | }
114 |
115 | override fun invalidateOptionsMenu() {
116 | delegate.invalidateOptionsMenu()
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/mobile/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
21 |
22 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
39 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
60 |
63 |
64 |
65 |
66 |
69 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
90 |
91 |
92 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/mobile/src/main/java/mycroft/ai/TTSManager.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017. Mycroft AI, Inc.
3 | *
4 | * This file is part of Mycroft-Android a client for Mycroft Core.
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | package mycroft.ai
22 |
23 | import android.app.Activity
24 | import android.app.Service
25 | import android.content.Context
26 | import android.speech.tts.TextToSpeech
27 | import android.util.Log
28 |
29 | import java.util.Locale
30 |
31 |
32 | /**
33 | * TTSManager is a wrapper around the Android System's Text-To-Speech ('TTS')
34 | * API.
35 | *
36 | *
37 | * All constructors in this class require a context reference.
38 | * Make sure to clean up with [.shutDown] when the context's
39 | * [Activity.onDestroy] or [Service.onDestroy] method is called.
40 | *
41 | *
42 | * @see TextToSpeech
43 | *
44 | *
45 | * @author Paul Scott
46 | */
47 |
48 | class TTSManager {
49 |
50 | /**
51 | * Backing TTS for this instance. Should not (ever) be null.
52 | */
53 | private lateinit var mTts: TextToSpeech
54 | /**
55 | * Whether the TTS is available for use (i.e. loaded into memory)
56 | */
57 | private var isLoaded = false
58 |
59 | /**
60 | * External listener for error and success events. May be null.
61 | */
62 | private var mTTSListener: TTSListener? = null
63 |
64 | var onInitListener: TextToSpeech.OnInitListener = TextToSpeech.OnInitListener { status ->
65 | if (status == TextToSpeech.SUCCESS) {
66 | val result = mTts.setLanguage(Locale.US)
67 | isLoaded = true
68 | Log.i(TAG, "TTS initialized")
69 |
70 | if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) {
71 | logError("This Language is not supported")
72 | }
73 | } else {
74 | logError("Initialization Failed!")
75 | }
76 | }
77 |
78 | /**
79 | * Create a new TTSManager attached to the given context.
80 | *
81 | * @param context any non-null context.
82 | */
83 | constructor(context: Context) {
84 | mTts = TextToSpeech(context, onInitListener)
85 | }
86 |
87 | /**
88 | * Special overload of constructor for testing purposes.
89 | *
90 | * @param textToSpeech the internal TTS this object will manage
91 | */
92 | constructor(textToSpeech: TextToSpeech) {
93 | mTts = textToSpeech
94 | }
95 |
96 | fun setTTSListener(mTTSListener: TTSListener) {
97 | this.mTTSListener = mTTSListener
98 | }
99 |
100 | /**
101 | * Wrapper for [TextToSpeech.shutdown]
102 | */
103 | fun shutDown() {
104 | mTts.shutdown()
105 | }
106 |
107 | fun addQueue(text: String) {
108 | if (isLoaded)
109 | mTts.speak(text, TextToSpeech.QUEUE_ADD, null)
110 | else {
111 | logError("TTS Not Initialized")
112 | }
113 | }
114 |
115 | fun initQueue(text: String) {
116 |
117 | if (isLoaded)
118 | mTts.speak(text, TextToSpeech.QUEUE_FLUSH, null)
119 | else
120 | logError("TTS Not Initialized")
121 | }
122 |
123 | /**
124 | * Wrapper around [Log.e] that also notifies
125 | * the [.setTTSListener], if present.
126 | *
127 | * @param msg any non-null message
128 | */
129 | private fun logError(msg: String) {
130 | if (mTTSListener != null) {
131 | mTTSListener!!.onError(msg)
132 | }
133 | Log.e(TAG, msg)
134 | }
135 |
136 | interface TTSListener {
137 | fun onError(message: String)
138 | }
139 |
140 | companion object {
141 |
142 | private val TAG = "TTSManager"
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/mobile/src/main/java/mycroft/ai/DiscoveryActivity.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017. Mycroft AI, Inc.
3 | *
4 | * This file is part of Mycroft-Android a client for Mycroft Core.
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | package mycroft.ai
22 |
23 | import android.content.Context
24 | import android.os.Bundle
25 | import android.util.Log
26 |
27 | import java.net.ServerSocket
28 |
29 |
30 | import android.net.nsd.NsdManager
31 | import android.net.nsd.NsdManager.DiscoveryListener
32 | import android.net.nsd.NsdManager.ResolveListener
33 | import android.net.nsd.NsdServiceInfo
34 | import android.annotation.SuppressLint
35 | import android.app.Activity
36 |
37 | @SuppressLint("NewApi")
38 | class DiscoveryActivity : Activity() {
39 |
40 | internal lateinit var mDiscoveryListener: DiscoveryListener
41 |
42 | internal var mServiceName: String? = null
43 | internal var mServiceInfo: NsdServiceInfo? = null
44 | internal var mServerSocket: ServerSocket? = null
45 | internal var mLocalPort: Int = 0
46 |
47 | internal lateinit var mNsdManager: NsdManager
48 |
49 | internal val TAG = "ServiceDiscovery"
50 | internal val SERVICE_TYPE = "_mycroft._tcp"
51 | internal val SERVICE_NAME = "MycroftAI Websocket"
52 |
53 | override fun onCreate(savedInstanceState: Bundle?) {
54 | super.onCreate(savedInstanceState)
55 | setContentView(R.layout.activity_main)
56 |
57 | mNsdManager = applicationContext.getSystemService(Context.NSD_SERVICE) as NsdManager
58 |
59 | initializeDiscoveryListener()
60 |
61 | mNsdManager.discoverServices(
62 | SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener)
63 |
64 | }
65 |
66 | fun initializeDiscoveryListener() {
67 |
68 | // Instantiate a new DiscoveryListener
69 | mDiscoveryListener = object : NsdManager.DiscoveryListener {
70 |
71 | // Called as soon as service discovery begins.
72 | override fun onDiscoveryStarted(regType: String) {
73 | Log.d(TAG, "Service discovery started")
74 | }
75 |
76 | override fun onServiceFound(service: NsdServiceInfo) {
77 | // A service was found! Do something with it.
78 | Log.d(TAG, "Service discovery success: $service")
79 | if (service.serviceType != SERVICE_TYPE) {
80 | // Service type is the string containing the protocol and
81 | // transport layer for this service.
82 | Log.d(TAG, "Mycroft found!: " + service.serviceType + " " + service.host + " " + service.port)
83 | resolveService(service)
84 | }
85 | }
86 |
87 | override fun onServiceLost(service: NsdServiceInfo) {
88 | // When the network service is no longer available.
89 | // Internal bookkeeping code goes here.
90 | Log.e(TAG, "service lost: $service")
91 | }
92 |
93 | override fun onDiscoveryStopped(serviceType: String) {
94 | Log.i(TAG, "Discovery stopped: $serviceType")
95 | }
96 |
97 | override fun onStartDiscoveryFailed(serviceType: String, errorCode: Int) {
98 | Log.e(TAG, "Discovery failed: Error code: $errorCode")
99 | mNsdManager.stopServiceDiscovery(this)
100 | }
101 |
102 | override fun onStopDiscoveryFailed(serviceType: String, errorCode: Int) {
103 | Log.e(TAG, "Discovery failed: Error code: $errorCode")
104 | mNsdManager.stopServiceDiscovery(this)
105 | }
106 | }
107 | }
108 |
109 | private fun resolveService(service: NsdServiceInfo) {
110 | mNsdManager.resolveService(service, object : ResolveListener {
111 |
112 | override fun onServiceResolved(serviceInfo: NsdServiceInfo) {
113 | Log.d(TAG, "Resolving service...")
114 | Log.i(TAG, serviceInfo.host.toString())
115 | Log.i(TAG, "Port: " + serviceInfo.port)
116 | }
117 |
118 | override fun onResolveFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {
119 | // TODO Auto-generated method stub
120 | Log.d(TAG, "Service resolve failed!")
121 | }
122 | })
123 | }
124 | }
--------------------------------------------------------------------------------
/mobile/src/main/res/layout/content_main.xml:
--------------------------------------------------------------------------------
1 |
20 |
21 |
30 |
31 |
37 |
38 |
47 |
48 |
59 |
60 |
64 |
65 |
77 |
78 |
90 |
91 |
100 |
101 |
110 |
111 |
122 |
123 |
124 |
125 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/mobile/src/test/java/mycroft/ai/TTSManagerTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017. Mycroft AI, Inc.
3 | *
4 | * This file is part of Mycroft-Android a client for Mycroft Core.
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | package mycroft.ai;
22 |
23 | import static org.junit.Assert.assertTrue;
24 | import static org.mockito.ArgumentMatchers.any;
25 | import static org.mockito.ArgumentMatchers.anyString;
26 | import static org.mockito.Mockito.atLeast;
27 | import static org.mockito.Mockito.never;
28 | import static org.mockito.Mockito.times;
29 | import static org.mockito.Mockito.verify;
30 | import static org.powermock.api.mockito.PowerMockito.mock;
31 | import static org.powermock.api.mockito.PowerMockito.mockStatic;
32 | import static org.powermock.api.mockito.PowerMockito.when;
33 |
34 | import android.os.Bundle;
35 | import android.speech.tts.TextToSpeech;
36 | import android.util.Log;
37 |
38 | import org.junit.Before;
39 | import org.junit.Test;
40 | import org.junit.runner.RunWith;
41 | import org.mockito.ArgumentCaptor;
42 | import org.powermock.core.classloader.annotations.PrepareForTest;
43 | import org.powermock.modules.junit4.PowerMockRunner;
44 |
45 | import java.util.HashMap;
46 | import java.util.Locale;
47 |
48 | /**
49 | * @author Philip Cohn-Cort
50 | */
51 | @RunWith(PowerMockRunner.class)
52 | @PrepareForTest({Log.class})
53 | public class TTSManagerTest {
54 |
55 | @Before
56 | public void setUp() throws Exception {
57 | mockStatic(Log.class);
58 |
59 | LogAnswer stdOut = new LogAnswer(System.out);
60 | when(Log.v(anyString(), anyString())).then(stdOut);
61 | when(Log.d(anyString(), anyString())).then(stdOut);
62 | when(Log.i(anyString(), anyString())).then(stdOut);
63 |
64 | LogAnswer stdErr = new LogAnswer(System.err);
65 | when(Log.w(anyString(), anyString())).then(stdErr);
66 | when(Log.e(anyString(), anyString())).then(stdErr);
67 | when(Log.wtf(anyString(), anyString())).then(stdErr);
68 | }
69 |
70 | @Test
71 | public void testAddingToQueueBeforeIsLoaded() throws Exception {
72 | TextToSpeech mock = mock(TextToSpeech.class);
73 | TTSManager ttsManager = new TTSManager(mock);
74 |
75 | TTSManager.TTSListener mockListener = mock(TTSManager.TTSListener.class);
76 | ttsManager.setTTSListener(mockListener);
77 |
78 | ttsManager.addQueue("text a");
79 |
80 | verify(mockListener).onError("TTS Not Initialized");
81 | }
82 |
83 | @Test
84 | public void testAddingToQueueAfterIsLoaded() throws Exception {
85 | TextToSpeech tts = mock(TextToSpeech.class);
86 | when(tts.setLanguage(any(Locale.class))).thenReturn(TextToSpeech.LANG_AVAILABLE);
87 |
88 | TTSManager ttsManager = new TTSManager(tts);
89 |
90 | TTSManager.TTSListener mockListener = mock(TTSManager.TTSListener.class);
91 | ttsManager.setTTSListener(mockListener);
92 |
93 | ttsManager.getOnInitListener().onInit(TextToSpeech.SUCCESS);
94 |
95 | verify(mockListener, never()).onError(anyString());
96 |
97 | ttsManager.addQueue("text a");
98 |
99 | verify(mockListener, never()).onError(anyString());
100 | }
101 |
102 | @Test
103 | public void testAddingToQueueTriggersSpeak() throws Exception {
104 | TextToSpeech tts = mock(TextToSpeech.class);
105 | when(tts.setLanguage(any(Locale.class))).thenReturn(TextToSpeech.LANG_AVAILABLE);
106 |
107 | TTSManager ttsManager = new TTSManager(tts);
108 |
109 | TTSManager.TTSListener mockListener = mock(TTSManager.TTSListener.class);
110 | ttsManager.setTTSListener(mockListener);
111 |
112 | ttsManager.getOnInitListener().onInit(TextToSpeech.SUCCESS);
113 |
114 | ttsManager.addQueue("text a");
115 |
116 | // Make sure that one of the speak methods was called, but we don't care which
117 |
118 | ArgumentCaptor paramCaptor1 = ArgumentCaptor.forClass(Integer.class);
119 | ArgumentCaptor paramCaptor2 = ArgumentCaptor.forClass(Integer.class);
120 |
121 | //noinspection unchecked
122 | verify(tts, atLeast(0)).speak(anyString(), paramCaptor1.capture(), any(HashMap.class));
123 | verify(tts, atLeast(0)).speak(any(CharSequence.class), paramCaptor2.capture(), any(Bundle.class), anyString());
124 |
125 | assertTrue("One of the speak methods should have been called.",
126 | !paramCaptor1.getAllValues().isEmpty()
127 | ||
128 | !paramCaptor2.getAllValues().isEmpty()
129 | );
130 | }
131 |
132 | @Test
133 | public void testFailureDoesNotSetIsLoaded() throws Exception {
134 | TextToSpeech tts = mock(TextToSpeech.class);
135 | when(tts.setLanguage(any(Locale.class))).thenReturn(TextToSpeech.LANG_AVAILABLE);
136 |
137 | TTSManager ttsManager = new TTSManager(tts);
138 |
139 | TTSManager.TTSListener mockListener = mock(TTSManager.TTSListener.class);
140 | ttsManager.setTTSListener(mockListener);
141 |
142 | ttsManager.getOnInitListener().onInit(TextToSpeech.ERROR);
143 |
144 | ttsManager.addQueue("text a");
145 |
146 | verify(mockListener, times(2)).onError(anyString());
147 | }
148 | }
--------------------------------------------------------------------------------
/mobile/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
20 |
21 |
22 | Mycroft
23 | Settings
24 | Mycroft
25 | Settings
26 | https://home.mycroft.ai
27 |
28 |
29 | Voice Responses
30 |
31 |
32 |
33 |
34 | General
35 |
36 | Enable social recommendations
37 | Recommendations for people to contact
38 | based on your message history
39 |
40 |
41 | Display name
42 | John Smith
43 |
44 | Mycroft IP address
45 | 192.168.0.2
46 |
47 | Voice Responses
48 | enable TTS responses
49 |
50 | Voice Responses
51 | display App Reader switch on main screen
52 |
53 | Add friends to messages
54 |
55 | Always
56 | When possible
57 | Never
58 |
59 |
60 | 1
61 | 0
62 | -1
63 |
64 |
65 |
66 | Resend
67 | Copy
68 | Share
69 |
70 |
71 | Data & sync
72 |
73 | Sync frequency
74 |
75 | 15 minutes
76 | 30 minutes
77 | 1 hour
78 | 3 hours
79 | 6 hours
80 | Never
81 |
82 |
83 | 15
84 | 30
85 | 60
86 | 180
87 | 360
88 | -1
89 |
90 |
91 |
92 | Entry 1
93 | Entry 2
94 | Entry 3
95 |
96 |
97 |
98 | 1
99 | 2
100 | 3
101 |
102 |
103 |
104 |
105 | System sync settings
106 |
107 |
108 | Notifications
109 |
110 | New message notifications
111 |
112 | Ringtone
113 | Silent
114 |
115 | Vibrate
116 |
117 | Say something to Mycroft…
118 | Send utterance
119 | Sorry! Your device doesn\'t support speech input
120 | Connected to Mycroft!
121 | Connection to Mycroft has closed! Please re-connect and check connection settings!
122 | Connection error! Please ensure Ezra is available and on the same network as your device!
123 |
124 | Connect
125 | Discover
126 | Register
127 | Send
128 |
129 | Hello World!
130 | Hello Notification!
131 | Notification posted
132 | 1
133 | Maximum Retries
134 | About
135 | Mycroft Management Website
136 | About
137 | Home.Mycroft.AI
138 | About
139 | Version Code
140 | Version Name
141 | License
142 | GPLv3
143 | not set
144 | 0
145 | Nothing to show yet.. your conversations with Mycroft will appear here
146 |
147 |
--------------------------------------------------------------------------------
/mobile/src/main/java/mycroft/ai/utils/NetworkAutoDiscoveryUtil.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017. Mycroft AI, Inc.
3 | *
4 | * This file is part of Mycroft-Android a client for Mycroft Core.
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | package mycroft.ai.utils
22 |
23 | import android.content.Context
24 | import android.net.nsd.NsdServiceInfo
25 | import android.net.nsd.NsdManager
26 | import android.util.Log
27 |
28 | /**
29 | * Created by paul on 2016/06/28.
30 | */
31 | class NetworkAutoDiscoveryUtil(internal var mContext: Context) {
32 | private var mServiceName = "MycroftAI Websocket"
33 | private var chosenServiceInfo: NsdServiceInfo? = null
34 | private var mNsdManager: NsdManager = mContext.getSystemService(Context.NSD_SERVICE) as NsdManager
35 |
36 | //declare late init as initialisation is after the fact.
37 | internal lateinit var mResolveListener: NsdManager.ResolveListener
38 |
39 | private var mDiscoveryListener: NsdManager.DiscoveryListener? = null
40 | private var mRegistrationListener: NsdManager.RegistrationListener? = null
41 |
42 |
43 | fun initializeNsd() {
44 | initializeResolveListener()
45 | //mNsdManager.init(mContext.getMainLooper(), this);
46 | }
47 |
48 | fun initializeDiscoveryListener() {
49 | mDiscoveryListener = object : NsdManager.DiscoveryListener {
50 | override fun onDiscoveryStarted(regType: String) {
51 | Log.d(TAG, "Service discovery started")
52 | }
53 |
54 | override fun onServiceFound(service: NsdServiceInfo) {
55 | Log.d(TAG, "Service discovery success$service")
56 | if (service.serviceType != SERVICE_TYPE) {
57 | Log.d(TAG, "Unknown Service Type: " + service.serviceType)
58 | } else if (service.serviceName == mServiceName) {
59 | Log.d(TAG, "Same machine: $mServiceName")
60 | } else if (service.serviceName.contains(mServiceName)) {
61 | mNsdManager.resolveService(service, mResolveListener)
62 | }
63 | }
64 |
65 | override fun onServiceLost(service: NsdServiceInfo) {
66 | Log.e(TAG, "service lost$service")
67 | if (chosenServiceInfo == service) {
68 | chosenServiceInfo = null
69 | }
70 | }
71 |
72 | override fun onDiscoveryStopped(serviceType: String) {
73 | Log.i(TAG, "Discovery stopped: $serviceType")
74 | }
75 |
76 | override fun onStartDiscoveryFailed(serviceType: String, errorCode: Int) {
77 | Log.e(TAG, "Discovery failed: Error code:$errorCode")
78 | }
79 |
80 | override fun onStopDiscoveryFailed(serviceType: String, errorCode: Int) {
81 | Log.e(TAG, "Discovery failed: Error code:$errorCode")
82 | }
83 | }
84 | }
85 |
86 | fun initializeResolveListener() {
87 | mResolveListener = object : NsdManager.ResolveListener {
88 | override fun onResolveFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {
89 | Log.e(TAG, "Resolve failed$errorCode")
90 | }
91 |
92 | override fun onServiceResolved(serviceInfo: NsdServiceInfo) {
93 | Log.e(TAG, "Resolve Succeeded. $serviceInfo")
94 | if (serviceInfo.serviceName == mServiceName) {
95 | Log.d(TAG, "Same IP.")
96 | return
97 | }
98 | chosenServiceInfo = serviceInfo
99 | }
100 | }
101 | }
102 |
103 | fun initializeRegistrationListener() {
104 | mRegistrationListener = object : NsdManager.RegistrationListener {
105 | override fun onServiceRegistered(NsdServiceInfo: NsdServiceInfo) {
106 | mServiceName = NsdServiceInfo.serviceName
107 | Log.d(TAG, "Service registered: $mServiceName")
108 | }
109 |
110 | override fun onRegistrationFailed(arg0: NsdServiceInfo, arg1: Int) {
111 | Log.d(TAG, "Service registration failed: $arg1")
112 | }
113 |
114 | override fun onServiceUnregistered(arg0: NsdServiceInfo) {
115 | Log.d(TAG, "Service unregistered: " + arg0.serviceName)
116 | }
117 |
118 | override fun onUnregistrationFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {
119 | Log.d(TAG, "Service unregistration failed: $errorCode")
120 | }
121 | }
122 | }
123 |
124 | fun registerService(port: Int) {
125 | tearDown() // Cancel any previous registration request
126 | initializeRegistrationListener()
127 | val serviceInfo = NsdServiceInfo()
128 | serviceInfo.port = port
129 | serviceInfo.serviceName = mServiceName
130 | serviceInfo.serviceType = SERVICE_TYPE
131 | mNsdManager.registerService(
132 | serviceInfo, NsdManager.PROTOCOL_DNS_SD, mRegistrationListener)
133 | }
134 |
135 | fun discoverServices() {
136 | stopDiscovery() // Cancel any existing discovery request
137 | initializeDiscoveryListener()
138 | mNsdManager.discoverServices(
139 | SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener)
140 | }
141 |
142 | fun stopDiscovery() {
143 | if (mDiscoveryListener != null) {
144 | try {
145 | mNsdManager.stopServiceDiscovery(mDiscoveryListener)
146 | } finally {
147 | }
148 | mDiscoveryListener = null
149 | }
150 | }
151 |
152 | fun tearDown() {
153 | if (mRegistrationListener != null) {
154 | try {
155 | mNsdManager.unregisterService(mRegistrationListener)
156 | } finally {
157 | }
158 | mRegistrationListener = null
159 | }
160 | }
161 |
162 | companion object {
163 |
164 | private val TAG = "NetworkDiscovery"
165 | private val SERVICE_TYPE = " _mycroft._tcp"
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/wear/src/main/java/mycroft/ai/MainActivity.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017. Mycroft AI, Inc.
3 | *
4 | * This file is part of Mycroft-Android a client for Mycroft Core.
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | package mycroft.ai
22 |
23 | import android.app.Activity
24 | import android.content.ActivityNotFoundException
25 | import android.content.Intent
26 | import android.graphics.Color
27 | import android.os.Bundle
28 | import android.speech.RecognizerIntent
29 | import android.support.wearable.activity.WearableActivity
30 | import android.support.wearable.view.BoxInsetLayout
31 | import android.util.Log
32 | import android.widget.ImageButton
33 | import android.widget.Toast
34 |
35 | import com.google.android.gms.common.ConnectionResult
36 | import com.google.android.gms.common.api.GoogleApiClient
37 | import com.google.android.gms.wearable.Node
38 | import com.google.android.gms.wearable.Wearable
39 |
40 | import java.util.Locale
41 |
42 | import mycroft.ai.shared.utilities.GuiUtilities
43 | import mycroft.ai.shared.wear.Constants.MycroftSharedConstants.MYCROFT_QUERY_MESSAGE_PATH
44 |
45 | class MainActivity : WearableActivity(), GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
46 | private val REQ_CODE_SPEECH_INPUT = 100
47 |
48 | private var containerView: BoxInsetLayout? = null
49 | private var inputImageButton: ImageButton? = null
50 | private var mGoogleApiClient: GoogleApiClient? = null
51 | private var mNode: Node? = null
52 |
53 |
54 | override fun onCreate(savedInstanceState: Bundle?) {
55 | super.onCreate(savedInstanceState)
56 | setContentView(R.layout.activity_main)
57 | setAmbientEnabled()
58 |
59 | mGoogleApiClient = GoogleApiClient.Builder(this)
60 | .addApi(Wearable.API)
61 | .addConnectionCallbacks(this)
62 | .addOnConnectionFailedListener(this)
63 | .build()
64 |
65 | containerView = findViewById(R.id.container)
66 |
67 | inputImageButton = findViewById(R.id.inputImageButton)
68 | inputImageButton!!.setOnClickListener {
69 | try {
70 | promptSpeechInput()
71 | } catch (e: Exception) {
72 | e.printStackTrace()
73 | }
74 | }
75 | }
76 |
77 | override fun onEnterAmbient(ambientDetails: Bundle?) {
78 | super.onEnterAmbient(ambientDetails)
79 | updateDisplay()
80 | }
81 |
82 | override fun onUpdateAmbient() {
83 | super.onUpdateAmbient()
84 | updateDisplay()
85 | }
86 |
87 | override fun onExitAmbient() {
88 | updateDisplay()
89 | super.onExitAmbient()
90 | }
91 |
92 | private fun updateDisplay() {
93 | if (isAmbient) {
94 | containerView!!.setBackgroundColor(Color.BLACK)
95 | } else {
96 | containerView!!.background = null
97 | }
98 | }
99 |
100 | /**
101 | * Showing google speech input dialog
102 | */
103 | private fun promptSpeechInput() {
104 | val intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH)
105 | intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
106 | RecognizerIntent.LANGUAGE_MODEL_FREE_FORM)
107 | intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.getDefault())
108 | intent.putExtra(RecognizerIntent.EXTRA_PROMPT,
109 | getString(R.string.speech_prompt))
110 | try {
111 | startActivityForResult(intent, REQ_CODE_SPEECH_INPUT)
112 | } catch (a: ActivityNotFoundException) {
113 | Toast.makeText(applicationContext,
114 | getString(R.string.speech_not_supported),
115 | Toast.LENGTH_SHORT).show()
116 | }
117 |
118 | }
119 |
120 | /**
121 | * Receiving speech input
122 | */
123 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
124 | super.onActivityResult(requestCode, resultCode, data)
125 |
126 | when (requestCode) {
127 | REQ_CODE_SPEECH_INPUT -> {
128 | if (resultCode == Activity.RESULT_OK && null != data) {
129 |
130 | val result = data
131 | .getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS)
132 |
133 | sendMessage(result[0])
134 | }
135 | }
136 | }
137 | }
138 |
139 | override fun onConnected(bundle: Bundle?) {
140 | Wearable.NodeApi.getConnectedNodes(mGoogleApiClient).setResultCallback { getConnectedNodesResult ->
141 | for (node in getConnectedNodesResult.nodes) {
142 | if (node != null && node.isNearby) {
143 | mNode = node
144 | showToast("Connected To " + node.displayName)
145 | Log.d(WEARABLE_MAIN, "Connected to " + node.displayName)
146 | } else {
147 | showToast("Not Connected")
148 | Log.d(WEARABLE_MAIN, "NOT CONNECTED")
149 | }
150 | }
151 | }
152 | }
153 |
154 | override fun onConnectionSuspended(i: Int) {
155 | showToast("Connection Suspended")
156 | }
157 |
158 | override fun onConnectionFailed(connectionResult: ConnectionResult) {
159 | showToast("Connection Failed")
160 | }
161 |
162 | override fun onStart() {
163 | super.onStart()
164 | mGoogleApiClient!!.connect()
165 | }
166 |
167 | override fun onStop() {
168 | super.onStop()
169 | mGoogleApiClient!!.disconnect()
170 | }
171 |
172 | fun sendMessage(message: String) {
173 | if (mNode != null && mGoogleApiClient != null) {
174 | Wearable.MessageApi.sendMessage(mGoogleApiClient, mNode!!.id, MYCROFT_QUERY_MESSAGE_PATH, message.toByteArray()).setResultCallback { sendMessageResult ->
175 | if (!sendMessageResult.status.isSuccess) {
176 | showToast("Message Failed")
177 | } else {
178 | showToast("Message Sent")
179 | }
180 | }
181 | } else {
182 | showToast("Unable To Send Message")
183 | }
184 | }
185 |
186 | private fun showToast(message: String) {
187 | GuiUtilities.showToast(applicationContext, message)
188 | }
189 |
190 | companion object {
191 |
192 | private val WEARABLE_MAIN = "WearableMain"
193 | }
194 | }
195 |
--------------------------------------------------------------------------------
/mobile/src/main/java/mycroft/ai/SettingsActivity.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017. Mycroft AI, Inc.
3 | *
4 | * This file is part of Mycroft-Android a client for Mycroft Core.
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | package mycroft.ai
22 |
23 | import android.annotation.TargetApi
24 | import android.content.Context
25 | import android.content.Intent
26 | import android.content.SharedPreferences
27 | import android.content.res.Configuration
28 | import android.media.RingtoneManager
29 | import android.net.Uri
30 | import android.os.Build
31 | import android.os.Bundle
32 | import android.preference.ListPreference
33 | import android.preference.Preference
34 | import android.preference.PreferenceActivity
35 | import android.preference.PreferenceFragment
36 | import android.preference.PreferenceManager
37 | import android.preference.RingtonePreference
38 | import android.text.TextUtils
39 | import android.view.MenuItem
40 | import androidx.core.app.NavUtils
41 | import mycroft.ai.Constants.MycroftMobileConstants.VERSION_CODE_PREFERENCE_KEY
42 | import mycroft.ai.Constants.MycroftMobileConstants.VERSION_NAME_PREFERENCE_KEY
43 |
44 |
45 | /**
46 | * A [PreferenceActivity] that presents a set of application settings. On
47 | * handset devices, settings are presented as a single list. On tablets,
48 | * settings are split by category, with category headers shown to the left of
49 | * the list of settings.
50 | *
51 | *
52 | * See [
53 | * Android Design: Settings](http://developer.android.com/design/patterns/settings.html) for design guidelines and the [Settings
54 | * API Guide](http://developer.android.com/guide/topics/ui/settings.html) for more information on developing a Settings UI.
55 | */
56 | class SettingsActivity : AppCompatPreferenceActivity() {
57 |
58 | private var sharedPreferences: SharedPreferences? = null
59 |
60 | override fun onCreate(savedInstanceState: Bundle?) {
61 | super.onCreate(savedInstanceState)
62 | sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
63 | setupActionBar()
64 | }
65 |
66 | override fun onOptionsItemSelected(item: MenuItem): Boolean {
67 | when (item.itemId) {
68 | // Respond to the action bar's Up/Home button
69 | android.R.id.home -> {
70 | NavUtils.navigateUpFromSameTask(this)
71 | return true
72 | }
73 | }
74 | return super.onOptionsItemSelected(item)
75 | }
76 |
77 | /**
78 | * Set up the [android.app.ActionBar], if the API is available.
79 | */
80 | private fun setupActionBar() {
81 | val actionBar = supportActionBar
82 | actionBar?.setDisplayHomeAsUpEnabled(true)
83 | }
84 |
85 | /**
86 | * {@inheritDoc}
87 | */
88 | override fun onIsMultiPane(): Boolean {
89 | return isXLargeTablet(this)
90 | }
91 |
92 | /**
93 | * {@inheritDoc}
94 | */
95 | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
96 | override fun onBuildHeaders(target: List) {
97 | loadHeadersFromResource(R.xml.pref_headers, target)
98 | }
99 |
100 | /**
101 | * This method stops fragment injection in malicious applications.
102 | * Make sure to deny any unknown fragments here.
103 | */
104 | override fun isValidFragment(fragmentName: String): Boolean {
105 | return (PreferenceFragment::class.java.name == fragmentName
106 | || GeneralPreferenceFragment::class.java.name == fragmentName
107 | || AboutPreferenceFragment::class.java.name == fragmentName)
108 | }
109 |
110 | /**
111 | * This fragment shows general preferences only. It is used when the
112 | * activity is showing a two-pane settings UI.
113 | */
114 | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
115 | class GeneralPreferenceFragment : PreferenceFragment() {
116 | override fun onCreate(savedInstanceState: Bundle?) {
117 | super.onCreate(savedInstanceState)
118 | addPreferencesFromResource(R.xml.pref_general)
119 | setHasOptionsMenu(true)
120 |
121 | // Bind the summaries of EditText/List/Dialog/Ringtone preferences
122 | // to their values. When their values change, their summaries are
123 | // updated to reflect the new value, per the Android Design
124 | // guidelines.
125 | bindPreferenceSummaryToValue(findPreference("ip"), 2)
126 | }
127 |
128 | override fun onOptionsItemSelected(item: MenuItem): Boolean {
129 | val id = item.itemId
130 | if (id == android.R.id.home) {
131 | startActivity(Intent(activity, SettingsActivity::class.java))
132 | return true
133 | }
134 |
135 | return super.onOptionsItemSelected(item)
136 | }
137 | }
138 |
139 | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
140 | class AboutPreferenceFragment : PreferenceFragment() {
141 | override fun onCreate(savedInstanceState: Bundle?) {
142 | super.onCreate(savedInstanceState)
143 | addPreferencesFromResource(R.xml.pref_about)
144 | setHasOptionsMenu(true)
145 |
146 | bindPreferenceSummaryToValue(findPreference(VERSION_NAME_PREFERENCE_KEY), 2)
147 | bindPreferenceSummaryToValue(findPreference(VERSION_CODE_PREFERENCE_KEY), 1)
148 |
149 | val licensePreference = findPreference("license")
150 |
151 | licensePreference.onPreferenceClickListener = Preference.OnPreferenceClickListener {
152 | val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://www.gnu.org/licenses/gpl-3.0.en.html"))
153 | startActivity(intent)
154 |
155 | true
156 | }
157 |
158 | bindPreferenceSummaryToValue(licensePreference)
159 | }
160 |
161 | override fun onOptionsItemSelected(item: MenuItem): Boolean {
162 | val id = item.itemId
163 | if (id == android.R.id.home) {
164 | startActivity(Intent(activity, SettingsActivity::class.java))
165 | return true
166 | }
167 |
168 | return super.onOptionsItemSelected(item)
169 | }
170 | }
171 |
172 | companion object {
173 |
174 | /**
175 | * A preference value change listener that updates the preference's summary
176 | * to reflect its new value.
177 | */
178 | private val sBindPreferenceSummaryToValueListener = Preference.OnPreferenceChangeListener { preference, value ->
179 | val stringValue = value.toString()
180 |
181 | if (preference is ListPreference) {
182 | // For list preferences, look up the correct display value in
183 | // the preference's 'entries' list.
184 | val index = preference.findIndexOfValue(stringValue)
185 |
186 | // Set the summary to reflect the new value.
187 | preference.setSummary(
188 | if (index >= 0)
189 | preference.entries[index]
190 | else
191 | null)
192 |
193 | } else if (preference is RingtonePreference) {
194 | // For ringtone preferences, look up the correct display value
195 | // using RingtoneManager.
196 | if (TextUtils.isEmpty(stringValue)) {
197 | // Empty values correspond to 'silent' (no ringtone).
198 | preference.setSummary(R.string.pref_ringtone_silent)
199 |
200 | } else {
201 | val ringtone = RingtoneManager.getRingtone(
202 | preference.getContext(), Uri.parse(stringValue))
203 |
204 | if (ringtone == null) {
205 | // Clear the summary if there was a lookup error.
206 | preference.setSummary(null)
207 | } else {
208 | // Set the summary to reflect the new ringtone display
209 | // name.
210 | val name = ringtone.getTitle(preference.getContext())
211 | preference.setSummary(name)
212 | }
213 | }
214 | } else {
215 | // For all other preferences, set the summary to the value's
216 | // simple string representation.
217 | preference.summary = stringValue
218 | }
219 | true
220 | }
221 |
222 | /**
223 | * Helper method to determine if the device has an extra-large screen. For
224 | * example, 10" tablets are extra-large.
225 | */
226 | private fun isXLargeTablet(context: Context): Boolean {
227 | return context.resources.configuration.screenLayout and Configuration.SCREENLAYOUT_SIZE_MASK >= Configuration.SCREENLAYOUT_SIZE_XLARGE
228 | }
229 |
230 | /**
231 | * Binds a preference's summary to its value. More specifically, when the
232 | * preference's value is changed, its summary (line of text below the
233 | * preference title) is updated to reflect the value. The summary is also
234 | * immediately updated upon calling this method. The exact display format is
235 | * dependent on the type of preference.
236 | *
237 | * @see .sBindPreferenceSummaryToValueListener
238 | */
239 | private fun bindPreferenceSummaryToValue(preference: Preference, type: Int = 2) {
240 | // Set the listener to watch for value changes.
241 | preference.onPreferenceChangeListener = sBindPreferenceSummaryToValueListener
242 |
243 | // Trigger the listener immediately with the preference's
244 | // current value.
245 | val preferences = PreferenceManager.getDefaultSharedPreferences(preference.context)
246 |
247 | var stringValue: String? = ""
248 | when (type) {
249 | 1 ->
250 | //integer
251 | stringValue = preferences.getInt(preference.key, 0).toString()
252 | 2 ->
253 | //string
254 | stringValue = preferences.getString(preference.key, "")
255 | 3 ->
256 | //boolean
257 | stringValue = java.lang.Boolean.toString(preferences.getBoolean(preference.key, false))
258 | }
259 |
260 | sBindPreferenceSummaryToValueListener.onPreferenceChange(preference, stringValue)
261 | }
262 | }
263 | }
264 |
--------------------------------------------------------------------------------
/mobile/src/main/java/mycroft/ai/MainActivity.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017. Mycroft AI, Inc.
3 | *
4 | * This file is part of Mycroft-Android a client for Mycroft Core.
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | package mycroft.ai
22 |
23 | import android.app.Activity
24 | import android.content.*
25 | import android.content.pm.PackageManager
26 | import android.net.Uri
27 | import android.os.Bundle
28 | import android.os.Handler
29 | import android.preference.PreferenceManager
30 | import android.speech.RecognizerIntent
31 | import android.util.Log
32 | import android.view.Menu
33 | import android.view.MenuItem
34 | import android.view.View
35 | import android.view.inputmethod.EditorInfo
36 | import android.widget.TextView
37 | import androidx.appcompat.app.AppCompatActivity
38 | import androidx.appcompat.widget.Toolbar
39 | import androidx.localbroadcastmanager.content.LocalBroadcastManager
40 | import androidx.recyclerview.widget.LinearLayoutManager
41 | import mycroft.ai.Constants.MycroftMobileConstants.VERSION_CODE_PREFERENCE_KEY
42 | import mycroft.ai.Constants.MycroftMobileConstants.VERSION_NAME_PREFERENCE_KEY
43 | import mycroft.ai.adapters.MycroftAdapter
44 | import mycroft.ai.databinding.ActivityMainBinding
45 | import mycroft.ai.receivers.NetworkChangeReceiver
46 | import mycroft.ai.shared.utilities.GuiUtilities
47 | import mycroft.ai.shared.wear.Constants.MycroftSharedConstants.MYCROFT_WEAR_REQUEST
48 | import mycroft.ai.shared.wear.Constants.MycroftSharedConstants.MYCROFT_WEAR_REQUEST_KEY_NAME
49 | import mycroft.ai.shared.wear.Constants.MycroftSharedConstants.MYCROFT_WEAR_REQUEST_MESSAGE
50 | import mycroft.ai.utils.NetworkUtil
51 | import org.java_websocket.client.WebSocketClient
52 | import org.java_websocket.exceptions.WebsocketNotConnectedException
53 | import org.java_websocket.handshake.ServerHandshake
54 | import java.net.URI
55 | import java.net.URISyntaxException
56 | import java.util.*
57 |
58 | class MainActivity : AppCompatActivity() {
59 | private val logTag = "Mycroft"
60 | private val utterances = mutableListOf()
61 | private val reqCodeSpeechInput = 100
62 | private var maximumRetries = 1
63 | private var currentItemPosition = -1
64 |
65 | private var isNetworkChangeReceiverRegistered = false
66 | private var isWearBroadcastRevieverRegistered = false
67 | private var launchedFromWidget = false
68 | private var autoPromptForSpeech = false
69 |
70 | private lateinit var ttsManager: TTSManager
71 | private lateinit var mycroftAdapter: MycroftAdapter
72 | private lateinit var wsip: String
73 | private lateinit var sharedPref: SharedPreferences
74 | private lateinit var networkChangeReceiver: NetworkChangeReceiver
75 | private lateinit var wearBroadcastReceiver: BroadcastReceiver
76 |
77 | var webSocketClient: WebSocketClient? = null
78 | private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
79 |
80 | override fun onCreate(savedInstanceState: Bundle?) {
81 | super.onCreate(savedInstanceState)
82 |
83 | //Fabric.with(this, Crashlytics()) TODO Replace fabric
84 | setContentView(binding.root)
85 | setSupportActionBar(binding.toolbar as Toolbar?)
86 |
87 |
88 | loadPreferences()
89 |
90 | ttsManager = TTSManager(this)
91 | mycroftAdapter = MycroftAdapter(utterances, applicationContext, menuInflater)
92 | mycroftAdapter.setOnLongItemClickListener(object : MycroftAdapter.OnLongItemClickListener {
93 | override fun itemLongClicked(v: View, position: Int) {
94 | currentItemPosition = position
95 | v.showContextMenu()
96 | }
97 | })
98 |
99 | binding.contentMain.kbMicSwitch.setOnCheckedChangeListener { _, isChecked ->
100 | val editor = sharedPref.edit()
101 | editor.putBoolean("kbMicSwitch", isChecked)
102 | editor.apply()
103 |
104 | if (isChecked) {
105 | // Switch to mic
106 | binding.contentMain.micButton.visibility = View.VISIBLE
107 | binding.contentMain.utteranceInput.visibility = View.INVISIBLE
108 | binding.contentMain.sendUtterance.visibility = View.INVISIBLE
109 | } else {
110 | // Switch to keyboard
111 | binding.contentMain.micButton.visibility = View.INVISIBLE
112 | binding.contentMain.utteranceInput.visibility = View.VISIBLE
113 | binding.contentMain.sendUtterance.visibility = View.VISIBLE
114 | }
115 | }
116 |
117 | binding.contentMain.utteranceInput.setOnEditorActionListener(TextView.OnEditorActionListener { _, actionId, _ ->
118 | if (actionId == EditorInfo.IME_ACTION_DONE) {
119 | sendUtterance()
120 | true
121 | } else {
122 | false
123 | }
124 | })
125 | binding.contentMain.micButton.setOnClickListener { promptSpeechInput() }
126 | binding.contentMain.sendUtterance.setOnClickListener { sendUtterance() }
127 |
128 | registerForContextMenu(binding.contentMain.cardList)
129 |
130 | //attach a listener to check for changes in state
131 | binding.contentMain.voxswitch.setOnCheckedChangeListener { _, isChecked ->
132 | val editor = sharedPref.edit()
133 | editor.putBoolean("appReaderSwitch", isChecked)
134 | editor.apply()
135 |
136 | // stop tts from speaking if app reader disabled
137 | if (!isChecked) ttsManager.initQueue("")
138 | }
139 |
140 | val llm = LinearLayoutManager(this)
141 | llm.stackFromEnd = true
142 | llm.orientation = LinearLayoutManager.VERTICAL
143 | with(binding.contentMain.cardList) {
144 | setHasFixedSize(true)
145 | layoutManager = llm
146 | adapter = mycroftAdapter
147 | }
148 |
149 | registerReceivers()
150 |
151 | // start the discovery activity (testing only)
152 | // startActivity(new Intent(this, DiscoveryActivity.class));
153 | }
154 |
155 | override fun onCreateOptionsMenu(menu: Menu): Boolean {
156 | // Inflate the menu; this adds items to the action bar if it is present.
157 | menuInflater.inflate(R.menu.menu_setup, menu)
158 | return true
159 | }
160 |
161 | override fun onOptionsItemSelected(item: MenuItem): Boolean {
162 | // Handle action bar item clicks here. The action bar will
163 | // automatically handle clicks on the Home/Up button, so long
164 | // as you specify a parent activity in AndroidManifest.xml.
165 | var consumed = false
166 | when (item.itemId) {
167 | R.id.action_settings -> {
168 | startActivity(Intent(this, SettingsActivity::class.java))
169 | consumed = true
170 | }
171 | R.id.action_home_mycroft_ai -> {
172 | val intent = Intent(
173 | Intent.ACTION_VIEW,
174 | Uri.parse(getString(R.string.mycroft_website_url))
175 | )
176 | startActivity(intent)
177 | }
178 | }
179 |
180 | return consumed && super.onOptionsItemSelected(item)
181 | }
182 |
183 | override fun onContextItemSelected(item: MenuItem): Boolean {
184 | super.onContextItemSelected(item)
185 | if (item.itemId == R.id.user_resend) {
186 | // Resend user utterance
187 | sendMessage(utterances[currentItemPosition].utterance)
188 | } else if (item.itemId == R.id.user_copy || item.itemId == R.id.mycroft_copy) {
189 | // Copy utterance to clipboard
190 | val clipboardManager = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
191 | val data = ClipData.newPlainText("text", utterances[currentItemPosition].utterance)
192 | clipboardManager.setPrimaryClip(data)
193 | showToast("Copied to clipboard")
194 | } else if (item.itemId == R.id.mycroft_share) {
195 | // Share utterance
196 | val sendIntent = Intent().apply {
197 | action = Intent.ACTION_SEND
198 | putExtra(Intent.EXTRA_TEXT, utterances[currentItemPosition].utterance)
199 | type = "text/plain"
200 | }
201 | startActivity(
202 | Intent.createChooser(
203 | sendIntent,
204 | resources.getText(R.string.action_share)
205 | )
206 | )
207 | } else {
208 | return super.onContextItemSelected(item)
209 | }
210 |
211 | return true
212 | }
213 |
214 | fun sendUtterance() {
215 | val utterance = binding.contentMain.utteranceInput.text.toString()
216 | if (utterance != "") {
217 | sendMessage(utterance)
218 | binding.contentMain.utteranceInput.text.clear()
219 | }
220 | }
221 |
222 | fun connectWebSocket() {
223 | val uri = deriveURI()
224 |
225 | if (uri != null) {
226 | webSocketClient = object : WebSocketClient(uri) {
227 | override fun onOpen(serverHandshake: ServerHandshake) {
228 | Log.i("Websocket", "Opened")
229 | }
230 |
231 | override fun onMessage(s: String) {
232 | // Log.i(TAG, s);
233 | runOnUiThread(MessageParser(s, object : SafeCallback {
234 | override fun call(param: Utterance) {
235 | addData(param)
236 | }
237 | }))
238 | }
239 |
240 | override fun onClose(i: Int, s: String, b: Boolean) {
241 | Log.i("Websocket", "Closed $s")
242 |
243 | }
244 |
245 | override fun onError(e: Exception) {
246 | Log.i("Websocket", "Error " + e.message)
247 | }
248 | }
249 | webSocketClient!!.connect()
250 | }
251 | }
252 |
253 | private fun addData(mycroftUtterance: Utterance) {
254 | utterances.add(mycroftUtterance)
255 | binding.contentMain.defaultMessageTextView.visibility = View.GONE
256 | mycroftAdapter.notifyItemInserted(utterances.size - 1)
257 | if (binding.contentMain.voxswitch.isChecked) {
258 | ttsManager.addQueue(mycroftUtterance.utterance)
259 | }
260 | binding.contentMain.cardList.smoothScrollToPosition(mycroftAdapter.itemCount - 1)
261 | }
262 |
263 | private fun registerReceivers() {
264 | registerNetworkReceiver()
265 | registerWearBroadcastReceiver()
266 | }
267 |
268 | private fun registerNetworkReceiver() {
269 | if (!isNetworkChangeReceiverRegistered) {
270 | // set up the dynamic broadcast receiver for maintaining the socket
271 | networkChangeReceiver = NetworkChangeReceiver()
272 | networkChangeReceiver.setMainActivityHandler(this)
273 |
274 | // set up the intent filters
275 | val connChange = IntentFilter("android.net.conn.CONNECTIVITY_CHANGE")
276 | val wifiChange = IntentFilter("android.net.wifi.WIFI_STATE_CHANGED")
277 | registerReceiver(networkChangeReceiver, connChange)
278 | registerReceiver(networkChangeReceiver, wifiChange)
279 |
280 | isNetworkChangeReceiverRegistered = true
281 | }
282 | }
283 |
284 | private fun registerWearBroadcastReceiver() {
285 | if (!isWearBroadcastRevieverRegistered) {
286 | wearBroadcastReceiver = object : BroadcastReceiver() {
287 | override fun onReceive(context: Context, intent: Intent) {
288 | val message = intent.getStringExtra(MYCROFT_WEAR_REQUEST_MESSAGE)
289 | // send to mycroft
290 | if (message != null) {
291 | Log.d(logTag, "Wear message received: [$message] sending to Mycroft")
292 | sendMessage(message)
293 | }
294 | }
295 | }
296 |
297 | LocalBroadcastManager.getInstance(this)
298 | .registerReceiver(wearBroadcastReceiver, IntentFilter(MYCROFT_WEAR_REQUEST))
299 | isWearBroadcastRevieverRegistered = true
300 | }
301 | }
302 |
303 | private fun unregisterReceivers() {
304 | unregisterBroadcastReceiver(networkChangeReceiver)
305 | unregisterBroadcastReceiver(wearBroadcastReceiver)
306 |
307 | isNetworkChangeReceiverRegistered = false
308 | isWearBroadcastRevieverRegistered = false
309 | }
310 |
311 | private fun unregisterBroadcastReceiver(broadcastReceiver: BroadcastReceiver) {
312 | LocalBroadcastManager.getInstance(this).unregisterReceiver(broadcastReceiver)
313 | }
314 |
315 | /**
316 | * This method will attach the correct path to the
317 | * [.wsip] hostname to allow for communication
318 | * with a Mycroft instance at that address.
319 | *
320 | *
321 | * If [.wsip] cannot be used as a hostname
322 | * in a [URI] (e.g. because it's null), then
323 | * this method will return null.
324 | *
325 | *
326 | * @return a valid uri, or null
327 | */
328 | private fun deriveURI(): URI? {
329 | return if (wsip.isNotEmpty()) {
330 | try {
331 | URI("ws://$wsip:8181/core")
332 | } catch (e: URISyntaxException) {
333 | Log.e(logTag, "Unable to build URI for websocket", e)
334 | null
335 | }
336 | } else {
337 | null
338 | }
339 | }
340 |
341 | fun sendMessage(msg: String) {
342 | val json =
343 | "{\"data\": {\"utterances\": [\"$msg\"]}, \"type\": \"recognizer_loop:utterance\", \"context\": null}"
344 |
345 | try {
346 | if (webSocketClient == null || webSocketClient!!.connection.isClosed) {
347 | // try and reconnect
348 | if (NetworkUtil.getConnectivityStatus(this) == NetworkUtil.NETWORK_STATUS_WIFI) { //TODO: add config to specify wifi only.
349 | connectWebSocket()
350 | }
351 | }
352 |
353 | val handler = Handler()
354 | handler.postDelayed({
355 | // Actions to do after 1 seconds
356 | try {
357 | webSocketClient!!.send(json)
358 | addData(Utterance(msg, UtteranceFrom.USER))
359 | } catch (exception: WebsocketNotConnectedException) {
360 | showToast(resources.getString(R.string.websocket_closed))
361 | }
362 | }, 1000)
363 |
364 | } catch (exception: WebsocketNotConnectedException) {
365 | showToast(resources.getString(R.string.websocket_closed))
366 | }
367 |
368 | }
369 |
370 | /**
371 | * Showing google speech input dialog
372 | */
373 | private fun promptSpeechInput() {
374 | val intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH)
375 | intent.putExtra(
376 | RecognizerIntent.EXTRA_LANGUAGE_MODEL,
377 | RecognizerIntent.LANGUAGE_MODEL_FREE_FORM
378 | )
379 | intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.getDefault())
380 | intent.putExtra(
381 | RecognizerIntent.EXTRA_PROMPT,
382 | getString(R.string.speech_prompt)
383 | )
384 | try {
385 | startActivityForResult(intent, reqCodeSpeechInput)
386 | } catch (a: ActivityNotFoundException) {
387 | showToast(getString(R.string.speech_not_supported))
388 | }
389 |
390 | }
391 |
392 | /**
393 | * Receiving speech input
394 | */
395 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
396 | super.onActivityResult(requestCode, resultCode, data)
397 |
398 | when (requestCode) {
399 | reqCodeSpeechInput -> {
400 | if (resultCode == Activity.RESULT_OK && null != data) {
401 |
402 | val result = data
403 | .getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS)
404 |
405 | sendMessage(result[0])
406 | }
407 | }
408 | }
409 | }
410 |
411 | public override fun onDestroy() {
412 | super.onDestroy()
413 | ttsManager.shutDown()
414 | isNetworkChangeReceiverRegistered = false
415 | isWearBroadcastRevieverRegistered = false
416 | }
417 |
418 | public override fun onStart() {
419 | super.onStart()
420 | recordVersionInfo()
421 | registerReceivers()
422 | checkIfLaunchedFromWidget(intent)
423 | }
424 |
425 | public override fun onStop() {
426 | super.onStop()
427 |
428 | unregisterReceivers()
429 |
430 | if (launchedFromWidget) {
431 | autoPromptForSpeech = true
432 | }
433 | }
434 |
435 | override fun onNewIntent(intent: Intent) {
436 | super.onNewIntent(intent)
437 | setIntent(intent)
438 | }
439 |
440 | private fun loadPreferences() {
441 | sharedPref = PreferenceManager.getDefaultSharedPreferences(this)
442 |
443 | // get mycroft-core ip address
444 | wsip = sharedPref.getString("ip", "")!!
445 | if (wsip!!.isEmpty()) {
446 | // eep, show the settings intent!
447 | startActivity(Intent(this, SettingsActivity::class.java))
448 | } else if (webSocketClient == null || webSocketClient!!.connection.isClosed) {
449 | connectWebSocket()
450 | }
451 |
452 | binding.contentMain.kbMicSwitch.isChecked = sharedPref.getBoolean("kbMicSwitch", true)
453 | if (binding.contentMain.kbMicSwitch.isChecked) {
454 | // Switch to mic
455 | binding.contentMain.micButton.visibility = View.VISIBLE
456 | binding.contentMain.utteranceInput.visibility = View.INVISIBLE
457 | binding.contentMain.sendUtterance.visibility = View.INVISIBLE
458 | } else {
459 | // Switch to keyboard
460 | binding.contentMain.micButton.visibility = View.INVISIBLE
461 | binding.contentMain.utteranceInput.visibility = View.VISIBLE
462 | binding.contentMain.sendUtterance.visibility = View.VISIBLE
463 | }
464 |
465 | // set app reader setting
466 | binding.contentMain.voxswitch.isChecked = sharedPref.getBoolean("appReaderSwitch", true)
467 |
468 | maximumRetries = Integer.parseInt(sharedPref.getString("maximumRetries", "1")!!)
469 | }
470 |
471 | private fun checkIfLaunchedFromWidget(intent: Intent) {
472 | val extras = getIntent().extras
473 | if (extras != null) {
474 | if (extras.containsKey("launchedFromWidget")) {
475 | launchedFromWidget = extras.getBoolean("launchedFromWidget")
476 | autoPromptForSpeech = extras.getBoolean("autoPromptForSpeech")
477 | }
478 |
479 | if (extras.containsKey(MYCROFT_WEAR_REQUEST_KEY_NAME)) {
480 | Log.d(
481 | logTag,
482 | "checkIfLaunchedFromWidget - extras contain key:$MYCROFT_WEAR_REQUEST_KEY_NAME"
483 | )
484 | extras.getString(MYCROFT_WEAR_REQUEST_KEY_NAME)?.let { sendMessage(it) }
485 | getIntent().removeExtra(MYCROFT_WEAR_REQUEST_KEY_NAME)
486 | }
487 | }
488 |
489 | if (autoPromptForSpeech) {
490 | promptSpeechInput()
491 | intent.putExtra("autoPromptForSpeech", false)
492 | }
493 | }
494 |
495 | private fun recordVersionInfo() {
496 | try {
497 | val packageInfo = packageManager.getPackageInfo(packageName, 0)
498 | val editor = sharedPref.edit()
499 | editor.putInt(VERSION_CODE_PREFERENCE_KEY, packageInfo.versionCode)
500 | editor.putString(VERSION_NAME_PREFERENCE_KEY, packageInfo.versionName)
501 | editor.apply()
502 | } catch (e: PackageManager.NameNotFoundException) {
503 | Log.e(logTag, "Couldn't find package info", e)
504 | }
505 | }
506 |
507 | private fun showToast(message: String) {
508 | GuiUtilities.showToast(applicationContext, message)
509 | }
510 | }
511 |
--------------------------------------------------------------------------------