diff options
author | Dianne Hackborn <hackbod@google.com> | 2015-03-13 18:02:54 -0700 |
---|---|---|
committer | Dianne Hackborn <hackbod@google.com> | 2015-03-16 11:29:12 -0700 |
commit | 3d07c94c393831091958fe6a98811843db8973bd (patch) | |
tree | 0cd5c4ea3dc580772b28ef76e9b5b76fe23de081 | |
parent | 872d191e6134b429f833013b8706c7b54ebd0d2a (diff) | |
download | frameworks_base-3d07c94c393831091958fe6a98811843db8973bd.zip frameworks_base-3d07c94c393831091958fe6a98811843db8973bd.tar.gz frameworks_base-3d07c94c393831091958fe6a98811843db8973bd.tar.bz2 |
Add new voice request for picking from a list.
Also add API for voice interaction service to control
whether the system should hold a wake lock while it is
working with an activity (and actually *do* hold a wake
lock while doing so, duh!).
And while in there, clean up the launching wake lock to
correctly give blame to the app that is launching.
Change-Id: I7cc4d566b80f59fe0a9ac51ae9bbb7188a01f433
22 files changed, 655 insertions, 89 deletions
@@ -559,6 +559,7 @@ aidl_files := \ frameworks/base/core/java/android/app/PendingIntent.aidl \ frameworks/base/core/java/android/app/AlarmManager.aidl \ frameworks/base/core/java/android/app/SearchableInfo.aidl \ + frameworks/base/core/java/android/app/VoiceInteractor.aidl \ frameworks/base/core/java/android/app/job/JobParameters.aidl \ frameworks/base/core/java/android/app/job/JobInfo.aidl \ frameworks/base/core/java/android/appwidget/AppWidgetProviderInfo.aidl \ diff --git a/api/current.txt b/api/current.txt index c05254a..9d3a83c 100644 --- a/api/current.txt +++ b/api/current.txt @@ -5355,8 +5355,25 @@ package android.app { method public void onConfirmationResult(boolean, android.os.Bundle); } + public static class VoiceInteractor.PickOptionRequest extends android.app.VoiceInteractor.Request { + ctor public VoiceInteractor.PickOptionRequest(java.lang.CharSequence, android.app.VoiceInteractor.PickOptionRequest.Option[], android.os.Bundle); + method public void onPickOptionResult(boolean, android.app.VoiceInteractor.PickOptionRequest.Option[], android.os.Bundle); + } + + public static final class VoiceInteractor.PickOptionRequest.Option implements android.os.Parcelable { + ctor public VoiceInteractor.PickOptionRequest.Option(java.lang.CharSequence); + method public android.app.VoiceInteractor.PickOptionRequest.Option addSynonym(java.lang.CharSequence); + method public int countSynonyms(); + method public int describeContents(); + method public android.os.Bundle getExtras(); + method public java.lang.CharSequence getLabel(); + method public java.lang.CharSequence getSynonymAt(int); + method public void setExtras(android.os.Bundle); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.app.VoiceInteractor.PickOptionRequest.Option> CREATOR; + } + public static abstract class VoiceInteractor.Request { - ctor public VoiceInteractor.Request(); method public void cancel(); method public android.app.Activity getActivity(); method public android.content.Context getContext(); @@ -27895,10 +27912,12 @@ package android.service.voice { method public boolean onKeyLongPress(int, android.view.KeyEvent); method public boolean onKeyMultiple(int, int, android.view.KeyEvent); method public boolean onKeyUp(int, android.view.KeyEvent); + method public abstract void onPickOption(android.service.voice.VoiceInteractionSession.Caller, android.service.voice.VoiceInteractionSession.Request, java.lang.CharSequence, android.app.VoiceInteractor.PickOptionRequest.Option[], android.os.Bundle); method public void onShow(android.os.Bundle, int); method public void onTaskFinished(android.content.Intent, int); method public void onTaskStarted(android.content.Intent, int); method public void setContentView(android.view.View); + method public void setKeepAwake(boolean); method public void setTheme(int); method public void show(); method public void showWindow(); @@ -27924,6 +27943,7 @@ package android.service.voice { method public void sendCommandResult(boolean, android.os.Bundle); method public void sendCompleteVoiceResult(android.os.Bundle); method public void sendConfirmResult(boolean, android.os.Bundle); + method public void sendPickOptionResult(boolean, android.app.VoiceInteractor.PickOptionRequest.Option[], android.os.Bundle); } public abstract class VoiceInteractionSessionService extends android.app.Service { diff --git a/api/system-current.txt b/api/system-current.txt index 9365ea3..7434858 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -5445,8 +5445,25 @@ package android.app { method public void onConfirmationResult(boolean, android.os.Bundle); } + public static class VoiceInteractor.PickOptionRequest extends android.app.VoiceInteractor.Request { + ctor public VoiceInteractor.PickOptionRequest(java.lang.CharSequence, android.app.VoiceInteractor.PickOptionRequest.Option[], android.os.Bundle); + method public void onPickOptionResult(boolean, android.app.VoiceInteractor.PickOptionRequest.Option[], android.os.Bundle); + } + + public static final class VoiceInteractor.PickOptionRequest.Option implements android.os.Parcelable { + ctor public VoiceInteractor.PickOptionRequest.Option(java.lang.CharSequence); + method public android.app.VoiceInteractor.PickOptionRequest.Option addSynonym(java.lang.CharSequence); + method public int countSynonyms(); + method public int describeContents(); + method public android.os.Bundle getExtras(); + method public java.lang.CharSequence getLabel(); + method public java.lang.CharSequence getSynonymAt(int); + method public void setExtras(android.os.Bundle); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.app.VoiceInteractor.PickOptionRequest.Option> CREATOR; + } + public static abstract class VoiceInteractor.Request { - ctor public VoiceInteractor.Request(); method public void cancel(); method public android.app.Activity getActivity(); method public android.content.Context getContext(); @@ -29590,10 +29607,12 @@ package android.service.voice { method public boolean onKeyLongPress(int, android.view.KeyEvent); method public boolean onKeyMultiple(int, int, android.view.KeyEvent); method public boolean onKeyUp(int, android.view.KeyEvent); + method public abstract void onPickOption(android.service.voice.VoiceInteractionSession.Caller, android.service.voice.VoiceInteractionSession.Request, java.lang.CharSequence, android.app.VoiceInteractor.PickOptionRequest.Option[], android.os.Bundle); method public void onShow(android.os.Bundle, int); method public void onTaskFinished(android.content.Intent, int); method public void onTaskStarted(android.content.Intent, int); method public void setContentView(android.view.View); + method public void setKeepAwake(boolean); method public void setTheme(int); method public void show(); method public void showWindow(); @@ -29619,6 +29638,7 @@ package android.service.voice { method public void sendCommandResult(boolean, android.os.Bundle); method public void sendCompleteVoiceResult(android.os.Bundle); method public void sendConfirmResult(boolean, android.os.Bundle); + method public void sendPickOptionResult(boolean, android.app.VoiceInteractor.PickOptionRequest.Option[], android.os.Bundle); } public abstract class VoiceInteractionSessionService extends android.app.Service { diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index 997f69d..1484af8 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -2440,6 +2440,16 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM reply.writeNoException(); return true; } + + case SET_VOICE_KEEP_AWAKE_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IVoiceInteractionSession session = IVoiceInteractionSession.Stub.asInterface( + data.readStrongBinder()); + boolean keepAwake = data.readInt() != 0; + setVoiceKeepAwake(session, keepAwake); + reply.writeNoException(); + return true; + } } return super.onTransact(code, data, reply, flags); @@ -5658,5 +5668,19 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); } + @Override + public void setVoiceKeepAwake(IVoiceInteractionSession session, boolean keepAwake) + throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(session.asBinder()); + data.writeInt(keepAwake ? 1 : 0); + mRemote.transact(SET_VOICE_KEEP_AWAKE_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + private IBinder mRemote; } diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index 3dcbdd2..d794aa3 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -485,6 +485,9 @@ public interface IActivityManager extends IInterface { public void setDumpHeapDebugLimit(String processName, long maxMemSize) throws RemoteException; public void dumpHeapFinished(String path) throws RemoteException; + public void setVoiceKeepAwake(IVoiceInteractionSession session, boolean keepAwake) + throws RemoteException; + /* * Private non-Binder interfaces */ @@ -818,4 +821,5 @@ public interface IActivityManager extends IInterface { int GET_LOCK_TASK_MODE_STATE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+286; int SET_DUMP_HEAP_DEBUG_LIMIT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+287; int DUMP_HEAP_FINISHED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+288; + int SET_VOICE_KEEP_AWAKE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+289; } diff --git a/core/java/android/app/VoiceInteractor.aidl b/core/java/android/app/VoiceInteractor.aidl new file mode 100644 index 0000000..40a4a0e --- /dev/null +++ b/core/java/android/app/VoiceInteractor.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2015, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +parcelable VoiceInteractor.PickOptionRequest.Option; diff --git a/core/java/android/app/VoiceInteractor.java b/core/java/android/app/VoiceInteractor.java index 7b84cb4..da7bb05 100644 --- a/core/java/android/app/VoiceInteractor.java +++ b/core/java/android/app/VoiceInteractor.java @@ -21,6 +21,8 @@ import android.os.Bundle; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.Parcel; +import android.os.Parcelable; import android.os.RemoteException; import android.util.ArrayMap; import android.util.Log; @@ -69,6 +71,7 @@ public class VoiceInteractor { public void executeMessage(Message msg) { SomeArgs args = (SomeArgs)msg.obj; Request request; + boolean complete; switch (msg.what) { case MSG_CONFIRMATION_RESULT: request = pullRequest((IVoiceInteractorRequest)args.arg1, true); @@ -81,13 +84,28 @@ public class VoiceInteractor { request.clear(); } break; + case MSG_PICK_OPTION_RESULT: + complete = msg.arg1 != 0; + request = pullRequest((IVoiceInteractorRequest)args.arg1, complete); + if (DEBUG) Log.d(TAG, "onPickOptionResult: req=" + + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request + + " finished=" + complete + " selection=" + args.arg2 + + " result=" + args.arg3); + if (request != null) { + ((PickOptionRequest)request).onPickOptionResult(complete, + (PickOptionRequest.Option[]) args.arg2, (Bundle) args.arg3); + if (complete) { + request.clear(); + } + } + break; case MSG_COMPLETE_VOICE_RESULT: request = pullRequest((IVoiceInteractorRequest)args.arg1, true); if (DEBUG) Log.d(TAG, "onCompleteVoice: req=" + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request + " result=" + args.arg1); if (request != null) { - ((CompleteVoiceRequest)request).onCompleteResult((Bundle) args.arg2); + ((CompleteVoiceRequest)request).onCompleteResult((Bundle) args.arg1); request.clear(); } break; @@ -95,21 +113,22 @@ public class VoiceInteractor { request = pullRequest((IVoiceInteractorRequest)args.arg1, true); if (DEBUG) Log.d(TAG, "onAbortVoice: req=" + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request - + " result=" + args.arg1); + + " result=" + args.arg2); if (request != null) { ((AbortVoiceRequest)request).onAbortResult((Bundle) args.arg2); request.clear(); } break; case MSG_COMMAND_RESULT: - request = pullRequest((IVoiceInteractorRequest)args.arg1, msg.arg1 != 0); + complete = msg.arg1 != 0; + request = pullRequest((IVoiceInteractorRequest)args.arg1, complete); if (DEBUG) Log.d(TAG, "onCommandResult: req=" + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request + " completed=" + msg.arg1 + " result=" + args.arg2); if (request != null) { ((CommandRequest)request).onCommandResult(msg.arg1 != 0, (Bundle) args.arg2); - if (msg.arg1 != 0) { + if (complete) { request.clear(); } } @@ -129,10 +148,17 @@ public class VoiceInteractor { final IVoiceInteractorCallback.Stub mCallback = new IVoiceInteractorCallback.Stub() { @Override - public void deliverConfirmationResult(IVoiceInteractorRequest request, boolean confirmed, + public void deliverConfirmationResult(IVoiceInteractorRequest request, boolean finished, Bundle result) { mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOO( - MSG_CONFIRMATION_RESULT, confirmed ? 1 : 0, request, result)); + MSG_CONFIRMATION_RESULT, finished ? 1 : 0, request, result)); + } + + @Override + public void deliverPickOptionResult(IVoiceInteractorRequest request, + boolean finished, PickOptionRequest.Option[] options, Bundle result) { + mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOOO( + MSG_PICK_OPTION_RESULT, finished ? 1 : 0, request, options, result)); } @Override @@ -164,17 +190,22 @@ public class VoiceInteractor { final ArrayMap<IBinder, Request> mActiveRequests = new ArrayMap<IBinder, Request>(); static final int MSG_CONFIRMATION_RESULT = 1; - static final int MSG_COMPLETE_VOICE_RESULT = 2; - static final int MSG_ABORT_VOICE_RESULT = 3; - static final int MSG_COMMAND_RESULT = 4; - static final int MSG_CANCEL_RESULT = 5; + static final int MSG_PICK_OPTION_RESULT = 2; + static final int MSG_COMPLETE_VOICE_RESULT = 3; + static final int MSG_ABORT_VOICE_RESULT = 4; + static final int MSG_COMMAND_RESULT = 5; + static final int MSG_CANCEL_RESULT = 6; + /** + * Base class for voice interaction requests that can be submitted to the interactor. + * Do not instantiate this directly -- instead, use the appropriate subclass. + */ public static abstract class Request { IVoiceInteractorRequest mRequestInterface; Context mContext; Activity mActivity; - public Request() { + Request() { } public void cancel() { @@ -212,22 +243,25 @@ public class VoiceInteractor { String packageName, IVoiceInteractorCallback callback) throws RemoteException; } + /** + * Confirms an operation with the user via the trusted system + * VoiceInteractionService. This allows an Activity to complete an unsafe operation that + * would require the user to touch the screen when voice interaction mode is not enabled. + * The result of the confirmation will be returned through an asynchronous call to + * either {@link #onConfirmationResult(boolean, android.os.Bundle)} or + * {@link #onCancel()}. + * + * <p>In some cases this may be a simple yes / no confirmation or the confirmation could + * include context information about how the action will be completed + * (e.g. booking a cab might include details about how long until the cab arrives) + * so the user can give a confirmation. + */ public static class ConfirmationRequest extends Request { final CharSequence mPrompt; final Bundle mExtras; /** - * Confirms an operation with the user via the trusted system - * VoiceInteractionService. This allows an Activity to complete an unsafe operation that - * would require the user to touch the screen when voice interaction mode is not enabled. - * The result of the confirmation will be returned through an asynchronous call to - * either {@link #onConfirmationResult(boolean, android.os.Bundle)} or - * {@link #onCancel()}. - * - * <p>In some cases this may be a simple yes / no confirmation or the confirmation could - * include context information about how the action will be completed - * (e.g. booking a cab might include details about how long until the cab arrives) - * so the user can give a confirmation. + * Create a new confirmation request. * @param prompt Optional confirmation text to read to the user as the action being * confirmed. * @param extras Additional optional information. @@ -246,19 +280,155 @@ public class VoiceInteractor { } } + /** + * Select a single option from multiple potential options with the user via the trusted system + * VoiceInteractionService. Typically, the application would present this visually as + * a list view to allow selecting the option by touch. + * The result of the confirmation will be returned through an asynchronous call to + * either {@link #onPickOptionResult} or {@link #onCancel()}. + */ + public static class PickOptionRequest extends Request { + final CharSequence mPrompt; + final Option[] mOptions; + final Bundle mExtras; + + /** + * Represents a single option that the user may select using their voice. + */ + public static final class Option implements Parcelable { + final CharSequence mLabel; + ArrayList<CharSequence> mSynonyms; + Bundle mExtras; + + /** + * Creates an option that a user can select with their voice by matching the label + * or one of several synonyms. + * @param label The label that will both be matched against what the user speaks + * and displayed visually. + */ + public Option(CharSequence label) { + mLabel = label; + } + + /** + * Add a synonym term to the option to indicate an alternative way the content + * may be matched. + * @param synonym The synonym that will be matched against what the user speaks, + * but not displayed. + */ + public Option addSynonym(CharSequence synonym) { + if (mSynonyms == null) { + mSynonyms = new ArrayList<>(); + } + mSynonyms.add(synonym); + return this; + } + + public CharSequence getLabel() { + return mLabel; + } + + public int countSynonyms() { + return mSynonyms != null ? mSynonyms.size() : 0; + } + + public CharSequence getSynonymAt(int index) { + return mSynonyms != null ? mSynonyms.get(index) : null; + } + + /** + * Set optional extra information associated with this option. Note that this + * method takes ownership of the supplied extras Bundle. + */ + public void setExtras(Bundle extras) { + mExtras = extras; + } + + /** + * Return any optional extras information associated with this option, or null + * if there is none. Note that this method returns a reference to the actual + * extras Bundle in the option, so modifications to it will directly modify the + * extras in the option. + */ + public Bundle getExtras() { + return mExtras; + } + + Option(Parcel in) { + mLabel = in.readCharSequence(); + mSynonyms = in.readCharSequenceList(); + mExtras = in.readBundle(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeCharSequence(mLabel); + dest.writeCharSequenceList(mSynonyms); + dest.writeBundle(mExtras); + } + + public static final Parcelable.Creator<Option> CREATOR + = new Parcelable.Creator<Option>() { + public Option createFromParcel(Parcel in) { + return new Option(in); + } + + public Option[] newArray(int size) { + return new Option[size]; + } + }; + }; + + /** + * Create a new pick option request. + * @param prompt Optional question to be spoken to the user via text to speech. + * @param options The set of {@link Option}s the user is selecting from. + * @param extras Additional optional information. + */ + public PickOptionRequest(CharSequence prompt, Option[] options, Bundle extras) { + mPrompt = prompt; + mOptions = options; + mExtras = extras; + } + + /** + * Called when a single option is confirmed or narrowed to one of several options. + * @param finished True if the voice interaction has finished making a selection, in + * which case {@code selections} contains the final result. If false, this request is + * still active and you will continue to get calls on it. + * @param selections Either a single {@link Option} or one of several {@link Option}s the + * user has narrowed the choices down to. + * @param result Additional optional information. + */ + public void onPickOptionResult(boolean finished, Option[] selections, Bundle result) { + } + + IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName, + IVoiceInteractorCallback callback) throws RemoteException { + return interactor.startPickOption(packageName, callback, mPrompt, mOptions, mExtras); + } + } + + /** + * Reports that the current interaction was successfully completed with voice, so the + * application can report the final status to the user. When the response 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 or finish. 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. + */ public static class CompleteVoiceRequest extends Request { final CharSequence mMessage; final Bundle mExtras; /** - * Reports that the current interaction was successfully completed with voice, so the - * application can report the final status to the user. When the response 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 or finish. 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. - * + * Create a new completed voice interaction request. * @param message Optional message to tell user about the completion status of the task. * @param extras Additional optional information. */ @@ -276,21 +446,23 @@ public class VoiceInteractor { } } + /** + * 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 response 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. + */ 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 response 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. - * + * Create a new voice abort request. * @param message Optional message to tell user about not being able to complete * the interaction with voice. * @param extras Additional optional information. @@ -309,25 +481,27 @@ public class VoiceInteractor { } } + /** + * Execute an extended command using the trusted system VoiceInteractionService. + * This allows an Activity to request additional information from the user needed to + * complete an action (e.g. booking a table might have several possible times that the + * user could select from or an app might need the user to agree to a terms of service). + * The result of the confirmation will be returned through an asynchronous call to + * either {@link #onCommandResult(boolean, android.os.Bundle)} or + * {@link #onCancel()}. + * + * <p>The command is a string that describes the generic operation to be performed. + * The command will determine how the properties in extras are interpreted and the set of + * available commands is expected to grow over time. An example might be + * "com.google.voice.commands.REQUEST_NUMBER_BAGS" to request the number of bags as part of + * airline check-in. (This is not an actual working example.) + */ public static class CommandRequest extends Request { final String mCommand; final Bundle mArgs; /** - * Execute a command using the trusted system VoiceInteractionService. - * This allows an Activity to request additional information from the user needed to - * complete an action (e.g. booking a table might have several possible times that the - * user could select from or an app might need the user to agree to a terms of service). - * The result of the confirmation will be returned through an asynchronous call to - * either {@link #onCommandResult(boolean, android.os.Bundle)} or - * {@link #onCancel()}. - * - * <p>The command is a string that describes the generic operation to be performed. - * The command will determine how the properties in extras are interpreted and the set of - * available commands is expected to grow over time. An example might be - * "com.google.voice.commands.REQUEST_NUMBER_BAGS" to request the number of bags as part of - * airline check-in. (This is not an actual working example.) - * + * Create a new generic command request. * @param command The desired command to perform. * @param args Additional arguments to control execution of the command. */ diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index 3d5215b..9d8a1ba 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -1059,6 +1059,21 @@ public final class Parcel { } } + /** + * @hide + */ + public final void writeCharSequenceList(ArrayList<CharSequence> val) { + if (val != null) { + int N = val.size(); + writeInt(N); + for (int i=0; i<N; i++) { + writeCharSequence(val.get(i)); + } + } else { + writeInt(-1); + } + } + public final IBinder[] createBinderArray() { int N = readInt(); if (N >= 0) { @@ -1828,6 +1843,25 @@ public final class Parcel { } /** + * Read and return an ArrayList<CharSequence> object from the parcel. + * {@hide} + */ + public final ArrayList<CharSequence> readCharSequenceList() { + ArrayList<CharSequence> array = null; + + int length = readInt(); + if (length >= 0) { + array = new ArrayList<CharSequence>(length); + + for (int i = 0 ; i < length ; i++) { + array.add(readCharSequence()); + } + } + + return array; + } + + /** * Read and return a new ArrayList object from the parcel at the current * dataPosition(). Returns null if the previously written list object was * null. The given class loader will be used to load any enclosed diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java index 4cf0e4c..11eaa06 100644 --- a/core/java/android/service/voice/VoiceInteractionSession.java +++ b/core/java/android/service/voice/VoiceInteractionSession.java @@ -18,6 +18,7 @@ package android.service.voice; import android.app.Dialog; import android.app.Instrumentation; +import android.app.VoiceInteractor; import android.content.Context; import android.content.Intent; import android.content.res.TypedArray; @@ -105,6 +106,17 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { } @Override + public IVoiceInteractorRequest startPickOption(String callingPackage, + IVoiceInteractorCallback callback, CharSequence prompt, + VoiceInteractor.PickOptionRequest.Option[] options, Bundle extras) { + Request request = newRequest(callback); + mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOOO(MSG_START_PICK_OPTION, + new Caller(callingPackage, Binder.getCallingUid()), request, + prompt, options, extras)); + return request.mInterface; + } + + @Override public IVoiceInteractorRequest startCompleteVoice(String callingPackage, IVoiceInteractorCallback callback, CharSequence message, Bundle extras) { Request request = newRequest(callback); @@ -232,6 +244,20 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { } } + public void sendPickOptionResult(boolean finished, + VoiceInteractor.PickOptionRequest.Option[] selections, Bundle result) { + try { + if (DEBUG) Log.d(TAG, "sendPickOptionResult: req=" + mInterface + + " finished=" + finished + " selections=" + selections + + " result=" + result); + if (finished) { + finishRequest(); + } + mCallback.deliverPickOptionResult(mInterface, finished, selections, result); + } catch (RemoteException e) { + } + } + public void sendCompleteVoiceResult(Bundle result) { try { if (DEBUG) Log.d(TAG, "sendCompleteVoiceResult: req=" + mInterface @@ -252,12 +278,14 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { } } - public void sendCommandResult(boolean complete, Bundle result) { + public void sendCommandResult(boolean finished, Bundle result) { try { if (DEBUG) Log.d(TAG, "sendCommandResult: req=" + mInterface + " result=" + result); - finishRequest(); - mCallback.deliverCommandResult(mInterface, complete, result); + if (finished) { + finishRequest(); + } + mCallback.deliverCommandResult(mInterface, finished, result); } catch (RemoteException e) { } } @@ -283,11 +311,12 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { } static final int MSG_START_CONFIRMATION = 1; - static final int MSG_START_COMPLETE_VOICE = 2; - static final int MSG_START_ABORT_VOICE = 3; - static final int MSG_START_COMMAND = 4; - static final int MSG_SUPPORTS_COMMANDS = 5; - static final int MSG_CANCEL = 6; + static final int MSG_START_PICK_OPTION = 2; + static final int MSG_START_COMPLETE_VOICE = 3; + static final int MSG_START_ABORT_VOICE = 4; + static final int MSG_START_COMMAND = 5; + static final int MSG_SUPPORTS_COMMANDS = 6; + static final int MSG_CANCEL = 7; static final int MSG_TASK_STARTED = 100; static final int MSG_TASK_FINISHED = 101; @@ -309,6 +338,15 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { onConfirm((Caller)args.arg1, (Request)args.arg2, (CharSequence)args.arg3, (Bundle)args.arg4); break; + case MSG_START_PICK_OPTION: + args = (SomeArgs)msg.obj; + if (DEBUG) Log.d(TAG, "onPickOption: req=" + ((Request) args.arg2).mInterface + + " prompt=" + args.arg3 + " options=" + args.arg4 + + " extras=" + args.arg5); + onPickOption((Caller)args.arg1, (Request)args.arg2, (CharSequence)args.arg3, + (VoiceInteractor.PickOptionRequest.Option[])args.arg4, + (Bundle)args.arg5); + break; case MSG_START_COMPLETE_VOICE: args = (SomeArgs)msg.obj; if (DEBUG) Log.d(TAG, "onCompleteVoice: req=" + ((Request) args.arg2).mInterface @@ -614,6 +652,26 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { } /** + * Set whether this session will keep the device awake while it is running a voice + * activity. By default, the system holds a wake lock for it while in this state, + * so that it can work even if the screen is off. Setting this to false removes that + * wake lock, allowing the CPU to go to sleep. This is typically used if the + * session decides it has been waiting too long for a response from the user and + * doesn't want to let this continue to drain the battery. + * + * <p>Passing false here will release the wake lock, and you can call later with + * true to re-acquire it. It will also be automatically re-acquired for you each + * time you start a new voice activity task -- that is when you call + * {@link #startVoiceActivity}.</p> + */ + public void setKeepAwake(boolean keepAwake) { + try { + mSystemService.setKeepAwake(mToken, keepAwake); + } catch (RemoteException e) { + } + } + + /** * Convenience for inflating views. */ public LayoutInflater getLayoutInflater() { @@ -814,6 +872,22 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { Bundle extras); /** + * Request for the user to pick one of N options, corresponding to a + * {@link android.app.VoiceInteractor.PickOptionRequest VoiceInteractor.PickOptionRequest}. + * + * @param caller Who is making the request. + * @param request The active request. + * @param prompt The prompt informing the user of what they are picking, as per + * {@link android.app.VoiceInteractor.PickOptionRequest VoiceInteractor.PickOptionRequest}. + * @param options The set of options the user is picking from, as per + * {@link android.app.VoiceInteractor.PickOptionRequest VoiceInteractor.PickOptionRequest}. + * @param extras Any additional information, as per + * {@link android.app.VoiceInteractor.PickOptionRequest VoiceInteractor.PickOptionRequest}. + */ + public abstract void onPickOption(Caller caller, Request request, CharSequence prompt, + VoiceInteractor.PickOptionRequest.Option[] options, Bundle extras); + + /** * Request to complete the voice interaction session because the voice activity successfully * completed its interaction using voice. Corresponds to * {@link android.app.VoiceInteractor.CompleteVoiceRequest diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl index 8f549a6..6450d52 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl @@ -32,6 +32,7 @@ interface IVoiceInteractionManagerService { boolean showSessionFromSession(IBinder token, in Bundle sessionArgs, int flags); boolean hideSessionFromSession(IBinder token); int startVoiceActivity(IBinder token, in Intent intent, String resolvedType); + void setKeepAwake(IBinder token, boolean keepAwake); void finish(IBinder token); /** diff --git a/core/java/com/android/internal/app/IVoiceInteractor.aidl b/core/java/com/android/internal/app/IVoiceInteractor.aidl index 3e0b021..84e9cf0 100644 --- a/core/java/com/android/internal/app/IVoiceInteractor.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractor.aidl @@ -16,6 +16,7 @@ package com.android.internal.app; +import android.app.VoiceInteractor; import android.os.Bundle; import com.android.internal.app.IVoiceInteractorCallback; @@ -27,6 +28,9 @@ import com.android.internal.app.IVoiceInteractorRequest; interface IVoiceInteractor { IVoiceInteractorRequest startConfirmation(String callingPackage, IVoiceInteractorCallback callback, CharSequence prompt, in Bundle extras); + IVoiceInteractorRequest startPickOption(String callingPackage, + IVoiceInteractorCallback callback, CharSequence prompt, + in VoiceInteractor.PickOptionRequest.Option[] options, in Bundle extras); IVoiceInteractorRequest startCompleteVoice(String callingPackage, IVoiceInteractorCallback callback, CharSequence message, in Bundle extras); IVoiceInteractorRequest startAbortVoice(String callingPackage, diff --git a/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl b/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl index dcd5759..1331e74 100644 --- a/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl @@ -16,6 +16,7 @@ package com.android.internal.app; +import android.app.VoiceInteractor; import android.os.Bundle; import com.android.internal.app.IVoiceInteractorRequest; @@ -26,8 +27,10 @@ import com.android.internal.app.IVoiceInteractorRequest; oneway interface IVoiceInteractorCallback { void deliverConfirmationResult(IVoiceInteractorRequest request, boolean confirmed, in Bundle result); + void deliverPickOptionResult(IVoiceInteractorRequest request, boolean finished, + in VoiceInteractor.PickOptionRequest.Option[] selections, in Bundle result); void deliverCompleteVoiceResult(IVoiceInteractorRequest request, in Bundle result); void deliverAbortVoiceResult(IVoiceInteractorRequest request, in Bundle result); - void deliverCommandResult(IVoiceInteractorRequest request, boolean complete, in Bundle result); + void deliverCommandResult(IVoiceInteractorRequest request, boolean finished, in Bundle result); void deliverCancel(IVoiceInteractorRequest request); } diff --git a/core/java/com/android/internal/os/HandlerCaller.java b/core/java/com/android/internal/os/HandlerCaller.java index 99286cb..113768e 100644 --- a/core/java/com/android/internal/os/HandlerCaller.java +++ b/core/java/com/android/internal/os/HandlerCaller.java @@ -164,6 +164,15 @@ public class HandlerCaller { return mH.obtainMessage(what, arg1, 0, args); } + public Message obtainMessageIIOOO(int what, int arg1, int arg2, Object arg3, Object arg4, + Object arg5) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = arg3; + args.arg2 = arg4; + args.arg3 = arg5; + return mH.obtainMessage(what, arg1, arg2, args); + } + public Message obtainMessageOO(int what, Object arg1, Object arg2) { SomeArgs args = SomeArgs.obtain(); args.arg1 = arg1; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index e7952c1..a366c7b 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -51,6 +51,8 @@ import android.graphics.Point; import android.graphics.Rect; import android.os.BatteryStats; import android.os.PersistableBundle; +import android.os.PowerManager; +import android.os.WorkSource; import android.os.storage.IMountService; import android.os.storage.StorageManager; import android.service.voice.IVoiceInteractionSession; @@ -991,7 +993,14 @@ public final class ActivityManagerService extends ActivityManagerNative * Set while we are running a voice interaction. This overrides * sleeping while it is active. */ - private boolean mRunningVoice = false; + private IVoiceInteractionSession mRunningVoice; + + /** + * We want to hold a wake lock while running a voice interaction session, since + * this may happen with the screen off and we need to keep the CPU running to + * be able to continue to interact with the user. + */ + PowerManager.WakeLock mVoiceWakeLock; /** * State of external calls telling us if the device is awake or asleep. @@ -2269,6 +2278,9 @@ public final class ActivityManagerService extends ActivityManagerNative public void initPowerManagement() { mStackSupervisor.initPowerManagement(); mBatteryStatsService.initPowerManagement(); + PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); + mVoiceWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*voice*"); + mVoiceWakeLock.setReferenceCounted(false); } @Override @@ -2472,7 +2484,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (DEBUG_FOCUS) Slog.d(TAG, "setFocusedActivityLocked: r=" + r); mFocusedActivity = r; if (r.task != null && r.task.voiceInteractor != null) { - startRunningVoiceLocked(); + startRunningVoiceLocked(r.task.voiceSession, r.info.applicationInfo.uid); } else { finishRunningVoiceLocked(); } @@ -3628,6 +3640,19 @@ public final class ActivityManagerService extends ActivityManagerNative } @Override + public void setVoiceKeepAwake(IVoiceInteractionSession session, boolean keepAwake) { + synchronized (this) { + if (mRunningVoice != null && mRunningVoice.asBinder() == session.asBinder()) { + if (keepAwake) { + mVoiceWakeLock.acquire(); + } else { + mVoiceWakeLock.release(); + } + } + } + } + + @Override public boolean startNextMatchingActivity(IBinder callingActivity, Intent intent, Bundle options) { // Refuse possible leaked file descriptors @@ -9685,8 +9710,8 @@ public final class ActivityManagerService extends ActivityManagerNative } void finishRunningVoiceLocked() { - if (mRunningVoice) { - mRunningVoice = false; + if (mRunningVoice != null) { + mRunningVoice = null; updateSleepIfNeededLocked(); } } @@ -9709,7 +9734,7 @@ public final class ActivityManagerService extends ActivityManagerNative private boolean shouldSleepLocked() { // Resume applications while running a voice interactor. - if (mRunningVoice) { + if (mRunningVoice != null) { return false; } @@ -9810,10 +9835,14 @@ public final class ActivityManagerService extends ActivityManagerNative + " mSleeping=" + mSleeping); } - void startRunningVoiceLocked() { - if (!mRunningVoice) { - mRunningVoice = true; - updateSleepIfNeededLocked(); + void startRunningVoiceLocked(IVoiceInteractionSession session, int targetUid) { + mVoiceWakeLock.setWorkSource(new WorkSource(targetUid)); + if (mRunningVoice == null || mRunningVoice.asBinder() != session.asBinder()) { + if (mRunningVoice == null) { + mVoiceWakeLock.acquire(); + updateSleepIfNeededLocked(); + } + mRunningVoice = session; } } @@ -12813,8 +12842,11 @@ public final class ActivityManagerService extends ActivityManagerNative + PowerManagerInternal.wakefulnessToString(mWakefulness)); pw.println(" mSleeping=" + mSleeping + " mLockScreenShown=" + lockScreenShownToString()); - pw.println(" mShuttingDown=" + mShuttingDown + " mRunningVoice=" + mRunningVoice - + " mTestPssMode=" + mTestPssMode); + pw.println(" mShuttingDown=" + mShuttingDown + " mTestPssMode=" + mTestPssMode); + if (mRunningVoice != null) { + pw.println(" mRunningVoice=" + mRunningVoice); + pw.println(" mVoiceWakeLock" + mVoiceWakeLock); + } } if (mDebugApp != null || mOrigDebugApp != null || mDebugTransient || mOrigWaitForDebugger) { diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index be95268..456ed33 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -869,7 +869,7 @@ final class ActivityStack { // If we are not going to sleep, we want to ensure the device is // awake until the next activity is started. - if (!mService.isSleepingOrShuttingDown()) { + if (!uiSleeping && !mService.isSleepingOrShuttingDown()) { mStackSupervisor.acquireLaunchWakelock(); } @@ -1671,6 +1671,8 @@ final class ActivityStack { } } + mStackSupervisor.setLaunchSource(next.info.applicationInfo.uid); + // We need to start pausing the current activity so the top one // can be resumed... boolean dontWaitForPause = (next.info.flags&ActivityInfo.FLAG_RESUME_WHILE_PAUSING) != 0; diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index cbbb11a8..f56f65f 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -84,6 +84,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; +import android.os.WorkSource; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.service.voice.IVoiceInteractionSession; @@ -316,8 +317,7 @@ public final class ActivityStackSupervisor implements DisplayListener { void initPowerManagement() { PowerManager pm = (PowerManager)mService.mContext.getSystemService(Context.POWER_SERVICE); mGoingToSleep = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ActivityManager-Sleep"); - mLaunchingActivity = - pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ActivityManager-Launch"); + mLaunchingActivity = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*launch*"); mLaunchingActivity.setReferenceCounted(false); } @@ -2273,6 +2273,10 @@ public final class ActivityStackSupervisor implements DisplayListener { } } + void setLaunchSource(int uid) { + mLaunchingActivity.setWorkSource(new WorkSource(uid)); + } + void acquireLaunchWakelock() { if (VALIDATE_WAKE_LOCK_CALLER && Binder.getCallingUid() != Process.myUid()) { throw new IllegalStateException("Calling must be system uid"); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 6b8c49c..f032ccf 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -479,6 +479,24 @@ public class VoiceInteractionManagerService extends SystemService { } @Override + public void setKeepAwake(IBinder token, boolean keepAwake) { + synchronized (this) { + if (mImpl == null) { + Slog.w(TAG, "setKeepAwake without running voice interaction service"); + return; + } + final int callingPid = Binder.getCallingPid(); + final int callingUid = Binder.getCallingUid(); + final long caller = Binder.clearCallingIdentity(); + try { + mImpl.setKeepAwakeLocked(callingPid, callingUid, token, keepAwake); + } finally { + Binder.restoreCallingIdentity(caller); + } + } + } + + @Override public void finish(IBinder token) { synchronized (this) { if (mImpl == null) { diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java index 9e92867..5a91b88 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java @@ -174,6 +174,18 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne } } + public void setKeepAwakeLocked(int callingPid, int callingUid, IBinder token, + boolean keepAwake) { + try { + if (mActiveSession == null || token != mActiveSession.mToken) { + Slog.w(TAG, "setKeepAwake does not match active session"); + return; + } + mAm.setVoiceKeepAwake(mActiveSession.mSession, keepAwake); + } catch (RemoteException e) { + throw new IllegalStateException("Unexpected remote error", e); + } + } public void finishLocked(int callingPid, int callingUid, IBinder token) { if (mActiveSession == null || token != mActiveSession.mToken) { diff --git a/tests/VoiceInteraction/res/layout/test_interaction.xml b/tests/VoiceInteraction/res/layout/test_interaction.xml index f4648b5..8c8151d 100644 --- a/tests/VoiceInteraction/res/layout/test_interaction.xml +++ b/tests/VoiceInteraction/res/layout/test_interaction.xml @@ -41,6 +41,13 @@ android:text="@string/completeVoice" /> + <Button android:id="@+id/pick" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:text="@string/pickVoice" + /> + <Button android:id="@+id/abort" android:layout_width="wrap_content" android:layout_height="wrap_content" diff --git a/tests/VoiceInteraction/res/values/strings.xml b/tests/VoiceInteraction/res/values/strings.xml index 9f99c97..5331457 100644 --- a/tests/VoiceInteraction/res/values/strings.xml +++ b/tests/VoiceInteraction/res/values/strings.xml @@ -22,7 +22,7 @@ <string name="complete">Complete</string> <string name="abortVoice">Abort Voice</string> <string name="completeVoice">Complete Voice</string> + <string name="pickVoice">Pick Voice</string> <string name="cancelVoice">Cancel</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 bcfc6f4..ad339be 100644 --- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java +++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java @@ -18,6 +18,7 @@ package com.android.test.voiceinteraction; import android.app.AssistContent; import android.app.AssistStructure; +import android.app.VoiceInteractor; import android.content.Context; import android.content.Intent; import android.os.Bundle; @@ -47,12 +48,15 @@ public class MainInteractionSession extends VoiceInteractionSession 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; - static final int STATE_COMPLETE_VOICE = 5; - static final int STATE_DONE=6; + static final int STATE_PICK_OPTION = 3; + static final int STATE_COMMAND = 4; + static final int STATE_ABORT_VOICE = 5; + static final int STATE_COMPLETE_VOICE = 6; + static final int STATE_DONE=7; int mState = STATE_IDLE; + VoiceInteractor.PickOptionRequest.Option[] mPendingOptions; + CharSequence mPendingPrompt; Request mPendingRequest; MainInteractionSession(Context context) { @@ -154,7 +158,8 @@ public class MainInteractionSession extends VoiceInteractionSession mAssistVisualizer.setVisibility(View.GONE); } mStartButton.setEnabled(mState == STATE_IDLE); - mConfirmButton.setEnabled(mState == STATE_CONFIRM || mState == STATE_COMMAND); + mConfirmButton.setEnabled(mState == STATE_CONFIRM || mState == STATE_PICK_OPTION + || mState == STATE_COMMAND); mAbortButton.setEnabled(mState == STATE_ABORT_VOICE); mCompleteButton.setEnabled(mState == STATE_COMPLETE_VOICE); } @@ -167,10 +172,32 @@ public class MainInteractionSession extends VoiceInteractionSession } else if (v == mConfirmButton) { if (mState == STATE_CONFIRM) { mPendingRequest.sendConfirmResult(true, null); - } else { + mPendingRequest = null; + mState = STATE_LAUNCHING; + } else if (mState == STATE_PICK_OPTION) { + int numReturn = mPendingOptions.length/2; + if (numReturn <= 0) { + numReturn = 1; + } + VoiceInteractor.PickOptionRequest.Option[] picked + = new VoiceInteractor.PickOptionRequest.Option[numReturn]; + for (int i=0; i<picked.length; i++) { + picked[i] = mPendingOptions[i*2]; + } + mPendingOptions = picked; + if (picked.length <= 1) { + mPendingRequest.sendPickOptionResult(true, picked, null); + mPendingRequest = null; + mState = STATE_LAUNCHING; + } else { + mPendingRequest.sendPickOptionResult(false, picked, null); + updatePickText(); + } + } else if (mPendingRequest != null) { mPendingRequest.sendCommandResult(true, null); + mPendingRequest = null; + mState = STATE_LAUNCHING; } - mPendingRequest = null; } else if (v == mAbortButton) { mPendingRequest.sendAbortVoiceResult(null); mPendingRequest = null; @@ -178,6 +205,7 @@ public class MainInteractionSession extends VoiceInteractionSession mPendingRequest.sendCompleteVoiceResult(null); mPendingRequest = null; } + updateState(); } @Override @@ -198,13 +226,40 @@ public class MainInteractionSession extends VoiceInteractionSession public void onConfirm(Caller caller, Request request, CharSequence prompt, Bundle extras) { Log.i(TAG, "onConfirm: prompt=" + prompt + " extras=" + extras); mText.setText(prompt); - mStartButton.setText("Confirm"); + mConfirmButton.setText("Confirm"); mPendingRequest = request; + mPendingPrompt = prompt; mState = STATE_CONFIRM; updateState(); } @Override + public void onPickOption(Caller caller, Request request, CharSequence prompt, + VoiceInteractor.PickOptionRequest.Option[] options, Bundle extras) { + Log.i(TAG, "onPickOption: prompt=" + prompt + " options=" + options + " extras=" + extras); + mConfirmButton.setText("Pick Option"); + mPendingRequest = request; + mPendingPrompt = prompt; + mPendingOptions = options; + mState = STATE_PICK_OPTION; + updatePickText(); + updateState(); + } + + void updatePickText() { + StringBuilder sb = new StringBuilder(); + sb.append(mPendingPrompt); + sb.append(": "); + for (int i=0; i<mPendingOptions.length; i++) { + if (i > 0) { + sb.append(", "); + } + sb.append(mPendingOptions[i].getLabel()); + } + mText.setText(sb.toString()); + } + + @Override public void onCompleteVoice(Caller caller, Request request, CharSequence message, Bundle extras) { Log.i(TAG, "onCompleteVoice: message=" + message + " extras=" + extras); mText.setText(message); @@ -226,7 +281,7 @@ public class MainInteractionSession extends VoiceInteractionSession public void onCommand(Caller caller, Request request, String command, Bundle extras) { Log.i(TAG, "onCommand: command=" + command + " extras=" + extras); mText.setText("Command: " + command); - mStartButton.setText("Finish Command"); + mConfirmButton.setText("Finish Command"); mPendingRequest = request; mState = STATE_COMMAND; updateState(); diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java index 023e0ec..e195c30 100644 --- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java +++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java @@ -26,14 +26,17 @@ import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.widget.Button; +import android.widget.TextView; public class TestInteractionActivity extends Activity implements View.OnClickListener { static final String TAG = "TestInteractionActivity"; VoiceInteractor mInteractor; VoiceInteractor.Request mCurrentRequest = null; + TextView mLog; Button mAbortButton; Button mCompleteButton; + Button mPickButton; Button mCancelButton; @Override @@ -54,10 +57,13 @@ public class TestInteractionActivity extends Activity implements View.OnClickLis } setContentView(R.layout.test_interaction); + mLog = (TextView)findViewById(R.id.log); mAbortButton = (Button)findViewById(R.id.abort); mAbortButton.setOnClickListener(this); mCompleteButton = (Button)findViewById(R.id.complete); mCompleteButton.setOnClickListener(this); + mPickButton = (Button)findViewById(R.id.pick); + mPickButton.setOnClickListener(this); mCancelButton = (Button)findViewById(R.id.cancel); mCancelButton.setOnClickListener(this); @@ -92,11 +98,13 @@ public class TestInteractionActivity extends Activity implements View.OnClickLis @Override public void onCancel() { Log.i(TAG, "Canceled!"); + mLog.append("Canceled abort\n"); } @Override public void onAbortResult(Bundle result) { Log.i(TAG, "Abort result: result=" + result); + mLog.append("Abort: result=" + result + "\n"); getActivity().finish(); } }; @@ -107,15 +115,56 @@ public class TestInteractionActivity extends Activity implements View.OnClickLis @Override public void onCancel() { Log.i(TAG, "Canceled!"); + mLog.append("Canceled complete\n"); } @Override public void onCompleteResult(Bundle result) { Log.i(TAG, "Complete result: result=" + result); + mLog.append("Complete: result=" + result + "\n"); getActivity().finish(); } }; mInteractor.submitRequest(req); + } else if (v == mPickButton) { + VoiceInteractor.PickOptionRequest.Option[] options = + new VoiceInteractor.PickOptionRequest.Option[5]; + options[0] = new VoiceInteractor.PickOptionRequest.Option("One"); + options[1] = new VoiceInteractor.PickOptionRequest.Option("Two"); + options[2] = new VoiceInteractor.PickOptionRequest.Option("Three"); + options[3] = new VoiceInteractor.PickOptionRequest.Option("Four"); + options[4] = new VoiceInteractor.PickOptionRequest.Option("Five"); + VoiceInteractor.PickOptionRequest req = new VoiceInteractor.PickOptionRequest( + "Need to pick something", options, null) { + @Override + public void onCancel() { + Log.i(TAG, "Canceled!"); + mLog.append("Canceled pick\n"); + } + + @Override + public void onPickOptionResult(boolean finished, Option[] selections, Bundle result) { + Log.i(TAG, "Pick result: finished=" + finished + " selections=" + selections + + " result=" + result); + StringBuilder sb = new StringBuilder(); + if (finished) { + sb.append("Pick final result: "); + } else { + sb.append("Pick intermediate result: "); + } + for (int i=0; i<selections.length; i++) { + if (i >= 1) { + sb.append(", "); + } + sb.append(selections[i].getLabel()); + } + mLog.append(sb.toString()); + if (finished) { + getActivity().finish(); + } + } + }; + mInteractor.submitRequest(req); } else if (v == mCancelButton && mCurrentRequest != null) { Log.i(TAG, "Cancel request"); mCurrentRequest.cancel(); |