From 3d07c94c393831091958fe6a98811843db8973bd Mon Sep 17 00:00:00 2001 From: Dianne Hackborn Date: Fri, 13 Mar 2015 18:02:54 -0700 Subject: [PATCH] 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 --- Android.mk | 1 + api/current.txt | 22 +- api/system-current.txt | 22 +- core/java/android/app/ActivityManagerNative.java | 24 ++ core/java/android/app/IActivityManager.java | 4 + core/java/android/app/VoiceInteractor.aidl | 19 ++ core/java/android/app/VoiceInteractor.java | 282 +++++++++++++++++---- core/java/android/os/Parcel.java | 34 +++ .../service/voice/VoiceInteractionSession.java | 90 ++++++- .../app/IVoiceInteractionManagerService.aidl | 1 + .../com/android/internal/app/IVoiceInteractor.aidl | 4 + .../internal/app/IVoiceInteractorCallback.aidl | 5 +- .../com/android/internal/os/HandlerCaller.java | 9 + .../android/server/am/ActivityManagerService.java | 54 +++- .../java/com/android/server/am/ActivityStack.java | 4 +- .../android/server/am/ActivityStackSupervisor.java | 8 +- .../VoiceInteractionManagerService.java | 18 ++ .../VoiceInteractionManagerServiceImpl.java | 12 + .../res/layout/test_interaction.xml | 7 + tests/VoiceInteraction/res/values/strings.xml | 2 +- .../voiceinteraction/MainInteractionSession.java | 73 +++++- .../voiceinteraction/TestInteractionActivity.java | 49 ++++ 22 files changed, 655 insertions(+), 89 deletions(-) create mode 100644 core/java/android/app/VoiceInteractor.aidl diff --git a/Android.mk b/Android.mk index 452d72c1e28d..84cad4e268f7 100644 --- a/Android.mk +++ b/Android.mk @@ -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 c05254a7c86a..9d3a83c62fb0 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 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 9365ea3f51a7..7434858492ab 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 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 997f69dc0f81..1484af8aa691 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 3dcbdd229288..d794aa3a37de 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 000000000000..40a4a0e9be1a --- /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 7b84cb4c82ff..da7bb05f4862 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 mActiveRequests = new ArrayMap(); 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()}. + * + *

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()}. - * - *

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 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

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()}. - * - *

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 3d5215b6f036..9d8a1bafb93e 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 val) { + if (val != null) { + int N = val.size(); + writeInt(N); + for (int i=0; i= 0) { @@ -1828,6 +1843,25 @@ public final class Parcel { } /** + * Read and return an ArrayList<CharSequence> object from the parcel. + * {@hide} + */ + public final ArrayList readCharSequenceList() { + ArrayList array = null; + + int length = readInt(); + if (length >= 0) { + array = new ArrayList(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 4cf0e4ca8765..11eaa0678870 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. + * + *

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}.

+ */ + 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 8f549a6b5fc0..6450d5255b13 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 3e0b02167cca..84e9cf027096 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 dcd57593c2ca..1331e744cdf0 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 99286cba3d87..113768ecd4eb 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 e7952c1a9b60..a366c7bb9003 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 be9526868107..456ed3330eac 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 cbbb11a8dfb3..f56f65fa8ec9 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 6b8c49cc82fc..f032ccfba9c3 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 9e92867349ce..5a91b88f147b 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 f4648b57dfab..8c8151d59ca0 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" /> +