├── .gitignore
├── COPYING
├── README.md
├── app
    ├── build.gradle.kts
    ├── proguard-rules.pro
    └── src
    │   └── main
    │       ├── AndroidManifest.xml
    │       ├── java
    │           └── org
    │           │   └── vosk
    │           │       └── service
    │           │           ├── VoskRecognitionService.java
    │           │           ├── download
    │           │               ├── Download.java
    │           │               ├── DownloadModelService.java
    │           │               ├── DownloadProgressInterceptor.java
    │           │               ├── DownloadProgressListener.java
    │           │               ├── DownloadProgressResponseBody.java
    │           │               ├── Error.java
    │           │               ├── EventBus.java
    │           │               ├── FileHelper.java
    │           │               ├── VoskModelStorage.java
    │           │               └── VoskModelStorageClient.java
    │           │           ├── ui
    │           │               ├── SpeechRecognizerActivity.java
    │           │               └── selector
    │           │               │   ├── DiffCallback.java
    │           │               │   ├── ModelItem.java
    │           │               │   ├── ModelListActivity.java
    │           │               │   └── ModelListAdapter.java
    │           │           └── utils
    │           │               ├── PreferenceConstants.java
    │           │               └── Tools.java
    │       └── res
    │           ├── drawable-hdpi
    │               ├── ic_service_trigger.xml
    │               ├── icon.png
    │               └── rounded.xml
    │           ├── drawable-ldpi
    │               └── icon.png
    │           ├── drawable-mdpi
    │               └── icon.png
    │           ├── drawable
    │               ├── circle.xml
    │               ├── gradient_progress_color.xml
    │               ├── ic_baseline_check_circle_24.xml
    │               ├── ic_baseline_cloud_download_24.xml
    │               └── ic_baseline_mic_24.xml
    │           ├── layout
    │               ├── activity_model_list.xml
    │               ├── main.xml
    │               ├── model_list_item.xml
    │               └── speech_recognizer_activity.xml
    │           ├── raw
    │               └── start_speech_effect.mp3
    │           ├── values
    │               ├── colors.xml
    │               ├── strings.xml
    │               └── styles.xml
    │           └── xml
    │               └── recognition_service.xml
├── build.gradle.kts
├── gradle.properties
├── gradle
    └── wrapper
    │   ├── gradle-wrapper.jar
    │   └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
 1 | *.iml
 2 | .gradle
 3 | /local.properties
 4 | /.idea
 5 | .DS_Store
 6 | /release
 7 | /captures
 8 | /app/ontest*
 9 | /app/production*
10 | /app/develop*
11 | /app/google-services.json
12 | /app/build
13 | .externalNativeBuild
14 | hs_err_*
15 | 
--------------------------------------------------------------------------------
/COPYING:
--------------------------------------------------------------------------------
  1 |                                  Apache License
  2 |                            Version 2.0, January 2004
  3 |                         http://www.apache.org/licenses/
  4 | 
  5 |    TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
  6 | 
  7 |    1. Definitions.
  8 | 
  9 |       "License" shall mean the terms and conditions for use, reproduction,
 10 |       and distribution as defined by Sections 1 through 9 of this document.
 11 | 
 12 |       "Licensor" shall mean the copyright owner or entity authorized by
 13 |       the copyright owner that is granting the License.
 14 | 
 15 |       "Legal Entity" shall mean the union of the acting entity and all
 16 |       other entities that control, are controlled by, or are under common
 17 |       control with that entity. For the purposes of this definition,
 18 |       "control" means (i) the power, direct or indirect, to cause the
 19 |       direction or management of such entity, whether by contract or
 20 |       otherwise, or (ii) ownership of fifty percent (50%) or more of the
 21 |       outstanding shares, or (iii) beneficial ownership of such entity.
 22 | 
 23 |       "You" (or "Your") shall mean an individual or Legal Entity
 24 |       exercising permissions granted by this License.
 25 | 
 26 |       "Source" form shall mean the preferred form for making modifications,
 27 |       including but not limited to software source code, documentation
 28 |       source, and configuration files.
 29 | 
 30 |       "Object" form shall mean any form resulting from mechanical
 31 |       transformation or translation of a Source form, including but
 32 |       not limited to compiled object code, generated documentation,
 33 |       and conversions to other media types.
 34 | 
 35 |       "Work" shall mean the work of authorship, whether in Source or
 36 |       Object form, made available under the License, as indicated by a
 37 |       copyright notice that is included in or attached to the work
 38 |       (an example is provided in the Appendix below).
 39 | 
 40 |       "Derivative Works" shall mean any work, whether in Source or Object
 41 |       form, that is based on (or derived from) the Work and for which the
 42 |       editorial revisions, annotations, elaborations, or other modifications
 43 |       represent, as a whole, an original work of authorship. For the purposes
 44 |       of this License, Derivative Works shall not include works that remain
 45 |       separable from, or merely link (or bind by name) to the interfaces of,
 46 |       the Work and Derivative Works thereof.
 47 | 
 48 |       "Contribution" shall mean any work of authorship, including
 49 |       the original version of the Work and any modifications or additions
 50 |       to that Work or Derivative Works thereof, that is intentionally
 51 |       submitted to Licensor for inclusion in the Work by the copyright owner
 52 |       or by an individual or Legal Entity authorized to submit on behalf of
 53 |       the copyright owner. For the purposes of this definition, "submitted"
 54 |       means any form of electronic, verbal, or written communication sent
 55 |       to the Licensor or its representatives, including but not limited to
 56 |       communication on electronic mailing lists, source code control systems,
 57 |       and issue tracking systems that are managed by, or on behalf of, the
 58 |       Licensor for the purpose of discussing and improving the Work, but
 59 |       excluding communication that is conspicuously marked or otherwise
 60 |       designated in writing by the copyright owner as "Not a Contribution."
 61 | 
 62 |       "Contributor" shall mean Licensor and any individual or Legal Entity
 63 |       on behalf of whom a Contribution has been received by Licensor and
 64 |       subsequently incorporated within the Work.
 65 | 
 66 |    2. Grant of Copyright License. Subject to the terms and conditions of
 67 |       this License, each Contributor hereby grants to You a perpetual,
 68 |       worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 69 |       copyright license to reproduce, prepare Derivative Works of,
 70 |       publicly display, publicly perform, sublicense, and distribute the
 71 |       Work and such Derivative Works in Source or Object form.
 72 | 
 73 |    3. Grant of Patent License. Subject to the terms and conditions of
 74 |       this License, each Contributor hereby grants to You a perpetual,
 75 |       worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 76 |       (except as stated in this section) patent license to make, have made,
 77 |       use, offer to sell, sell, import, and otherwise transfer the Work,
 78 |       where such license applies only to those patent claims licensable
 79 |       by such Contributor that are necessarily infringed by their
 80 |       Contribution(s) alone or by combination of their Contribution(s)
 81 |       with the Work to which such Contribution(s) was submitted. If You
 82 |       institute patent litigation against any entity (including a
 83 |       cross-claim or counterclaim in a lawsuit) alleging that the Work
 84 |       or a Contribution incorporated within the Work constitutes direct
 85 |       or contributory patent infringement, then any patent licenses
 86 |       granted to You under this License for that Work shall terminate
 87 |       as of the date such litigation is filed.
 88 | 
 89 |    4. Redistribution. You may reproduce and distribute copies of the
 90 |       Work or Derivative Works thereof in any medium, with or without
 91 |       modifications, and in Source or Object form, provided that You
 92 |       meet the following conditions:
 93 | 
 94 |       (a) You must give any other recipients of the Work or
 95 |           Derivative Works a copy of this License; and
 96 | 
 97 |       (b) You must cause any modified files to carry prominent notices
 98 |           stating that You changed the files; and
 99 | 
100 |       (c) You must retain, in the Source form of any Derivative Works
101 |           that You distribute, all copyright, patent, trademark, and
102 |           attribution notices from the Source form of the Work,
103 |           excluding those notices that do not pertain to any part of
104 |           the Derivative Works; and
105 | 
106 |       (d) If the Work includes a "NOTICE" text file as part of its
107 |           distribution, then any Derivative Works that You distribute must
108 |           include a readable copy of the attribution notices contained
109 |           within such NOTICE file, excluding those notices that do not
110 |           pertain to any part of the Derivative Works, in at least one
111 |           of the following places: within a NOTICE text file distributed
112 |           as part of the Derivative Works; within the Source form or
113 |           documentation, if provided along with the Derivative Works; or,
114 |           within a display generated by the Derivative Works, if and
115 |           wherever such third-party notices normally appear. The contents
116 |           of the NOTICE file are for informational purposes only and
117 |           do not modify the License. You may add Your own attribution
118 |           notices within Derivative Works that You distribute, alongside
119 |           or as an addendum to the NOTICE text from the Work, provided
120 |           that such additional attribution notices cannot be construed
121 |           as modifying the License.
122 | 
123 |       You may add Your own copyright statement to Your modifications and
124 |       may provide additional or different license terms and conditions
125 |       for use, reproduction, or distribution of Your modifications, or
126 |       for any such Derivative Works as a whole, provided Your use,
127 |       reproduction, and distribution of the Work otherwise complies with
128 |       the conditions stated in this License.
129 | 
130 |    5. Submission of Contributions. Unless You explicitly state otherwise,
131 |       any Contribution intentionally submitted for inclusion in the Work
132 |       by You to the Licensor shall be under the terms and conditions of
133 |       this License, without any additional terms or conditions.
134 |       Notwithstanding the above, nothing herein shall supersede or modify
135 |       the terms of any separate license agreement you may have executed
136 |       with Licensor regarding such Contributions.
137 | 
138 |    6. Trademarks. this License does not grant permission to use the trade
139 |       names, trademarks, service marks, or product names of the Licensor,
140 |       except as required for reasonable and customary use in describing the
141 |       origin of the Work and reproducing the content of the NOTICE file.
142 | 
143 |    7. Disclaimer of Warranty. Unless required by applicable law or
144 |       agreed to in writing, Licensor provides the Work (and each
145 |       Contributor provides its Contributions) on an "AS IS" BASIS,
146 |       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 |       implied, including, without limitation, any warranties or conditions
148 |       of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 |       PARTICULAR PURPOSE. You are solely responsible for determining the
150 |       appropriateness of using or redistributing the Work and assume any
151 |       risks associated with Your exercise of permissions under this License.
152 | 
153 |    8. Limitation of Liability. In no event and under no legal theory,
154 |       whether in tort (including negligence), contract, or otherwise,
155 |       unless required by applicable law (such as deliberate and grossly
156 |       negligent acts) or agreed to in writing, shall any Contributor be
157 |       liable to You for damages, including any direct, indirect, special,
158 |       incidental, or consequential damages of any character arising as a
159 |       result of this License or out of the use or inability to use the
160 |       Work (including but not limited to damages for loss of goodwill,
161 |       work stoppage, computer failure or malfunction, or any and all
162 |       other commercial damages or losses), even if such Contributor
163 |       has been advised of the possibility of such damages.
164 | 
165 |    9. Accepting Warranty or Additional Liability. While redistributing
166 |       the Work or Derivative Works thereof, You may choose to offer,
167 |       and charge a fee for, acceptance of support, warranty, indemnity,
168 |       or other liability obligations and/or rights consistent with this
169 |       License. However, in accepting such obligations, You may act only
170 |       on Your own behalf and on Your sole responsibility, not on behalf
171 |       of any other Contributor, and only if You agree to indemnify,
172 |       defend, and hold each Contributor harmless for any liability
173 |       incurred by, or claims asserted against, such Contributor by reason
174 |       of your accepting any such warranty or additional liability.
175 | 
176 |    END OF TERMS AND CONDITIONS
177 | 
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vosk android service
2 | 
3 | This is a service module for android, 
4 | allowing other applications to call vosk to perform speech to text.
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
 1 | plugins {
 2 |     id("com.android.application")
 3 | }
 4 | 
 5 | repositories {
 6 |     google()
 7 |     maven("https://alphacephei.com/maven/")
 8 | }
 9 | 
