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 \
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();
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();
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 {
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();
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();
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 {
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);
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;
}
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
*/
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;
}
--- /dev/null
+/**
+ * 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;
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;
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);
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;
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();
}
}
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
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() {
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.
}
}
+ /**
+ * 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.
*/
}
}
+ /**
+ * 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.
}
}
+ /**
+ * 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.
*/
}
}
+ /**
+ * @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) {
}
/**
+ * 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
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;
}
@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);
}
}
+ 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
}
}
- 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) {
}
}
}
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;
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
}
/**
+ * 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() {
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
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);
/**
package com.android.internal.app;
+import android.app.VoiceInteractor;
import android.os.Bundle;
import com.android.internal.app.IVoiceInteractorCallback;
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,
package com.android.internal.app;
+import android.app.VoiceInteractor;
import android.os.Bundle;
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);
}
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;
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;
* 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.
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
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();
}
}
@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
}
void finishRunningVoiceLocked() {
- if (mRunningVoice) {
- mRunningVoice = false;
+ if (mRunningVoice != null) {
+ mRunningVoice = null;
updateSleepIfNeededLocked();
}
}
private boolean shouldSleepLocked() {
// Resume applications while running a voice interactor.
- if (mRunningVoice) {
+ if (mRunningVoice != null) {
return false;
}
+ " 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;
}
}
+ 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) {
// 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();
}
}
}
+ 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;
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;
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);
}
}
}
+ 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");
}
@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) {
}
}
+ 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) {
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"
<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>
-
import android.app.AssistContent;
import android.app.AssistStructure;
+import android.app.VoiceInteractor;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
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) {
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);
}
} 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;
mPendingRequest.sendCompleteVoiceResult(null);
mPendingRequest = null;
}
+ updateState();
}
@Override
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);
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();
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
}
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);
@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();
}
};
@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();