diff options
10 files changed, 437 insertions, 66 deletions
diff --git a/api/current.txt b/api/current.txt index 3c95508..5a64d43 100644 --- a/api/current.txt +++ b/api/current.txt @@ -5029,6 +5029,11 @@ package android.app { method public boolean[] supportsCommands(java.lang.String[]); } + public static class VoiceInteractor.AbortVoiceRequest extends android.app.VoiceInteractor.Request { + ctor public VoiceInteractor.AbortVoiceRequest(java.lang.CharSequence, android.os.Bundle); + method public void onAbortResult(android.os.Bundle); + } + public static class VoiceInteractor.CommandRequest extends android.app.VoiceInteractor.Request { ctor public VoiceInteractor.CommandRequest(java.lang.String, android.os.Bundle); method public void onCommandResult(android.os.Bundle); @@ -26261,16 +26266,17 @@ package android.service.voice { method public android.view.LayoutInflater getLayoutInflater(); method public android.app.Dialog getWindow(); method public void hideWindow(); + method public void onAbortVoice(android.service.voice.VoiceInteractionSession.Caller, android.service.voice.VoiceInteractionSession.Request, java.lang.CharSequence, android.os.Bundle); method public void onBackPressed(); method public abstract void onCancel(android.service.voice.VoiceInteractionSession.Request); method public void onCloseSystemDialogs(); method public abstract void onCommand(android.service.voice.VoiceInteractionSession.Caller, android.service.voice.VoiceInteractionSession.Request, java.lang.String, android.os.Bundle); method public void onComputeInsets(android.service.voice.VoiceInteractionSession.Insets); - method public abstract void onConfirm(android.service.voice.VoiceInteractionSession.Caller, android.service.voice.VoiceInteractionSession.Request, java.lang.String, android.os.Bundle); + method public abstract void onConfirm(android.service.voice.VoiceInteractionSession.Caller, android.service.voice.VoiceInteractionSession.Request, java.lang.CharSequence, android.os.Bundle); method public void onCreate(android.os.Bundle); method public android.view.View onCreateContentView(); method public void onDestroy(); - method public abstract boolean[] onGetSupportedCommands(android.service.voice.VoiceInteractionSession.Caller, java.lang.String[]); + method public boolean[] onGetSupportedCommands(android.service.voice.VoiceInteractionSession.Caller, java.lang.String[]); method public boolean onKeyDown(int, android.view.KeyEvent); method public boolean onKeyLongPress(int, android.view.KeyEvent); method public boolean onKeyMultiple(int, int, android.view.KeyEvent); @@ -26297,6 +26303,7 @@ package android.service.voice { } public static class VoiceInteractionSession.Request { + method public void sendAbortVoiceResult(android.os.Bundle); method public void sendCancelResult(); method public void sendCommandResult(boolean, android.os.Bundle); method public void sendConfirmResult(boolean, android.os.Bundle); diff --git a/core/java/android/app/VoiceInteractor.java b/core/java/android/app/VoiceInteractor.java index fe85ef4..f332c9d 100644 --- a/core/java/android/app/VoiceInteractor.java +++ b/core/java/android/app/VoiceInteractor.java @@ -33,7 +33,26 @@ import com.android.internal.os.SomeArgs; import java.util.ArrayList; /** - * Interface for an {@link Activity} to interact with the user through voice. + * Interface for an {@link Activity} to interact with the user through voice. Use + * {@link android.app.Activity#getVoiceInteractor() Activity.getVoiceInteractor} + * to retrieve the interface, if the activity is currently involved in a voice interaction. + * + * <p>The voice interactor revolves around submitting voice interaction requests to the + * back-end voice interaction service that is working with the user. These requests are + * submitted with {@link #submitRequest}, providing a new instance of a + * {@link Request} subclass describing the type of operation to perform -- currently the + * possible requests are {@link ConfirmationRequest} and {@link CommandRequest}. + * + * <p>Once a request is submitted, the voice system will process it and evetually deliver + * the result to the request object. The application can cancel a pending request at any + * time. + * + * <p>The VoiceInteractor is integrated with Activity's state saving mechanism, so that + * if an activity is being restarted with retained state, it will retain the current + * VoiceInteractor and any outstanding requests. Because of this, you should always use + * {@link Request#getActivity() Request.getActivity} to get back to the activity of a + * request, rather than holding on to the actvitity instance yourself, either explicitly + * or implicitly through a non-static inner class. */ public class VoiceInteractor { static final String TAG = "VoiceInteractor"; @@ -62,6 +81,16 @@ public class VoiceInteractor { request.clear(); } break; + case MSG_ABORT_VOICE_RESULT: + request = pullRequest((IVoiceInteractorRequest)args.arg1, true); + if (DEBUG) Log.d(TAG, "onAbortVoice: req=" + + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request + + " result=" + args.arg1); + if (request != null) { + ((AbortVoiceRequest)request).onAbortResult((Bundle) args.arg2); + request.clear(); + } + break; case MSG_COMMAND_RESULT: request = pullRequest((IVoiceInteractorRequest)args.arg1, msg.arg1 != 0); if (DEBUG) Log.d(TAG, "onCommandResult: req=" @@ -96,6 +125,12 @@ public class VoiceInteractor { } @Override + public void deliverAbortVoiceResult(IVoiceInteractorRequest request, Bundle result) { + mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOO( + MSG_ABORT_VOICE_RESULT, request, result)); + } + + @Override public void deliverCommandResult(IVoiceInteractorRequest request, boolean complete, Bundle result) { mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOO( @@ -112,8 +147,9 @@ public class VoiceInteractor { final ArrayMap<IBinder, Request> mActiveRequests = new ArrayMap<IBinder, Request>(); static final int MSG_CONFIRMATION_RESULT = 1; - static final int MSG_COMMAND_RESULT = 2; - static final int MSG_CANCEL_RESULT = 3; + static final int MSG_ABORT_VOICE_RESULT = 2; + static final int MSG_COMMAND_RESULT = 3; + static final int MSG_CANCEL_RESULT = 4; public static abstract class Request { IVoiceInteractorRequest mRequestInterface; @@ -188,9 +224,42 @@ public class VoiceInteractor { IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName, IVoiceInteractorCallback callback) throws RemoteException { - return interactor.startConfirmation(packageName, callback, mPrompt.toString(), mExtras); + return interactor.startConfirmation(packageName, callback, mPrompt, mExtras); } - } + } + + public static class AbortVoiceRequest extends Request { + final CharSequence mMessage; + final Bundle mExtras; + + /** + * Reports that the current interaction can not be complete with voice, so the + * application will need to switch to a traditional input UI. Applications should + * only use this when they need to completely bail out of the voice interaction + * and switch to a traditional UI. When the resonsponse comes back, the voice + * system has handled the request and is ready to switch; at that point the application + * can start a new non-voice activity. Be sure when starting the new activity + * to use {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK + * Intent.FLAG_ACTIVITY_NEW_TASK} to keep the new activity out of the current voice + * interaction task. + * + * @param message Optional message to tell user about not being able to complete + * the interaction with voice. + * @param extras Additional optional information. + */ + public AbortVoiceRequest(CharSequence message, Bundle extras) { + mMessage = message; + mExtras = extras; + } + + public void onAbortResult(Bundle result) { + } + + IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName, + IVoiceInteractorCallback callback) throws RemoteException { + return interactor.startAbortVoice(packageName, callback, mMessage, mExtras); + } + } public static class CommandRequest extends Request { final String mCommand; diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java index 1e29f8e..2e9077a 100644 --- a/core/java/android/service/voice/VoiceInteractionSession.java +++ b/core/java/android/service/voice/VoiceInteractionSession.java @@ -47,9 +47,22 @@ import com.android.internal.app.IVoiceInteractorRequest; import com.android.internal.os.HandlerCaller; import com.android.internal.os.SomeArgs; +import java.lang.ref.WeakReference; + import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; +/** + * An active voice interaction session, providing a facility for the implementation + * to interact with the user in the voice interaction layer. This interface is no shown + * by default, but you can request that it be shown with {@link #showWindow()}, which + * will result in a later call to {@link #onCreateContentView()} in which the UI can be + * built + * + * <p>A voice interaction session can be self-contained, ultimately calling {@link #finish} + * when done. It can also initiate voice interactions with applications by calling + * {@link #startVoiceActivity}</p>. + */ public abstract class VoiceInteractionSession implements KeyEvent.Callback { static final String TAG = "VoiceInteractionSession"; static final boolean DEBUG = true; @@ -80,11 +93,14 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { final Insets mTmpInsets = new Insets(); final int[] mTmpLocation = new int[2]; + final WeakReference<VoiceInteractionSession> mWeakRef + = new WeakReference<VoiceInteractionSession>(this); + final IVoiceInteractor mInteractor = new IVoiceInteractor.Stub() { @Override public IVoiceInteractorRequest startConfirmation(String callingPackage, - IVoiceInteractorCallback callback, String prompt, Bundle extras) { - Request request = findRequest(callback, true); + IVoiceInteractorCallback callback, CharSequence prompt, Bundle extras) { + Request request = newRequest(callback); mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOO(MSG_START_CONFIRMATION, new Caller(callingPackage, Binder.getCallingUid()), request, prompt, extras)); @@ -92,9 +108,19 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { } @Override + public IVoiceInteractorRequest startAbortVoice(String callingPackage, + IVoiceInteractorCallback callback, CharSequence message, Bundle extras) { + Request request = newRequest(callback); + mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOO(MSG_START_ABORT_VOICE, + new Caller(callingPackage, Binder.getCallingUid()), request, + message, extras)); + return request.mInterface; + } + + @Override public IVoiceInteractorRequest startCommand(String callingPackage, IVoiceInteractorCallback callback, String command, Bundle extras) { - Request request = findRequest(callback, true); + Request request = newRequest(callback); mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOO(MSG_START_COMMAND, new Caller(callingPackage, Binder.getCallingUid()), request, command, extras)); @@ -143,29 +169,60 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { final IVoiceInteractorRequest mInterface = new IVoiceInteractorRequest.Stub() { @Override public void cancel() throws RemoteException { - mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(MSG_CANCEL, Request.this)); + VoiceInteractionSession session = mSession.get(); + if (session != null) { + session.mHandlerCaller.sendMessage( + session.mHandlerCaller.obtainMessageO(MSG_CANCEL, Request.this)); + } } }; final IVoiceInteractorCallback mCallback; - final HandlerCaller mHandlerCaller; - Request(IVoiceInteractorCallback callback, HandlerCaller handlerCaller) { + final WeakReference<VoiceInteractionSession> mSession; + + Request(IVoiceInteractorCallback callback, VoiceInteractionSession session) { mCallback = callback; - mHandlerCaller = handlerCaller; + mSession = session.mWeakRef; + } + + void finishRequest() { + VoiceInteractionSession session = mSession.get(); + if (session == null) { + throw new IllegalStateException("VoiceInteractionSession has been destroyed"); + } + Request req = session.removeRequest(mInterface.asBinder()); + if (req == null) { + throw new IllegalStateException("Request not active: " + this); + } else if (req != this) { + throw new IllegalStateException("Current active request " + req + + " not same as calling request " + this); + } } public void sendConfirmResult(boolean confirmed, Bundle result) { try { if (DEBUG) Log.d(TAG, "sendConfirmResult: req=" + mInterface + " confirmed=" + confirmed + " result=" + result); + finishRequest(); mCallback.deliverConfirmationResult(mInterface, confirmed, result); } catch (RemoteException e) { } } + public void sendAbortVoiceResult(Bundle result) { + try { + if (DEBUG) Log.d(TAG, "sendConfirmResult: req=" + mInterface + + " result=" + result); + finishRequest(); + mCallback.deliverAbortVoiceResult(mInterface, result); + } catch (RemoteException e) { + } + } + public void sendCommandResult(boolean complete, Bundle result) { try { if (DEBUG) Log.d(TAG, "sendCommandResult: req=" + mInterface + " result=" + result); + finishRequest(); mCallback.deliverCommandResult(mInterface, complete, result); } catch (RemoteException e) { } @@ -174,6 +231,7 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { public void sendCancelResult() { try { if (DEBUG) Log.d(TAG, "sendCancelResult: req=" + mInterface); + finishRequest(); mCallback.deliverCancel(mInterface); } catch (RemoteException e) { } @@ -191,9 +249,10 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { } static final int MSG_START_CONFIRMATION = 1; - static final int MSG_START_COMMAND = 2; - static final int MSG_SUPPORTS_COMMANDS = 3; - static final int MSG_CANCEL = 4; + static final int MSG_START_ABORT_VOICE = 2; + static final int MSG_START_COMMAND = 3; + static final int MSG_SUPPORTS_COMMANDS = 4; + static final int MSG_CANCEL = 5; static final int MSG_TASK_STARTED = 100; static final int MSG_TASK_FINISHED = 101; @@ -209,9 +268,16 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { args = (SomeArgs)msg.obj; if (DEBUG) Log.d(TAG, "onConfirm: req=" + ((Request) args.arg2).mInterface + " prompt=" + args.arg3 + " extras=" + args.arg4); - onConfirm((Caller)args.arg1, (Request)args.arg2, (String)args.arg3, + onConfirm((Caller)args.arg1, (Request)args.arg2, (CharSequence)args.arg3, (Bundle)args.arg4); break; + case MSG_START_ABORT_VOICE: + args = (SomeArgs)msg.obj; + if (DEBUG) Log.d(TAG, "onAbortVoice: req=" + ((Request) args.arg2).mInterface + + " message=" + args.arg3 + " extras=" + args.arg4); + onAbortVoice((Caller) args.arg1, (Request) args.arg2, (CharSequence) args.arg3, + (Bundle) args.arg4); + break; case MSG_START_COMMAND: args = (SomeArgs)msg.obj; if (DEBUG) Log.d(TAG, "onCommand: req=" + ((Request) args.arg2).mInterface @@ -329,18 +395,20 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { mCallbacks, true); } - Request findRequest(IVoiceInteractorCallback callback, boolean newRequest) { + Request newRequest(IVoiceInteractorCallback callback) { + synchronized (this) { + Request req = new Request(callback, this); + mActiveRequests.put(req.mInterface.asBinder(), req); + return req; + } + } + + Request removeRequest(IBinder reqInterface) { synchronized (this) { - Request req = mActiveRequests.get(callback.asBinder()); + Request req = mActiveRequests.get(reqInterface); if (req != null) { - if (newRequest) { - throw new IllegalArgumentException("Given request callback " + callback - + " is already active"); - } - return req; + mActiveRequests.remove(req); } - req = new Request(callback, mHandlerCaller); - mActiveRequests.put(callback.asBinder(), req); return req; } } @@ -425,6 +493,27 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { mTheme = theme; } + /** + * Ask that a new activity be started for voice interaction. This will create a + * new dedicated task in the activity manager for this voice interaction session; + * this means that {@link Intent#FLAG_ACTIVITY_NEW_TASK Intent.FLAG_ACTIVITY_NEW_TASK} + * will be set for you to make it a new task. + * + * <p>The newly started activity will be displayed to the user in a special way, as + * a layer under the voice interaction UI.</p> + * + * <p>As the voice activity runs, it can retrieve a {@link android.app.VoiceInteractor} + * through which it can perform voice interactions through your session. These requests + * for voice interactions will appear as callbacks on {@link #onGetSupportedCommands}, + * {@link #onConfirm}, {@link #onCommand}, and {@link #onCancel}. + * + * <p>You will receive a call to {@link #onTaskStarted} when the task starts up + * and {@link #onTaskFinished} when the last activity has finished. + * + * @param intent The Intent to start this voice interaction. The given Intent will + * always have {@link Intent#CATEGORY_VOICE Intent.CATEGORY_VOICE} added to it, since + * this is part of a voice interaction. + */ public void startVoiceActivity(Intent intent) { if (mToken == null) { throw new IllegalStateException("Can't call before onCreate()"); @@ -439,14 +528,23 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { } } + /** + * Convenience for inflating views. + */ public LayoutInflater getLayoutInflater() { return mInflater; } + /** + * Retrieve the window being used to show the session's UI. + */ public Dialog getWindow() { return mWindow; } + /** + * Finish the session. + */ public void finish() { if (mToken == null) { throw new IllegalStateException("Can't call before onCreate()"); @@ -458,6 +556,12 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { } } + /** + * Initiatize a new session. + * + * @param args The arguments that were supplied to + * {@link VoiceInteractionService#startSession VoiceInteractionService.startSession}. + */ public void onCreate(Bundle args) { mTheme = mTheme != 0 ? mTheme : com.android.internal.R.style.Theme_DeviceDefault_VoiceInteractionSession; @@ -472,9 +576,15 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { mWindow.setToken(mToken); } + /** + * Last callback to the session as it is being finished. + */ public void onDestroy() { } + /** + * Hook in which to create the session's UI. + */ public View onCreateContentView() { return null; } @@ -507,6 +617,11 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { finish(); } + /** + * Sessions automatically watch for requests that all system UI be closed (such as when + * the user presses HOME), which will appear here. The default implementation always + * calls {@link #finish}. + */ public void onCloseSystemDialogs() { finish(); } @@ -530,15 +645,98 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { outInsets.touchableRegion.setEmpty(); } + /** + * Called when a task initiated by {@link #startVoiceActivity(android.content.Intent)} + * has actually started. + * + * @param intent The original {@link Intent} supplied to + * {@link #startVoiceActivity(android.content.Intent)}. + * @param taskId Unique ID of the now running task. + */ public void onTaskStarted(Intent intent, int taskId) { } + /** + * Called when the last activity of a task initiated by + * {@link #startVoiceActivity(android.content.Intent)} has finished. The default + * implementation calls {@link #finish()} on the assumption that this represents + * the completion of a voice action. You can override the implementation if you would + * like a different behavior. + * + * @param intent The original {@link Intent} supplied to + * {@link #startVoiceActivity(android.content.Intent)}. + * @param taskId Unique ID of the finished task. + */ public void onTaskFinished(Intent intent, int taskId) { finish(); } - public abstract boolean[] onGetSupportedCommands(Caller caller, String[] commands); - public abstract void onConfirm(Caller caller, Request request, String prompt, Bundle extras); + /** + * Request to query for what extended commands the session supports. + * + * @param caller Who is making the request. + * @param commands An array of commands that are being queried. + * @return Return an array of booleans indicating which of each entry in the + * command array is supported. A true entry in the array indicates the command + * is supported; false indicates it is not. The default implementation returns + * an array of all false entries. + */ + public boolean[] onGetSupportedCommands(Caller caller, String[] commands) { + return new boolean[commands.length]; + } + + /** + * Request to confirm with the user before proceeding with an unrecoverable operation, + * corresponding to a {@link android.app.VoiceInteractor.ConfirmationRequest + * VoiceInteractor.ConfirmationRequest}. + * + * @param caller Who is making the request. + * @param request The active request. + * @param prompt The prompt informing the user of what will happen, as per + * {@link android.app.VoiceInteractor.ConfirmationRequest VoiceInteractor.ConfirmationRequest}. + * @param extras Any additional information, as per + * {@link android.app.VoiceInteractor.ConfirmationRequest VoiceInteractor.ConfirmationRequest}. + */ + public abstract void onConfirm(Caller caller, Request request, CharSequence prompt, + Bundle extras); + + /** + * Request to abort the voice interaction session because the voice activity can not + * complete its interaction using voice. Corresponds to + * {@link android.app.VoiceInteractor.AbortVoiceRequest + * VoiceInteractor.AbortVoiceRequest}. The default implementation just sends an empty + * confirmation back to allow the activity to exit. + * + * @param caller Who is making the request. + * @param request The active request. + * @param message The message informing the user of the problem, as per + * {@link android.app.VoiceInteractor.AbortVoiceRequest VoiceInteractor.AbortVoiceRequest}. + * @param extras Any additional information, as per + * {@link android.app.VoiceInteractor.AbortVoiceRequest VoiceInteractor.AbortVoiceRequest}. + */ + public void onAbortVoice(Caller caller, Request request, CharSequence message, Bundle extras) { + request.sendAbortVoiceResult(null); + } + + /** + * Process an arbitrary extended command from the caller, + * corresponding to a {@link android.app.VoiceInteractor.CommandRequest + * VoiceInteractor.CommandRequest}. + * + * @param caller Who is making the request. + * @param request The active request. + * @param command The command that is being executed, as per + * {@link android.app.VoiceInteractor.CommandRequest VoiceInteractor.CommandRequest}. + * @param extras Any additional information, as per + * {@link android.app.VoiceInteractor.CommandRequest VoiceInteractor.CommandRequest}. + */ public abstract void onCommand(Caller caller, Request request, String command, Bundle extras); + + /** + * Called when the {@link android.app.VoiceInteractor} has asked to cancel a {@link Request} + * that was previously delivered to {@link #onConfirm} or {@link #onCommand}. + * + * @param request The request that is being canceled. + */ public abstract void onCancel(Request request); } diff --git a/core/java/com/android/internal/app/IVoiceInteractor.aidl b/core/java/com/android/internal/app/IVoiceInteractor.aidl index 737906a..2900595 100644 --- a/core/java/com/android/internal/app/IVoiceInteractor.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractor.aidl @@ -26,7 +26,9 @@ import com.android.internal.app.IVoiceInteractorRequest; */ interface IVoiceInteractor { IVoiceInteractorRequest startConfirmation(String callingPackage, - IVoiceInteractorCallback callback, String prompt, in Bundle extras); + IVoiceInteractorCallback callback, CharSequence prompt, in Bundle extras); + IVoiceInteractorRequest startAbortVoice(String callingPackage, + IVoiceInteractorCallback callback, CharSequence message, in Bundle extras); IVoiceInteractorRequest startCommand(String callingPackage, IVoiceInteractorCallback callback, String command, in Bundle extras); boolean[] supportsCommands(String callingPackage, in String[] commands); diff --git a/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl b/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl index c6f93e1..8dbf9d4 100644 --- a/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl @@ -26,6 +26,7 @@ import com.android.internal.app.IVoiceInteractorRequest; oneway interface IVoiceInteractorCallback { void deliverConfirmationResult(IVoiceInteractorRequest request, boolean confirmed, in Bundle result); + void deliverAbortVoiceResult(IVoiceInteractorRequest request, in Bundle result); void deliverCommandResult(IVoiceInteractorRequest request, boolean complete, in Bundle result); void deliverCancel(IVoiceInteractorRequest request); } diff --git a/tests/VoiceInteraction/res/layout/test_interaction.xml b/tests/VoiceInteraction/res/layout/test_interaction.xml index 2abf651..4c0c67a 100644 --- a/tests/VoiceInteraction/res/layout/test_interaction.xml +++ b/tests/VoiceInteraction/res/layout/test_interaction.xml @@ -18,6 +18,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" + android:padding="8dp" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" @@ -29,9 +30,16 @@ android:layout_width="match_parent" android:layout_height="0px" android:layout_weight="1" - android:layout_marginTop="10dp" - android:textSize="12sp" + android:layout_marginTop="16dp" + android:textAppearance="?android:attr/textAppearanceMedium" android:textColor="#ffffffff" /> + <Button android:id="@+id/abort" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:text="@string/abortVoice" + /> + </LinearLayout> diff --git a/tests/VoiceInteraction/res/layout/voice_interaction_session.xml b/tests/VoiceInteraction/res/layout/voice_interaction_session.xml index 563fa44..142d781 100644 --- a/tests/VoiceInteraction/res/layout/voice_interaction_session.xml +++ b/tests/VoiceInteraction/res/layout/voice_interaction_session.xml @@ -14,26 +14,49 @@ limitations under the License. --> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="match_parent" - android:orientation="vertical" - android:background="#ffffffff" - android:fitsSystemWindows="true" - > - - <TextView android:id="@+id/text" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:layout_marginBottom="32dp" - /> - - <Button android:id="@+id/start" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/start" - /> - -</LinearLayout> - - + android:fitsSystemWindows="true"> + + <FrameLayout android:layout_width="fill_parent" + android:layout_height="match_parent" + android:padding="8dp"> + + <LinearLayout android:id="@+id/content" + android:layout_width="fill_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:background="#ffffffff" + android:elevation="8dp" + > + + <TextView android:id="@+id/text" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="16dp" + android:textAppearance="?android:attr/textAppearanceMedium" + /> + + <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" + android:orientation="horizontal"> + <Button android:id="@+id/start" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/start" + /> + <Button android:id="@+id/confirm" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/confirm" + /> + <Button android:id="@+id/abort" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/abort" + /> + </LinearLayout> + + </LinearLayout> + </FrameLayout> +</FrameLayout> diff --git a/tests/VoiceInteraction/res/values/strings.xml b/tests/VoiceInteraction/res/values/strings.xml index 12edb31..70baa52 100644 --- a/tests/VoiceInteraction/res/values/strings.xml +++ b/tests/VoiceInteraction/res/values/strings.xml @@ -16,7 +16,10 @@ <resources> - <string name="start">Start!</string> + <string name="start">Start</string> + <string name="confirm">Confirm</string> + <string name="abort">Abort</string> + <string name="abortVoice">Abort Voice</string> </resources> diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java index a3af284..c24a088 100644 --- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java +++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java @@ -33,9 +33,17 @@ public class MainInteractionSession extends VoiceInteractionSession View mContentView; TextView mText; Button mStartButton; + Button mConfirmButton; + Button mAbortButton; + static final int STATE_IDLE = 0; + static final int STATE_LAUNCHING = 1; + static final int STATE_CONFIRM = 2; + static final int STATE_COMMAND = 3; + static final int STATE_ABORT_VOICE = 4; + + int mState = STATE_IDLE; Request mPendingRequest; - boolean mPendingConfirm; MainInteractionSession(Context context) { super(context); @@ -54,21 +62,39 @@ public class MainInteractionSession extends VoiceInteractionSession mText = (TextView)mContentView.findViewById(R.id.text); mStartButton = (Button)mContentView.findViewById(R.id.start); mStartButton.setOnClickListener(this); + mConfirmButton = (Button)mContentView.findViewById(R.id.confirm); + mConfirmButton.setOnClickListener(this); + mAbortButton = (Button)mContentView.findViewById(R.id.abort); + mAbortButton.setOnClickListener(this); + updateState(); return mContentView; } + void updateState() { + mStartButton.setEnabled(mState == STATE_IDLE); + mConfirmButton.setEnabled(mState == STATE_CONFIRM || mState == STATE_COMMAND); + mAbortButton.setEnabled(mState == STATE_ABORT_VOICE); + } + public void onClick(View v) { - if (mPendingRequest == null) { - mStartButton.setEnabled(false); + if (v == mStartButton) { + mState = STATE_LAUNCHING; + updateState(); startVoiceActivity(mStartIntent); - } else { - if (mPendingConfirm) { + } else if (v == mConfirmButton) { + if (mState == STATE_CONFIRM) { mPendingRequest.sendConfirmResult(true, null); } else { mPendingRequest.sendCommandResult(true, null); } mPendingRequest = null; - mStartButton.setText("Start"); + mState = STATE_IDLE; + updateState(); + } else if (v == mAbortButton) { + mPendingRequest.sendAbortVoiceResult(null); + mPendingRequest = null; + mState = STATE_IDLE; + updateState(); } } @@ -78,23 +104,32 @@ public class MainInteractionSession extends VoiceInteractionSession } @Override - public void onConfirm(Caller caller, Request request, String prompt, Bundle extras) { + public void onConfirm(Caller caller, Request request, CharSequence prompt, Bundle extras) { Log.i(TAG, "onConfirm: prompt=" + prompt + " extras=" + extras); mText.setText(prompt); - mStartButton.setEnabled(true); mStartButton.setText("Confirm"); mPendingRequest = request; - mPendingConfirm = true; + mState = STATE_CONFIRM; + updateState(); + } + + @Override + public void onAbortVoice(Caller caller, Request request, CharSequence message, Bundle extras) { + Log.i(TAG, "onAbortVoice: message=" + message + " extras=" + extras); + mText.setText(message); + mPendingRequest = request; + mState = STATE_ABORT_VOICE; + updateState(); } @Override public void onCommand(Caller caller, Request request, String command, Bundle extras) { Log.i(TAG, "onCommand: command=" + command + " extras=" + extras); mText.setText("Command: " + command); - mStartButton.setEnabled(true); mStartButton.setText("Finish Command"); mPendingRequest = request; - mPendingConfirm = false; + mState = STATE_COMMAND; + updateState(); } @Override diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java index a61e0da..3ae6a36 100644 --- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java +++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java @@ -21,12 +21,15 @@ import android.app.VoiceInteractor; import android.os.Bundle; import android.util.Log; import android.view.Gravity; +import android.view.View; import android.view.ViewGroup; +import android.widget.Button; -public class TestInteractionActivity extends Activity { +public class TestInteractionActivity extends Activity implements View.OnClickListener { static final String TAG = "TestInteractionActivity"; VoiceInteractor mInteractor; + Button mAbortButton; @Override public void onCreate(Bundle savedInstanceState) { @@ -39,6 +42,8 @@ public class TestInteractionActivity extends Activity { } setContentView(R.layout.test_interaction); + mAbortButton = (Button)findViewById(R.id.abort); + mAbortButton.setOnClickListener(this); // Framework should take care of these. getWindow().setGravity(Gravity.TOP); @@ -69,6 +74,26 @@ public class TestInteractionActivity extends Activity { } @Override + public void onClick(View v) { + if (v == mAbortButton) { + VoiceInteractor.AbortVoiceRequest req = new VoiceInteractor.AbortVoiceRequest( + "Dammit, we suck :(", null) { + @Override + public void onCancel() { + Log.i(TAG, "Canceled!"); + } + + @Override + public void onAbortResult(Bundle result) { + Log.i(TAG, "Abort result: result=" + result); + getActivity().finish(); + } + }; + mInteractor.submitRequest(req); + } + } + + @Override public void onDestroy() { super.onDestroy(); } |