10 | android {
11 |     compileSdk = 33
12 |     defaultConfig {
13 |         applicationId = "org.vosk.service"
14 |         minSdk = 24
15 |         targetSdk = 33
16 |         versionCode = 1
17 |         versionName = "1.1"
18 |         ndk {
19 |             abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86_64", "x86")
20 |         }
21 |         splits {
22 |             abi {
23 |                 isEnable = true
24 | 
25 |                 isUniversalApk = true
26 |             }
27 |         }
28 |     }
29 |     buildTypes {
30 |         release {
31 |             isMinifyEnabled = true
32 |             proguardFiles(
33 |                 getDefaultProguardFile("proguard-android-optimize.txt"),
34 |                 "proguard-rules.pro"
35 |             )
36 |         }
37 |     }
38 |     compileOptions {
39 |         sourceCompatibility = JavaVersion.VERSION_1_8
40 |         targetCompatibility = JavaVersion.VERSION_1_8
41 |     }
42 | }
43 | 
44 | dependencies {
45 |     implementation("com.alphacephei:vosk-android:0.3.46@aar")
46 |     implementation("net.java.dev.jna:jna:5.13.0@aar")
47 |     implementation("androidx.appcompat:appcompat:1.5.1")
48 |     implementation("com.google.code.gson:gson:2.9.0")
49 |     implementation("com.google.android.material:material:1.6.1")
50 |     implementation("io.reactivex.rxjava2:rxandroid:2.1.1")
51 |     implementation("io.reactivex.rxjava2:rxjava:2.2.9")
52 |     implementation("com.squareup.retrofit2:retrofit:2.9.0")
53 |     implementation("com.squareup.retrofit2:converter-gson:2.9.0")
54 |     implementation("com.squareup.retrofit2:adapter-rxjava2:2.3.0")
55 |     implementation("androidx.constraintlayout:constraintlayout:2.1.4")
56 |     implementation("com.github.pwittchen:reactivenetwork-rx2:0.12.3")
57 |     implementation("commons-io:commons-io:2.11.0")
58 | }
59 | 
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | -keep class com.sun.jna.* { *; }
2 | -keepclassmembers class * extends com.sun.jna.* { public *; }
3 | 
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
  1 | 
  2 | 
  4 | 
  5 |      
  6 |      
  7 |      
  8 |      
  9 | 
 10 |     
 11 |         
 12 |              
 13 |          
 14 |      
 15 | 
 16 |     
 21 |         
 25 |             
 26 |                  
 27 | 
 28 |                  
 29 |              
 30 |          
 31 |         
 35 |             
 36 |                  
 37 | 
 38 |                  
 39 |              
 40 |             
 41 |                  
 42 | 
 43 |                  
 44 |              
 45 | 
 46 |             
 47 |             
 48 |             
 49 |                  
 50 | 
 51 |                  
 52 |              
 53 | 
 54 |             
 71 |             
 72 |                  
 73 | 
 74 |                  
 75 |              
 76 | 
 77 |             
 78 |             
 79 |             
 80 |             
 81 |                  
 82 | 
 83 |                  
 84 |              
 85 | 
 86 |             
 87 |             
 88 |                  
 89 | 
 90 |                  
 91 |              
 92 |          
 93 | 
 94 |          
 97 | 
 98 |         
