Skip to content

Commit

Permalink
Multiple language support!
Browse files Browse the repository at this point in the history
  • Loading branch information
ElishaAz committed Oct 18, 2022
1 parent 3157961 commit 9d61c77
Show file tree
Hide file tree
Showing 24 changed files with 718 additions and 110 deletions.
2 changes: 1 addition & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ android {
buildFeatures {
viewBinding true
}
namespace 'org.vosk.ime'
}

dependencies {
implementation 'androidx.appcompat:appcompat:1.5.1'
implementation 'net.java.dev.jna:jna:5.9.0@aar'
implementation group: 'com.alphacephei', name: 'vosk-android', version: '0.3.32'
implementation project(':models')
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'com.google.android.material:material:1.6.1'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.1'
Expand Down
11 changes: 8 additions & 3 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.vosk.ime">
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

<application
android:theme="@style/AppTheme"
android:allowBackup="false"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="true"
android:icon="@drawable/icon"
android:label="@string/app_name"
android:theme="@style/AppTheme"
tools:targetApi="s">

<service
Expand All @@ -38,6 +39,10 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<service
android:name=".FileDownloadService"
android:enabled="true" />
</application>

</manifest>
26 changes: 26 additions & 0 deletions app/src/main/java/org/vosk/ime/Constants.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.vosk.ime;

import android.content.Context;

import java.io.File;
import java.util.Locale;

public class Constants {
public static File getTemporaryDownloadLocation(Context context){
return new File(context.getCacheDir().getAbsolutePath(), "ModelZips");
}

public static File getTemporaryUnzipLocation(Context context) {
return new File(context.getCacheDir(), "TempUnzip");
}

public static File getModelsDirectory(Context context){
return new File(context.getFilesDir().getAbsolutePath(), "Models");
}

public static File getDirectoryForModel(Context context, Locale locale){
File dataFolder = Constants.getModelsDirectory(context);
String folderName = locale.toLanguageTag();
return new File(dataFolder, folderName);
}
}
71 changes: 37 additions & 34 deletions app/src/main/java/org/vosk/ime/FileDownloadService.java
Original file line number Diff line number Diff line change
Expand Up @@ -113,16 +113,22 @@ protected void onHandleIntent(Intent intent) {

if (downloadDetails.isRequiresUnzip()) {

String unzipDestination = downloadDetails.getUnzipAtFilePath();

if (unzipDestination == null) {
File unzipDestination;
if (downloadDetails.getUnzipAtFilePath() == null) {

File file = new File(localPath);

unzipDestination = file.getParentFile().getAbsolutePath();
unzipDestination = file.getParentFile();
} else {

unzipDestination = new File(downloadDetails.getUnzipAtFilePath());
}

unzip(localPath, unzipDestination);

File unzipFolder = Constants.getTemporaryUnzipLocation(this);
File currentUnzipFolder = new File(unzipFolder, unzipDestination.getName());

unzip(localPath, currentUnzipFolder, unzipDestination);
}

downloadCompleted(resultReceiver);
Expand Down Expand Up @@ -170,24 +176,35 @@ public void downloadFailed(ResultReceiver resultReceiver) {
resultReceiver.send(STATUS_FAILED, progressBundle);
}

private void unzip(String zipFilePath, String unzipAtLocation) throws Exception {

private void unzip(String zipFilePath, File tempUnzipLocation, File unzipFinalDestination) throws IOException {
File archive = new File(zipFilePath);

try {
if (tempUnzipLocation.exists()) {
Tools.deleteRecursive(tempUnzipLocation);
}

ZipFile zipfile = new ZipFile(archive);
ZipFile zipfile = new ZipFile(archive);

for (Enumeration e = zipfile.entries(); e.hasMoreElements(); ) {
for (Enumeration<? extends ZipEntry> e = zipfile.entries(); e.hasMoreElements(); ) {

ZipEntry entry = (ZipEntry) e.nextElement();
ZipEntry entry = (ZipEntry) e.nextElement();

unzipEntry(zipfile, entry, unzipAtLocation);
unzipEntry(zipfile, entry, tempUnzipLocation.getAbsolutePath());
}
boolean moveSuccess;
if (unzipFinalDestination.exists()) {
moveSuccess = true;
for (File f : tempUnzipLocation.listFiles()) {
moveSuccess = f.renameTo(new File(unzipFinalDestination, f.getName()));
if (!moveSuccess) break;
}
tempUnzipLocation.delete();
} else {
moveSuccess = tempUnzipLocation.renameTo(unzipFinalDestination);
}

} catch (Exception e) {

Log.e("Unzip zip", "Unzip exception", e);
if (!moveSuccess) {
throw new IOException("Renaming temporary unzip directory failed");
}
}

Expand All @@ -206,27 +223,13 @@ private void unzipEntry(ZipFile zipfile, ZipEntry entry, String outputDir) throw
Log.v("ZIP E", "Extracting: " + entry);

InputStream zin = zipfile.getInputStream(entry);
BufferedInputStream inputStream = new BufferedInputStream(zin);
BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(outputFile));

try {

//IOUtils.copy(inputStream, outputStream);

try {

for (int c = inputStream.read(); c != -1; c = inputStream.read()) {
outputStream.write(c);
}

} finally {

outputStream.close();
try (BufferedInputStream inputStream = new BufferedInputStream(zin); BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(outputFile))) {
byte[] b = new byte[1024];
int n;
while ((n = inputStream.read(b, 0, 1024)) >= 0) {
outputStream.write(b, 0, n);
}

} finally {
outputStream.close();
inputStream.close();
}
}

Expand Down
10 changes: 4 additions & 6 deletions app/src/main/java/org/vosk/ime/Model.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
package org.vosk.ime;

import android.text.TextUtils;

import java.io.Serializable;
import java.util.Locale;

public class Model implements Serializable {
public final String path;
public final Locale locale;
public final String name;
public final String filename;

public Model(String path, Locale locale, String name) {
public Model(String path, Locale locale, String filename) {
this.path = path;
this.locale = locale;
this.name = name;
this.filename = filename;
}

public String serialize() {
Expand All @@ -23,7 +21,7 @@ public String serialize() {
public static String serialize(Model model) {
return "[path:\"" + encode(model.path) +
"\", locale:\"" + model.locale +
"\", name:\"" + encode(model.name) + "\"]";
"\", name:\"" + encode(model.filename) + "\"]";
}

public static Model deserialize(String serialized) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
package org.vosk.ime;

import android.os.LocaleList;
import android.content.Context;

import androidx.annotation.StringRes;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;

// Locale list available at: https://stackoverflow.com/questions/7973023/what-is-the-list-of-supported-languages-locales-on-android

/**
*
*
*/
public enum ModelLinks {
public enum ModelLink {
ENGLISH_US("https://alphacephei.com/vosk/models/vosk-model-small-en-us-0.15.zip", Locale.US, R.string.model_en_us),
ENGLISH_IN("https://alphacephei.com/vosk/models/vosk-model-small-en-in-0.4.zip", new Locale("en", "IN"), R.string.model_en_in),
CHINESE("https://alphacephei.com/vosk/models/vosk-model-small-cn-0.22.zip", Locale.CHINESE, R.string.model_cn),
Expand All @@ -38,9 +41,13 @@ public enum ModelLinks {
public final Locale locale;
public final int name;

ModelLinks(String link, Locale locale, @StringRes int name) {
ModelLink(String link, Locale locale, @StringRes int name) {
this.link = link;
this.locale = locale;
this.name = name;
}

public String getFilename() {
return link.substring(link.lastIndexOf('/') + 1, link.lastIndexOf('.'));
}
}
128 changes: 128 additions & 0 deletions app/src/main/java/org/vosk/ime/Tools.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@

import androidx.core.content.ContextCompat;

import org.vosk.ime.settingsfragments.ModelsAdapter;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

public class Tools {

public static boolean isMicrophonePermissionGranted(Activity activity) {
Expand All @@ -26,4 +35,123 @@ public static boolean isIMEEnabled(Activity activity) {
}
return false;
}

public static void downloadModelFromLink(ModelLink model, FileDownloadService.OnDownloadStatusListener listener, Context context) {
String serverFilePath = model.link;

File tempFolder = Constants.getTemporaryDownloadLocation(context);
if (!tempFolder.exists()) {
tempFolder.mkdirs();
}

String fileName = model.link.substring(model.link.lastIndexOf('/') + 1); // file name
File tempFile = new File(tempFolder, fileName);

String localPath = tempFile.getAbsolutePath();

File modelFolder = Constants.getDirectoryForModel(context, model.locale);

if (!modelFolder.exists()) {
modelFolder.mkdirs();
}

String unzipPath = modelFolder.getAbsolutePath();

FileDownloadService.DownloadRequest downloadRequest = new FileDownloadService.DownloadRequest(serverFilePath, localPath, true);
downloadRequest.setRequiresUnzip(true);
downloadRequest.setDeleteZipAfterExtract(true);
downloadRequest.setUnzipAtFilePath(unzipPath);

FileDownloadService.FileDownloader downloader = FileDownloadService.FileDownloader.getInstance(downloadRequest, listener);
downloader.download(context);
}

public static void deleteRecursive(File fileOrDirectory) {
if (fileOrDirectory.isDirectory())
for (File child : fileOrDirectory.listFiles())
deleteRecursive(child);

fileOrDirectory.delete();
}

public static Map<Locale, List<Model>> getInstalledModelsMap(Context context) {
Map<Locale, List<Model>> localeMap = new HashMap<>();

File modelsDir = Constants.getModelsDirectory(context);

if (!modelsDir.exists()) return localeMap;

for (File localeFolder : modelsDir.listFiles()) {
if (!localeFolder.isDirectory()) continue;
Locale locale = Locale.forLanguageTag(localeFolder.getName());
List<Model> models = new ArrayList<>();
for (File modelFolder : localeFolder.listFiles()) {
if (!modelFolder.isDirectory()) continue;
String name = modelFolder.getName();
Model model = new Model(modelFolder.getAbsolutePath(), locale, name);
models.add(model);
}
localeMap.put(locale, models);
}
return localeMap;
}

public static List<Model> getInstalledModelsList(Context context){
List<Model> models = new ArrayList<>();

File modelsDir = Constants.getModelsDirectory(context);

if (!modelsDir.exists()) return models;

for (File localeFolder : modelsDir.listFiles()) {
if (!localeFolder.isDirectory()) continue;
Locale locale = Locale.forLanguageTag(localeFolder.getName());
for (File modelFolder : localeFolder.listFiles()) {
if (!modelFolder.isDirectory()) continue;
String name = modelFolder.getName();
Model model = new Model(modelFolder.getAbsolutePath(), locale, name);
models.add(model);
}
}
return models;
}

public static List<ModelsAdapter.Data> getModelsData(Context context) {
List<ModelsAdapter.Data> data = new ArrayList<>();
Map<Locale, List<Model>> installedModels = getInstalledModelsMap(context);
for (ModelLink link : ModelLink.values()) {
boolean found = false;
if (installedModels.containsKey(link.locale)) {
List<Model> localeModels = installedModels.get(link.locale);
for (int i = 0; i < localeModels.size(); i++) {
Model model = localeModels.get(i);
if (model.filename.equals(link.getFilename())) {
data.add(new ModelsAdapter.Data(link, model));
localeModels.remove(i);
found = true;
break;
}
}
}
if (!found)
data.add(new ModelsAdapter.Data(link));
}
for (List<Model> models : installedModels.values()) {
for (Model model : models) {
data.add(new ModelsAdapter.Data(model));
}
}

return data;
}

public static Model getModelForLink(ModelLink modelLink, Context context) {
File modelsDir = Constants.getModelsDirectory(context);
File localeDir = new File(modelsDir, modelLink.locale.toLanguageTag());
File modelDir = new File(localeDir, modelLink.getFilename());
if (!localeDir.exists() || modelDir.exists() || !modelDir.isDirectory()) {
return null;
}
return new Model(modelDir.getAbsolutePath(), modelLink.locale, modelLink.getFilename());
}
}

0 comments on commit 9d61c77

Please sign in to comment.