From 82dcd57e1687072fc2b1a8df04ee32591771a414 Mon Sep 17 00:00:00 2001 From: Arne Jans Date: Wed, 3 Nov 2010 01:21:05 +0100 Subject: [PATCH 1/3] first working version of voiceinput and voiceoutput, yet without PowerWidget-microphone-button --- .../astrid/reminders/Notifications.java | 18 +- astrid/res/drawable/Thumbs.db | Bin 0 -> 65024 bytes astrid/res/drawable/ic_btn_speak_now.png | Bin 0 -> 954 bytes astrid/res/layout/task_edit_activity.xml | 8 + astrid/res/layout/task_list_activity.xml | 9 + astrid/res/values-de/strings.xml | 9 + astrid/res/values/keys.xml | 6 + astrid/res/values/strings-core.xml | 23 +++ astrid/res/xml/preferences.xml | 15 ++ .../astrid/activity/EditPreferences.java | 13 +- .../astrid/activity/TaskEditActivity.java | 41 +++- .../astrid/activity/TaskListActivity.java | 29 ++- .../astrid/voice/VoiceInputAssistant.java | 193 ++++++++++++++++++ .../astrid/voice/VoiceOutputAssistant.java | 135 ++++++++++++ 14 files changed, 494 insertions(+), 5 deletions(-) create mode 100644 astrid/res/drawable/Thumbs.db create mode 100644 astrid/res/drawable/ic_btn_speak_now.png create mode 100644 astrid/src/com/todoroo/astrid/voice/VoiceInputAssistant.java create mode 100644 astrid/src/com/todoroo/astrid/voice/VoiceOutputAssistant.java diff --git a/astrid/plugin-src/com/todoroo/astrid/reminders/Notifications.java b/astrid/plugin-src/com/todoroo/astrid/reminders/Notifications.java index 69f3f3a33..d781d104c 100644 --- a/astrid/plugin-src/com/todoroo/astrid/reminders/Notifications.java +++ b/astrid/plugin-src/com/todoroo/astrid/reminders/Notifications.java @@ -22,11 +22,12 @@ import com.todoroo.andlib.service.ExceptionService; import com.todoroo.andlib.service.NotificationManager; import com.todoroo.andlib.service.NotificationManager.AndroidNotificationManager; import com.todoroo.andlib.utility.DateUtilities; +import com.todoroo.andlib.utility.Preferences; import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.data.Task; import com.todoroo.astrid.service.AstridDependencyInjector; import com.todoroo.astrid.utility.Constants; -import com.todoroo.andlib.utility.Preferences; +import com.todoroo.astrid.voice.VoiceOutputAssistant; public class Notifications extends BroadcastReceiver { @@ -217,6 +218,7 @@ public class Notifications extends BroadcastReceiver { TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); int callState = tm.getCallState(); + boolean voiceReminder = Preferences.getBoolean(R.string.p_voiceRemindersEnabled, false); // if non-stop mode is activated, set up the flags for insistent // notification, and increase the volume to full volume, so the user // will actually pay attention to the alarm @@ -225,6 +227,7 @@ public class Notifications extends BroadcastReceiver { notification.audioStreamType = AudioManager.STREAM_ALARM; audioManager.setStreamVolume(AudioManager.STREAM_ALARM, audioManager.getStreamMaxVolume(AudioManager.STREAM_ALARM), 0); + voiceReminder = false; } else { notification.audioStreamType = AudioManager.STREAM_NOTIFICATION; } @@ -232,10 +235,12 @@ public class Notifications extends BroadcastReceiver { // quiet hours = no sound if(quietHours || callState != TelephonyManager.CALL_STATE_IDLE) { notification.sound = null; + voiceReminder = false; } else { String notificationPreference = Preferences.getStringValue(R.string.p_rmd_ringtone); if(audioManager.getStreamVolume(AudioManager.STREAM_RING) == 0) { notification.sound = null; + voiceReminder = false; } else if(notificationPreference != null) { if(notificationPreference.length() > 0) { Uri notificationSound = Uri.parse(notificationPreference); @@ -266,6 +271,17 @@ public class Notifications extends BroadcastReceiver { Log.w("Astrid", "Logging notification: " + text); //$NON-NLS-1$ //$NON-NLS-2$ notificationManager.notify(notificationId, notification); + if (voiceReminder) { + while (audioManager.getMode() == AudioManager.MODE_RINGTONE) { + try { + Thread.sleep(50); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + VoiceOutputAssistant.getInstance().queueSpeak(text); + } } /** diff --git a/astrid/res/drawable/Thumbs.db b/astrid/res/drawable/Thumbs.db new file mode 100644 index 0000000000000000000000000000000000000000..6fc9a28a13d79752df97146a1702d86896d98166 GIT binary patch literal 65024 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 z zD0EUQ?ndw(6{dUC{_69c`i)oo49b>|sk^~;RX3(Wm-5o(IgcR%wq~%inaLE}mz&qBwJcZ96nV7i3sCC9I641_<5~G=n%l)O>+<&gT|7Qr)*T+VVFzA%zJ{!t1Xp!)0)lDF^Fp3A_ z*1vMjJ@&t{r-Zli?N7LIR zsham*^dTklM@^I7Sx;~Y*p$XS2t)Dv9^-<(P2((;2b1q>7zT&V8b=(x$6h?6Oyfkelzb6NOe&22Ta8y>GCi?kwbnOB?uhWGPF4l?ur70|I66K#u zP)@2(8L<+(j%l>WFp(D*=R5v^PoG_eZ9{Ta-Ap9Y>Fl!i2zqpvJ7ogPuJiP>V(}E0 zo$)(|o7X$C@3vbBE?TTvCi;9~EsuFd;x&PJXErBT*yb?m&^FWAKMZ-K9o1|)Jrzr| z$AtM-b9LP*6ZcLwd($Gl9d^-E{~4*RrbrTmX*pT+uu+MA?#}#%%gd+A45tfT%%pO8 z@(H(mmtp&~2q)_w4^f!raQP@NOGD4M&pe41vENtqh0vZpo5xl%=ehXxx61vS`5&U6q=SW-a`)v*azZb^mmbWCHZm|hd!qo-&?+9!TdVE&M*B3 z%ufRIFaO&AUFHv%{}{N}3jO-}f82i${Ym22KlOJ%;<5}NF1wHF&%FPwN}pt~0;N_W ziR4;Z{EPk$G9BRnIT3M*x#2c0#<*YI^>wA}o%-p(d+MUMJwDjBu;Sm}Tev)a2eIBr z_YOB}EH;tt!ucvV!?Ry&2t{-bjp155_Vv*H1f(PH2j2enN$hGuPjI1X(=2<|R&JEE zdGXGsT>gKjKY zK_4mCTv@`EjI`Gn8jlh5)jnYf=(0N@<0M4f=+l9yTN`H1Az+825d2)wOqx-4o`74A zKGAh&c>TMXd?^Wu?JVg-DAmTsgjPyAXBjNq;4LWP)l+sd=gD}~(ULgK zee?FYW&Q~~nvwuR>oVkb%d0UynEg0d7T(0D*0N+Ip-NfJ+pKiE45WS+z-qs^st74H z^2s<@&pyJO^z4x&M%U(M-P9b6rD~|A`M%FRqY@z&OPxeH;l{FU1@!BU7+8FtT8=TcuGB9p}X=G!}TS2o#ibo#p3A;JMDb+3kXjB zoH0Yi)lVB)sH5(M24`1aEwFgXlAy)SWt)x6C=C3{2V6K-Ri#jEP)k}pR>4^NC1nYf zZ8_CBe1||LWqH$vepQ4{g7uLsvi^J5?nIJKDTi)cp*`#(w<+N&Mlk?;yspgSNR3_3 zz!%E>sMBiZC$huy+4XOJf_`QcoInS{Ddnv&qom6=_uQvAS!IK@yR%1fnLM=YDDM1K z_s#Ba81VTNe&*e`oQ|Y+u@Dg}`mr3n_6MNM?B?S&$!Zc;e{aqELCFCmey*+Eb(8|9 zJcWK98{(jpJce+-E<{yc`oTg-Ab2=ch$=$*O{CenYhfvO(DSS9@b!C=^dU)LXhuAOa3PU>c z0YP4$wCnNwm#@&aJp3o}rsU-Vd1(LIQ~Z~V=s%|czYVS*P?zwy{&!LFt6jugu3JmFLIBRaS8)%TtIW) z9z>G05sj1k05+ytv}x{I#l=mD;*Y?Q^GdxoHV+>+BKhviHc3SbWZyH=)A5Eo@Ae#w z9#_oNxE^9R#T8sAC{i|62c1qvfL?Jl`cQ(2#di+-=kn)>X`rm9%2|cfT@|TQjo+Un z!EIe0$z{H_bhlb>Q2L@p*nSvuZ(2sPhGPlayeV{R1xi9{uHHi&q9N>{hC}!z$GGwc zU*Rms@dePyD)`;%B$DgY+@xqQ6ypjsSdD#4eZw4W6;B~p2EnP?2* z18NwBs<9p$q???3Qq5~PUb;g4lbFR@rd){OHu)=uYb zPH$jr3d81H_pkVPl7^-7+6VRu|ZHf`W+z_;u$_aJG7^sICj?@~CREp1 zkL^J|%^f41^$A2QXw@8(H-|3BdhbEf=(UZtnvE{dJREpMWX0FVWt6vChR%leWicb&A;;$&({TtVqY-IlXaaOsF;vwkv>kKNG;NT-! z!*21Hy)=HQUor!VpnISe*zzy?Y>@ZkHV5b3|BF`Wm)w`^@3LCvV7fEpDS>VKXP*0q z{hI*(;2#nHq5!@i&~rd20aO5LzlplIIZyM>T*b z;5^_0Knx%bkN`*mqyS`nvkX8MAP2YzkOwFL6ai4cCBS9C6@U`pDnJ>a0#F5z=Od#5 z{;dhP2G9ar2fzR~0NMZ@z)b-8+&|m+=gS~|sekyn{`t@GzoiwAN4VdQHqjx{efrFJ z{K>m_FbFMlf`xm7r5B3f>l>KYG(Fvlsk(=Z1O4f<79~Ajp)tXzUD^dSjtN;;SWNHd z!RiMst|{P&{B`|8>KLLK%uQ{6?I_ZCe8x7#xzu3X9Bbmo1RUh^=oVbfI%}Wx9^}Gv zFA?Z>!FR}UU=e6xIAyRqDEX6|Rz6Z&9r7G#W%?Oum_uluS|uzkknq}-}Ylk(s?dU{uOuu z6|eKf8seRezJ3DTEnk7ss7u^IH5jA39XTWrw-3Cw0Skrxui!;czuSXErekV|fzdU) zCf>>qN-4KeUD8s)JSYG4OG|Mh;3-{k^_ZS1RTSaWTZDb>`g}RfbIHR3ea01ZO|K`P z%&LXQ=L|k+yuo;hQnr>;5L8zO^b+j%AW6m(dk_*`Ud@hJkiaGA#3u4M=nJEVLxR>( zi>st-DI{)vYz@iJK41@m6RF;VOtu zB9-Rq-Obyg1U%fat=LqUdtDI$?(6&exd|p$+9!MEajOkuIB8JFJh_+|aYP`r;WkIN z{N>j*JI`ZA+moKwX?obKKoz03t6??FBu>KZo{g$wlR#a!sM0l=kX(#CJgsrNT}J52 zr~M9ZEC;l2+Y^{9QDM9GMBg`{4RLUgAfCt1`b(+0U#9lxb%AAch~?2x`MTme*^yA~ z%?UIcB9c_skKC1AP-A^;u*CuH&CVm=(St_*1;jXKVf0p54ixR~I`aW0<$hkeb;wQp zjzyr%Z0Vivg1B_qJqR+7BhnX=@%B{ zvgC7r5AsnZ%tzj;ne^rca$dQS360T4?}!Zk=G{a+LryC_+k@p z;9AW*l3H>QA(Ay_eCBz^2;10eV$@hCUV|-m;iXR8H&-*D5olFBhwP-UL*foPgOV;L zzMIZ==7x7u~_q6R45lWqrUlb~JD2LoqtY!1UaJ2^fJ21W%@eU^sWthK^kd z91M7xBFLT7L42eyACVo;!Pex0gr|$Zc+XAYzil)xRp3Ps`XpNIaRt~UvSCEYptFQ1 zt{YjOKZLmcAZOWVl-ZL$FV;NRLOva*rfkNAfM^0$>GM8e`@^J z=lh>}&VS{3{+;>%RsT;6c-s`GfrOHRl7V8c0rIncgo=h-7elen4M532MSbeRe#Xn% zGr?uvDj7w zUNnY#yLc-=sdF+CpS(43&3q41Rm_#b6FU#=Xn9{1aO9Ngt7$RIb6d9C4RDtE#KWGQ zjYe#_At4P6-zwh(^qTk~pi58j=W#c;-u9p;X+m6f?0i@rY&(4??Zc>trxf~k?;qTb zq~d9hHl$=ZmlY^&cl?U487_S*AJY*gxtL?$fG(o{NPpBu-`J-sW1)tL!0+rR)q9{~ z+C^wUSC?I$n(|FH8utJeP_tph4S`~&aY}oT(EhPN{8_?LWm?4}2rAzT9sWXrgwMBX zFsUb8TUM4*KK#>9$nQSfo#!`f& z>=(;oX88KK?sNp~c7HFrsCijw94yAJR90e2+S_w8)1i^?9>`@YDwEJ>)rN%J6+Rw+ z8U8}W>A;EfhSdT3aj;m}Z_JI;AC&g9zcgZ-g)sf1AT>J(n+U&lO6jP3y+2nk23?P0 zz?Dr{E~}3Z<=9Q+dbv&HaSVA~z$#{^<`H=1?!U<$v~8GzN(7yZD&tZMI~1WWL9sih z%yfgM7S2#J-3Pt12f?4Hq}hX{3Y%tv%H47{qMr}@9%LAp-0N_{g~uY|TTgn7pVTT6j=!R1q+aFsQ5H5=bN;qN5Dqs=5(RwH`L(1#T{U%2H9 zz4ZY2hoIvsiI$l&rXK0XO(VG|EOHS_hhv=-dHnA7;J}rFddquuXF>!{`!ekR-bvn{ zQcs_74Bi}VN{>;=!qAvhloeysAD0|&EGrRxnlZojrKj1Ap0IJ{zMVc!gL^VBNAV<% zP6?MXo?}_(!&_*gs?@t2+qWsmqXgL;uxCa4lE#F|@d`(&mIBJ#PDu z#`%KuWr{RE?e8?-_{T6QAWhw|%S3`ACofx}34xcmGq`xLTrm@e_wPZB`!?yk1=aHX zaz*m&rWqO!S&M!CjDYA*r=DqIxSzr&!%%1!)_?%v3F+GDrDMZYdhFd+FJS=*pP}{GhFqp37($q!$5MNodT5cU} zWhThqK(Y7j_ntL5E6YbVJ-=3O*!{&9|9w5|?=91h^~+OWPydMc^}lMrj)MsFPx}Q7 fcgde$KXfxWh!64`DF5*i|Mx$zKR^53JMe!1GsdWt literal 0 HcmV?d00001 diff --git a/astrid/res/drawable/ic_btn_speak_now.png b/astrid/res/drawable/ic_btn_speak_now.png new file mode 100644 index 0000000000000000000000000000000000000000..83ee68b5d1f5ccca87d156e682f32f091619ceb7 GIT binary patch literal 954 zcmV;r14aCaP)Z|f_uko1XpHsEzTcIa4p!2fGBF(_uI@lO$wr8 zYqWz6MOSxK-CO6>se7*{pU;c0czmT!92y!5dA(kvqM{pu+ZEbC^zP?^|c6JK91&|5QXODGsblB*QkB>w6Ot^9c z!&+lwBOQv$%1YaCF$+>kaesd=U5kOn=|s`>l4?3uW3E+4V+zSjN2OkmcO5B1kn^iX zo&JBykut`&(|B1NDPyl0EE^)_;4MdGMf4VRpF%V^IA}m&8WyKEHa1p@X;T-~2~jv4 z4x%5&8WU^v;(5TmzQq`)#>T!d`nAEcXxL^*rkOq zv)9zrBp)6gp3P-|rSsF%)1GWLYhiP_?(S|olFZJ|dK{@M9B!I{6S+p5ot;G&7Z)v{ ziq+NC*#=aU;5T;;H2U1!oM~CsBsWb&7|;BN{1qyGbC<2Ht?b7`p^!E+GqVCT;kLH6 zOdt>tZ0z&j`1tq=@|A}E59TYLDb^)R%0~+e3)|p3xVyV64i68ltE(%0b8|E8kQAx3 zb$+K%Do8z$@jO%& zr?PMsKj-n3y{_Z4v$G>ur0{GYkPj7*?;;u)7%+ffl1gL!z`BkR0hJGHI=dl)B(}D; zVy+M&q-cc$w=W~#2%NUkZf|cH69u9|6jR+fLeZF_QQj4zqO4WAU<6^PIyyQ^l~oLG co__)i0CI`-J_oP&Gynhq07*qoM6N<$f^Q$SFaQ7m literal 0 HcmV?d00001 diff --git a/astrid/res/layout/task_edit_activity.xml b/astrid/res/layout/task_edit_activity.xml index a06d28bdb..9152eb096 100644 --- a/astrid/res/layout/task_edit_activity.xml +++ b/astrid/res/layout/task_edit_activity.xml @@ -88,6 +88,14 @@ android:layout_height="wrap_content" android:text="@string/TEA_note_label" style="@style/TextAppearance.GEN_EditLabel" /> + + + + + Notizen zu Aufgaben anzeigen + + Spracheingabe aktivieren Notizen werden angezeigt, wenn Sie auf eine Aufgabe tippen Notizen werden immer angezeigt + + Sprachfunktionen + + Mikrofon-Button wird ausgeblendet + + Mikrofon-Button wird angezeigt + Neue Standardeinstellungen für Aufgaben diff --git a/astrid/res/values/keys.xml b/astrid/res/values/keys.xml index 45aa8f343..ca51de520 100644 --- a/astrid/res/values/keys.xml +++ b/astrid/res/values/keys.xml @@ -22,6 +22,9 @@ notification_ringtone + + notification_voice + notif_theme @@ -127,6 +130,9 @@ colorize font_size notesVisible + voiceInputEnabled + voiceInputCreatesTask + voiceRemindersEnabled diff --git a/astrid/res/values/strings-core.xml b/astrid/res/values/strings-core.xml index d0bbc9c67..2927ad48d 100644 --- a/astrid/res/values/strings-core.xml +++ b/astrid/res/values/strings-core.xml @@ -90,6 +90,10 @@ Add to this list... + + Speak to create a task + Speak to add a tasktitle + @@ -209,6 +213,12 @@ Add-ons + + Speak to add to current title + + + Speak to add to current note + Title @@ -330,11 +340,24 @@ Show Notes In Task + + Enable voice-input + + Enable direct task-creation per voice + + Enable voice-reminders Notes will be displayed when you tap a task Notes will always be displayed + + Voice + + Voice-Button will be hidden + + Voice-Button will be shown + New Task Defaults diff --git a/astrid/res/xml/preferences.xml b/astrid/res/xml/preferences.xml index 8acdef547..ac859cf47 100644 --- a/astrid/res/xml/preferences.xml +++ b/astrid/res/xml/preferences.xml @@ -26,4 +26,19 @@ android:defaultValue="true" /> + + + + + diff --git a/astrid/src/com/todoroo/astrid/activity/EditPreferences.java b/astrid/src/com/todoroo/astrid/activity/EditPreferences.java index 00c35aa71..0e455a2f4 100644 --- a/astrid/src/com/todoroo/astrid/activity/EditPreferences.java +++ b/astrid/src/com/todoroo/astrid/activity/EditPreferences.java @@ -17,9 +17,9 @@ import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.os.Bundle; import android.preference.Preference; +import android.preference.Preference.OnPreferenceClickListener; import android.preference.PreferenceCategory; import android.preference.PreferenceScreen; -import android.preference.Preference.OnPreferenceClickListener; import android.widget.Toast; import com.timsu.astrid.R; @@ -38,6 +38,7 @@ import com.todoroo.astrid.service.StartupService; import com.todoroo.astrid.service.TaskService; import com.todoroo.astrid.utility.Constants; import com.todoroo.astrid.utility.Flags; +import com.todoroo.astrid.voice.VoiceOutputAssistant; /** * Displays the preference screen for users to edit their preferences @@ -200,6 +201,10 @@ public class EditPreferences extends TodorooPreferences { taskService.clearDetails(Criterion.all); Flags.set(Flags.REFRESH); } + } else if (r.getString(R.string.p_voiceRemindersEnabled).equals(preference.getKey())) { + if (value != null && (Boolean)value) + VoiceOutputAssistant.getInstance().checkIsTTSInstalled(); + } else if (r.getString(R.string.p_statistics).equals(preference.getKey())) { if (value != null && !(Boolean)value) @@ -209,5 +214,11 @@ public class EditPreferences extends TodorooPreferences { } } + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + VoiceOutputAssistant.getInstance().handleActivityResult(requestCode, resultCode, data); + super.onActivityResult(requestCode, resultCode, data); + } + } \ No newline at end of file diff --git a/astrid/src/com/todoroo/astrid/activity/TaskEditActivity.java b/astrid/src/com/todoroo/astrid/activity/TaskEditActivity.java index dd28d9b60..7604375d7 100644 --- a/astrid/src/com/todoroo/astrid/activity/TaskEditActivity.java +++ b/astrid/src/com/todoroo/astrid/activity/TaskEditActivity.java @@ -40,6 +40,7 @@ import android.content.IntentFilter; import android.content.res.Resources; import android.graphics.Color; import android.os.Bundle; +import android.speech.RecognizerIntent; import android.text.format.DateUtils; import android.util.Log; import android.view.LayoutInflater; @@ -90,6 +91,7 @@ import com.todoroo.astrid.tags.TagsControlSet; import com.todoroo.astrid.timers.TimerControlSet; import com.todoroo.astrid.ui.DeadlineTimePickerDialog; import com.todoroo.astrid.ui.DeadlineTimePickerDialog.OnDeadlineTimeSetListener; +import com.todoroo.astrid.voice.VoiceInputAssistant; /** * This activity is responsible for creating new tasks and editing existing @@ -154,6 +156,9 @@ public final class TaskEditActivity extends TabActivity { // --- UI components + private ImageButton voiceAddNoteButton; + + private EditTextControlSet notesControlSet = null; private EditText title; private final List controls = @@ -173,6 +178,11 @@ public final class TaskEditActivity extends TabActivity { /** edit control receiver */ private final ControlReceiver controlReceiver = new ControlReceiver(); + /** voice assistant for notes-creation */ + private VoiceInputAssistant voiceNoteAssistant = null; + + private EditText notesEditText; + /* ====================================================================== * ======================================================= initialization * ====================================================================== */ @@ -235,6 +245,15 @@ public final class TaskEditActivity extends TabActivity { controls.add(new ImportanceControlSet(R.id.importance_container)); controls.add(new UrgencyControlSet(R.id.urgency)); + // prepare and set listener for voice-button + voiceAddNoteButton = (ImageButton) findViewById(R.id.voiceAddNoteButton); + notesEditText = (EditText) findViewById(R.id.notes); + int prompt = R.string.TEA_voice_edit_note_prompt; + voiceNoteAssistant = new VoiceInputAssistant(this, voiceAddNoteButton, + notesEditText); + voiceNoteAssistant.setLanguageModel(RecognizerIntent.LANGUAGE_MODEL_FREE_FORM); + voiceNoteAssistant.configureMicrophoneButton(prompt); + new Thread() { @Override public void run() { @@ -253,7 +272,7 @@ public final class TaskEditActivity extends TabActivity { try { if(ProducteevUtilities.INSTANCE.isLoggedIn()) { controls.add(new ProducteevControlSet(TaskEditActivity.this, addonsAddons)); - ((TextView)findViewById(R.id.notes)).setHint(R.string.producteev_TEA_notes); + notesEditText.setHint(R.string.producteev_TEA_notes); ((TextView)findViewById(R.id.notes_label)).setHint(R.string.producteev_TEA_notes); } } catch (Exception e) { @@ -284,7 +303,8 @@ public final class TaskEditActivity extends TabActivity { } }); - controls.add(new EditTextControlSet(Task.NOTES, R.id.notes)); + notesControlSet = new EditTextControlSet(Task.NOTES, R.id.notes); + controls.add(notesControlSet); controls.add( new ReminderControlSet(R.id.reminder_due, R.id.reminder_overdue, R.id.reminder_alarm)); controls.add( new RandomReminderControlSet(R.id.reminder_random, @@ -590,6 +610,18 @@ public final class TaskEditActivity extends TabActivity { populateFields(); } + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + // handle the result of voice recognition, put it into the appropiate textfield + voiceNoteAssistant.handleActivityResult(requestCode, resultCode, data); + + // write the voicenote into the model, or it will be deleted by onResume.populateFields + // (due to the activity-change) + notesControlSet.writeToModel(model); + + super.onActivityResult(requestCode, resultCode, data); + } + @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); @@ -598,6 +630,11 @@ public final class TaskEditActivity extends TabActivity { outState.putParcelable(TASK_IN_PROGRESS, model); } + @Override + protected void onRestoreInstanceState(Bundle inState) { + super.onRestoreInstanceState(inState); + } + @Override protected void onStart() { super.onStart(); diff --git a/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java b/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java index 0c200f4cc..0139ee3f0 100644 --- a/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java +++ b/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java @@ -88,6 +88,7 @@ import com.todoroo.astrid.service.StatisticsService; import com.todoroo.astrid.service.TaskService; import com.todoroo.astrid.utility.Constants; import com.todoroo.astrid.utility.Flags; +import com.todoroo.astrid.voice.VoiceInputAssistant; import com.todoroo.astrid.widget.TasksWidget; /** @@ -157,6 +158,7 @@ public class TaskListActivity extends ListActivity implements OnScrollListener, protected int sortFlags; protected int sortSort; + private ImageButton voiceAddButton; private ImageButton quickAddButton; private EditText quickAddBox; private Timer backgroundTimer; @@ -164,6 +166,7 @@ public class TaskListActivity extends ListActivity implements OnScrollListener, private final TaskListContextMenuExtensionLoader contextMenuExtensionLoader = new TaskListContextMenuExtensionLoader(); + private VoiceInputAssistant voiceInputAssistant; /* ====================================================================== * ======================================================= initialization @@ -374,6 +377,14 @@ public class TaskListActivity extends ListActivity implements OnScrollListener, } }); + // prepare and set listener for voice add button + voiceAddButton = (ImageButton) findViewById(R.id.voiceAddButton); + int prompt = R.string.TLA_voice_edit_prompt; + if (Preferences.getBoolean(R.string.p_voiceInputCreatesTask, false)) + prompt = R.string.TLA_voice_add_prompt; + voiceInputAssistant = new VoiceInputAssistant(this,voiceAddButton,quickAddBox); + voiceInputAssistant.configureMicrophoneButton(prompt); + // set listener for extended add button ((ImageButton)findViewById(R.id.extendedAddButton)).setOnClickListener(new OnClickListener() { public void onClick(View v) { @@ -437,6 +448,12 @@ public class TaskListActivity extends ListActivity implements OnScrollListener, @Override protected void onResume() { super.onResume(); + if (Preferences.getBoolean(R.string.p_voiceInputEnabled, true) && voiceInputAssistant.isVoiceInputAvailable()) { + voiceAddButton.setVisibility(View.VISIBLE); + } else { + voiceAddButton.setVisibility(View.GONE); + } + registerReceiver(detailReceiver, new IntentFilter(AstridApiConstants.BROADCAST_SEND_DETAILS)); registerReceiver(detailReceiver, @@ -549,6 +566,17 @@ public class TaskListActivity extends ListActivity implements OnScrollListener, @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { + // handle the result of voice recognition, put it into the textfield + if (voiceInputAssistant.handleActivityResult(requestCode, resultCode, data)) { + // if user wants, create the task directly (with defaultvalues) after saying it + if (Preferences.getBoolean(R.string.p_voiceInputCreatesTask, false)) + quickAddTask(quickAddBox.getText().toString(), true); + super.onActivityResult(requestCode, resultCode, data); + + // the rest of onActivityResult is totally unrelated to voicerecognition, so bail out + return; + } + super.onActivityResult(requestCode, resultCode, data); if(resultCode != RESULT_CANCELED) { @@ -986,5 +1014,4 @@ public class TaskListActivity extends ListActivity implements OnScrollListener, setUpTaskList(); } - } diff --git a/astrid/src/com/todoroo/astrid/voice/VoiceInputAssistant.java b/astrid/src/com/todoroo/astrid/voice/VoiceInputAssistant.java new file mode 100644 index 000000000..d02abc028 --- /dev/null +++ b/astrid/src/com/todoroo/astrid/voice/VoiceInputAssistant.java @@ -0,0 +1,193 @@ +package com.todoroo.astrid.voice; + +import java.security.InvalidParameterException; +import java.util.ArrayList; +import java.util.List; + +import junit.framework.Assert; +import android.app.Activity; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.speech.RecognizerIntent; +import android.text.Editable; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.EditText; +import android.widget.ImageButton; + +import com.timsu.astrid.R; +import com.todoroo.andlib.utility.Preferences; + +/** + * This class handles taking voice-input and appends the text to the registered EditText-instance. + * You can have multiple VoiceInputAssistants per Activity, just use the additional constructor + * to specify unique requestCodes for the RecognizerIntent (e.g. VoiceInputAssistant.VOICE_RECOGNITION_REQUEST_CODE+i). + * If you have only one VoiceInputAssitant on an Activity, just use the normal constructor. + *

+ * You can query voiceinput-capabilities by calling isVoiceInputAvailable() for external checking, + * but the visibility for the microphone-button specified by the constructor is handled in configureMicrophoneButton(int). + * + * @author Arne Jans + */ +public class VoiceInputAssistant { + + /** requestcode for activityresult from voicerecognizer-intent */ + public static final int VOICE_RECOGNITION_REQUEST_CODE = 1234; + + /** + * This requestcode is used to differentiate between multiple microphone-buttons on a single activity. + * Use the mightier constructor to specify your own requestCode in this case for every additional use on an activity. + * If you only use one microphone-button on an activity, you can leave it to its default, VOICE_RECOGNITION_REQUEST_CODE. + */ + private int requestCode = VOICE_RECOGNITION_REQUEST_CODE; + private final Activity activity; + private final ImageButton voiceButton; + private final EditText textField; + + private boolean voiceInputAvailable; + + private String languageModel = RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH; + + /** + * @param languageModel the languageModel to set + */ + public void setLanguageModel(String languageModel) { + this.languageModel = languageModel; + } + + /** + * @return the languageModel + */ + public String getLanguageModel() { + return languageModel; + } + + /** + * Creates a new VoiceInputAssistance-instance for use with a specified button and textfield. + * If you need more than one microphone-button on a given Activity, use the other constructor. + * + * @param activity the Activity which holds the microphone-buttone and the textField to insert recognized test + * @param voiceButton the microphone-Button + * @param textField the textfield that should get the resulttext + */ + public VoiceInputAssistant(Activity activity, ImageButton voiceButton, EditText textField) { + Assert.assertNotNull("Each VoiceInputAssistant must be bound to an activity!", activity); + Assert.assertNotNull("A VoiceInputAssistant without a voiceButton makes no sense!", voiceButton); + Assert.assertNotNull("You have to specify a textfield that is bound to this VoiceInputAssistant!!", textField); + this.activity = activity; + this.voiceButton = voiceButton; + this.textField = textField; + } + + /** + * The param requestCode is used to differentiate between multiple + * microphone-buttons on a single activity. + * Use the this constructor to specify your own requestCode in + * this case for every additional use on an activity. + * If you only use one microphone-button on an activity, + * you can leave it to its default, VOICE_RECOGNITION_REQUEST_CODE. + * + * + * @param activity + * @param voiceButton + * @param textField + * @param requestCode has to be unique in a single Activity-context, + * dont use VOICE_RECOGNITION_REQUEST_CODE, this is reserved for the other constructor + */ + public VoiceInputAssistant(Activity activity, ImageButton voiceButton, EditText textField, int requestCode) { + this(activity, voiceButton, textField); + if (requestCode == VOICE_RECOGNITION_REQUEST_CODE) + throw new InvalidParameterException("You have to specify a unique requestCode for this VoiceInputAssistant!"); + this.requestCode = requestCode; + } + + /** + * Fire an intent to start the speech recognition activity. + * This is fired by the listener on the microphone-button. + * + * @param prompt Specify the R.string.string_id resource for the prompt-text during voice-recognition here + */ + public void startVoiceRecognitionActivity(int prompt) { + Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); + intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, languageModel); + intent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 1); + intent.putExtra(RecognizerIntent.EXTRA_PROMPT, activity.getString(prompt)); + activity.startActivityForResult(intent, requestCode); + } + + /** + * This callback-method has to be called from Activity.onActivityResult within your activity + * with parameters directly on passthru.
+ * You can check in your activity if it was really a RecognizerIntent that was handled here, + * if so, this method returns true. In this case, you should call super.onActivityResult in your + * activity.onActivityResult. + *

+ * If this method returns false, then it wasnt a request with a RecognizerIntent, so you can handle + * these other requests as you need. + * + * @param requestCode if this equals the requestCode specified by constructor, then results of voice-recognition + * @param resultCode + * @param data + * @return + */ + public boolean handleActivityResult(int requestCode, int resultCode, Intent data) { + boolean result = false; + // handle the result of voice recognition, put it into the textfield + if (requestCode == this.requestCode) { + // this was handled here, even if voicerecognition fails for any reason + // so your program flow wont get chaotic if you dont explicitly state + // your own requestCodes. + result = true; + if (resultCode == Activity.RESULT_OK) { + // Fill the quickAddBox-view with the string the recognizer thought it could have heard + ArrayList match = data.getStringArrayListExtra( + RecognizerIntent.EXTRA_RESULTS); + // make sure we only do this if there is SomeThing (tm) returned + if (match != null && match.size() > 0 && match.get(0).length() > 0) { + Editable currentText = textField.getText(); + String recognizedSpeech = match.get(0); + + if (currentText.length() > 0) { + // if something is already typed in, append the recognized speech, + // add a space if it isn't already there + textField.append((currentText.toString().endsWith(" ") ? recognizedSpeech : " "+recognizedSpeech )); + } else { + textField.setText(recognizedSpeech); + } + } + } + } + + return result; + } + + /** + * Call this to see if your phone supports voiceinput in its current configuration. + * If this method returns false, it could also mean that Google Voicesearch is simply + * not installed. + * If this method returns true, internal use of it enables the registered microphone-button. + * + * @return whether this phone supports voiceinput + */ + public boolean isVoiceInputAvailable() { + // Check to see if a recognition activity is present + PackageManager pm = activity.getPackageManager(); + List activities = pm.queryIntentActivities( + new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), 0); + return (activities.size() != 0); + } + + public void configureMicrophoneButton(final int prompt) { + if (Preferences.getBoolean(R.string.p_voiceInputEnabled, true) && isVoiceInputAvailable()) { + voiceButton.setVisibility(View.VISIBLE); + voiceButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + startVoiceRecognitionActivity(prompt); + } + }); + } else { + voiceButton.setVisibility(View.GONE); + } + } +} diff --git a/astrid/src/com/todoroo/astrid/voice/VoiceOutputAssistant.java b/astrid/src/com/todoroo/astrid/voice/VoiceOutputAssistant.java new file mode 100644 index 000000000..a905e3033 --- /dev/null +++ b/astrid/src/com/todoroo/astrid/voice/VoiceOutputAssistant.java @@ -0,0 +1,135 @@ +/** + * + */ +package com.todoroo.astrid.voice; + +import java.util.HashMap; +import java.util.Locale; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.media.AudioManager; +import android.speech.tts.TextToSpeech; +import android.speech.tts.TextToSpeech.OnInitListener; +import android.util.Log; + +import com.todoroo.andlib.service.ContextManager; + +/** + * @author Arne Jans + * + */ +public class VoiceOutputAssistant implements OnInitListener { + + private static final int MY_DATA_CHECK_CODE = 2534; + private static final String TAG = "Astrid.VoiceOutputAssistant"; + private final Context context; + private static VoiceOutputAssistant instance = null; + private TextToSpeech mTts; + private boolean isTTSInitialized; + private boolean retryLastSpeak; + private String textToSpeak; + private static final HashMap ttsParams = new HashMap(); + + static { + ttsParams.put(TextToSpeech.Engine.KEY_PARAM_STREAM, + String.valueOf(AudioManager.STREAM_NOTIFICATION)); + } + + private VoiceOutputAssistant() { + this.context = ContextManager.getContext().getApplicationContext(); + } + + public static VoiceOutputAssistant getInstance() { + if (instance == null) { + instance = new VoiceOutputAssistant(); + } + return instance; + } + + public void checkIsTTSInstalled() { + if (!isTTSInitialized && context instanceof Activity) { + Intent checkIntent = new Intent(); + checkIntent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA); + ((Activity) context).startActivityForResult(checkIntent, + MY_DATA_CHECK_CODE); + } + } + + public boolean handleActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == MY_DATA_CHECK_CODE) { + if (resultCode == TextToSpeech.Engine.CHECK_VOICE_DATA_PASS) { + // success, create the TTS instance + initTTS(); + } else { + // missing data, install it + Intent installIntent = new Intent(); + installIntent.setAction(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA); + context.startActivity(installIntent); + } + + return true; + } + + return false; + } + + private void initTTS() { + mTts = new TextToSpeech(context, (OnInitListener)this); + } + + public void queueSpeak(String textToSpeak) { + if (mTts != null && isTTSInitialized) { + mTts.speak(textToSpeak, TextToSpeech.QUEUE_ADD, ttsParams); + while (mTts.isSpeaking()) { + try { + Thread.sleep(50); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } else { + retryLastSpeak = true; + this.textToSpeak = textToSpeak; + initTTS(); + } + } + + @Override + public void onInit(int status) { + // status can be either TextToSpeech.SUCCESS or TextToSpeech.ERROR. + if (status == TextToSpeech.SUCCESS) { + // Set preferred language to US english. + // Note that a language may not be available, and the result will indicate this. + int result = mTts.setLanguage(Locale.getDefault()); + // Try this someday for some interesting results. + // int result mTts.setLanguage(Locale.FRANCE); + if (result == TextToSpeech.LANG_MISSING_DATA || + result == TextToSpeech.LANG_NOT_SUPPORTED) { + // Language data is missing or the language is not supported. + Log.e(TAG, "Language is not available."); + } else { + // Check the documentation for other possible result codes. + // For example, the language may be available for the locale, + // but not for the specified country and variant. + + mTts.speak("", 0, null); + + // The TTS engine has been successfully initialized. + isTTSInitialized = true; + // if this request came from queueSpeak, then speak it and reset the memento + if (retryLastSpeak && this.textToSpeak != null) { + this.queueSpeak(this.textToSpeak); + retryLastSpeak = false; + textToSpeak = null; + } + } + } else { + // Initialization failed. + Log.e(TAG, "Could not initialize TextToSpeech."); + } + } + +} From e9bcec144483e8a29a89aa0fcaf5187ac2e1519c Mon Sep 17 00:00:00 2001 From: Arne Jans Date: Wed, 3 Nov 2010 22:57:08 +0100 Subject: [PATCH 2/3] some polishing on voice-features. please test with a fresh phone that doesnt have voicesearch and/or text-to-speech installed --- .../astrid/reminders/Notifications.java | 2 + astrid/res/values/strings-core.xml | 4 ++ astrid/res/xml/preferences.xml | 4 +- .../astrid/activity/EditPreferences.java | 52 ++++++++++++++++++- .../astrid/voice/VoiceInputAssistant.java | 12 +++++ .../astrid/voice/VoiceOutputAssistant.java | 11 ++++ 6 files changed, 82 insertions(+), 3 deletions(-) diff --git a/astrid/plugin-src/com/todoroo/astrid/reminders/Notifications.java b/astrid/plugin-src/com/todoroo/astrid/reminders/Notifications.java index d781d104c..fe1df45e5 100644 --- a/astrid/plugin-src/com/todoroo/astrid/reminders/Notifications.java +++ b/astrid/plugin-src/com/todoroo/astrid/reminders/Notifications.java @@ -95,6 +95,8 @@ public class Notifications extends BroadcastReceiver { if(!showTaskNotification(id, type, reminder)) { notificationManager.cancel((int)id); } + // shutdown the VoiceOutputAssistant for now + VoiceOutputAssistant.getInstance().onDestroy(); } // --- notification creation diff --git a/astrid/res/values/strings-core.xml b/astrid/res/values/strings-core.xml index 2927ad48d..956e13c1b 100644 --- a/astrid/res/values/strings-core.xml +++ b/astrid/res/values/strings-core.xml @@ -340,6 +340,10 @@ Show Notes In Task + + Voice-input is not installed.\nDo you want to go to the market and install it? + + Unfortunately voice-input is not available for your system.\nIf possible, please update your system to 2.1 or later. Enable voice-input diff --git a/astrid/res/xml/preferences.xml b/astrid/res/xml/preferences.xml index ac859cf47..dbdc45d0d 100644 --- a/astrid/res/xml/preferences.xml +++ b/astrid/res/xml/preferences.xml @@ -31,7 +31,7 @@ + android:defaultValue="false" /> + android:defaultValue="false" /> diff --git a/astrid/src/com/todoroo/astrid/activity/EditPreferences.java b/astrid/src/com/todoroo/astrid/activity/EditPreferences.java index 0e455a2f4..8998a0e8e 100644 --- a/astrid/src/com/todoroo/astrid/activity/EditPreferences.java +++ b/astrid/src/com/todoroo/astrid/activity/EditPreferences.java @@ -11,11 +11,15 @@ import java.util.Map.Entry; import org.weloveastrid.rmilk.MilkPreferences; import org.weloveastrid.rmilk.MilkUtilities; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Resources; +import android.net.Uri; import android.os.Bundle; +import android.preference.CheckBoxPreference; import android.preference.Preference; import android.preference.Preference.OnPreferenceClickListener; import android.preference.PreferenceCategory; @@ -27,6 +31,7 @@ import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.ContextManager; import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.andlib.sql.Criterion; +import com.todoroo.andlib.utility.AndroidUtilities; import com.todoroo.andlib.utility.DialogUtilities; import com.todoroo.andlib.utility.Preferences; import com.todoroo.andlib.widget.TodorooPreferences; @@ -38,6 +43,7 @@ import com.todoroo.astrid.service.StartupService; import com.todoroo.astrid.service.TaskService; import com.todoroo.astrid.utility.Constants; import com.todoroo.astrid.utility.Flags; +import com.todoroo.astrid.voice.VoiceInputAssistant; import com.todoroo.astrid.voice.VoiceOutputAssistant; /** @@ -58,6 +64,8 @@ public class EditPreferences extends TodorooPreferences { @Autowired private Database database; + private VoiceInputAssistant voiceInputAssistant; + public EditPreferences() { DependencyInjectionService.getInstance().inject(this); } @@ -74,6 +82,7 @@ public class EditPreferences extends TodorooPreferences { ContextManager.setContext(this); PreferenceScreen screen = getPreferenceScreen(); + voiceInputAssistant = new VoiceInputAssistant(this); // load plug-ins Intent queryIntent = new Intent(AstridApiConstants.ACTION_SETTINGS); @@ -190,7 +199,7 @@ public class EditPreferences extends TodorooPreferences { } @Override - public void updatePreferences(Preference preference, Object value) { + public void updatePreferences(final Preference preference, Object value) { Resources r = getResources(); if (r.getString(R.string.p_showNotes).equals(preference.getKey())) { if (value != null && !(Boolean)value) @@ -201,6 +210,47 @@ public class EditPreferences extends TodorooPreferences { taskService.clearDetails(Criterion.all); Flags.set(Flags.REFRESH); } + } else if (r.getString(R.string.p_voiceInputEnabled).equals(preference.getKey())) { + if (value != null && (Boolean)value) + if (!voiceInputAssistant.isVoiceInputAvailable()) { + // voicesearch available since 2.1 + if (AndroidUtilities.getSdkVersion() > 6) { + + DialogUtilities.okCancelDialog(this, + r.getString(R.string.EPr_voiceInputInstall_dlg), + new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, + int which) { + // User wants to install voicesearch, take him to the market + Intent marketIntent = new Intent(Intent.ACTION_VIEW, + Uri.parse("market://search?q=pname:" + //$NON-NLS-1$ + "com.google.android.voicesearch.x")); + startActivity(marketIntent); + } + }, + new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, + int which) { + ((CheckBoxPreference)preference).setChecked(false); + dialog.dismiss(); + } + }); + } else { + DialogUtilities.okDialog(this, + r.getString(R.string.EPr_voiceInputUnavailable_dlg), + new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, + int which) { + ((CheckBoxPreference)preference).setChecked(false); + dialog.dismiss(); + } + }); + + } + } } else if (r.getString(R.string.p_voiceRemindersEnabled).equals(preference.getKey())) { if (value != null && (Boolean)value) VoiceOutputAssistant.getInstance().checkIsTTSInstalled(); diff --git a/astrid/src/com/todoroo/astrid/voice/VoiceInputAssistant.java b/astrid/src/com/todoroo/astrid/voice/VoiceInputAssistant.java index d02abc028..53524a03e 100644 --- a/astrid/src/com/todoroo/astrid/voice/VoiceInputAssistant.java +++ b/astrid/src/com/todoroo/astrid/voice/VoiceInputAssistant.java @@ -63,6 +63,18 @@ public class VoiceInputAssistant { return languageModel; } + /** + * Creates a new VoiceInputAssistant-instance simply for checking the availability of the + * RecognizerService. This is used for Preferences-Screens that dont want to provide + * a microphone-button themselves. + */ + public VoiceInputAssistant(Activity activity) { + Assert.assertNotNull("Each VoiceInputAssistant must be bound to an activity!", activity); + this.activity = activity; + this.voiceButton = null; + this.textField = null; + } + /** * Creates a new VoiceInputAssistance-instance for use with a specified button and textfield. * If you need more than one microphone-button on a given Activity, use the other constructor. diff --git a/astrid/src/com/todoroo/astrid/voice/VoiceOutputAssistant.java b/astrid/src/com/todoroo/astrid/voice/VoiceOutputAssistant.java index a905e3033..476a45728 100644 --- a/astrid/src/com/todoroo/astrid/voice/VoiceOutputAssistant.java +++ b/astrid/src/com/todoroo/astrid/voice/VoiceOutputAssistant.java @@ -132,4 +132,15 @@ public class VoiceOutputAssistant implements OnInitListener { } } + /** + * Has to be called in onDestroy of the activity that uses this instance of VoiceOutputAssistant. + */ + public void onDestroy() { + if (mTts != null && isTTSInitialized) { + mTts.shutdown(); + mTts = null; + isTTSInitialized = false; + } + } + } From b55a819f859d008e1c76163ed5f88a473924a4b1 Mon Sep 17 00:00:00 2001 From: Arne Jans Date: Sun, 7 Nov 2010 22:23:58 +0100 Subject: [PATCH 3/3] changed title of voiceprompt and placement of microphone-button in taskedit, handle the case a device doesnt have market installed and would crash with ActivityNotFoundException --- astrid/res/layout/task_edit_activity.xml | 43 +++++++++++-------- astrid/res/values/strings-core.xml | 6 ++- .../astrid/activity/EditPreferences.java | 20 +++++++-- 3 files changed, 45 insertions(+), 24 deletions(-) diff --git a/astrid/res/layout/task_edit_activity.xml b/astrid/res/layout/task_edit_activity.xml index 9152eb096..db68ed98c 100644 --- a/astrid/res/layout/task_edit_activity.xml +++ b/astrid/res/layout/task_edit_activity.xml @@ -88,26 +88,31 @@ android:layout_height="wrap_content" android:text="@string/TEA_note_label" style="@style/TextAppearance.GEN_EditLabel" /> - - - - + android:layout_height="wrap_content"> + + + + + Add to this list... - Speak to create a task - Speak to add a tasktitle + Speak to add a task + Speak to add to tasktitle @@ -344,6 +344,8 @@ Voice-input is not installed.\nDo you want to go to the market and install it? Unfortunately voice-input is not available for your system.\nIf possible, please update your system to 2.1 or later. + + Unfortunately the market is not available for your system.\nIf possible, try downloading voicesearch from another source. Enable voice-input diff --git a/astrid/src/com/todoroo/astrid/activity/EditPreferences.java b/astrid/src/com/todoroo/astrid/activity/EditPreferences.java index 8998a0e8e..464282929 100644 --- a/astrid/src/com/todoroo/astrid/activity/EditPreferences.java +++ b/astrid/src/com/todoroo/astrid/activity/EditPreferences.java @@ -11,6 +11,7 @@ import java.util.Map.Entry; import org.weloveastrid.rmilk.MilkPreferences; import org.weloveastrid.rmilk.MilkUtilities; +import android.content.ActivityNotFoundException; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.Intent; @@ -200,7 +201,7 @@ public class EditPreferences extends TodorooPreferences { @Override public void updatePreferences(final Preference preference, Object value) { - Resources r = getResources(); + final Resources r = getResources(); if (r.getString(R.string.p_showNotes).equals(preference.getKey())) { if (value != null && !(Boolean)value) preference.setSummary(R.string.EPr_showNotes_desc_disabled); @@ -222,11 +223,25 @@ public class EditPreferences extends TodorooPreferences { @Override public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); // User wants to install voicesearch, take him to the market Intent marketIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("market://search?q=pname:" + //$NON-NLS-1$ "com.google.android.voicesearch.x")); - startActivity(marketIntent); + try { + startActivity(marketIntent); + } catch (ActivityNotFoundException ane) { + DialogUtilities.okDialog(EditPreferences.this, + r.getString(R.string.EPr_marketUnavailable_dlg), + new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, + int which) { + ((CheckBoxPreference)preference).setChecked(false); + dialog.dismiss(); + } + }); + } } }, new OnClickListener() { @@ -248,7 +263,6 @@ public class EditPreferences extends TodorooPreferences { dialog.dismiss(); } }); - } } } else if (r.getString(R.string.p_voiceRemindersEnabled).equals(preference.getKey())) {