105 |             
106 | 
107 |                 
108 |                  
109 | 
110 |                  
111 |              
112 | 
113 |              
116 |          
117 | 
118 |      
119 |  
--------------------------------------------------------------------------------
/app/src/main/java/org/vosk/service/VoskRecognitionService.java:
--------------------------------------------------------------------------------
  1 | // Copyright 2020 Ciaran O'Reilly
  2 | // Copyright 2019 Alpha Cephei Inc.
  3 | //
  4 | // This program is free software: you can redistribute it and/or modify
  5 | // it under the terms of the GNU General Public License as published by
  6 | // the Free Software Foundation, either version 3 of the License, or
  7 | // (at your option) any later version.
  8 | 
  9 | // This program is distributed in the hope that it will be useful,
 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 12 | // GNU General Public License for more details.
 13 | 
 14 | // You should have received a copy of the GNU General Public License
 15 | // along with this program.  If not, see  .
 16 | 
 17 | package org.vosk.service;
 18 | 
 19 | import android.content.Intent;
 20 | import android.content.SharedPreferences;
 21 | import android.os.Bundle;
 22 | import android.os.RemoteException;
 23 | import android.preference.PreferenceManager;
 24 | import android.speech.RecognitionService;
 25 | import android.util.Log;
 26 | 
 27 | import com.google.gson.Gson;
 28 | import com.google.gson.reflect.TypeToken;
 29 | 
 30 | import org.vosk.Model;
 31 | import org.vosk.Recognizer;
 32 | import org.vosk.android.RecognitionListener;
 33 | import org.vosk.android.SpeechService;
 34 | import org.vosk.service.utils.PreferenceConstants;
 35 | import org.vosk.service.utils.Tools;
 36 | 
 37 | import java.io.File;
 38 | import java.io.IOException;
 39 | import java.lang.reflect.Type;
 40 | import java.util.ArrayList;
 41 | import java.util.Map;
 42 | import java.util.concurrent.TimeUnit;
 43 | 
 44 | import io.reactivex.Single;
 45 | import io.reactivex.android.schedulers.AndroidSchedulers;
 46 | import io.reactivex.disposables.CompositeDisposable;
 47 | import io.reactivex.schedulers.Schedulers;
 48 | 
 49 | public class VoskRecognitionService extends RecognitionService implements RecognitionListener {
 50 |     private final static String TAG = VoskRecognitionService.class.getSimpleName();
 51 |     private Recognizer recognizer;
 52 |     private SpeechService speechService;
 53 |     private Model model;
 54 | 
 55 |     private RecognitionService.Callback mCallback;
 56 | 
 57 |     private final CompositeDisposable compositeDisposable = new CompositeDisposable();
 58 | 
 59 |     @Override
 60 |     protected void onStartListening(Intent intent, Callback callback) {
 61 |         Log.v(TAG, "onStartListening");
 62 |         mCallback = callback;
 63 |         runRecognizerSetup();
 64 |     }
 65 | 
 66 |     @Override
 67 |     protected void onCancel(Callback callback) {
 68 |         Log.v(TAG, "onCancel");
 69 |         results(new Bundle(), true);
 70 |     }
 71 | 
 72 |     @Override
 73 |     protected void onStopListening(Callback callback) {
 74 |         Log.v(TAG, "onStopListening");
 75 |         results(new Bundle(), true);
 76 |     }
 77 | 
 78 |     private void runRecognizerSetup() {
 79 |         Log.v(TAG, "runRecognizerSetup");
 80 |         if (this.model == null) {
 81 |             SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 82 | 
 83 |             if (sharedPreferences.contains(PreferenceConstants.ACTIVE_MODEL)) {
 84 |                 final File MODEL_FILE_ROOT_PATH = Tools.getModelFileRootPath(this);
 85 |                 final String ACTIVE_MODEL = sharedPreferences.getString(PreferenceConstants.ACTIVE_MODEL, "");
 86 | 
 87 |                 File outputFile = new File(
 88 |                         MODEL_FILE_ROOT_PATH,
 89 |                         ACTIVE_MODEL + "/" + ACTIVE_MODEL
 90 |                 );
 91 | 
 92 |                 final String outputPath = outputFile.getAbsolutePath();
 93 |                 Log.d(TAG, outputPath);
 94 | 
 95 |                 compositeDisposable.add(Single.fromCallable(() -> new Model(outputPath))
 96 |                         .doOnSuccess(model_ -> this.model = model_)
 97 |                         .delay(1, TimeUnit.MICROSECONDS)
 98 |                         .subscribeOn(Schedulers.io())
 99 |                         .observeOn(AndroidSchedulers.mainThread())
100 |                         .subscribe(model_ -> startSpeech(), Throwable::printStackTrace));
101 |             }
102 |         } else {
103 |             startSpeech();
104 |         }
105 |     }
106 | 
107 |     private void startSpeech() {
108 |         Log.v(TAG, "startSpeech");
109 |         setupRecognizer();
110 |         this.readyForSpeech(new Bundle());
111 |         beginningOfSpeech();
112 |     }
113 | 
114 |     @Override
115 |     public void onDestroy() {
116 |         Log.v(TAG, "onDestroy");
117 |         super.onDestroy();
118 | 
119 |         if (speechService != null) {
120 |             speechService.cancel();
121 |             speechService.shutdown();
122 |         }
123 |     }
124 | 
125 |     private void setupRecognizer() {
126 |         Log.v(TAG, "setupRecognizer");
127 |         try {
128 |             if (recognizer == null) {
129 |                 Log.i(TAG, "Creating recognizer");
130 | 
131 |                 recognizer = new Recognizer(model, 16000.0f);
132 |             }
133 | 
134 |             if (speechService == null) {
135 |                 Log.i(TAG, "Creating speechService");
136 | 
137 |                 speechService = new SpeechService(recognizer, 16000.0f);
138 |             } else {
139 |                 speechService.cancel();
140 |             }
141 |             speechService.startListening(this);
142 |         } catch (IOException e) {
143 |             Log.e(TAG, e.getMessage());
144 |         }
145 |     }
146 | 
147 |     private void readyForSpeech(Bundle bundle) {
148 |         Log.v(TAG, "readyForSpeech");
149 |         try {
150 |             mCallback.readyForSpeech(bundle);
151 |         } catch (RemoteException e) {
152 |             // empty
153 |         }
154 |     }
155 | 
156 |     private void results(Bundle bundle, boolean isFinal) {
157 |         Log.v(TAG, "results");
158 |         try {
159 |             if (isFinal) {
160 |                 speechService.cancel();
161 |                 mCallback.results(bundle);
162 |             } else {
163 |                 mCallback.partialResults(bundle);
164 |             }
165 |         } catch (RemoteException e) {
166 |             // empty
167 |         }
168 |     }
169 | 
170 |     private Bundle createResultsBundle(String hypothesis) {
171 |         Log.v(TAG, "createResultsBundle");
172 |         ArrayList hypotheses = new ArrayList<>();
173 |         hypotheses.add(hypothesis);
174 |         Bundle bundle = new Bundle();
175 |         bundle.putStringArrayList(android.speech.SpeechRecognizer.RESULTS_RECOGNITION, hypotheses);
176 |         return bundle;
177 |     }
178 | 
179 |     private void beginningOfSpeech() {
180 |         Log.v(TAG, "beginningOfSpeech");
181 |         try {
182 |             mCallback.beginningOfSpeech();
183 |         } catch (RemoteException e) {
184 |             // empty
185 |         }
186 |     }
187 | 
188 |     private void error(int errorCode) {
189 |         Log.v(TAG, "error");
190 |         if (speechService != null) {
191 |             speechService.cancel();
192 |         }
193 |         try {
194 |             mCallback.error(errorCode);
195 |         } catch (RemoteException e) {
196 |             // empty
197 |         }
198 |     }
199 | 
200 |     Type mapType = new TypeToken>() {
201 |     }.getType();
202 | 
203 |     @Override
204 |     public void onResult(String hypothesis) {
205 |         Log.v(TAG, "onResult");
206 |         if (hypothesis != null) {
207 |             Log.i(TAG, hypothesis);
208 |             Gson gson = new Gson();
209 |             Map map = gson.fromJson(hypothesis, mapType);
210 |             String text = map.get("text");
211 |             results(createResultsBundle(text), true);
212 |         }
213 |     }
214 | 
215 |     @Override
216 |     public void onFinalResult(String hypothesis) {
217 |         Log.v(TAG, "onFinalResult");
218 |         if (hypothesis != null) {
219 |             Log.i(TAG, hypothesis);
220 |             Gson gson = new Gson();
221 |             Map map = gson.fromJson(hypothesis, mapType);
222 |             String text = map.get("text");
223 |             results(createResultsBundle(text), true);
224 |         }
225 |     }
226 | 
227 |     @Override
228 |     public void onPartialResult(String hypothesis) {
229 |         Log.v(TAG, "onPartialResult");
230 |         if (hypothesis != null) {
231 |             Log.i(TAG, hypothesis);
232 |             Gson gson = new Gson();
233 |             Map map = gson.fromJson(hypothesis, mapType);
234 |             String text = map.get("partial");
235 |             results(createResultsBundle(text), false);
236 |         }
237 |     }
238 | 
239 |     @Override
240 |     public void onError(Exception e) {
241 |         Log.v(TAG, "onError");
242 |         Log.e(TAG, e.getMessage());
243 |         error(android.speech.SpeechRecognizer.ERROR_CLIENT);
244 |     }
245 | 
246 |     @Override
247 |     public void onTimeout() {
248 |         Log.v(TAG, "onTimeout");
249 |         speechService.cancel();
250 |         speechService.startListening(this);
251 |     }
252 | }
253 | 
--------------------------------------------------------------------------------
/app/src/main/java/org/vosk/service/download/Download.java:
--------------------------------------------------------------------------------
 1 | package org.vosk.service.download;
 2 | 
 3 | public class Download {
 4 | 
 5 |     public final static int CLEAR = 200;
 6 |     public final static int STARTING = 0;
 7 |     public final static int UNZIPPING = 202;
 8 |     public final static int COMPLETE = 203;
 9 |     public final static int RESTARTING = 204;
10 | 
11 |     private int progress;
12 |     private long currentFileSize;
13 |     private long totalFileSize;
14 |     String modelName;
15 | 
16 |     public Download() {
17 | 
18 |     }
19 | 
20 |     public Download(int progress, String modelName) {
21 |         this.progress = progress;
22 |         this.modelName = modelName;
23 |     }
24 | 
25 |     public Download(int progress, long currentFileSize, long totalFileSize) {
26 |         this.progress = progress;
27 |         this.currentFileSize = currentFileSize;
28 |         this.totalFileSize = totalFileSize;
29 |     }
30 | 
31 |     public Download(int progress) {
32 |         this.progress = progress;
33 |     }
34 | 
35 |     public int getProgress() {
36 |         return progress;
37 |     }
38 | 
39 |     public void setProgress(int progress) {
40 |         this.progress = progress;
41 |     }
42 | 
43 |     public long getCurrentFileSize() {
44 |         return currentFileSize;
45 |     }
46 | 
47 |     public void setCurrentFileSize(long currentFileSize) {
48 |         this.currentFileSize = currentFileSize;
49 |     }
50 | 
51 |     public long getTotalFileSize() {
52 |         return totalFileSize;
53 |     }
54 | 
55 |     public void setTotalFileSize(long totalFileSize) {
56 |         this.totalFileSize = totalFileSize;
57 |     }
58 | 
59 |     public String getModelName() {
60 |         return modelName;
61 |     }
62 | 
63 |     public void setModelName(String modelName) {
64 |         this.modelName = modelName;
65 |     }
66 | }
67 | 
--------------------------------------------------------------------------------
/app/src/main/java/org/vosk/service/download/DownloadModelService.java:
--------------------------------------------------------------------------------
  1 | package org.vosk.service.download;
  2 | 
  3 | import static org.vosk.service.download.Download.CLEAR;
  4 | import static org.vosk.service.download.Download.COMPLETE;
  5 | import static org.vosk.service.download.Download.UNZIPPING;
  6 | import static org.vosk.service.download.FileHelper.writeFile;
  7 | import static org.vosk.service.download.VoskModelStorageClient.ServiceType.DOWNLOAD_MODEL;
  8 | 
  9 | import android.app.NotificationChannel;
 10 | import android.app.NotificationManager;
 11 | import android.app.PendingIntent;
 12 | import android.app.Service;
 13 | import android.content.Context;
 14 | import android.content.Intent;
 15 | import android.content.SharedPreferences;
 16 | import android.media.AudioAttributes;
 17 | import android.net.Uri;
 18 | import android.os.Build;
 19 | import android.os.Environment;
 20 | import android.os.IBinder;
 21 | import android.preference.PreferenceManager;
 22 | import android.util.Log;
 23 | 
 24 | import androidx.annotation.Nullable;
 25 | import androidx.annotation.RequiresApi;
 26 | import androidx.core.app.NotificationCompat;
 27 | 
 28 | import org.vosk.service.R;
 29 | import org.vosk.service.ui.selector.ModelListActivity;
 30 | import org.vosk.service.utils.PreferenceConstants;
 31 | import org.vosk.service.utils.Tools;
 32 | 
 33 | import java.io.File;
 34 | 
 35 | import io.reactivex.android.schedulers.AndroidSchedulers;
 36 | import io.reactivex.disposables.CompositeDisposable;
 37 | import io.reactivex.schedulers.Schedulers;
 38 | import okhttp3.ResponseBody;
 39 | 
 40 | public class DownloadModelService extends Service {
 41 | 
 42 |     public static final String DOWNLOAD_MODEL_CHANNEL_ID_VALUE = "download_model_channel_id";
 43 |     public static final String DOWNLOAD_MODEL_CHANNEL_NAME = "Vosk model downloader";
 44 |     public static final int DOWNLOAD_MODEL_NOTIFICATION_ID = 1;
 45 |     public static final int DOWNLOAD_MODEL_MAX_PROGRESS = 100;
 46 | 
 47 |     private static File MODEL_FILE_ROOT_PATH ;
 48 |     private final CompositeDisposable compositeDisposable = new CompositeDisposable();
 49 |     private final VoskModelStorage service = VoskModelStorageClient.getClient(getListener(), DOWNLOAD_MODEL);
 50 |     private SharedPreferences sharedPreferences;
 51 |     private final EventBus eventBus = EventBus.getInstance();
 52 |     private NotificationManager notificationManager;
 53 |     private NotificationCompat.Builder notificationBuilder;
 54 | 
 55 |     private int actualProgress = 0;
 56 |     private String modelName;
 57 | 
 58 |     @Nullable
 59 |     @Override
 60 |     public IBinder onBind(Intent intent) {
 61 |         return null;
 62 |     }
 63 | 
 64 |     @Override
 65 |     public void onCreate() {
 66 |         super.onCreate();
 67 |         sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 68 |         MODEL_FILE_ROOT_PATH = Tools.getModelFileRootPath(this);
 69 |         modelName = sharedPreferences.getString(PreferenceConstants.DOWNLOADING_FILE, "");
 70 |         downloadModel(modelName);
 71 |         observeEvents();
 72 |     }
 73 | 
 74 |     private void observeEvents() {
 75 |         compositeDisposable.add(eventBus.getDownloadStatusObservable()
 76 |                 .subscribeOn(Schedulers.io())
 77 |                 .observeOn(AndroidSchedulers.mainThread())
 78 |                 .subscribe(download -> {
 79 |                     if (download.getProgress() == UNZIPPING) {
 80 | 
 81 |                         File outputFile = new File(MODEL_FILE_ROOT_PATH, modelName + ".zip");
 82 |                         File destinationFile = new File(MODEL_FILE_ROOT_PATH, modelName);
 83 | 
 84 |                         FileHelper.unzipFIle(this, outputFile, destinationFile);
 85 |                         actualProgress = CLEAR;
 86 |                     } else if (download.getProgress() == COMPLETE) {
 87 |                         sharedPreferences.edit()
 88 |                                 .remove(PreferenceConstants.DOWNLOADING_FILE)
 89 |                                 .apply();
 90 |                         if (!sharedPreferences.contains(PreferenceConstants.ACTIVE_MODEL))
 91 |                             sharedPreferences.edit().putString(PreferenceConstants.ACTIVE_MODEL, modelName).apply();
 92 |                         stopSelf();
 93 |                     } else {
 94 |                         if (actualProgress != download.getProgress()) {
 95 |                             actualProgress = download.getProgress();
 96 |                             updateNotificationProgress();
 97 |                         }
 98 |                     }
 99 |                 }));
100 |         compositeDisposable.add(EventBus.getInstance().geErrorObservable().subscribeOn(Schedulers.io()).subscribe(error -> stopSelf()));
101 |     }
102 | 
103 |     private void updateNotificationProgress() {
104 |         notificationBuilder.setProgress(DOWNLOAD_MODEL_MAX_PROGRESS, actualProgress, false);
105 |         notificationBuilder.setSilent(true);
106 |         notificationManager.notify(DOWNLOAD_MODEL_NOTIFICATION_ID, notificationBuilder.build());
107 |     }
108 | 
109 |     private DownloadProgressListener getListener() {
110 |         return (bytesRead, contentLength, done) -> {
111 |             Download download = new Download();
112 |             download.setTotalFileSize(contentLength);
113 |             download.setCurrentFileSize(bytesRead);
114 |             int progress = (int) ((bytesRead * 100) / contentLength);
115 |             download.setProgress(progress);
116 |             Log.d("DOWNLOAD", "Progress: " + progress);
117 |             EventBus.getInstance().postDownloadStatus(download);
118 |         };
119 |     }
120 | 
121 |     private void downloadModel(String modelName) {
122 |         File outputFile = new File(MODEL_FILE_ROOT_PATH, modelName + ".zip");
123 |         FileHelper.createDir(MODEL_FILE_ROOT_PATH);
124 | 
125 |         compositeDisposable.add(service.downloadFile(outputFile.getName())
126 |                 .subscribeOn(Schedulers.io())
127 |                 .map(ResponseBody::byteStream)
128 |                 .doOnNext(inputStream -> writeFile(inputStream, outputFile))
129 |                 .subscribe(inputStream -> EventBus.getInstance().postDownloadStatus(new Download(UNZIPPING, modelName)),
130 |                         error -> EventBus.getInstance().postErrorStatus(Error.CONNECTION)));
131 | 
132 |     }
133 | 
134 |     @Override
135 |     public int onStartCommand(Intent intent, int flags, int startId) {
136 |         registerNotification();
137 |         return START_NOT_STICKY;
138 |     }
139 | 
140 |     private void registerNotification() {
141 |         notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
142 |         Intent notificationIntent = new Intent(this, ModelListActivity.class);
143 |         PendingIntent pendingIntent;
144 |         int flags = 0;
145 |         if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
146 |             flags = PendingIntent.FLAG_MUTABLE;
147 |         }
148 |         pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, flags);
149 |         notificationBuilder = getNotification(notificationManager, pendingIntent);
150 | 
151 |         startForeground(DOWNLOAD_MODEL_NOTIFICATION_ID, notificationBuilder.build());
152 |     }
153 | 
154 |     private NotificationCompat.Builder getNotification(NotificationManager notificationManager, PendingIntent pendingIntent) {
155 | 
156 |         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
157 |             createNotificationChannel(notificationManager, DOWNLOAD_MODEL_CHANNEL_ID_VALUE, DOWNLOAD_MODEL_CHANNEL_ID_VALUE, null);
158 |         }
159 |         return new NotificationCompat.Builder(this, DOWNLOAD_MODEL_CHANNEL_ID_VALUE)
160 |                 .setContentTitle(getString(R.string.download_model_service_notification_title))
161 |                 .setSmallIcon(R.drawable.icon)
162 |                 .setAutoCancel(false)
163 |                 .setProgress(DOWNLOAD_MODEL_MAX_PROGRESS, 0, false)
164 |                 .setContentIntent(pendingIntent);
165 |     }
166 | 
167 |     @RequiresApi(api = Build.VERSION_CODES.O)
168 |     public static void createNotificationChannel(NotificationManager notificationManager, String channelId, String channelName, Uri notificationSoundUri) {
169 |         NotificationChannel notificationChannel = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH);
170 |         notificationChannel.setVibrationPattern(new long[]{1000, 1000, 1000, 1000, 1000});
171 | 
172 |         if (notificationSoundUri != null) {
173 |             AudioAttributes audioAttributes = new AudioAttributes.Builder()
174 |                     .setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
175 |                     .setUsage(AudioAttributes.USAGE_NOTIFICATION)
176 |                     .build();
177 |             notificationChannel.setSound(notificationSoundUri, audioAttributes);
178 |         }
179 | 
180 |         notificationManager.createNotificationChannel(notificationChannel);
181 |     }
182 | 
183 |     @Override
184 |     public void onDestroy() {
185 |         super.onDestroy();
186 |         compositeDisposable.clear();
187 |     }
188 | }
189 | 
--------------------------------------------------------------------------------
/app/src/main/java/org/vosk/service/download/DownloadProgressInterceptor.java:
--------------------------------------------------------------------------------
 1 | package org.vosk.service.download;
 2 | 
 3 | import androidx.annotation.NonNull;
 4 | 
 5 | import java.io.IOException;
 6 | 
 7 | import okhttp3.Interceptor;
 8 | import okhttp3.Response;
 9 | 
10 | public class DownloadProgressInterceptor implements Interceptor {
11 | 
12 |     private DownloadProgressListener listener;
13 | 
14 |     public DownloadProgressInterceptor(DownloadProgressListener listener) {
15 |         this.listener = listener;
16 |     }
17 | 
18 |     @NonNull
19 |     @Override
20 |     public Response intercept(Chain chain) throws IOException {
21 |         Response originalResponse = chain.proceed(chain.request());
22 | 
23 |         return originalResponse.newBuilder()
24 |                 .body(new DownloadProgressResponseBody(originalResponse.body(), listener))
25 |                 .build();
26 |     }
27 | }
28 | 
--------------------------------------------------------------------------------
/app/src/main/java/org/vosk/service/download/DownloadProgressListener.java:
--------------------------------------------------------------------------------
1 | package org.vosk.service.download;
2 | 
3 | public interface DownloadProgressListener {
4 |     void update(long bytesRead, long contentLength, boolean done);
5 | }
6 | 
--------------------------------------------------------------------------------
/app/src/main/java/org/vosk/service/download/DownloadProgressResponseBody.java:
--------------------------------------------------------------------------------
 1 | package org.vosk.service.download;
 2 | 
 3 | import androidx.annotation.NonNull;
 4 | 
 5 | import java.io.IOException;
 6 | 
 7 | import okhttp3.MediaType;
 8 | import okhttp3.ResponseBody;
 9 | import okio.Buffer;
