Skip to content
Snippets Groups Projects
Unverified Commit d776f51e authored by mueller-ma's avatar mueller-ma Committed by GitHub
Browse files

Improve UX if no speech to text app is installed (#1324)


* Hide voice widgets
* Always show mic in action bar, but open snackbar with link to Google's
stt app if no stt app is installed

Fixes #1323

* Always close Snackbar before showing new one

Signed-off-by: default avatarmueller-ma <mueller-ma@users.noreply.github.com>
parent 62a654a4
No related branches found
No related tags found
No related merge requests found
...@@ -14,10 +14,12 @@ import android.app.Dialog; ...@@ -14,10 +14,12 @@ import android.app.Dialog;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.ActivityNotFoundException; import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager; import android.content.pm.ShortcutManager;
import android.content.res.ColorStateList; import android.content.res.ColorStateList;
...@@ -31,6 +33,7 @@ import android.net.NetworkInfo; ...@@ -31,6 +33,7 @@ import android.net.NetworkInfo;
import android.net.Uri; import android.net.Uri;
import android.net.wifi.WifiManager; import android.net.wifi.WifiManager;
import android.nfc.NfcAdapter; import android.nfc.NfcAdapter;
import android.os.AsyncTask;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
...@@ -50,7 +53,6 @@ import android.view.View; ...@@ -50,7 +53,6 @@ import android.view.View;
import android.view.ViewStub; import android.view.ViewStub;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.Toast;
import androidx.annotation.ColorInt; import androidx.annotation.ColorInt;
import androidx.annotation.DrawableRes; import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
...@@ -67,7 +69,6 @@ import androidx.recyclerview.widget.RecyclerView; ...@@ -67,7 +69,6 @@ import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.navigation.NavigationView; import com.google.android.material.navigation.NavigationView;
import com.google.android.material.snackbar.Snackbar; import com.google.android.material.snackbar.Snackbar;
import es.dmoral.toasty.Toasty;
import okhttp3.Headers; import okhttp3.Headers;
import okhttp3.Request; import okhttp3.Request;
import org.openhab.habdroid.R; import org.openhab.habdroid.R;
...@@ -86,6 +87,8 @@ import org.openhab.habdroid.model.LinkedPage; ...@@ -86,6 +87,8 @@ import org.openhab.habdroid.model.LinkedPage;
import org.openhab.habdroid.model.ServerProperties; import org.openhab.habdroid.model.ServerProperties;
import org.openhab.habdroid.model.Sitemap; import org.openhab.habdroid.model.Sitemap;
import org.openhab.habdroid.ui.activity.ContentController; import org.openhab.habdroid.ui.activity.ContentController;
import org.openhab.habdroid.ui.homescreenwidget.VoiceWidget;
import org.openhab.habdroid.ui.homescreenwidget.VoiceWidgetWithIcon;
import org.openhab.habdroid.util.AsyncHttpClient; import org.openhab.habdroid.util.AsyncHttpClient;
import org.openhab.habdroid.util.AsyncServiceResolver; import org.openhab.habdroid.util.AsyncServiceResolver;
import org.openhab.habdroid.util.Constants; import org.openhab.habdroid.util.Constants;
...@@ -261,6 +264,19 @@ public class MainActivity extends AppCompatActivity implements ...@@ -261,6 +264,19 @@ public class MainActivity extends AppCompatActivity implements
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
mShortcutManager = getSystemService(ShortcutManager.class); mShortcutManager = getSystemService(ShortcutManager.class);
} }
final boolean isSpeechRecognizerAvailable = SpeechRecognizer.isRecognitionAvailable(this);
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... voids) {
manageVoiceRecognitionShortcut(isSpeechRecognizerAvailable);
setVoiceWidgetComponentEnabledSetting(VoiceWidget.class,
isSpeechRecognizerAvailable);
setVoiceWidgetComponentEnabledSetting(VoiceWidgetWithIcon.class,
isSpeechRecognizerAvailable);
return null;
}
}.execute();
} }
private void handleConnectionChange() { private void handleConnectionChange() {
...@@ -858,9 +874,7 @@ public class MainActivity extends AppCompatActivity implements ...@@ -858,9 +874,7 @@ public class MainActivity extends AppCompatActivity implements
Log.d(TAG, "onPrepareOptionsMenu()"); Log.d(TAG, "onPrepareOptionsMenu()");
MenuItem voiceRecognitionItem = menu.findItem(R.id.mainmenu_voice_recognition); MenuItem voiceRecognitionItem = menu.findItem(R.id.mainmenu_voice_recognition);
@ColorInt int iconColor = ContextCompat.getColor(this, R.color.light); @ColorInt int iconColor = ContextCompat.getColor(this, R.color.light);
voiceRecognitionItem.setVisible(mConnection != null voiceRecognitionItem.setVisible(mConnection != null);
&& SpeechRecognizer.isRecognitionAvailable(this));
manageVoiceRecognitionShortcut(SpeechRecognizer.isRecognitionAvailable(this));
voiceRecognitionItem.getIcon().setColorFilter(iconColor, PorterDuff.Mode.SRC_IN); voiceRecognitionItem.getIcon().setColorFilter(iconColor, PorterDuff.Mode.SRC_IN);
return true; return true;
} }
...@@ -994,13 +1008,19 @@ public class MainActivity extends AppCompatActivity implements ...@@ -994,13 +1008,19 @@ public class MainActivity extends AppCompatActivity implements
try { try {
startActivity(speechIntent); startActivity(speechIntent);
} catch (ActivityNotFoundException speechRecognizerNotFoundException) { } catch (ActivityNotFoundException speechRecognizerNotFoundException) {
showSnackbar(R.string.error_no_speech_to_text_app_found, R.string.install, v -> {
try { try {
startActivity(new Intent(Intent.ACTION_VIEW, startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(
Uri.parse("market://details?id=com.google.android.googlequicksearchbox"))); "market://details?id=com.google.android.googlequicksearchbox")));
} catch (ActivityNotFoundException appStoreNotFoundException) { } catch (ActivityNotFoundException appStoreNotFoundException) {
Toasty.error(this, R.string.error_no_app_store_found, try {
Toast.LENGTH_LONG, true).show(); startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(
"http://play.google.com/store/apps/details?id=com.google.android.googlequicksearchbox")));
} catch (ActivityNotFoundException browserNotFoundException) {
showSnackbar(R.string.error_no_browser_found);
}
} }
});
} }
} }
...@@ -1010,31 +1030,35 @@ public class MainActivity extends AppCompatActivity implements ...@@ -1010,31 +1030,35 @@ public class MainActivity extends AppCompatActivity implements
return; return;
} }
mLastSnackbar = Snackbar.make(findViewById(android.R.id.content), showSnackbar(R.string.swipe_to_refresh_description, R.string.swipe_to_refresh_dismiss,
R.string.swipe_to_refresh_description, Snackbar.LENGTH_LONG); v -> {
mLastSnackbar.setAction(R.string.swipe_to_refresh_dismiss, v -> {
prefs.edit() prefs.edit()
.putBoolean(Constants.PREFERENCE_SWIPE_REFRESH_EXPLAINED, true) .putBoolean(Constants.PREFERENCE_SWIPE_REFRESH_EXPLAINED, true)
.apply(); .apply();
}); });
mLastSnackbar.show();
} }
public void showDemoModeHintSnackbar() { public void showDemoModeHintSnackbar() {
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
mLastSnackbar = Snackbar.make(findViewById(android.R.id.content), showSnackbar(R.string.info_demo_mode_short, R.string.turn_off, v -> {
R.string.info_demo_mode_short, Snackbar.LENGTH_LONG);
mLastSnackbar.setAction(R.string.turn_off, v -> {
prefs.edit() prefs.edit()
.putBoolean(Constants.PREFERENCE_DEMOMODE, false) .putBoolean(Constants.PREFERENCE_DEMOMODE, false)
.apply(); .apply();
}); });
mLastSnackbar.show();
} }
private void showSnackbar(@StringRes int messageResId) { private void showSnackbar(@StringRes int messageResId) {
mLastSnackbar = Snackbar.make(findViewById(android.R.id.content), showSnackbar(messageResId, 0, null);
messageResId, Snackbar.LENGTH_LONG); }
private void showSnackbar(@StringRes int messageResId, @StringRes int actionResId,
View.OnClickListener onClickListener) {
hideSnackbar();
mLastSnackbar = Snackbar.make(findViewById(android.R.id.content), messageResId,
Snackbar.LENGTH_LONG);
if (actionResId != 0 && onClickListener != null) {
mLastSnackbar.setAction(actionResId, onClickListener);
}
mLastSnackbar.show(); mLastSnackbar.show();
} }
...@@ -1164,4 +1188,13 @@ public class MainActivity extends AppCompatActivity implements ...@@ -1164,4 +1188,13 @@ public class MainActivity extends AppCompatActivity implements
getString(disableMessage)); getString(disableMessage));
} }
} }
private void setVoiceWidgetComponentEnabledSetting(Class<?> component,
boolean isSpeechRecognizerAvailable) {
ComponentName voiceWidget = new ComponentName(this, component);
PackageManager pm = getPackageManager();
int newState = isSpeechRecognizerAvailable ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
: PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
pm.setComponentEnabledSetting(voiceWidget, newState, PackageManager.DONT_KILL_APP);
}
} }
\ No newline at end of file
...@@ -14,9 +14,7 @@ import android.appwidget.AppWidgetManager; ...@@ -14,9 +14,7 @@ import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider; import android.appwidget.AppWidgetProvider;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri;
import android.speech.RecognizerIntent; import android.speech.RecognizerIntent;
import android.speech.SpeechRecognizer;
import android.util.Log; import android.util.Log;
import android.widget.RemoteViews; import android.widget.RemoteViews;
import androidx.annotation.LayoutRes; import androidx.annotation.LayoutRes;
...@@ -43,14 +41,12 @@ public class VoiceWidget extends AppWidgetProvider { ...@@ -43,14 +41,12 @@ public class VoiceWidget extends AppWidgetProvider {
// Construct the RemoteViews object // Construct the RemoteViews object
RemoteViews views = new RemoteViews(context.getPackageName(), getLayoutRes()); RemoteViews views = new RemoteViews(context.getPackageName(), getLayoutRes());
Intent intent;
if (SpeechRecognizer.isRecognitionAvailable(context)) {
Log.d(TAG, "Voice recognizer available, build speech intent"); Log.d(TAG, "Voice recognizer available, build speech intent");
Intent callbackIntent = new Intent(context, VoiceService.class); Intent callbackIntent = new Intent(context, VoiceService.class);
final PendingIntent callbackPendingIntent = PendingIntent.getService(context, final PendingIntent callbackPendingIntent = PendingIntent.getService(context,
9, callbackIntent, 0); 9, callbackIntent, 0);
intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
// Display an hint to the user about what he should say. // Display an hint to the user about what he should say.
intent.putExtra(RecognizerIntent.EXTRA_PROMPT, intent.putExtra(RecognizerIntent.EXTRA_PROMPT,
context.getString(R.string.info_voice_input)); context.getString(R.string.info_voice_input));
...@@ -59,11 +55,7 @@ public class VoiceWidget extends AppWidgetProvider { ...@@ -59,11 +55,7 @@ public class VoiceWidget extends AppWidgetProvider {
intent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 1); intent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 1);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(RecognizerIntent.EXTRA_RESULTS_PENDINGINTENT, callbackPendingIntent); intent.putExtra(RecognizerIntent.EXTRA_RESULTS_PENDINGINTENT, callbackPendingIntent);
} else {
Log.d(TAG, "Voice recognizer not available, build open app store intent");
intent = new Intent(Intent.ACTION_VIEW,
Uri.parse("market://details?id=com.google.android.googlequicksearchbox"));
}
PendingIntent pendingIntent = PendingIntent.getActivity(context, 6, intent, 0); PendingIntent pendingIntent = PendingIntent.getActivity(context, 6, intent, 0);
views.setOnClickPendingIntent(R.id.outer_layout, pendingIntent); views.setOnClickPendingIntent(R.id.outer_layout, pendingIntent);
......
...@@ -85,7 +85,7 @@ ...@@ -85,7 +85,7 @@
<string name="info_conn_rem_url">Connecting to remote URL</string> <string name="info_conn_rem_url">Connecting to remote URL</string>
<string name="app_shortcut_diabled_habpanel">Your server doesn\'t have HABPanel installed</string> <string name="app_shortcut_diabled_habpanel">Your server doesn\'t have HABPanel installed</string>
<string name="app_shortcut_diabled_notifications">Your remote server isn\'t available</string> <string name="app_shortcut_diabled_notifications">Your remote server isn\'t available</string>
<string name="app_shortcut_diabled_voice_recognition">Your device doesn\'t have a speech-to-text app installed</string> <string name="app_shortcut_diabled_voice_recognition">Your device doesn\'t have a voice recognition app installed</string>
<!-- Error messages --> <!-- Error messages -->
<string name="error_empty_sitemap_list">openHAB returned empty Sitemap list</string> <string name="error_empty_sitemap_list">openHAB returned empty Sitemap list</string>
...@@ -121,8 +121,9 @@ ...@@ -121,8 +121,9 @@
<string name="error_http_code_511">Network authentication is required (HTTP response code 511)</string> <string name="error_http_code_511">Network authentication is required (HTTP response code 511)</string>
<string name="error_about_no_conn">Error while fetching openHAB server information</string> <string name="error_about_no_conn">Error while fetching openHAB server information</string>
<string name="error_openhab_offline">Your openHAB server is offline while the cloud instance is running</string> <string name="error_openhab_offline">Your openHAB server is offline while the cloud instance is running</string>
<string name="error_no_app_store_found">No app store found to install voice recognizer app</string> <string name="error_no_browser_found">No browser found on your device</string>
<string name="error_no_browser_found">No browser found to open website</string> <string name="error_no_speech_to_text_app_found">No voice recognition app found</string>
<string name="install">Install</string>
<string name="title_activity_openhabwritetag">Write NFC tag</string> <string name="title_activity_openhabwritetag">Write NFC tag</string>
<string name="title_activity_libraries">Used libraries</string> <string name="title_activity_libraries">Used libraries</string>
<string name="info_write_tag">Touch the tag and keep it close until the confirmation message appear</string> <string name="info_write_tag">Touch the tag and keep it close until the confirmation message appear</string>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment