package net.osmand.plus.voice; import android.content.Context; import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationManagerCompat; import android.media.AudioManager; import net.osmand.IndexConstants; import net.osmand.PlatformUtil; import net.osmand.StateChangedListener; import net.osmand.plus.ApplicationMode; import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandSettings; import net.osmand.plus.OsmandSettings.MetricsConstants; import net.osmand.plus.R; import net.osmand.plus.api.AudioFocusHelper; import org.apache.commons.logging.Log; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import alice.tuprolog.InvalidLibraryException; import alice.tuprolog.InvalidTheoryException; import alice.tuprolog.NoSolutionException; import alice.tuprolog.Number; import alice.tuprolog.Prolog; import alice.tuprolog.SolveInfo; import alice.tuprolog.Struct; import alice.tuprolog.Term; import alice.tuprolog.Theory; import alice.tuprolog.Var; public abstract class AbstractPrologCommandPlayer implements CommandPlayer, StateChangedListener<ApplicationMode> { private static final Log log = PlatformUtil.getLog(AbstractPrologCommandPlayer.class); protected OsmandApplication ctx; protected File voiceDir; protected Prolog prologSystem; protected static final String P_VERSION = "version"; protected static final String P_RESOLVE = "resolve"; public static final String A_LEFT = "left"; public static final String A_LEFT_SH = "left_sh"; public static final String A_LEFT_SL = "left_sl"; public static final String A_LEFT_KEEP = "left_keep"; public static final String A_RIGHT = "right"; public static final String A_RIGHT_SH = "right_sh"; public static final String A_RIGHT_SL = "right_sl"; public static final String A_RIGHT_KEEP = "right_keep"; protected static final String DELAY_CONST = "delay_"; private static final String WEAR_ALERT = "WEAR_ALERT"; /** Must be sorted array! */ private final int[] sortedVoiceVersions; private static AudioFocusHelper mAudioFocusHelper; protected String language = ""; protected int streamType; private static int currentVersion; private ApplicationMode applicationMode; protected AbstractPrologCommandPlayer(OsmandApplication ctx, ApplicationMode applicationMode, String voiceProvider, String configFile, int[] sortedVoiceVersions) throws CommandPlayerException { this.ctx = ctx; this.sortedVoiceVersions = sortedVoiceVersions; this.applicationMode = applicationMode; long time = System.currentTimeMillis(); try { this.ctx = ctx; prologSystem = new Prolog(getLibraries()); } catch (InvalidLibraryException e) { log.error("Initializing error", e); //$NON-NLS-1$ throw new RuntimeException(e); } if (log.isInfoEnabled()) { log.info("Initializing prolog system : " + (System.currentTimeMillis() - time)); //$NON-NLS-1$ } this.streamType = ctx.getSettings().AUDIO_STREAM_GUIDANCE.getModeValue(applicationMode); init(voiceProvider, ctx.getSettings(), configFile); final Term langVal = solveSimplePredicate("language"); if (langVal instanceof Struct) { language = ((Struct) langVal).getName(); } } public ApplicationMode getApplicationMode() { return applicationMode; } public String getLanguage() { return language; } public String[] getLibraries(){ return new String[] { "alice.tuprolog.lib.BasicLibrary", "alice.tuprolog.lib.ISOLibrary"/*, "alice.tuprolog.lib.IOLibrary"*/}; } public void sendAlertToAndroidWear(Context ctx, String message) { int notificationId = 1; NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(ctx) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setSmallIcon(R.drawable.icon) .setContentTitle(ctx.getString(R.string.app_name)) .setContentText(message) .setGroup(WEAR_ALERT); // Get an instance of the NotificationManager service NotificationManagerCompat notificationManager = NotificationManagerCompat.from(ctx); // Build the notification and issues it with notification manager. notificationManager.notify(notificationId, notificationBuilder.build()); } @Override public void stateChanged(ApplicationMode change) { if(prologSystem != null) { prologSystem.getTheoryManager().retract(new Struct("appMode", new Var())); prologSystem.getTheoryManager() .assertA( new Struct("appMode", new Struct(ctx.getSettings().APPLICATION_MODE.get().getStringKey() .toLowerCase())), true, "", true); } } private void init(String voiceProvider, OsmandSettings settings, String configFile) throws CommandPlayerException { prologSystem.clearTheory(); voiceDir = null; if (voiceProvider != null) { File parent = ctx.getAppPath(IndexConstants.VOICE_INDEX_DIR); voiceDir = new File(parent, voiceProvider); if (!voiceDir.exists()) { voiceDir = null; throw new CommandPlayerException( ctx.getString(R.string.voice_data_unavailable)); } } // see comments below why it is impossible to read from zip (don't know // how to play file from zip) // voiceZipFile = null; if (voiceDir != null) { long time = System.currentTimeMillis(); boolean wrong = false; try { InputStream config; // if (voiceDir.getName().endsWith(".zip")) { //$NON-NLS-1$ // voiceZipFile = new ZipFile(voiceDir); // config = voiceZipFile.getInputStream(voiceZipFile.getEntry("_config.p")); //$NON-NLS-1$ // } else { config = new FileInputStream(new File(voiceDir, configFile)); //$NON-NLS-1$ // } MetricsConstants mc = settings.METRIC_SYSTEM.get(); ApplicationMode m = settings.getApplicationMode(); if(m.getParent() != null) { m = m.getParent(); } settings.APPLICATION_MODE.addListener(this); prologSystem.getTheoryManager() .assertA( new Struct("appMode", new Struct(ctx.getSettings().APPLICATION_MODE.get().getStringKey() .toLowerCase())), true, "", true); prologSystem.addTheory(new Theory("measure('"+mc.toTTSString()+"').")); prologSystem.addTheory(new Theory(config)); config.close(); } catch (InvalidTheoryException e) { log.error("Loading voice config exception " + voiceProvider, e); //$NON-NLS-1$ wrong = true; } catch (IOException e) { log.error("Loading voice config exception " + voiceProvider, e); //$NON-NLS-1$ wrong = true; } if (wrong) { throw new CommandPlayerException(ctx.getString(R.string.voice_data_corrupted)); } else { Term val = solveSimplePredicate(P_VERSION); if (!(val instanceof Number) || Arrays.binarySearch(sortedVoiceVersions,((Number)val).intValue()) < 0) { throw new CommandPlayerException(ctx.getString(R.string.voice_data_not_supported)); } currentVersion = ((Number)val).intValue(); } if (log.isInfoEnabled()) { log.info("Initializing voice subsystem " + voiceProvider + " : " + (System.currentTimeMillis() - time)); //$NON-NLS-1$ //$NON-NLS-2$ } } } protected Term solveSimplePredicate(String predicate) { Term val = null; Var v = new Var("MyVariable"); //$NON-NLS-1$ SolveInfo s = prologSystem.solve(new Struct(predicate, v)); if (s.isSuccess()) { prologSystem.solveEnd(); try { val = s.getVarValue(v.getName()); } catch (NoSolutionException e) { } } return val; } @Override public List<String> execute(List<Struct> listCmd){ Struct list = new Struct(listCmd.toArray(new Term[listCmd.size()])); Var result = new Var("RESULT"); //$NON-NLS-1$ List<String> files = new ArrayList<String>(); if(prologSystem == null) { return files; } if (log.isInfoEnabled()) { log.info("Query speak files " + listCmd); } SolveInfo res = prologSystem.solve(new Struct(P_RESOLVE, list, result)); if (res.isSuccess()) { try { prologSystem.solveEnd(); Term solution = res.getVarValue(result.getName()); Iterator<?> listIterator = ((Struct) solution).listIterator(); while(listIterator.hasNext()){ Object term = listIterator.next(); if(term instanceof Struct){ files.add(((Struct) term).getName()); } } } catch (NoSolutionException e) { } } if (log.isInfoEnabled()) { log.info("Speak files " + files); } return files; } public static int getCurrentVersion() { return currentVersion; } @Override public String getCurrentVoice() { if (voiceDir == null) { return null; } return voiceDir.getName(); } @Override public CommandBuilder newCommandBuilder() { return new CommandBuilder(this); } @Override public void clear() { if(ctx != null && ctx.getSettings() != null) { ctx.getSettings().APPLICATION_MODE.removeListener(this); } abandonAudioFocus(); ctx = null; prologSystem = null; } @Override public void updateAudioStream(int streamType) { this.streamType = streamType; } protected synchronized void requestAudioFocus() { log.debug("requestAudioFocus"); if (android.os.Build.VERSION.SDK_INT >= 8) { mAudioFocusHelper = getAudioFocus(); } if (mAudioFocusHelper != null && ctx != null) { boolean audioFocusGranted = mAudioFocusHelper.requestFocus(ctx, applicationMode, streamType); // If AudioManager.STREAM_VOICE_CALL try using BT SCO: if (audioFocusGranted && ctx.getSettings().AUDIO_STREAM_GUIDANCE.getModeValue(applicationMode) == 0) { toggleBtSco(true); } } } private AudioFocusHelper getAudioFocus() { try { return new net.osmand.plus.api.AudioFocusHelperImpl(); } catch (Exception e) { log.error(e.getMessage(), e); } return null; } protected synchronized void abandonAudioFocus() { log.debug("abandonAudioFocus"); if ((ctx != null && ctx.getSettings().AUDIO_STREAM_GUIDANCE.getModeValue(applicationMode) == 0) || (btScoStatus == true)) { toggleBtSco(false); } if (ctx != null && mAudioFocusHelper != null) { mAudioFocusHelper.abandonFocus(ctx, applicationMode, streamType); } mAudioFocusHelper = null; } public static boolean btScoStatus = false; // BT_SCO_DELAY now in Settings. 1500 ms works for most configurations. //public static final int BT_SCO_DELAY = 1500; // This only needed for init debugging in TestVoiceActivity: public static String btScoInit = ""; private synchronized boolean toggleBtSco(boolean on) { // Hardy, 2016-07-03: Establish a low quality BT SCO (Synchronous Connection-Oriented) link to interrupt e.g. a car stereo FM radio if (on) { try { AudioManager mAudioManager = (AudioManager) ctx.getSystemService(Context.AUDIO_SERVICE); if (mAudioManager == null || !mAudioManager.isBluetoothScoAvailableOffCall()) { btScoInit = "BT SCO available: NO"; return false; } mAudioManager.setMode(0); mAudioManager.startBluetoothSco(); mAudioManager.setBluetoothScoOn(true); mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION); btScoStatus = true; } catch (Exception e) { System.out.println("Exception starting BT SCO " + e.getMessage() ); btScoStatus = false; btScoInit = "BT SCO available: YES\nBT SCO initializes: NO\n(" + e.getMessage() + ")"; return false; } btScoInit = "BT SCO available: YES\nBT SCO initializes: YES"; return true; } else { AudioManager mAudioManager = (AudioManager) ctx.getSystemService(Context.AUDIO_SERVICE); if (mAudioManager == null) { return false; } mAudioManager.setBluetoothScoOn(false); mAudioManager.stopBluetoothSco(); mAudioManager.setMode(AudioManager.MODE_NORMAL); btScoStatus = false; return true; } } }