10 | import okio.BufferedSource;
11 | import okio.ForwardingSource;
12 | import okio.Okio;
13 | import okio.Source;
14 | 
15 | public class DownloadProgressResponseBody extends ResponseBody {
16 | 
17 |     private ResponseBody responseBody;
18 |     DownloadProgressListener progressListener;
19 |     private BufferedSource bufferedSource;
20 | 
21 |     public DownloadProgressResponseBody(ResponseBody responseBody,
22 |                                         DownloadProgressListener progressListener) {
23 |         this.responseBody = responseBody;
24 |         this.progressListener = progressListener;
25 |     }
26 | 
27 |     @Override
28 |     public MediaType contentType() {
29 |         return responseBody.contentType();
30 |     }
31 | 
32 |     @Override
33 |     public long contentLength() {
34 |         return responseBody.contentLength();
35 |     }
36 | 
37 |     @NonNull
38 |     @Override
39 |     public BufferedSource source() {
40 |         if (bufferedSource == null) {
41 |             bufferedSource = Okio.buffer(source(responseBody.source()));
42 |         }
43 |         return bufferedSource;
44 |     }
45 | 
46 |     private Source source(Source source) {
47 |         return new ForwardingSource(source) {
48 |             long totalBytesRead = 0L;
49 | 
50 |             @Override
51 |             public long read(Buffer sink, long byteCount) throws IOException {
52 |                 long bytesRead = super.read(sink, byteCount);
53 |                 // read() returns the number of bytes read, or -1 if this source is exhausted.
54 |                 totalBytesRead += bytesRead != -1 ? bytesRead : 0;
55 | 
56 |                 if (null != progressListener) {
57 |                     progressListener.update(totalBytesRead, responseBody.contentLength(), bytesRead == -1);
58 |                 }
59 |                 return bytesRead;
60 |             }
61 |         };
62 | 
63 |     }
64 | }
--------------------------------------------------------------------------------
/app/src/main/java/org/vosk/service/download/Error.java:
--------------------------------------------------------------------------------
1 | package org.vosk.service.download;
2 | 
3 | public enum Error {
4 |     CONNECTION,
5 |     WRITE_STORAGE
6 | }
7 | 
--------------------------------------------------------------------------------
/app/src/main/java/org/vosk/service/download/EventBus.java:
--------------------------------------------------------------------------------
 1 | package org.vosk.service.download;
 2 | 
 3 | import android.net.NetworkInfo;
 4 | 
 5 | import org.vosk.service.ui.selector.ModelItem;
 6 | 
 7 | import io.reactivex.Observable;
 8 | import io.reactivex.subjects.PublishSubject;
 9 | 
10 | public class EventBus {
11 |     private static EventBus _instance;
12 | 
13 |     private final PublishSubject downloadModelProgressEventSubject = PublishSubject.create();
14 |     private final PublishSubject startDownloadEventSubject = PublishSubject.create();
15 |     private final PublishSubject modelSelectedEventSubject = PublishSubject.create();
16 |     private final PublishSubject errorEventSubject = PublishSubject.create();
17 |     private final PublishSubject connectionEventSubject = PublishSubject.create();
18 |     private final PublishSubject deleteDownloadedModel = PublishSubject.create();
19 | 
20 |     public EventBus() {
21 |     }
22 | 
23 |     public static EventBus getInstance() {
24 |         if (_instance == null) {
25 |             _instance = new EventBus();
26 |         }
27 |         return _instance;
28 |     }
29 | 
30 |     public Observable getDownloadStatusObservable() {
31 |         return downloadModelProgressEventSubject;
32 |     }
33 | 
34 |     public void postDownloadStatus(Download stateId) {
35 |         downloadModelProgressEventSubject.onNext(stateId);
36 |     }
37 | 
38 | 
39 |     public Observable getDownloadStartObservable() {
40 |         return startDownloadEventSubject;
41 |     }
42 | 
43 |     public void postDownloadStart(ModelItem modelItem) {
44 |         startDownloadEventSubject.onNext(modelItem);
45 |     }
46 | 
47 |     public Observable getModelSelectedObservable() {
48 |         return modelSelectedEventSubject;
49 |     }
50 | 
51 |     public void postModelSelectedObservable(ModelItem modelItem) {
52 |         modelSelectedEventSubject.onNext(modelItem);
53 |     }
54 | 
55 |     public Observable geErrorObservable() {
56 |         return errorEventSubject;
57 |     }
58 | 
59 |     public void postErrorStatus(Error error) {
60 |         errorEventSubject.onNext(error);
61 |     }
62 | 
63 |     public void postConnectionEvent(NetworkInfo.State connection) {
64 |         connectionEventSubject.onNext(connection);
65 |     }
66 | 
67 |     public Observable getConnectionEvent() {
68 |         return connectionEventSubject;
69 |     }
70 | 
71 |     public Observable getDeleteDownloadedModelObservable() {
72 |         return deleteDownloadedModel;
73 |     }
74 | 
75 |     public void postDeleteDownloadedModel(ModelItem modelItem) {
76 |         deleteDownloadedModel.onNext(modelItem);
77 |     }
78 | }
79 | 
--------------------------------------------------------------------------------
/app/src/main/java/org/vosk/service/download/FileHelper.java:
--------------------------------------------------------------------------------
  1 | package org.vosk.service.download;
  2 | 
  3 | import static org.vosk.service.download.Download.CLEAR;
  4 | import static org.vosk.service.download.Download.COMPLETE;
  5 | 
  6 | import android.content.Context;
  7 | 
  8 | import org.apache.commons.io.IOUtils;
  9 | import org.vosk.service.ui.selector.ModelListActivity;
 10 | import org.vosk.service.utils.Tools;
 11 | 
 12 | import java.io.BufferedInputStream;
 13 | import java.io.BufferedOutputStream;
 14 | import java.io.File;
 15 | import java.io.FileOutputStream;
 16 | import java.io.IOException;
 17 | import java.io.InputStream;
 18 | import java.io.OutputStream;
 19 | import java.util.Enumeration;
 20 | import java.util.zip.ZipEntry;
 21 | import java.util.zip.ZipFile;
 22 | 
 23 | public class FileHelper {
 24 | 
 25 |     public static void unzipFIle(Context context,File zipFilePath, File unzipAtLocation) {
 26 | 
 27 |         //noinspection ResultOfMethodCallIgnored
 28 |         unzipAtLocation.mkdir();
 29 | 
 30 |         try (ZipFile zipfile = new ZipFile(zipFilePath)) {
 31 |             for (Enumeration extends ZipEntry> e = zipfile.entries(); e.hasMoreElements(); ) {
 32 |                 ZipEntry entry = e.nextElement();
 33 |                 unzipEntry(zipfile, entry, unzipAtLocation);
 34 |             }
 35 |             EventBus.getInstance().postDownloadStatus(new Download(COMPLETE));
 36 |             FileHelper.deleteFileOrDirectory(new File(Tools.getModelFileRootPath(context), unzipAtLocation.getName() + ".zip"));
 37 |         } catch (IOException e) {
 38 |             ModelListActivity.progress = CLEAR;
 39 |             EventBus.getInstance().postErrorStatus(Error.CONNECTION);
 40 |             e.printStackTrace();
 41 |         }
 42 |     }
 43 | 
 44 |     private static void unzipEntry(ZipFile zipfile, ZipEntry entry, File outputDir) throws IOException {
 45 |         if (entry.isDirectory()) {
 46 |             createDir(new File(outputDir, entry.getName()));
 47 |             return;
 48 |         }
 49 | 
 50 |         File outputFile = new File(outputDir, entry.getName());
 51 |         if (outputFile.getParentFile() != null && !outputFile.getParentFile().exists()) {
 52 |             createDir(outputFile.getParentFile());
 53 |         }
 54 | 
 55 |         String message = "unzipEntry(" + entry + ")[" + entry.getSize() + "] ";
 56 | 
 57 |         InputStream zin = zipfile.getInputStream(entry);
 58 | 
 59 |         try (BufferedInputStream input = new BufferedInputStream(zin);
 60 |              BufferedOutputStream output = new BufferedOutputStream(new FileOutputStream(outputFile))) {
 61 |             copy(input, output);
 62 |         } catch (IOException e) {
 63 |             throw new IOException(message, e);
 64 |         }
 65 |     }
 66 | 
 67 |     public static void createDir(File dir) {
 68 |         if (dir.exists()) {
 69 |             return;
 70 |         }
 71 |         //noinspection ResultOfMethodCallIgnored
 72 |         dir.mkdir();
 73 |     }
 74 | 
 75 |     public static void copy(InputStream input, OutputStream output) throws IOException {
 76 |         byte[] data = new byte[10240];
 77 |         int count;
 78 | 
 79 |         while ((count = input.read(data)) != -1) {
 80 |             output.write(data, 0, count);
 81 |         }
 82 |         output.flush();
 83 |     }
 84 | 
 85 |     public static void writeFile(InputStream inputStream, File file) {
 86 |         try (OutputStream outputStream = new FileOutputStream(file)) {
 87 |             IOUtils.copy(inputStream, outputStream);
 88 |         } catch (IOException e) {
 89 |             EventBus.getInstance().postErrorStatus(Error.WRITE_STORAGE);
 90 |         }
 91 |     }
 92 | 
 93 |     public static void deleteFileOrDirectory(File fileOrDirectory) {
 94 | 
 95 |         if (fileOrDirectory.isDirectory()) {
 96 |             for (File child : fileOrDirectory.listFiles()) {
 97 |                 deleteFileOrDirectory(child);
 98 |             }
 99 |         }
100 | 
101 |         fileOrDirectory.delete();
102 |     }
103 | }
104 | 
--------------------------------------------------------------------------------
/app/src/main/java/org/vosk/service/download/VoskModelStorage.java:
--------------------------------------------------------------------------------
 1 | package org.vosk.service.download;
 2 | 
 3 | import org.vosk.service.ui.selector.ModelItem;
 4 | 
 5 | import java.util.List;
 6 | 
 7 | import io.reactivex.Observable;
 8 | import okhttp3.ResponseBody;
 9 | import retrofit2.http.GET;
10 | import retrofit2.http.Streaming;
11 | import retrofit2.http.Url;
12 | 
13 | public interface VoskModelStorage {
14 |     @Streaming
15 |     @GET
16 |     Observable downloadFile(@Url String url);
17 | 
18 |     @GET("model-list.json")
19 |     Observable> getModelList();
20 | }
--------------------------------------------------------------------------------
/app/src/main/java/org/vosk/service/download/VoskModelStorageClient.java:
--------------------------------------------------------------------------------
 1 | package org.vosk.service.download;
 2 | 
 3 | import java.util.concurrent.TimeUnit;
 4 | 
 5 | import okhttp3.OkHttpClient;
 6 | import retrofit2.Retrofit;
 7 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
 8 | import retrofit2.converter.gson.GsonConverterFactory;
 9 | 
