From d8b6b423719a0765c515488ce35cd2883ab757d4 Mon Sep 17 00:00:00 2001 From: Manuel Martin Date: Fri, 15 Nov 2019 15:02:27 +0100 Subject: [PATCH] Handle the Model download Give a little more feedback through alerts --- app/build.gradle | 1 + .../vrbrowser/ui/widgets/TrayWidget.java | 14 +- .../ui/widgets/dialogs/VoiceSearchWidget.java | 150 +++++++++++++++++- 3 files changed, 160 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index cd1aedb75..fe5bb6734 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -448,6 +448,7 @@ dependencies { // Android Components implementation deps.mozilla_speech + implementation 'net.lingala.zip4j:zip4j:1.3.2' implementation deps.android_components.telemetry implementation deps.android_components.browser_errorpages implementation deps.android_components.browser_search diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/TrayWidget.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/TrayWidget.java index 659c37f1e..e1eb5b57a 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/TrayWidget.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/TrayWidget.java @@ -16,6 +16,7 @@ import android.view.animation.AccelerateDecelerateInterpolator; import androidx.annotation.NonNull; +import androidx.annotation.StringRes; import org.mozilla.gecko.util.ThreadUtils; import org.mozilla.geckoview.GeckoSession; @@ -499,6 +500,11 @@ public void showTabSentNotification() { ThreadUtils.postToUiThread(() -> showNotification(mTabsButton, R.string.tab_sent_notification)); } + public void showNotification(String text) { + mSettingsButton.setNotificationMode(true); + ThreadUtils.postToUiThread(() -> showNotification(mSettingsButton, text)); + } + private BookmarksStore.BookmarkListener mBookmarksListener = new BookmarksStore.BookmarkListener() { @Override public void onBookmarksUpdated() { @@ -512,7 +518,11 @@ public void onBookmarkAdded() { } }; - private void showNotification(UIButton button, int stringRes) { + private void showNotification(UIButton button, @StringRes int stringRes) { + showNotification(button, getResources().getString(stringRes)); + } + + private void showNotification(UIButton button, String string) { if (mLibraryNotification != null && mLibraryNotification.isVisible()) { return; } @@ -530,7 +540,7 @@ private void showNotification(UIButton button, int stringRes) { mLibraryNotification.getPlacement().translationY = ((offsetViewBounds.top - 60) * ratio); mLibraryNotification.getPlacement().translationZ = 25.0f; mLibraryNotification.getPlacement().density = WidgetPlacement.floatDimension(getContext(), R.dimen.tray_tooltip_density); - mLibraryNotification.setText(stringRes); + mLibraryNotification.setText(string); mLibraryNotification.setCurvedMode(false); mLibraryNotification.show(UIWidget.CLEAR_FOCUS); diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/VoiceSearchWidget.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/VoiceSearchWidget.java index 3b64b95f9..617c53271 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/VoiceSearchWidget.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/VoiceSearchWidget.java @@ -3,11 +3,18 @@ import android.Manifest; import android.app.Activity; import android.app.Application; +import android.app.DownloadManager; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.PackageManager; +import android.database.Cursor; import android.graphics.drawable.ClipDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; +import android.net.Uri; +import android.os.AsyncTask; import android.os.Bundle; import android.util.AttributeSet; import android.util.Log; @@ -25,6 +32,8 @@ import com.mozilla.speechlibrary.MozillaSpeechService; import com.mozilla.speechlibrary.STTResult; +import net.lingala.zip4j.core.ZipFile; + import org.mozilla.gecko.util.ThreadUtils; import org.mozilla.vrbrowser.R; import org.mozilla.vrbrowser.audio.AudioEngine; @@ -35,6 +44,8 @@ import org.mozilla.vrbrowser.ui.widgets.WidgetPlacement; import org.mozilla.vrbrowser.utils.LocaleUtils; +import java.io.File; + public class VoiceSearchWidget extends UIDialog implements WidgetManagerDelegate.PermissionListener, Application.ActivityLifecycleCallbacks { @@ -65,6 +76,7 @@ public interface VoiceSearchDelegate { private boolean mIsSpeechRecognitionRunning = false; private boolean mWasSpeechRecognitionRunning = false; private AudioEngine mAudio; + private String mModelPath; public VoiceSearchWidget(Context aContext) { super(aContext); @@ -86,6 +98,8 @@ private void initialize(Context aContext) { mAudio = AudioEngine.fromContext(aContext); + mModelPath = getContext().getExternalFilesDir("models").getAbsolutePath(); + mWidgetManager.addPermissionListener(this); mMozillaSpeechService = MozillaSpeechService.getInstance(); @@ -234,10 +248,19 @@ public void startVoiceSearch() { } mMozillaSpeechService.storeSamples(storeData); mMozillaSpeechService.storeTranscriptions(storeData); - mMozillaSpeechService.setModelPath(getContext().getExternalFilesDir("models").getAbsolutePath()); + mMozillaSpeechService.setModelPath(mModelPath); mMozillaSpeechService.useDeepSpeech(true); - mMozillaSpeechService.start(getContext().getApplicationContext()); - mIsSpeechRecognitionRunning = true; + if (mMozillaSpeechService.ensureModelInstalled()) { + mMozillaSpeechService.start(getContext().getApplicationContext()); + mIsSpeechRecognitionRunning = true; + + } else if (mDownloadId == 0){ + maybeDownloadOrExtractModel(mModelPath, mMozillaSpeechService.getLanguageDir()); + onDismiss(); + + } else { + mWidgetManager.getFocusedWindow().showAlert("Deep Speech", "Model not ready yet", null); + } } } @@ -397,4 +420,125 @@ public void onActivitySaveInstanceState(Activity activity, Bundle bundle) { public void onActivityDestroyed(Activity activity) { } + + private static long mDownloadId = 0; + private static DownloadManager sDownloadManager = null; + public void maybeDownloadOrExtractModel(String aModelsPath, String aLang) { + String zipFile = aModelsPath + "/" + aLang + ".zip"; + String aModelPath= aModelsPath + "/" + aLang + "/"; + + File aModelFolder = new File(aModelPath); + if (!aModelFolder.exists()) { + aModelFolder.mkdirs(); + } + + Uri modelZipURL = Uri.parse(mMozillaSpeechService.getModelDownloadURL()); + Uri modelZipFile = Uri.parse("file://" + zipFile); + + BroadcastReceiver receiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) { + long downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0); + DownloadManager.Query query = new DownloadManager.Query(); + query.setFilterById(downloadId); + Cursor c = sDownloadManager.query(query); + if (c.moveToFirst()) { + int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS); + if (DownloadManager.STATUS_SUCCESSFUL == c.getInt(columnIndex)) { + Log.d(LOGTAG, "Model download finished"); + mWidgetManager.getTray().showNotification("Download finished"); + mWidgetManager.getFocusedWindow().showAlert("Deep Speech", "Model download finished", null); + + new AsyncUnzip(new AsyncUnzip.Delegate() { + @Override + public void onDownloadStarted() { + Log.d(LOGTAG, "Model unzipping started"); + mWidgetManager.getTray().showNotification("Model unzipping started"); + mWidgetManager.getFocusedWindow().showAlert("Deep Speech", "Model unzipping started", null); + } + + @Override + public void onDownloadFinished() { + Log.d(LOGTAG, "Model unzipping finished"); + mWidgetManager.getTray().showNotification("Model unzipping finished"); + mWidgetManager.getFocusedWindow().showAlert("Deep Speech", "Model unzipping finished", null); + } + + @Override + public void onDownloadError(String error) { + Log.d(LOGTAG, "Model unzipping error: " + error); + mWidgetManager.getTray().showNotification("Model unzipping error"); + mWidgetManager.getFocusedWindow().showAlert("Deep Speech", "Model unzipping error: " + error, null); + } + }).execute(zipFile, aModelPath); + } + } + } + } + }; + + sDownloadManager = (DownloadManager) getContext().getSystemService(Context.DOWNLOAD_SERVICE); + DownloadManager.Request request = new DownloadManager.Request(modelZipURL); + request.setTitle("DeepSpeech " + aLang); + request.setDescription("DeepSpeech Model"); + request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); + request.setVisibleInDownloadsUi(false); + request.setDestinationUri(modelZipFile); + mDownloadId = sDownloadManager.enqueue(request); + + Log.d(LOGTAG, "Model download started"); + mWidgetManager.getTray().showNotification("Model download started"); + mWidgetManager.getFocusedWindow().showAlert("Deep Speech", "Model download started", null); + + getContext().getApplicationContext().registerReceiver(receiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)); + } + + private static class AsyncUnzip extends AsyncTask { + + public interface Delegate { + void onDownloadStarted(); + void onDownloadFinished(); + void onDownloadError(String error); + } + + private Delegate mDelegate; + + public AsyncUnzip(Delegate delegate) { + mDelegate = delegate; + } + + @Override + protected void onPreExecute() { + if (mDelegate != null) { + mDelegate.onDownloadStarted(); + } + } + + @Override + protected Boolean doInBackground(String...params) { + String aZipFile = params[0], aRootModelsPath = params[1]; + try { + ZipFile zf = new ZipFile(aZipFile); + zf.extractAll(aRootModelsPath); + + } catch (Exception e) { + e.printStackTrace(); + if (mDelegate != null) { + mDelegate.onDownloadError(e.getMessage()); + } + } + + return (new File(aZipFile)).delete(); + } + + @Override + protected void onPostExecute(Boolean result) { + if (mDelegate != null) { + mDelegate.onDownloadFinished(); + } + } + + } }