10 | public class VoskModelStorageClient {
11 | 
12 |     private static final String TAG = "DownloadAPI";
13 |     private static final int DEFAULT_TIMEOUT = 15;
14 |     public static Retrofit retrofit;
15 |     private static final String BASE_URL = "https://alphacephei.com/vosk/models/";
16 | 
17 |     public static VoskModelStorage getClient(DownloadProgressListener listener, ServiceType serviceType) {
18 | 
19 |         DownloadProgressInterceptor interceptor = new DownloadProgressInterceptor(listener);
20 | 
21 |         OkHttpClient client = serviceType == ServiceType.DOWNLOAD_MODEL ? new OkHttpClient.Builder()
22 |                 .retryOnConnectionFailure(true)
23 |                 .addNetworkInterceptor(interceptor)
24 |                 .connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
25 |                 .build()
26 |                 : new OkHttpClient.Builder()
27 |                 .retryOnConnectionFailure(true)
28 |                 .connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
29 |                 .build();
30 | 
31 |         Retrofit retrofit = new Retrofit.Builder()
32 |                 .baseUrl(BASE_URL)
33 |                 .addConverterFactory(GsonConverterFactory.create())
34 |                 .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
35 |                 .client(client)
36 |                 .build();
37 |         return retrofit.create(VoskModelStorage.class);
38 |     }
39 | 
40 |     public enum ServiceType {
41 |         DOWNLOAD_MODEL,
42 |         DOWNLOAD_MODEL_LIST
43 |     }
44 | }
45 | 
--------------------------------------------------------------------------------
/app/src/main/java/org/vosk/service/ui/SpeechRecognizerActivity.java:
--------------------------------------------------------------------------------
  1 | // Copyright 2020 Ciaran O'Reilly
  2 | // Copyright 2011-2020, Institute of Cybernetics at Tallinn University of Technology
  3 | //
  4 | // This program is free software: you can redistribute it and/or modify
  5 | // it under the terms of the GNU General Public License as published by
  6 | // the Free Software Foundation, either version 3 of the License, or
  7 | // (at your option) any later version.
  8 | 
  9 | // This program is distributed in the hope that it will be useful,
 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 12 | // GNU General Public License for more details.
 13 | 
 14 | // You should have received a copy of the GNU General Public License
 15 | // along with this program.  If not, see  .
 16 | package org.vosk.service.ui;
 17 | 
 18 | import android.Manifest;
 19 | import android.app.Activity;
 20 | import android.app.PendingIntent;
 21 | import android.content.Context;
 22 | import android.content.Intent;
 23 | import android.content.pm.PackageManager;
 24 | import android.media.MediaPlayer;
 25 | import android.os.Bundle;
 26 | import android.os.Handler;
 27 | import android.os.Looper;
 28 | import android.os.Message;
 29 | import android.os.Parcelable;
 30 | import android.speech.RecognitionListener;
 31 | import android.speech.RecognizerIntent;
 32 | import android.speech.SpeechRecognizer;
 33 | import android.util.Log;
 34 | import android.widget.Toast;
 35 | 
 36 | import org.vosk.service.R;
 37 | 
 38 | import androidx.annotation.NonNull;
 39 | import androidx.appcompat.app.AppCompatActivity;
 40 | import androidx.core.app.ActivityCompat;
 41 | 
 42 | import java.lang.ref.WeakReference;
 43 | import java.util.ArrayList;
 44 | import java.util.List;
 45 | import java.util.Locale;
 46 | 
 47 | public class SpeechRecognizerActivity extends AppCompatActivity {
 48 |     protected static final String TAG = SpeechRecognizerActivity.class.getSimpleName();
 49 | 
 50 |     private SpeechRecognizer speechRecognizer;
 51 | 
 52 |     private static final String MSG = "MSG";
 53 |     private static final int MSG_TOAST = 1;
 54 |     private static final int MSG_RESULT_ERROR = 2;
 55 | 
 56 |     int PERMISSION_ALL = 1;
 57 | 
 58 |     String[] PERMISSIONS = {
 59 |             Manifest.permission.RECORD_AUDIO,
 60 |             Manifest.permission.WRITE_EXTERNAL_STORAGE
 61 |     };
 62 | 
 63 |     protected static class SimpleMessageHandler extends Handler {
 64 |         private final WeakReference mRef;
 65 | 
 66 |         private SimpleMessageHandler(Looper looper, SpeechRecognizerActivity activity) {
 67 |             super(looper);
 68 |             mRef = new WeakReference<>(activity);
 69 |         }
 70 | 
 71 |         public void handleMessage(Message msg) {
 72 |             SpeechRecognizerActivity outerClass = mRef.get();
 73 |             if (outerClass != null) {
 74 |                 Bundle b = msg.getData();
 75 |                 String msgAsString = b.getString(MSG);
 76 |                 switch (msg.what) {
 77 |                     case MSG_TOAST:
 78 |                         outerClass.toast(msgAsString);
 79 |                         break;
 80 |                     case MSG_RESULT_ERROR:
 81 |                         outerClass.showError(msgAsString);
 82 |                         break;
 83 |                     default:
 84 |                         break;
 85 |                 }
 86 |             }
 87 |         }
 88 |     }
 89 | 
 90 |     protected static Message createMessage(String str) {
 91 |         Bundle b = new Bundle();
 92 |         b.putString(MSG, str);
 93 |         Message msg = Message.obtain();
 94 |         msg.what = SpeechRecognizerActivity.MSG_TOAST;
 95 |         msg.setData(b);
 96 |         return msg;
 97 |     }
 98 | 
 99 |     protected void toast(String message) {
100 |         Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
101 |     }
102 | 
103 |     void showError(String msg) {
104 |         Log.d(TAG, msg);
105 |     }
106 | 
107 |     void setupRecognizer() {
108 |         final Intent speechRecognizerIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
109 |         speechRecognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
110 |             RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
111 |         speechRecognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.getDefault().toString().replace("_","-"));
112 |         speechRecognizer.startListening(speechRecognizerIntent);
113 |     }
114 | 
115 |     @Override
116 |     protected void onCreate(final Bundle savedInstanceState) {
117 |         super.onCreate(savedInstanceState);
118 |         setContentView(R.layout.speech_recognizer_activity);
119 | 
120 |         speechRecognizer = SpeechRecognizer.createSpeechRecognizer(this);
121 | 
122 |         speechRecognizer.setRecognitionListener(new RecognitionListener() {
123 |             @Override
124 |             public void onReadyForSpeech(Bundle bundle) {
125 | 
126 |             }
127 | 
128 |             @Override
129 |             public void onBeginningOfSpeech() {
130 |             }
131 | 
132 |             @Override
133 |             public void onRmsChanged(float v) {
134 | 
135 |             }
136 | 
137 |             @Override
138 |             public void onBufferReceived(byte[] bytes) {
139 | 
140 |             }
141 | 
142 |             @Override
143 |             public void onEndOfSpeech() {
144 |                 speechRecognizer.stopListening();
145 |             }
146 | 
147 |             @Override
148 |             public void onError(int i) {
149 |                 showError();
150 |             }
151 | 
152 |             @Override
153 |             public void onResults(Bundle bundle) {
154 |                 Log.i(TAG, "onResults");
155 |                 ArrayList results = bundle.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
156 |                 Log.i(TAG, results.get(0));
157 |                 returnResults(results);
158 |             }
159 | 
160 |             @Override
161 |             public void onPartialResults(Bundle bundle) {
162 |                 Log.i(TAG, "onPartialResults");
163 |                 ArrayList data = bundle.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
164 |                 Log.i(TAG, data.get(0));
165 |             }
166 | 
167 |             @Override
168 |             public void onEvent(int i, Bundle bundle) {
169 |                 Log.d(TAG, bundle.toString());
170 |             }
171 |         });
172 |     }
173 | 
174 |     @Override
175 |     public void onStart() {
176 |         super.onStart();
177 |         Log.i(TAG, "onStart");
178 | 
179 |         if (!hasPermissions(this, PERMISSIONS)) {
180 |             ActivityCompat.requestPermissions(this, PERMISSIONS, PERMISSION_ALL);
181 |         }
182 |         else {
183 |             setupRecognizer();
184 |         }
185 |     }
186 | 
187 |     @Override
188 |     protected void onDestroy() {
189 |         super.onDestroy();
190 |         Log.i(TAG, "onDestroy");
191 |         speechRecognizer.destroy();
192 |     }
193 | 
194 | 
195 |     public void startSpeechSound() {
196 |         MediaPlayer mp = MediaPlayer.create(this, R.raw.start_speech_effect);
197 |         mp.start();
198 |     }
199 | 
200 |     public static boolean hasPermissions(Context context, String... permissions) {
201 |         if (context != null && permissions != null) {
202 |             for (String permission : permissions) {
203 |                 if (ActivityCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
204 |                     return false;
205 |                 }
206 |             }
207 |         }
208 |         return true;
209 |     }
210 | 
211 |     @Override
212 |     public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
213 |                                            @NonNull int[] grantResults) {
214 |         super.onRequestPermissionsResult(requestCode, permissions, grantResults);
215 |         for (int i = 0; i < permissions.length - 1; i++) {
216 |             if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
217 |                 finish();
218 |             }
219 |         }
220 |         setupRecognizer();
221 |     }
222 | 
223 |     private void returnResults(List results) {
224 |         Handler handler = new SimpleMessageHandler(Looper.getMainLooper(), this);
225 | 
226 |         Intent incomingIntent = getIntent();
227 |         Log.d(TAG, incomingIntent.toString());
228 |         Bundle extras = incomingIntent.getExtras();
229 |         if (extras == null) {
230 |             return;
231 |         }
232 |         Log.d(TAG, extras.toString());
233 |         PendingIntent pendingIntent = getPendingIntent(extras);
234 |         if (pendingIntent == null) {
235 |             Log.d(TAG, "No pending intent, setting result intent.");
236 |             setResultIntent(results);
237 |         } else {
238 |             Log.d(TAG, pendingIntent.toString());
239 | 
240 |             Bundle bundle = extras.getBundle(RecognizerIntent.EXTRA_RESULTS_PENDINGINTENT_BUNDLE);
241 |             if (bundle == null) {
242 |                 bundle = new Bundle();
243 |             }
244 | 
245 |             Intent intent = new Intent();
246 |             intent.putExtras(bundle);
247 |             handler.sendMessage(
248 |                     createMessage(String.format(getString(R.string.recognized), results.get(0))));
249 |             try {
250 |                 Log.d(TAG, "Sending result via pendingIntent");
251 |                 pendingIntent.send(this, AppCompatActivity.RESULT_OK, intent);
252 |             } catch (PendingIntent.CanceledException e) {
253 |                 Log.e(TAG, e.getMessage());
254 |                 handler.sendMessage(createMessage(e.getMessage()));
255 |             }
256 |         }
257 |         finish();
258 |     }
259 | 
260 |     private void showError() {
261 |         toast("Error loading recognizer");
262 |     }
263 | 
264 |     private void setResultIntent(List matches) {
265 |         Intent intent = new Intent();
266 |         intent.putStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS, new ArrayList<>(matches));
267 |         setResult(Activity.RESULT_OK, intent);
268 |     }
269 | 
270 |     private PendingIntent getPendingIntent(Bundle extras) {
271 |         Parcelable extraResultsPendingIntentAsParceable = extras
272 |                 .getParcelable(RecognizerIntent.EXTRA_RESULTS_PENDINGINTENT);
273 |         if (extraResultsPendingIntentAsParceable != null) {
274 |             if (extraResultsPendingIntentAsParceable instanceof PendingIntent) {
275 |                 return (PendingIntent) extraResultsPendingIntentAsParceable;
276 |             }
277 |         }
278 |         return null;
279 |     }
280 | }
--------------------------------------------------------------------------------
/app/src/main/java/org/vosk/service/ui/selector/DiffCallback.java:
--------------------------------------------------------------------------------
 1 | package org.vosk.service.ui.selector;
 2 | 
 3 | import androidx.annotation.NonNull;
 4 | import androidx.recyclerview.widget.DiffUtil;
 5 | 
 6 | public class DiffCallback extends DiffUtil.ItemCallback {
 7 | 
 8 |     @Override
 9 |     public boolean areItemsTheSame(@NonNull ModelItem oldItem, @NonNull ModelItem newItem) {
10 |         return oldItem.getName().equals(newItem.getName());
11 |     }
12 | 
13 |     @Override
14 |     public boolean areContentsTheSame(@NonNull ModelItem oldItem, @NonNull ModelItem newItem) {
15 |         return oldItem.getName().equals(newItem.getName());
16 |     }
17 | }
18 | 
--------------------------------------------------------------------------------
/app/src/main/java/org/vosk/service/ui/selector/ModelItem.java:
--------------------------------------------------------------------------------
  1 | package org.vosk.service.ui.selector;
  2 | 
  3 | public class ModelItem {
  4 |     private String lang;
  5 |     private String lang_text;
  6 |     private String md5;
  7 |     private String name;
  8 |     private boolean obsolete;
  9 |     private long size;
 10 |     private String size_text;
 11 |     private String type;
 12 |     private String url;
 13 |     private String version;
 14 | 
 15 |     public ModelItem() {
 16 |     }
 17 | 
 18 |     public ModelItem(String lang_text, String name, String size_text) {
 19 |         this.lang_text = lang_text;
 20 |         this.name = name;
 21 |         this.size_text = size_text;
 22 |     }
 23 | 
 24 |     public ModelItem(String lang, String lang_text, String md5, String name, boolean obsolete, long size, String size_text, String type, String url, String version) {
 25 |         this.lang = lang;
 26 |         this.lang_text = lang_text;
 27 |         this.md5 = md5;
 28 |         this.name = name;
 29 |         this.obsolete = obsolete;
 30 |         this.size = size;
 31 |         this.size_text = size_text;
 32 |         this.type = type;
 33 |         this.url = url;
 34 |         this.version = version;
 35 |     }
 36 | 
 37 |     public String getLang() {
 38 |         return lang;
 39 |     }
 40 | 
 41 |     public void setLang(String lang) {
 42 |         this.lang = lang;
 43 |     }
 44 | 
 45 |     public String getLang_text() {
 46 |         return lang_text;
 47 |     }
 48 | 
 49 |     public void setLang_text(String lang_text) {
 50 |         this.lang_text = lang_text;
 51 |     }
 52 | 
 53 |     public String getMd5() {
 54 |         return md5;
 55 |     }
 56 | 
 57 |     public void setMd5(String md5) {
 58 |         this.md5 = md5;
 59 |     }
 60 | 
 61 |     public String getName() {
 62 |         return name;
 63 |     }
 64 | 
 65 |     public void setName(String name) {
 66 |         this.name = name;
 67 |     }
 68 | 
 69 |     public boolean getObsolete() {
 70 |         return obsolete;
 71 |     }
 72 | 
 73 |     public void setObsolete(boolean obsolete) {
 74 |         this.obsolete = obsolete;
 75 |     }
 76 | 
 77 |     public long getSize() {
 78 |         return size;
 79 |     }
 80 | 
 81 |     public void setSize(long size) {
 82 |         this.size = size;
 83 |     }
 84 | 
 85 |     public String getSize_text() {
 86 |         return size_text;
 87 |     }
 88 | 
 89 |     public void setSize_text(String size_text) {
 90 |         this.size_text = size_text;
 91 |     }
 92 | 
 93 |     public String getType() {
 94 |         return type;
 95 |     }
 96 | 
 97 |     public void setType(String type) {
 98 |         this.type = type;
 99 |     }
100 | 
101 |     public String getUrl() {
102 |         return url;
103 |     }
104 | 
105 |     public void setUrl(String url) {
106 |         this.url = url;
107 |     }
108 | 
109 |     public String getVersion() {
110 |         return version;
111 |     }
112 | 
113 |     public void setVersion(String version) {
114 |         this.version = version;
115 |     }
116 | }
117 | 
--------------------------------------------------------------------------------
/app/src/main/java/org/vosk/service/ui/selector/ModelListActivity.java:
--------------------------------------------------------------------------------
  1 | package org.vosk.service.ui.selector;
  2 | 
  3 | import static org.vosk.service.download.Download.CLEAR;
  4 | import static org.vosk.service.download.Download.COMPLETE;
  5 | import static org.vosk.service.download.Download.RESTARTING;
  6 | import static org.vosk.service.download.Download.STARTING;
  7 | import static org.vosk.service.download.VoskModelStorageClient.ServiceType.DOWNLOAD_MODEL_LIST;
  8 | import static org.vosk.service.utils.Tools.isServiceRunning;
  9 | 
 10 | import android.app.Dialog;
 11 | import android.content.DialogInterface;
 12 | import android.content.Intent;
 13 | import android.content.SharedPreferences;
 14 | import android.net.NetworkInfo;
 15 | import android.os.Bundle;
 16 | import android.preference.PreferenceManager;
 17 | import android.view.View;
 18 | import android.widget.ProgressBar;
 19 | import android.widget.Toast;
 20 | 
 21 | import androidx.appcompat.app.AlertDialog;
 22 | import androidx.appcompat.app.AppCompatActivity;
 23 | import androidx.core.content.ContextCompat;
 24 | import androidx.recyclerview.widget.LinearLayoutManager;
 25 | import androidx.recyclerview.widget.RecyclerView;
 26 | import androidx.recyclerview.widget.SimpleItemAnimator;
 27 | 
 28 | import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork;
 29 | import com.google.gson.Gson;
 30 | import com.google.gson.reflect.TypeToken;
 31 | 
 32 | import org.vosk.service.download.DownloadModelService;
 33 | import org.vosk.service.R;
 34 | import org.vosk.service.download.VoskModelStorageClient;
 35 | import org.vosk.service.download.VoskModelStorage;
 36 | import org.vosk.service.download.Error;
 37 | import org.vosk.service.download.EventBus;
 38 | import org.vosk.service.utils.PreferenceConstants;
 39 | import org.vosk.service.download.FileHelper;
 40 | import org.vosk.service.utils.Tools;
 41 | 
 42 | import java.io.File;
 43 | import java.util.List;
 44 | import java.util.stream.Collectors;
 45 | 
 46 | import io.reactivex.android.schedulers.AndroidSchedulers;
 47 | import io.reactivex.disposables.CompositeDisposable;
 48 | import io.reactivex.schedulers.Schedulers;
 49 | 
 50 | public class ModelListActivity extends AppCompatActivity {
 51 | 
 52 |     private final EventBus eventBus = EventBus.getInstance();
 53 |     private final CompositeDisposable compositeDisposable = new CompositeDisposable();
 54 |     private final VoskModelStorage service = VoskModelStorageClient.getClient(null, DOWNLOAD_MODEL_LIST);
 55 |     private final Gson gson = new Gson();
 56 | 
 57 |     private ModelListAdapter modelListAdapter;
 58 |     private SharedPreferences sharedPreferences;
 59 | 
 60 |     private AlertDialog alertDialog;
 61 |     private RecyclerView recyclerView;
 62 |     private ProgressBar progressBar;
 63 | 
 64 |     private List offlineModelList;
 65 |     public String modelDownloadingName = "";
 66 |     public static int progress = CLEAR;
 67 |     private boolean isOnline;
 68 | 
 69 | 
 70 |     @Override
 71 |     protected void onCreate(Bundle savedInstanceState) {
 72 |         super.onCreate(savedInstanceState);
 73 |         setContentView(R.layout.activity_model_list);
 74 | 
 75 |         //Init fields
 76 |         sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 77 |         modelListAdapter = new ModelListAdapter(sharedPreferences);
 78 |         checkIfIsDownloading();
 79 |         loadOfflineModels();
 80 |         initViews();
 81 |         observeEvents();
 82 |         loadModels();
 83 |     }
 84 | 
 85 |     private void checkIfIsDownloading() {
 86 |         modelDownloadingName = sharedPreferences.getString(PreferenceConstants.DOWNLOADING_FILE, "");
 87 |         if (isOnline && !modelDownloadingName.equals("") && !isServiceRunning(this)) {
 88 |             progress = RESTARTING;
 89 |             startDownloadModelService();
 90 |         }
 91 |     }
 92 | 
 93 |     private void initViews() {
 94 |         progressBar = findViewById(R.id.progress_circular);
 95 | 
 96 |         recyclerView = findViewById(R.id.model_recycler_view);
 97 |         recyclerView.setLayoutManager(new LinearLayoutManager(this));
 98 |         recyclerView.setAdapter(modelListAdapter);
 99 |         if (recyclerView.getItemAnimator() != null)
100 |             ((SimpleItemAnimator) recyclerView.getItemAnimator()).setSupportsChangeAnimations(false);
101 |     }
102 | 
103 |     private void loadModels() {
104 |         compositeDisposable.add(service.getModelList()
105 |                 .subscribeOn(Schedulers.io())
106 |                 .observeOn(AndroidSchedulers.mainThread())
107 |                 .subscribe(
108 |                         newDataset -> {
109 |                             showList();
110 |                             modelListAdapter.updateDataset(newDataset.stream().filter(it -> it.getType().equals("small") && !it.getObsolete()).collect(Collectors.toList()), offlineModelList.stream().filter(it -> !it.getName().equals(modelDownloadingName)).collect(Collectors.toList()));
111 |                         },
112 |                         error -> {
113 |                             showList();
114 |                             modelListAdapter.updateDataset(offlineModelList.stream().filter(it -> !it.getName().equals(modelDownloadingName)).collect(Collectors.toList()));
115 |                         }));
116 |     }
117 | 
118 |     private void showList() {
119 |         progressBar.setVisibility(View.GONE);
120 |         recyclerView.setVisibility(View.VISIBLE);
121 |     }
122 | 
123 |     private void observeEvents() {
124 |         compositeDisposable.add(eventBus.getDownloadStatusObservable()
125 |                 .subscribeOn(Schedulers.io())
126 |                 .observeOn(AndroidSchedulers.mainThread())
127 |                 .subscribe(download -> {
128 |                     if (download.getProgress() == COMPLETE) {
129 |                         Toast.makeText(this, R.string.download_complete, Toast.LENGTH_SHORT).show();
130 |                         loadOfflineModels();
131 |                         progress = CLEAR;
132 |                         sharedPreferences.edit()
133 |                                 .remove(PreferenceConstants.DOWNLOADING_FILE)
134 |                                 .apply();
135 |                         modelListAdapter.updateOfflineModels(offlineModelList);
136 |                     } else if (progress != download.getProgress()) {
137 |                         progress = download.getProgress();
138 |                         if (modelDownloadingName != null && modelListAdapter.getDataset().stream().anyMatch(it -> it.getName().equals(modelDownloadingName)))
139 |                             modelListAdapter.getDataset().stream()
140 |                                     .filter(it -> it.getName().equals(modelDownloadingName)).findFirst()
141 |                                     .ifPresent(modelItem -> modelListAdapter.notifyItemChanged(modelListAdapter.getDataset().indexOf(modelItem)));
142 |                     }
143 |                 }));
144 | 
145 |         compositeDisposable.add(eventBus.getDownloadStartObservable()
146 |                 .subscribeOn(Schedulers.io())
147 |                 .observeOn(AndroidSchedulers.mainThread())
148 |                 .subscribe(modelItem -> {
149 |                     progress = STARTING;
150 |                     //start service
151 |                     startDownloadModelService();
152 |                     modelDownloadingName = modelItem.getName();
153 |                     sharedPreferences.edit().putString(PreferenceConstants.DOWNLOADING_FILE, modelItem.getName()).apply();
154 |                     addOfflineModel(modelItem);
155 |                 }));
156 | 
157 |         compositeDisposable.add(eventBus.getModelSelectedObservable()
158 |                 .subscribeOn(Schedulers.io())
159 |                 .observeOn(AndroidSchedulers.mainThread())
160 |                 .subscribe(this::manageModelSelected));
161 | 
162 |         compositeDisposable.add(eventBus.geErrorObservable()
163 |                 .subscribeOn(Schedulers.io())
164 |                 .observeOn(AndroidSchedulers.mainThread())
165 |                 .subscribe(this::handleError));
166 | 
167 |         compositeDisposable.add(EventBus.getInstance().getConnectionEvent().subscribe(state -> {
168 |                     if (state == NetworkInfo.State.CONNECTED) {
169 |                         isOnline = true;
170 |                         checkIfIsDownloading();
171 |                     } else {
172 |                         isOnline = false;
173 |                     }
174 |                 })
175 |         );
176 | 
177 |         compositeDisposable.add(ReactiveNetwork.observeNetworkConnectivity(getApplicationContext())
178 |                 .subscribeOn(Schedulers.io())
179 |                 .observeOn(AndroidSchedulers.mainThread())
180 |                 .subscribe(connectivity -> {
181 |                     if (connectivity.getState() == NetworkInfo.State.CONNECTED || connectivity.getState() == NetworkInfo.State.DISCONNECTED)
182 |                         EventBus.getInstance().postConnectionEvent(connectivity.getState());
183 |                 })
184 |         );
185 | 
186 |         compositeDisposable.add(eventBus.getDeleteDownloadedModelObservable()
187 |                 .subscribeOn(Schedulers.io())
188 |                 .observeOn(AndroidSchedulers.mainThread())
189 |                 .subscribe(this::showDeleteModelDialog));
190 |     }
191 | 
192 |     private void showDeleteModelDialog(ModelItem modelItem) {
193 |         DialogInterface.OnClickListener clickListener = (dialog, which) -> {
194 |             deleteOfflineModel(modelItem);
195 |             modelListAdapter.updateOfflineModels(offlineModelList);
196 |             Toast.makeText(this, getString(R.string.model_delete), Toast.LENGTH_LONG).show();
197 |             dialog.dismiss();
198 | 
199 |         };
200 |         showDialog(R.string.delete_model_dialog_title, R.string.delete_model_dialog_message, clickListener);
201 |     }
202 | 
203 |     private void loadOfflineModels() {
204 |         String offlineListJson = sharedPreferences.getString(PreferenceConstants.OFFLINE_LIST, "[]");
205 |         offlineModelList = gson.fromJson(offlineListJson, new TypeToken>() {
206 |         }.getType());
207 |     }
208 | 
209 |     private void addOfflineModel(ModelItem modelItem) {
210 |         offlineModelList.add(modelItem);
211 |         saveOfflineModelList();
212 |     }
213 | 
214 |     private void deleteOfflineModel(ModelItem modelItem) {
215 |         FileHelper.deleteFileOrDirectory(new File(Tools.getModelFileRootPath(this), modelItem.getName()));
216 |         offlineModelList.stream()
217 |                 .filter(it -> it.getName().equals(modelItem.getName())).findFirst()
218 |                 .ifPresent(item -> offlineModelList.remove(item));
219 |         saveOfflineModelList();
220 |     }
221 | 
222 |     private void saveOfflineModelList() {
223 |         Gson gson = new Gson();
224 |         String offlineModelsJson = gson.toJson(offlineModelList);
225 |         sharedPreferences.edit().putString(PreferenceConstants.OFFLINE_LIST, offlineModelsJson).apply();
226 |     }
227 | 
228 |     private void handleError(Error error) {
229 |         switch (error) {
230 |             case CONNECTION: {
231 |                 Toast.makeText(this, getString(R.string.connection_error), Toast.LENGTH_LONG).show();
232 |                 if (!modelDownloadingName.equals("")) {
233 |                     modelListAdapter.notifyDataSetChanged();
234 |                 }
235 |             }
236 |             break;
237 |             case WRITE_STORAGE:
238 |                 Toast.makeText(this, getString(R.string.write_storage_error), Toast.LENGTH_LONG).show();
239 |                 break;
240 |         }
241 |     }
242 | 
243 |     private void startDownloadModelService() {
244 |         if (!isServiceRunning(this)) {
245 |             Intent service = new Intent(this, DownloadModelService.class);
246 |             ContextCompat.startForegroundService(this, service);
247 |         }
248 |     }
249 | 
250 |     public void manageModelSelected(ModelItem modelItem) {
251 |         if (isDownloaded(modelItem)) {
252 |             selectDefaultModel(modelItem);
253 |             modelListAdapter.notifyDataSetChanged();
254 |         } else if (!sharedPreferences.contains(PreferenceConstants.DOWNLOADING_FILE)) {
255 |             EventBus.getInstance().postDownloadStart(modelItem);
256 |             modelListAdapter.notifyDataSetChanged();
257 |         } else {
258 |             showDialog(R.string.warning, R.string.wait_for_download, ((dialog, which) -> dialog.dismiss()));
259 |         }
260 |     }
261 | 
262 |     private void showDialog(int title, int message, Dialog.OnClickListener onClickListener) {
263 |         if (alertDialog != null && alertDialog.isShowing())
264 |             alertDialog.dismiss();
265 |         alertDialog = new AlertDialog.Builder(this)
266 |                 .setTitle(getString(title))
267 |                 .setMessage(message)
268 |                 .setPositiveButton("Accept", onClickListener)
269 |                 .setNegativeButton("Cancel", (dialog, which) -> dialog.dismiss())
270 |                 .show();
271 |     }
272 | 
273 |     private boolean isDownloaded(ModelItem modelItem) {
274 |         if (!modelItem.getName().equals(sharedPreferences.getString(PreferenceConstants.DOWNLOADING_FILE, "")))
275 |             return offlineModelList.stream().anyMatch(it -> it.getName().equals(modelItem.getName()));
276 |         return false;
277 |     }
278 | 
279 |     private void selectDefaultModel(ModelItem modelItem) {
280 |         sharedPreferences.edit().putString(PreferenceConstants.ACTIVE_MODEL, modelItem.getName()).apply();
281 |     }
282 | 
283 |     @Override
284 |     protected void onStop() {
285 |         super.onStop();
286 |         if (alertDialog != null && alertDialog.isShowing())
287 |             alertDialog.dismiss();
288 |     }
289 | 
290 |     @Override
291 |     protected void onDestroy() {
292 |         super.onDestroy();
293 |         compositeDisposable.clear();
294 |     }
295 | }
--------------------------------------------------------------------------------
/app/src/main/java/org/vosk/service/ui/selector/ModelListAdapter.java:
--------------------------------------------------------------------------------
  1 | package org.vosk.service.ui.selector;
  2 | 
  3 | import static org.vosk.service.ui.selector.ModelListActivity.progress;
  4 | 
  5 | import android.content.SharedPreferences;
  6 | import android.content.res.ColorStateList;
  7 | import android.view.LayoutInflater;
  8 | import android.view.View;
  9 | import android.view.ViewGroup;
 10 | import android.widget.ImageView;
 11 | import android.widget.ProgressBar;
 12 | import android.widget.TextView;
 13 | 
 14 | import androidx.annotation.NonNull;
 15 | import androidx.appcompat.content.res.AppCompatResources;
 16 | import androidx.constraintlayout.widget.Group;
 17 | import androidx.recyclerview.widget.ListAdapter;
 18 | import androidx.recyclerview.widget.RecyclerView;
 19 | 
 20 | import org.vosk.service.R;
 21 | import org.vosk.service.download.EventBus;
 22 | import org.vosk.service.utils.PreferenceConstants;
 23 | 
 24 | import java.util.ArrayList;
 25 | import java.util.List;
 26 | 
 27 | public class ModelListAdapter extends ListAdapter {
 28 | 
 29 |     List dataset = new ArrayList<>();
 30 |     List offlineModelItems = new ArrayList<>();
 31 |     SharedPreferences sharedPreferences;
 32 | 
 33 |     public ModelListAdapter(SharedPreferences sharedPreferences) {
 34 |         super(new DiffCallback());
 35 |         this.sharedPreferences = sharedPreferences;
 36 |     }
 37 | 
 38 |     @NonNull
 39 |     @Override
 40 |     public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
 41 |         return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.model_list_item, parent, false));
 42 |     }
 43 | 
 44 |     @Override
 45 |     public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
 46 |         holder.bind(dataset.get(position));
 47 |     }
 48 | 
 49 |     public void updateDataset(List newDataset) {
 50 | 
 51 |         dataset.clear();
 52 |         dataset.addAll(newDataset);
 53 | 
 54 |         this.offlineModelItems.clear();
 55 |         this.offlineModelItems.addAll(newDataset);
 56 | 
 57 |         submitList(newDataset);
 58 |     }
 59 | 
 60 |     public void updateDataset(List newDataset, List offlineModelItems) {
 61 | 
 62 |         dataset.clear();
 63 |         dataset.addAll(newDataset);
 64 | 
 65 |         this.offlineModelItems.clear();
 66 |         this.offlineModelItems.addAll(offlineModelItems);
 67 | 
 68 |         submitList(newDataset);
 69 |     }
 70 | 
 71 |     @Override
 72 |     public int getItemCount() {
 73 |         return dataset.size();
 74 |     }
 75 | 
 76 |     public void updateOfflineModels(List offlineModels) {
 77 |         this.offlineModelItems.clear();
 78 |         this.offlineModelItems.addAll(offlineModels);
 79 |         notifyDataSetChanged();
 80 |     }
 81 | 
 82 |     class ViewHolder extends RecyclerView.ViewHolder {
 83 |         TextView modelLangText;
 84 |         TextView modelSize;
 85 |         TextView modelName;
 86 |         TextView downloadProgressText;
 87 |         ProgressBar downloadProgressBar;
 88 |         ImageView modelIndicator;
 89 |         Group downloadProgressGroup;
 90 | 
 91 |         ViewHolder(View v) {
 92 |             super(v);
 93 |             modelLangText = v.findViewById(R.id.model_lang_text);
 94 |             modelSize = v.findViewById(R.id.model_size);
 95 |             modelName = v.findViewById(R.id.model_name);
 96 |             downloadProgressText = v.findViewById(R.id.model_download_progress_text);
 97 |             downloadProgressBar = v.findViewById(R.id.model_download_progress_bar);
 98 |             modelIndicator = v.findViewById(R.id.model_indicator);
 99 |             downloadProgressGroup = v.findViewById(R.id.model_download_progress_group);
100 |         }
101 | 
102 |         public void bind(ModelItem modelItem) {
103 |             ModelListState modelListState = getModelListState();
104 | 
105 |             modelLangText.setText(modelItem.getLang_text());
106 |             modelSize.setText(itemView.getContext().getString(R.string.model_size, modelItem.getSize_text()));
107 |             modelName.setText(itemView.getContext().getString(R.string.model_name, modelItem.getName()));
108 |             View.OnClickListener sendSelectedEvent = v -> EventBus.getInstance().postModelSelectedObservable(getCurrentList().get(getAdapterPosition()));
109 |             itemView.setOnClickListener(sendSelectedEvent);
110 |             modelIndicator.setOnClickListener(sendSelectedEvent);
111 |             itemView.setOnLongClickListener(v -> {
112 |                 if (modelListState == ModelListState.DOWNLOADED) {
113 |                     EventBus.getInstance().postDeleteDownloadedModel(getCurrentList().get(getAdapterPosition()));
114 |                     return true;
115 |                 }
116 |                 return false;
117 |             });
118 | 
119 |             switch (modelListState) {
120 | 
121 |                 case NOT_DOWNLOADED:
122 |                     downloadProgressGroup.setVisibility(View.GONE);
123 |                     setDownloadProgress(0);
124 |                     modelIndicator.setVisibility(View.VISIBLE);
125 |                     setIndicator(R.drawable.ic_baseline_cloud_download_24);
126 |                     break;
127 |                 case DOWNLOADED:
128 |                     downloadProgressGroup.setVisibility(View.GONE);
129 |                     setDownloadProgress(0);
130 |                     modelIndicator.setVisibility(View.GONE);
131 |                     break;
132 |                 case DOWNLOADING:
133 |                     downloadProgressGroup.setVisibility(View.VISIBLE);
134 |                     setDownloadProgress(progress);
135 |                     modelIndicator.setVisibility(View.GONE);
136 |                     break;
137 |                 case SELECTED:
138 |                     downloadProgressGroup.setVisibility(View.GONE);
139 |                     modelIndicator.setVisibility(View.VISIBLE);
140 |                     setDownloadProgress(0);
141 |                     setIndicator(R.drawable.ic_baseline_check_circle_24);
142 |                     break;
143 |             }
144 |         }
145 | 
146 |         private void setDownloadProgress(int progress) {
147 |             if (progress <= 100) {
148 |                 downloadProgressBar.setProgress(progress);
149 |                 downloadProgressText.setText(itemView.getContext().getString(R.string.model_downloading_progress, progress));
150 |             }
151 |         }
152 | 
153 |         private void setIndicator(int indicatorIcon) {
154 |             ColorStateList csl;
155 |             if (indicatorIcon == R.drawable.ic_baseline_check_circle_24)
156 |                 csl = AppCompatResources.getColorStateList(itemView.getContext(), R.color.indicator_green);
157 |             else
158 |                 csl = AppCompatResources.getColorStateList(itemView.getContext(), R.color.indicator_gray);
159 | 
160 |             modelIndicator.setBackgroundTintList(csl);
161 |             modelIndicator.setBackground(AppCompatResources.getDrawable(itemView.getContext(), indicatorIcon));
162 |         }
163 | 
164 |         public ModelListState getModelListState() {
165 |             if (getAdapterPosition() != -1 && sharedPreferences.contains(PreferenceConstants.DOWNLOADING_FILE) && sharedPreferences.getString(PreferenceConstants.DOWNLOADING_FILE, "").equals(dataset.get(getAdapterPosition()).getName()))
166 |                 return ModelListState.DOWNLOADING;
167 |             if (sharedPreferences.getString(PreferenceConstants.ACTIVE_MODEL, "").equals(dataset.get(getAdapterPosition()).getName()))
168 |                 return ModelListState.SELECTED;
169 |             if (offlineModelItems.stream().anyMatch(it -> it.getName().equals(dataset.get(getAdapterPosition()).getName()))) {
170 |                 return ModelListState.DOWNLOADED;
171 |             }
172 |             return ModelListState.NOT_DOWNLOADED;
173 |         }
174 |     }
175 | 
176 | 
177 |     public List getDataset() {
178 |         return dataset;
179 |     }
180 | 
181 |     enum ModelListState {
182 |         NOT_DOWNLOADED,
183 |         DOWNLOADED,
184 |         DOWNLOADING,
185 |         SELECTED
186 |     }
187 | }
188 | 
--------------------------------------------------------------------------------
/app/src/main/java/org/vosk/service/utils/PreferenceConstants.java:
--------------------------------------------------------------------------------
1 | package org.vosk.service.utils;
2 | 
3 | public class PreferenceConstants {
4 |     public static final String DOWNLOADING_FILE = "downloading_file";
5 |     public static final String ACTIVE_MODEL = "active_model";
6 |     public static final String OFFLINE_LIST = "offline_list";
7 | }
8 | 
--------------------------------------------------------------------------------
/app/src/main/java/org/vosk/service/utils/Tools.java:
--------------------------------------------------------------------------------
 1 | package org.vosk.service.utils;
 2 | 
 3 | import android.app.ActivityManager;
 4 | import android.content.Context;
 5 | 
 6 | import org.jetbrains.annotations.NotNull;
 7 | import org.vosk.service.download.DownloadModelService;
 8 | 
 9 | import java.io.File;
10 | 
11 | public class Tools {
12 | 
13 |     public static File getModelFileRootPath(@NotNull Context context) {
14 |         return new File(context.getFilesDir(), "models");
15 |     }
16 | 
17 |     public static boolean isServiceRunning(Context context) {
18 |         ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
19 |         for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
20 |             if (DownloadModelService.class.getName().equals(service.service.getClassName())) {
21 |                 return true;
22 |             }
23 |         }
24 |         return false;
25 |     }
26 | }
27 | 
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_service_trigger.xml:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 7 | 
 8 |      
12 |      
23 |      
33 |  
34 | 
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alphacep/vosk-android-service/5e02806198d67fea7ad31a4310fff3c932ed0b09/app/src/main/res/drawable-hdpi/icon.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/rounded.xml:
--------------------------------------------------------------------------------
1 | 
2 | 
3 |   
4 |   
6 |  
8 |  
9 | 
--------------------------------------------------------------------------------
/app/src/main/res/drawable-ldpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alphacep/vosk-android-service/5e02806198d67fea7ad31a4310fff3c932ed0b09/app/src/main/res/drawable-ldpi/icon.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alphacep/vosk-android-service/5e02806198d67fea7ad31a4310fff3c932ed0b09/app/src/main/res/drawable-mdpi/icon.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/circle.xml:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 4 | 
 5 |      
 6 | 
 7 |      
10 |  
--------------------------------------------------------------------------------
/app/src/main/res/drawable/gradient_progress_color.xml:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |     
 4 | 
 5 |         - 
 6 |             
 7 |                  
 8 |                  
 9 |              
10 |          
11 | 
12 |         - 
13 |             
14 |                  
15 |                  
18 |              
19 |          
20 | 
21 |      
22 |  
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_check_circle_24.xml:
--------------------------------------------------------------------------------
1 | 
4 |      
5 |  
6 | 
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_cloud_download_24.xml:
--------------------------------------------------------------------------------
1 | 
4 |      
5 |  
6 | 
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_mic_24.xml:
--------------------------------------------------------------------------------
1 | 
4 |      
5 |  
6 | 
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_model_list.xml:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 7 | 
 8 |      
17 | 
18 |      
30 | 
31 |  
--------------------------------------------------------------------------------
/app/src/main/res/layout/main.xml:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 7 | 
 8 |      
13 | 
14 |      
19 | 
20 |      
25 | 
26 |      
32 | 
33 |      
40 | 
41 |  
42 | 
--------------------------------------------------------------------------------
/app/src/main/res/layout/model_list_item.xml:
--------------------------------------------------------------------------------
 1 | 
 2 | 
11 | 
12 |     
17 | 
18 |          
28 | 
29 |          
41 | 
42 |          
51 | 
52 |          
62 | 
63 |          
72 | 
73 |          
82 | 
83 |          
90 | 
91 |      
92 |  
--------------------------------------------------------------------------------
/app/src/main/res/layout/speech_recognizer_activity.xml:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 7 | 
 8 |     
15 | 
16 |          
25 | 
26 |          
41 | 
42 |          
49 |      
50 |  
--------------------------------------------------------------------------------
/app/src/main/res/raw/start_speech_effect.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alphacep/vosk-android-service/5e02806198d67fea7ad31a4310fff3c932ed0b09/app/src/main/res/raw/start_speech_effect.mp3
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |     #000000 
 4 |     #3700B3 
 5 |     #03DAC5 
 6 |     #EEEEEE 
 7 |     #44CD44 
 8 |     #646464 
 9 |  
10 | 
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 
 4 |     Vosk 
 5 |     Recognize File 
 6 |     Recognize Microphone 
 7 |     Stop Microphone 
 8 |     Preparing the recognizer\n 
 9 |     Ready\n 
10 |     Failed to init recognizer %s\n 
11 |     Result %s elapsed %d milliseconds\n 
12 |     Starting\n 
13 |     Say something\n 
14 |     Stop File 
15 |     Pause 
16 |     Continue 
17 |     Recognized: %1$s 
18 |     Start speaking now! 
19 |     Loading… 
20 |     Another download is in progress. Please wait. 
21 |     Warning 
22 |     Model List 
23 |     Download Success 
24 |     Starting download… 
25 |     Downloading vosk languaje model 
26 |     Connection error, please check your network 
27 |     You must accept this permission to use the app 
28 |     You must accept this permission to access model list 
29 |     Extracting… 
30 |     Restarting… 
31 |     download_indicator 
32 |     Storage write error, please contact support. 
33 |     "Size: %1$s" 
34 |     "%1$d%%" 
35 |     "Name: %1$s" 
36 |     Delete model 
37 |     Are you sure you want to delete the selected model? 
38 |     Model deleted 
39 |     Vosk Speech Recognition Service 
40 | 
41 |  
42 | 
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |     
 4 |     
10 |     
11 |     
19 | 
20 |  
21 | 
--------------------------------------------------------------------------------
/app/src/main/res/xml/recognition_service.xml:
--------------------------------------------------------------------------------
1 | 
2 |  
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
 2 | 
 3 | buildscript {
 4 |     repositories {
 5 |         mavenCentral()
 6 |         google()
 7 |     }
 8 |     dependencies {
 9 |         classpath("com.android.tools.build:gradle:7.2.2")
10 |     }
11 | }
12 | 
13 | allprojects {
14 |     repositories {
15 |         mavenCentral()
16 |         google()
17 |     }
18 | }
19 | 
--------------------------------------------------------------------------------
/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 | android.enableJetifier=true
20 | 
21 | 
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alphacep/vosk-android-service/5e02806198d67fea7ad31a4310fff3c932ed0b09/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | 
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
  1 | #!/usr/bin/env sh
  2 | 
  3 | #
  4 | # Copyright 2015 the original author or authors.
  5 | #
  6 | # Licensed under the Apache License, Version 2.0 (the "License");
  7 | # you may not use this file except in compliance with the License.
  8 | # You may obtain a copy of the License at
  9 | #
 10 | #      https://www.apache.org/licenses/LICENSE-2.0
 11 | #
 12 | # Unless required by applicable law or agreed to in writing, software
 13 | # distributed under the License is distributed on an "AS IS" BASIS,
 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 15 | # See the License for the specific language governing permissions and
 16 | # limitations under the License.
 17 | #
 18 | 
 19 | ##############################################################################
 20 | ##
 21 | ##  Gradle start up script for UN*X
 22 | ##
 23 | ##############################################################################
 24 | 
 25 | # Attempt to set APP_HOME
 26 | # Resolve links: $0 may be a link
 27 | PRG="$0"
 28 | # Need this for relative symlinks.
 29 | while [ -h "$PRG" ] ; do
 30 |     ls=`ls -ld "$PRG"`
 31 |     link=`expr "$ls" : '.*-> \(.*\)$'`
 32 |     if expr "$link" : '/.*' > /dev/null; then
 33 |         PRG="$link"
 34 |     else
 35 |         PRG=`dirname "$PRG"`"/$link"
 36 |     fi
 37 | done
 38 | SAVED="`pwd`"
 39 | cd "`dirname \"$PRG\"`/" >/dev/null
 40 | APP_HOME="`pwd -P`"
 41 | cd "$SAVED" >/dev/null
 42 | 
 43 | APP_NAME="Gradle"
 44 | APP_BASE_NAME=`basename "$0"`
 45 | 
 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
 48 | 
 49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
 50 | MAX_FD="maximum"
 51 | 
 52 | warn () {
 53 |     echo "$*"
 54 | }
 55 | 
 56 | die () {
 57 |     echo
 58 |     echo "$*"
 59 |     echo
 60 |     exit 1
 61 | }
 62 | 
 63 | # OS specific support (must be 'true' or 'false').
 64 | cygwin=false
 65 | msys=false
 66 | darwin=false
 67 | nonstop=false
 68 | case "`uname`" in
 69 |   CYGWIN* )
 70 |     cygwin=true
 71 |     ;;
 72 |   Darwin* )
 73 |     darwin=true
 74 |     ;;
 75 |   MINGW* )
 76 |     msys=true
 77 |     ;;
 78 |   NONSTOP* )
 79 |     nonstop=true
 80 |     ;;
 81 | esac
 82 | 
 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
 84 | 
 85 | 
 86 | # Determine the Java command to use to start the JVM.
 87 | if [ -n "$JAVA_HOME" ] ; then
 88 |     if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
 89 |         # IBM's JDK on AIX uses strange locations for the executables
 90 |         JAVACMD="$JAVA_HOME/jre/sh/java"
 91 |     else
 92 |         JAVACMD="$JAVA_HOME/bin/java"
 93 |     fi
 94 |     if [ ! -x "$JAVACMD" ] ; then
 95 |         die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
 96 | 
 97 | Please set the JAVA_HOME variable in your environment to match the
 98 | location of your Java installation."
 99 |     fi
100 | else
101 |     JAVACMD="java"
102 |     which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 | 
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 | 
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 |     MAX_FD_LIMIT=`ulimit -H -n`
111 |     if [ $? -eq 0 ] ; then
112 |         if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 |             MAX_FD="$MAX_FD_LIMIT"
114 |         fi
115 |         ulimit -n $MAX_FD
116 |         if [ $? -ne 0 ] ; then
117 |             warn "Could not set maximum file descriptor limit: $MAX_FD"
118 |         fi
119 |     else
120 |         warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 |     fi
122 | fi
123 | 
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 |     GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 | 
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 |     APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 |     CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 | 
134 |     JAVACMD=`cygpath --unix "$JAVACMD"`
135 | 
136 |     # We build the pattern for arguments to be converted via cygpath
137 |     ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 |     SEP=""
139 |     for dir in $ROOTDIRSRAW ; do
140 |         ROOTDIRS="$ROOTDIRS$SEP$dir"
141 |         SEP="|"
142 |     done
143 |     OURCYGPATTERN="(^($ROOTDIRS))"
144 |     # Add a user-defined pattern to the cygpath arguments
145 |     if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 |         OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 |     fi
148 |     # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 |     i=0
150 |     for arg in "$@" ; do
151 |         CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 |         CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
153 | 
154 |         if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
155 |             eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 |         else
157 |             eval `echo args$i`="\"$arg\""
158 |         fi
159 |         i=`expr $i + 1`
160 |     done
161 |     case $i in
162 |         0) set -- ;;
163 |         1) set -- "$args0" ;;
164 |         2) set -- "$args0" "$args1" ;;
165 |         3) set -- "$args0" "$args1" "$args2" ;;
166 |         4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 |         5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 |         6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 |         7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 |         8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 |         9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 |     esac
173 | fi
174 | 
175 | # Escape application args
176 | save () {
177 |     for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 |     echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 | 
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 | 
185 | exec "$JAVACMD" "$@"
186 | 
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
 1 | @rem
 2 | @rem Copyright 2015 the original author or authors.
 3 | @rem
 4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
 5 | @rem you may not use this file except in compliance with the License.
 6 | @rem You may obtain a copy of the License at
 7 | @rem
 8 | @rem      https://www.apache.org/licenses/LICENSE-2.0
 9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 | 
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem  Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 | 
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 | 
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 | 
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 | 
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 | 
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 | 
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 | 
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 | 
51 | goto fail
52 | 
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 | 
57 | if exist "%JAVA_EXE%" goto execute
58 | 
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 | 
65 | goto fail
66 | 
67 | :execute
68 | @rem Setup the command line
69 | 
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 | 
72 | 
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 | 
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 | 
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 | 
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 | 
89 | :omega
90 | 
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 | 
--------------------------------------------------------------------------------