summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDianne Hackborn <hackbod@google.com>2014-04-25 17:06:18 -0700
committerDianne Hackborn <hackbod@google.com>2014-04-28 10:54:15 -0700
commit18f0d357f9693fe787a3e3777d8fdf01357a6e3f (patch)
tree87ffa17a98fa81355a37e25b2c7fc649ffc4e9be
parent01c70711d5e4f1c3405bcd169be70605e92166f2 (diff)
downloadframeworks_base-18f0d357f9693fe787a3e3777d8fdf01357a6e3f.zip
frameworks_base-18f0d357f9693fe787a3e3777d8fdf01357a6e3f.tar.gz
frameworks_base-18f0d357f9693fe787a3e3777d8fdf01357a6e3f.tar.bz2
Rework some of the voice interaction APIs.
On the app side, requests are now composed by subclassing from various types of Request objects. On the service side, starting a voice interaction session involves starting another service that will then manage the session. This leads the service design much more to what we want, where the long-running main service is very tiny and all the heavy-weight transient session work is elsewhere in another process. Change-Id: I46c074c6fe27b6c1cf2583c6d216aed1de2f1143
-rw-r--r--Android.mk1
-rw-r--r--api/current.txt43
-rw-r--r--core/java/android/app/Activity.java2
-rw-r--r--core/java/android/app/VoiceInteractor.java262
-rw-r--r--core/java/android/service/voice/IVoiceInteractionSessionService.aidl28
-rw-r--r--core/java/android/service/voice/VoiceInteractionService.java8
-rw-r--r--core/java/android/service/voice/VoiceInteractionServiceInfo.java125
-rw-r--r--core/java/android/service/voice/VoiceInteractionSession.java4
-rw-r--r--core/java/android/service/voice/VoiceInteractionSessionService.java82
-rw-r--r--core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl7
-rw-r--r--core/java/com/android/internal/app/IVoiceInteractorCallback.aidl2
-rw-r--r--core/res/res/values/attrs.xml1
-rw-r--r--core/res/res/values/public.xml16
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java50
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java169
-rw-r--r--tests/VoiceInteraction/AndroidManifest.xml6
-rw-r--r--tests/VoiceInteraction/res/xml/interaction_service.xml21
-rw-r--r--tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java3
-rw-r--r--tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java7
-rw-r--r--tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSessionService.java28
-rw-r--r--tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java22
21 files changed, 699 insertions, 188 deletions
diff --git a/Android.mk b/Android.mk
index 852247c..232f5bf 100644
--- a/Android.mk
+++ b/Android.mk
@@ -201,6 +201,7 @@ LOCAL_SRC_FILES += \
core/java/android/service/trust/ITrustAgentServiceCallback.aidl \
core/java/android/service/voice/IVoiceInteractionService.aidl \
core/java/android/service/voice/IVoiceInteractionSession.aidl \
+ core/java/android/service/voice/IVoiceInteractionSessionService.aidl \
core/java/android/service/wallpaper/IWallpaperConnection.aidl \
core/java/android/service/wallpaper/IWallpaperEngine.aidl \
core/java/android/service/wallpaper/IWallpaperService.aidl \
diff --git a/api/current.txt b/api/current.txt
index 4fef0d0..34b1ac8 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -1009,6 +1009,7 @@ package android {
field public static final int selectedDateVerticalBar = 16843591; // 0x1010347
field public static final int selectedWeekBackgroundColor = 16843586; // 0x1010342
field public static final int sequence = 16843815; // 0x1010427
+ field public static final int sessionService = 16843850; // 0x101044a
field public static final int settingsActivity = 16843301; // 0x1010225
field public static final int shadowColor = 16843105; // 0x1010161
field public static final int shadowDx = 16843106; // 0x1010162
@@ -1663,10 +1664,14 @@ package android {
field public static final int decelerate_cubic = 17563651; // 0x10c0003
field public static final int decelerate_quad = 17563649; // 0x10c0001
field public static final int decelerate_quint = 17563653; // 0x10c0005
- field public static final int fast_out_linear_in = 17563663; // 0x10c000f
- field public static final int fast_out_slow_in = 17563661; // 0x10c000d
+ field public static final int fast_out_linear_in = 17563667; // 0x10c0013
+ field public static final int fast_out_slow_in = 17563665; // 0x10c0011
+ field public static final int l_resource_pad1 = 17563664; // 0x10c0010
+ field public static final int l_resource_pad2 = 17563663; // 0x10c000f
+ field public static final int l_resource_pad3 = 17563662; // 0x10c000e
+ field public static final int l_resource_pad4 = 17563661; // 0x10c000d
field public static final int linear = 17563659; // 0x10c000b
- field public static final int linear_out_slow_in = 17563662; // 0x10c000e
+ field public static final int linear_out_slow_in = 17563666; // 0x10c0012
field public static final int overshoot = 17563656; // 0x10c0008
}
@@ -4842,20 +4847,26 @@ package android.app {
}
public class VoiceInteractor {
- method public android.app.VoiceInteractor.Request startCommand(android.app.VoiceInteractor.Callback, java.lang.String, android.os.Bundle);
- method public android.app.VoiceInteractor.Request startConfirmation(android.app.VoiceInteractor.Callback, java.lang.String, android.os.Bundle);
+ method public boolean submitRequest(android.app.VoiceInteractor.Request);
method public boolean[] supportsCommands(java.lang.String[]);
}
- public static class VoiceInteractor.Callback {
- ctor public VoiceInteractor.Callback();
- method public void onCancel(android.app.VoiceInteractor.Request);
- method public void onCommandResult(android.app.VoiceInteractor.Request, android.os.Bundle);
- method public void onConfirmationResult(android.app.VoiceInteractor.Request, boolean, android.os.Bundle);
+ public static class VoiceInteractor.CommandRequest extends android.app.VoiceInteractor.Request {
+ ctor public VoiceInteractor.CommandRequest(java.lang.String, android.os.Bundle);
+ method public void onCommandResult(android.os.Bundle);
}
- public static class VoiceInteractor.Request {
+ public static class VoiceInteractor.ConfirmationRequest extends android.app.VoiceInteractor.Request {
+ ctor public VoiceInteractor.ConfirmationRequest(java.lang.CharSequence, android.os.Bundle);
+ method public void onConfirmationResult(boolean, android.os.Bundle);
+ }
+
+ 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 void onCancel();
}
public final class WallpaperInfo implements android.os.Parcelable {
@@ -24919,7 +24930,7 @@ package android.service.voice {
public class VoiceInteractionService extends android.app.Service {
ctor public VoiceInteractionService();
method public android.os.IBinder onBind(android.content.Intent);
- method public void startVoiceActivity(android.content.Intent, android.service.voice.VoiceInteractionSession);
+ method public void startVoiceActivity(android.content.Intent, android.os.Bundle);
field public static final java.lang.String SERVICE_INTERFACE = "android.service.voice.VoiceInteractionService";
field public static final java.lang.String SERVICE_META_DATA = "android.voice_interaction";
}
@@ -24938,10 +24949,16 @@ package android.service.voice {
public static class VoiceInteractionSession.Request {
method public void sendCancelResult();
- method public void sendCommandResult(android.os.Bundle);
+ method public void sendCommandResult(boolean, android.os.Bundle);
method public void sendConfirmResult(boolean, android.os.Bundle);
}
+ public abstract class VoiceInteractionSessionService extends android.app.Service {
+ ctor public VoiceInteractionSessionService();
+ method public android.os.IBinder onBind(android.content.Intent);
+ method public abstract android.service.voice.VoiceInteractionSession onNewSession(android.os.Bundle);
+ }
+
}
package android.service.wallpaper {
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 197eaf8..8981c88 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -5454,7 +5454,7 @@ public class Activity extends ContextThemeWrapper
mEmbeddedID = id;
mLastNonConfigurationInstances = lastNonConfigurationInstances;
mVoiceInteractor = voiceInteractor != null
- ? new VoiceInteractor(this, voiceInteractor, Looper.myLooper()) : null;
+ ? new VoiceInteractor(this, this, voiceInteractor, Looper.myLooper()) : null;
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
diff --git a/core/java/android/app/VoiceInteractor.java b/core/java/android/app/VoiceInteractor.java
index 6820dfd..6dc48b0 100644
--- a/core/java/android/app/VoiceInteractor.java
+++ b/core/java/android/app/VoiceInteractor.java
@@ -22,6 +22,7 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
+import android.util.ArrayMap;
import android.util.Log;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.app.IVoiceInteractorCallback;
@@ -39,50 +40,85 @@ public class VoiceInteractor {
static final boolean DEBUG = true;
final Context mContext;
+ final Activity mActivity;
final IVoiceInteractor mInteractor;
final HandlerCaller mHandlerCaller;
final HandlerCaller.Callback mHandlerCallerCallback = new HandlerCaller.Callback() {
@Override
public void executeMessage(Message msg) {
SomeArgs args = (SomeArgs)msg.obj;
+ Request request;
switch (msg.what) {
case MSG_CONFIRMATION_RESULT:
+ request = pullRequest((IVoiceInteractorRequest)args.arg1, true);
if (DEBUG) Log.d(TAG, "onConfirmResult: req="
- + ((IVoiceInteractorRequest)args.arg2).asBinder()
- + " confirmed=" + msg.arg1 + " result=" + args.arg3);
- ((Callback)args.arg1).onConfirmationResult(
- findRequest((IVoiceInteractorRequest)args.arg2),
- msg.arg1 != 0, (Bundle)args.arg3);
+ + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request
+ + " confirmed=" + msg.arg1 + " result=" + args.arg2);
+ if (request != null) {
+ ((ConfirmationRequest)request).onConfirmationResult(msg.arg1 != 0,
+ (Bundle) args.arg2);
+ request.clear();
+ }
break;
case MSG_COMMAND_RESULT:
+ request = pullRequest((IVoiceInteractorRequest)args.arg1, msg.arg1 != 0);
if (DEBUG) Log.d(TAG, "onCommandResult: req="
- + ((IVoiceInteractorRequest)args.arg2).asBinder()
+ + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request
+ " result=" + args.arg2);
- ((Callback)args.arg1).onCommandResult(
- findRequest((IVoiceInteractorRequest) args.arg2),
- (Bundle) args.arg3);
+ if (request != null) {
+ ((CommandRequest)request).onCommandResult((Bundle) args.arg2);
+ if (msg.arg1 != 0) {
+ request.clear();
+ }
+ }
break;
case MSG_CANCEL_RESULT:
+ request = pullRequest((IVoiceInteractorRequest)args.arg1, true);
if (DEBUG) Log.d(TAG, "onCancelResult: req="
- + ((IVoiceInteractorRequest)args.arg2).asBinder());
- ((Callback)args.arg1).onCancel(
- findRequest((IVoiceInteractorRequest) args.arg2));
+ + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request);
+ if (request != null) {
+ request.onCancel();
+ request.clear();
+ }
break;
}
}
};
- final WeakHashMap<IBinder, Request> mActiveRequests = new WeakHashMap<IBinder, Request>();
+ final IVoiceInteractorCallback.Stub mCallback = new IVoiceInteractorCallback.Stub() {
+ @Override
+ public void deliverConfirmationResult(IVoiceInteractorRequest request, boolean confirmed,
+ Bundle result) {
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOO(
+ MSG_CONFIRMATION_RESULT, confirmed ? 1 : 0, request, result));
+ }
+
+ @Override
+ public void deliverCommandResult(IVoiceInteractorRequest request, boolean complete,
+ Bundle result) {
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOO(
+ MSG_COMMAND_RESULT, complete ? 1 : 0, request, result));
+ }
+
+ @Override
+ public void deliverCancel(IVoiceInteractorRequest request) throws RemoteException {
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(
+ MSG_CANCEL_RESULT, request));
+ }
+ };
+
+ final ArrayMap<IBinder, Request> mActiveRequests = new ArrayMap<IBinder, Request>();
static final int MSG_CONFIRMATION_RESULT = 1;
static final int MSG_COMMAND_RESULT = 2;
static final int MSG_CANCEL_RESULT = 3;
- public static class Request {
- final IVoiceInteractorRequest mRequestInterface;
+ public static abstract class Request {
+ IVoiceInteractorRequest mRequestInterface;
+ Context mContext;
+ Activity mActivity;
- Request(IVoiceInteractorRequest requestInterface) {
- mRequestInterface = requestInterface;
+ public Request() {
}
public void cancel() {
@@ -92,126 +128,130 @@ public class VoiceInteractor {
Log.w(TAG, "Voice interactor has died", e);
}
}
+
+ public Context getContext() {
+ return mContext;
+ }
+
+ public Activity getActivity() {
+ return mActivity;
+ }
+
+ public void onCancel() {
+ }
+
+ void clear() {
+ mRequestInterface = null;
+ mContext = null;
+ mActivity = null;
+ }
+
+ abstract IVoiceInteractorRequest submit(IVoiceInteractor interactor,
+ String packageName, IVoiceInteractorCallback callback) throws RemoteException;
}
- public static class Callback {
- VoiceInteractor mInteractor;
+ public static class ConfirmationRequest extends Request {
+ final CharSequence mPrompt;
+ final Bundle mExtras;
- final IVoiceInteractorCallback.Stub mWrapper = new IVoiceInteractorCallback.Stub() {
- @Override
- public void deliverConfirmationResult(IVoiceInteractorRequest request, boolean confirmed,
- Bundle result) {
- mInteractor.mHandlerCaller.sendMessage(mInteractor.mHandlerCaller.obtainMessageIOOO(
- MSG_CONFIRMATION_RESULT, confirmed ? 1 : 0, Callback.this, request,
- result));
- }
+ /**
+ * 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.
+ * @param prompt Optional confirmation text to read to the user as the action being
+ * confirmed.
+ * @param extras Additional optional information.
+ */
+ public ConfirmationRequest(CharSequence prompt, Bundle extras) {
+ mPrompt = prompt;
+ mExtras = extras;
+ }
- @Override
- public void deliverCommandResult(IVoiceInteractorRequest request, Bundle result) {
- mInteractor.mHandlerCaller.sendMessage(mInteractor.mHandlerCaller.obtainMessageOOO(
- MSG_COMMAND_RESULT, Callback.this, request, result));
- }
+ public void onConfirmationResult(boolean confirmed, Bundle result) {
+ }
- @Override
- public void deliverCancel(IVoiceInteractorRequest request) throws RemoteException {
- mInteractor.mHandlerCaller.sendMessage(mInteractor.mHandlerCaller.obtainMessageOO(
- MSG_CANCEL_RESULT, Callback.this, request));
- }
- };
+ IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName,
+ IVoiceInteractorCallback callback) throws RemoteException {
+ return interactor.startConfirmation(packageName, callback, mPrompt.toString(), mExtras);
+ }
+ }
- public void onConfirmationResult(Request request, boolean confirmed, Bundle result) {
+ 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(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.)
+ *
+ * @param command The desired command to perform.
+ * @param args Additional arguments to control execution of the command.
+ */
+ public CommandRequest(String command, Bundle args) {
+ mCommand = command;
+ mArgs = args;
}
- public void onCommandResult(Request request, Bundle result) {
+ public void onCommandResult(Bundle result) {
}
- public void onCancel(Request request) {
+ IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName,
+ IVoiceInteractorCallback callback) throws RemoteException {
+ return interactor.startConfirmation(packageName, callback, mCommand, mArgs);
}
- }
+ }
- VoiceInteractor(Context context, IVoiceInteractor interactor, Looper looper) {
+ VoiceInteractor(Context context, Activity activity, IVoiceInteractor interactor,
+ Looper looper) {
mContext = context;
+ mActivity = activity;
mInteractor = interactor;
mHandlerCaller = new HandlerCaller(context, looper, mHandlerCallerCallback, true);
}
- Request storeRequest(IVoiceInteractorRequest request) {
- synchronized (mActiveRequests) {
- Request req = new Request(request);
- mActiveRequests.put(request.asBinder(), req);
- return req;
- }
- }
-
- Request findRequest(IVoiceInteractorRequest request) {
+ Request pullRequest(IVoiceInteractorRequest request, boolean complete) {
synchronized (mActiveRequests) {
Request req = mActiveRequests.get(request.asBinder());
- if (req == null) {
- throw new IllegalStateException("Received callback without active request: "
- + request);
+ if (req != null && complete) {
+ mActiveRequests.remove(request.asBinder());
}
return req;
}
}
- /**
- * Asynchronously 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 by calling the
- * {@link Callback#onConfirmationResult Callback.onConfirmationResult} method.
- *
- * 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 informed consent.
- * @param callback Required callback target for interaction results.
- * @param prompt Optional confirmation text to read to the user as the action being confirmed.
- * @param extras Additional optional information.
- * @return Returns a new {@link Request} object representing this operation.
- */
- public Request startConfirmation(Callback callback, String prompt, Bundle extras) {
+ public boolean submitRequest(Request request) {
try {
- callback.mInteractor = this;
- Request req = storeRequest(mInteractor.startConfirmation(
- mContext.getOpPackageName(), callback.mWrapper, prompt, extras));
- if (DEBUG) Log.d(TAG, "startConfirmation: req=" + req.mRequestInterface.asBinder()
- + " prompt=" + prompt + " extras=" + extras);
- return req;
- } catch (RemoteException e) {
- throw new RuntimeException("Voice interactor has died", e);
- }
- }
-
- /**
- * Asynchronously executes 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 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.)
- * The result of the command will be returned by calling the
- * {@link Callback#onCommandResult Callback.onCommandResult} method.
- *
- * @param callback Required callback target for interaction results.
- * @param command
- * @param extras
- * @return Returns a new {@link Request} object representing this operation.
- */
- public Request startCommand(Callback callback, String command, Bundle extras) {
- try {
- callback.mInteractor = this;
- Request req = storeRequest(mInteractor.startCommand(
- mContext.getOpPackageName(), callback.mWrapper, command, extras));
- if (DEBUG) Log.d(TAG, "startCommand: req=" + req.mRequestInterface.asBinder()
- + " command=" + command + " extras=" + extras);
- return req;
+ IVoiceInteractorRequest ireq = request.submit(mInteractor,
+ mContext.getOpPackageName(), mCallback);
+ request.mRequestInterface = ireq;
+ request.mContext = mContext;
+ request.mActivity = mActivity;
+ synchronized (mActiveRequests) {
+ mActiveRequests.put(ireq.asBinder(), request);
+ }
+ return true;
} catch (RemoteException e) {
- throw new RuntimeException("Voice interactor has died", e);
+ Log.w(TAG, "Remove voice interactor service died", e);
+ return false;
}
}
diff --git a/core/java/android/service/voice/IVoiceInteractionSessionService.aidl b/core/java/android/service/voice/IVoiceInteractionSessionService.aidl
new file mode 100644
index 0000000..2519442
--- /dev/null
+++ b/core/java/android/service/voice/IVoiceInteractionSessionService.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2014 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.service.voice;
+
+import android.os.Bundle;
+
+import android.service.voice.IVoiceInteractionSession;
+
+/**
+ * @hide
+ */
+oneway interface IVoiceInteractionSessionService {
+ void newSession(IBinder token, in Bundle args);
+}
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index ed93b74..d005890 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -21,6 +21,7 @@ import android.app.Instrumentation;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
+import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -50,12 +51,11 @@ public class VoiceInteractionService extends Service {
IVoiceInteractionManagerService mSystemService;
- public void startVoiceActivity(Intent intent, VoiceInteractionSession session) {
+ public void startVoiceActivity(Intent intent, Bundle sessionArgs) {
try {
- int res = mSystemService.startVoiceActivity(intent,
+ mSystemService.startVoiceActivity(intent,
intent.resolveType(getContentResolver()),
- mInterface, session.mSession, session.mInteractor);
- Instrumentation.checkStartActivityResult(res, intent);
+ mInterface, sessionArgs);
} catch (RemoteException e) {
}
}
diff --git a/core/java/android/service/voice/VoiceInteractionServiceInfo.java b/core/java/android/service/voice/VoiceInteractionServiceInfo.java
new file mode 100644
index 0000000..a909ead
--- /dev/null
+++ b/core/java/android/service/voice/VoiceInteractionServiceInfo.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2014 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.service.voice;
+
+import android.Manifest;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.speech.RecognitionService;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Xml;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/** @hide */
+public class VoiceInteractionServiceInfo {
+ static final String TAG = "VoiceInteractionServiceInfo";
+
+ private String mParseError;
+
+ private ServiceInfo mServiceInfo;
+ private String mSessionService;
+ private String mSettingsActivity;
+
+ public VoiceInteractionServiceInfo(PackageManager pm, ComponentName comp)
+ throws PackageManager.NameNotFoundException {
+ this(pm, pm.getServiceInfo(comp, PackageManager.GET_META_DATA));
+ }
+
+ public VoiceInteractionServiceInfo(PackageManager pm, ServiceInfo si) {
+ if (!Manifest.permission.BIND_VOICE_INTERACTION.equals(si.permission)) {
+ mParseError = "Service does not require permission "
+ + Manifest.permission.BIND_VOICE_INTERACTION;
+ return;
+ }
+
+ XmlResourceParser parser = null;
+ try {
+ parser = si.loadXmlMetaData(pm, VoiceInteractionService.SERVICE_META_DATA);
+ if (parser == null) {
+ mParseError = "No " + VoiceInteractionService.SERVICE_META_DATA
+ + " meta-data for " + si.packageName;
+ return;
+ }
+
+ Resources res = pm.getResourcesForApplication(si.applicationInfo);
+
+ AttributeSet attrs = Xml.asAttributeSet(parser);
+
+ int type;
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && type != XmlPullParser.START_TAG) {
+ }
+
+ String nodeName = parser.getName();
+ if (!"voice-interaction-service".equals(nodeName)) {
+ mParseError = "Meta-data does not start with voice-interaction-service tag";
+ return;
+ }
+
+ TypedArray array = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.VoiceInteractionService);
+ mSessionService = array.getString(
+ com.android.internal.R.styleable.VoiceInteractionService_sessionService);
+ mSettingsActivity = array.getString(
+ com.android.internal.R.styleable.VoiceInteractionService_settingsActivity);
+ array.recycle();
+ if (mSessionService == null) {
+ mParseError = "No sessionService specified";
+ return;
+ }
+ } catch (XmlPullParserException e) {
+ mParseError = "Error parsing voice interation service meta-data: " + e;
+ Log.w(TAG, "error parsing voice interaction service meta-data", e);
+ return;
+ } catch (IOException e) {
+ mParseError = "Error parsing voice interation service meta-data: " + e;
+ Log.w(TAG, "error parsing voice interaction service meta-data", e);
+ return;
+ } catch (PackageManager.NameNotFoundException e) {
+ mParseError = "Error parsing voice interation service meta-data: " + e;
+ Log.w(TAG, "error parsing voice interaction service meta-data", e);
+ return;
+ } finally {
+ if (parser != null) parser.close();
+ }
+ mServiceInfo = si;
+ }
+
+ public String getParseError() {
+ return mParseError;
+ }
+
+ public ServiceInfo getServiceInfo() {
+ return mServiceInfo;
+ }
+
+ public String getSessionService() {
+ return mSessionService;
+ }
+
+ public String getSettingsActivity() {
+ return mSettingsActivity;
+ }
+}
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index 59544be..963b6b4 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -96,11 +96,11 @@ public abstract class VoiceInteractionSession {
}
}
- public void sendCommandResult(Bundle result) {
+ public void sendCommandResult(boolean complete, Bundle result) {
try {
if (DEBUG) Log.d(TAG, "sendCommandResult: req=" + mInterface
+ " result=" + result);
- mCallback.deliverCommandResult(mInterface, result);
+ mCallback.deliverCommandResult(mInterface, complete, result);
} catch (RemoteException e) {
}
}
diff --git a/core/java/android/service/voice/VoiceInteractionSessionService.java b/core/java/android/service/voice/VoiceInteractionSessionService.java
new file mode 100644
index 0000000..40e5bba
--- /dev/null
+++ b/core/java/android/service/voice/VoiceInteractionSessionService.java
@@ -0,0 +1,82 @@
+/**
+ * Copyright (C) 2014 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.service.voice;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import com.android.internal.app.IVoiceInteractionManagerService;
+import com.android.internal.os.HandlerCaller;
+import com.android.internal.os.SomeArgs;
+
+public abstract class VoiceInteractionSessionService extends Service {
+
+ static final int MSG_NEW_SESSION = 1;
+
+ IVoiceInteractionManagerService mSystemService;
+
+ IVoiceInteractionSessionService mInterface = new IVoiceInteractionSessionService.Stub() {
+ public void newSession(IBinder token, Bundle args) {
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOO(MSG_NEW_SESSION,
+ token, args));
+
+ }
+ };
+
+ HandlerCaller mHandlerCaller;
+ final HandlerCaller.Callback mHandlerCallerCallback = new HandlerCaller.Callback() {
+ @Override
+ public void executeMessage(Message msg) {
+ SomeArgs args = (SomeArgs)msg.obj;
+ switch (msg.what) {
+ case MSG_NEW_SESSION:
+ doNewSession((IBinder)args.arg1, (Bundle)args.arg2);
+ break;
+ }
+ }
+ };
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mSystemService = IVoiceInteractionManagerService.Stub.asInterface(
+ ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
+ mHandlerCaller = new HandlerCaller(this, Looper.myLooper(),
+ mHandlerCallerCallback, true);
+ }
+
+ public abstract VoiceInteractionSession onNewSession(Bundle args);
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mInterface.asBinder();
+ }
+
+ void doNewSession(IBinder token, Bundle args) {
+ VoiceInteractionSession session = onNewSession(args);
+ try {
+ mSystemService.deliverNewSession(token, session.mSession, session.mInteractor);
+ } catch (RemoteException e) {
+ }
+ }
+}
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index e3c0728..3219ddd 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -17,12 +17,15 @@
package com.android.internal.app;
import android.content.Intent;
+import android.os.Bundle;
import com.android.internal.app.IVoiceInteractor;
import android.service.voice.IVoiceInteractionService;
import android.service.voice.IVoiceInteractionSession;
interface IVoiceInteractionManagerService {
- int startVoiceActivity(in Intent intent, String resolvedType, IVoiceInteractionService service,
- IVoiceInteractionSession session, IVoiceInteractor interactor);
+ void startVoiceActivity(in Intent intent, String resolvedType, IVoiceInteractionService service,
+ in Bundle sessionArgs);
+ int deliverNewSession(IBinder token, IVoiceInteractionSession session,
+ IVoiceInteractor interactor);
}
diff --git a/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl b/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl
index f5392e9..c6f93e1 100644
--- a/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl
@@ -26,6 +26,6 @@ import com.android.internal.app.IVoiceInteractorRequest;
oneway interface IVoiceInteractorCallback {
void deliverConfirmationResult(IVoiceInteractorRequest request, boolean confirmed,
in Bundle result);
- void deliverCommandResult(IVoiceInteractorRequest request, in Bundle result);
+ void deliverCommandResult(IVoiceInteractorRequest request, boolean complete, in Bundle result);
void deliverCancel(IVoiceInteractorRequest request);
}
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 326485d..69440be 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -6279,6 +6279,7 @@
its {@link android.service.voice.VoiceInteractionService#SERVICE_META_DATA} meta-data entry.
Described here are the attributes that can be included in that tag. -->
<declare-styleable name="VoiceInteractionService">
+ <attr name="sessionService" format="string" />
<attr name="settingsActivity" />
</declare-styleable>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 6491b33..085b9c3 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2155,6 +2155,13 @@
<public type="attr" name="colorPrimaryDark" />
<public type="attr" name="colorAccent" />
<public type="attr" name="nestedScrollingEnabled" />
+ <public type="attr" name="windowEnterTransition" />
+ <public type="attr" name="windowExitTransition" />
+ <public type="attr" name="windowSharedElementEnterTransition" />
+ <public type="attr" name="windowSharedElementExitTransition" />
+ <public type="attr" name="windowAllowExitTransitionOverlap" />
+ <public type="attr" name="windowAllowEnterTransitionOverlap" />
+ <public type="attr" name="sessionService" />
<public-padding type="dimen" name="l_resource_pad" end="0x01050010" />
@@ -2385,17 +2392,14 @@
<public type="style" name="Widget.Holo.Light.Button.Borderless" />
+ <public-padding type="interpolator" name="l_resource_pad" end="0x010c0010" />
+
<!-- An interpolator which accelerates fast but decelerates slowly. -->
<public type="interpolator" name="fast_out_slow_in" />
<!-- An interpolator which starts with a peak non-zero velocity and decelerates slowly. -->
<public type="interpolator" name="linear_out_slow_in" />
<!-- An interpolator which accelerates fast and keeps accelerating until the end. -->
<public type="interpolator" name="fast_out_linear_in" />
- <public type="attr" name="windowEnterTransition" />
- <public type="attr" name="windowExitTransition" />
- <public type="attr" name="windowSharedElementEnterTransition" />
- <public type="attr" name="windowSharedElementExitTransition" />
- <public type="attr" name="windowAllowExitTransitionOverlap" />
- <public type="attr" name="windowAllowEnterTransitionOverlap" />
+
<public type="transition" name="no_transition" id="0x010f0000"/>
</resources>
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 9e2bcab..045c0f6 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -16,14 +16,18 @@
package com.android.server.voiceinteraction;
+import android.Manifest;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.os.Binder;
+import android.os.Bundle;
import android.os.Handler;
+import android.os.IBinder;
import android.os.UserHandle;
import android.provider.Settings;
import android.service.voice.IVoiceInteractionService;
@@ -134,9 +138,8 @@ public class VoiceInteractionManagerService extends SystemService {
}
@Override
- public int startVoiceActivity(Intent intent, String resolvedType,
- IVoiceInteractionService service,
- IVoiceInteractionSession session, IVoiceInteractor interactor) {
+ public void startVoiceActivity(Intent intent, String resolvedType,
+ IVoiceInteractionService service, Bundle args) {
synchronized (this) {
if (mImpl == null || service.asBinder() != mImpl.mService.asBinder()) {
throw new SecurityException(
@@ -146,8 +149,8 @@ public class VoiceInteractionManagerService extends SystemService {
final int callingUid = Binder.getCallingUid();
final long caller = Binder.clearCallingIdentity();
try {
- return mImpl.startVoiceActivityLocked(callingPid, callingUid,
- intent, resolvedType, session, interactor);
+ mImpl.startVoiceActivityLocked(callingPid, callingUid,
+ intent, resolvedType, args);
} finally {
Binder.restoreCallingIdentity(caller);
}
@@ -155,8 +158,43 @@ public class VoiceInteractionManagerService extends SystemService {
}
@Override
+ public int deliverNewSession(IBinder token, IVoiceInteractionSession session,
+ IVoiceInteractor interactor) {
+ synchronized (this) {
+ if (mImpl == null) {
+ Slog.w(TAG, "deliverNewSession without running voice interaction service");
+ return ActivityManager.START_CANCELED;
+ }
+ final int callingPid = Binder.getCallingPid();
+ final int callingUid = Binder.getCallingUid();
+ final long caller = Binder.clearCallingIdentity();
+ try {
+ return mImpl.deliverNewSessionLocked(callingPid, callingUid, token, session,
+ interactor);
+ } finally {
+ Binder.restoreCallingIdentity(caller);
+ }
+ }
+
+ }
+
+ @Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP)
+ != PackageManager.PERMISSION_GRANTED) {
+ pw.println("Permission Denial: can't dump PowerManager from from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid());
+ return;
+ }
+ synchronized (this) {
+ pw.println("VOICE INTERACTION MANAGER (dumpsys voiceinteraction)\n");
+ if (mImpl == null) {
+ pw.println(" (No active implementation)");
+ return;
+ }
+ mImpl.dumpLocked(fd, pw, args);
+ }
}
class SettingsObserver extends ContentObserver {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index af8ae1e..6bbd1c1 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -16,6 +16,7 @@
package com.android.server.voiceinteraction;
+import android.app.ActivityManager;
import android.app.ActivityManagerNative;
import android.app.IActivityManager;
import android.content.ComponentName;
@@ -24,29 +25,40 @@ import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
+import android.os.Binder;
+import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.service.voice.IVoiceInteractionService;
import android.service.voice.IVoiceInteractionSession;
+import android.service.voice.IVoiceInteractionSessionService;
import android.service.voice.VoiceInteractionService;
+import android.service.voice.VoiceInteractionServiceInfo;
import android.util.Slog;
import com.android.internal.app.IVoiceInteractor;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
class VoiceInteractionManagerServiceImpl {
final static String TAG = "VoiceInteractionServiceManager";
+ final boolean mValid;
+
final Context mContext;
final Handler mHandler;
final Object mLock;
final int mUser;
final ComponentName mComponent;
final IActivityManager mAm;
+ final VoiceInteractionServiceInfo mInfo;
+ final ComponentName mSessionComponentName;
boolean mBound = false;
IVoiceInteractionService mService;
- IVoiceInteractionSession mActiveSession;
- IVoiceInteractor mActiveInteractor;
+
+ SessionConnection mActiveSession;
final ServiceConnection mConnection = new ServiceConnection() {
@Override
@@ -62,6 +74,72 @@ class VoiceInteractionManagerServiceImpl {
}
};
+ final class SessionConnection implements ServiceConnection {
+ final IBinder mToken = new Binder();
+ final Intent mIntent;
+ final String mResolvedType;
+ final Bundle mArgs;
+ boolean mBound;
+ IVoiceInteractionSessionService mService;
+ IVoiceInteractionSession mSession;
+ IVoiceInteractor mInteractor;
+
+ SessionConnection(Intent intent, String resolvedType, Bundle args) {
+ mIntent = intent;
+ mResolvedType = resolvedType;
+ mArgs = args;
+ Intent serviceIntent = new Intent(VoiceInteractionService.SERVICE_INTERFACE);
+ serviceIntent.setComponent(mSessionComponentName);
+ mBound = mContext.bindServiceAsUser(serviceIntent, this,
+ Context.BIND_AUTO_CREATE, new UserHandle(mUser));
+ if (!mBound) {
+ Slog.w(TAG, "Failed binding to voice interaction session service " + mComponent);
+ }
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ synchronized (mLock) {
+ mService = IVoiceInteractionSessionService.Stub.asInterface(service);
+ if (mActiveSession == this) {
+ try {
+ mService.newSession(mToken, mArgs);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed making new session", e);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ mService = null;
+ }
+
+ public void cancel() {
+ if (mBound) {
+ mContext.unbindService(this);
+ mBound = false;
+ mService = null;
+ mSession = null;
+ mInteractor = null;
+ }
+ }
+
+ public void dump(String prefix, PrintWriter pw) {
+ pw.print(prefix); pw.print("mToken="); pw.println(mToken);
+ pw.print(prefix); pw.print("mIntent="); pw.println(mIntent);
+ pw.print(" mResolvedType="); pw.println(mResolvedType);
+ pw.print(prefix); pw.print("mArgs="); pw.println(mArgs);
+ pw.print(prefix); pw.print("mBound="); pw.println(mBound);
+ if (mBound) {
+ pw.print(prefix); pw.print("mService="); pw.println(mService);
+ pw.print(prefix); pw.print("mSession="); pw.println(mSession);
+ pw.print(prefix); pw.print("mInteractor="); pw.println(mInteractor);
+ }
+ }
+ };
+
VoiceInteractionManagerServiceImpl(Context context, Handler handler, Object lock,
int userHandle, ComponentName service) {
mContext = context;
@@ -70,50 +148,85 @@ class VoiceInteractionManagerServiceImpl {
mUser = userHandle;
mComponent = service;
mAm = ActivityManagerNative.getDefault();
- }
-
- public int startVoiceActivityLocked(int callingPid, int callingUid, Intent intent,
- String resolvedType, IVoiceInteractionSession session, IVoiceInteractor interactor) {
- if (session == null) {
- throw new NullPointerException("session is null");
+ VoiceInteractionServiceInfo info;
+ try {
+ info = new VoiceInteractionServiceInfo(context.getPackageManager(), service);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.w(TAG, "Voice interaction service not found: " + service);
+ mInfo = null;
+ mSessionComponentName = null;
+ mValid = false;
+ return;
}
- if (interactor == null) {
- throw new NullPointerException("interactor is null");
+ mInfo = info;
+ if (mInfo.getParseError() != null) {
+ Slog.w(TAG, "Bad voice interaction service: " + mInfo.getParseError());
+ mSessionComponentName = null;
+ mValid = false;
+ return;
}
+ mValid = true;
+ mSessionComponentName = new ComponentName(service.getPackageName(),
+ mInfo.getSessionService());
+ }
+
+ public void startVoiceActivityLocked(int callingPid, int callingUid, Intent intent,
+ String resolvedType, Bundle args) {
if (mActiveSession != null) {
- // XXX cancel current session.
+ mActiveSession.cancel();
+ mActiveSession = null;
}
+ mActiveSession = new SessionConnection(intent, resolvedType, args);
intent.addCategory(Intent.CATEGORY_VOICE);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
- mActiveSession = session;
- mActiveInteractor = interactor;
+ }
+
+ public int deliverNewSessionLocked(int callingPid, int callingUid, IBinder token,
+ IVoiceInteractionSession session, IVoiceInteractor interactor) {
try {
+ if (mActiveSession == null || token != mActiveSession.mToken) {
+ Slog.w(TAG, "deliverNewSession does not match active session");
+ return ActivityManager.START_CANCELED;
+ }
+ mActiveSession.mSession = session;
+ mActiveSession.mInteractor = interactor;
return mAm.startVoiceActivity(mComponent.getPackageName(), callingPid, callingUid,
- intent, resolvedType, mActiveSession, mActiveInteractor,
+ mActiveSession.mIntent, mActiveSession.mResolvedType,
+ mActiveSession.mSession, mActiveSession.mInteractor,
0, null, null, null, mUser);
} catch (RemoteException e) {
throw new IllegalStateException("Unexpected remote error", e);
}
}
- void startLocked() {
- Intent intent = new Intent(VoiceInteractionService.SERVICE_INTERFACE);
- intent.setComponent(mComponent);
- try {
- ServiceInfo si = mContext.getPackageManager().getServiceInfo(mComponent, 0);
- if (!android.Manifest.permission.BIND_VOICE_INTERACTION.equals(si.permission)) {
- Slog.w(TAG, "Not using voice interaction service " + mComponent
- + ": does not require permission "
- + android.Manifest.permission.BIND_VOICE_INTERACTION);
- return;
+ public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (!mValid) {
+ pw.print(" NOT VALID: ");
+ if (mInfo == null) {
+ pw.println("no info");
+ } else {
+ pw.println(mInfo.getParseError());
}
- } catch (PackageManager.NameNotFoundException e) {
- Slog.w(TAG, "Unable to find voice interaction service: " + mComponent, e);
return;
}
- mContext.bindServiceAsUser(intent, mConnection,
+ pw.print(" mComponent="); pw.println(mComponent.flattenToShortString());
+ pw.print(" Session service="); pw.println(mInfo.getSessionService());
+ pw.print(" Settings activity="); pw.println(mInfo.getSettingsActivity());
+ pw.print(" mBound="); pw.print(mBound); pw.print(" mService="); pw.println(mService);
+ if (mActiveSession != null) {
+ pw.println(" Active session:");
+ mActiveSession.dump(" ", pw);
+ }
+ }
+
+ void startLocked() {
+ Intent intent = new Intent(VoiceInteractionService.SERVICE_INTERFACE);
+ intent.setComponent(mComponent);
+ mBound = mContext.bindServiceAsUser(intent, mConnection,
Context.BIND_AUTO_CREATE, new UserHandle(mUser));
- mBound = true;
+ if (!mBound) {
+ Slog.w(TAG, "Failed binding to voice interaction service " + mComponent);
+ }
}
void shutdownLocked() {
diff --git a/tests/VoiceInteraction/AndroidManifest.xml b/tests/VoiceInteraction/AndroidManifest.xml
index 9c5acf9..ac0f701 100644
--- a/tests/VoiceInteraction/AndroidManifest.xml
+++ b/tests/VoiceInteraction/AndroidManifest.xml
@@ -12,10 +12,16 @@
<service android:name="MainInteractionService"
android:permission="android.permission.BIND_VOICE_INTERACTION"
android:process=":interactor">
+ <meta-data android:name="android.voice_interaction"
+ android:resource="@xml/interaction_service" />
<intent-filter>
<action android:name="android.service.voice.VoiceInteractionService" />
</intent-filter>
</service>
+ <service android:name="MainInteractionSessionService"
+ android:permission="android.permission.BIND_VOICE_INTERACTION"
+ android:process=":session">
+ </service>
<activity android:name="TestInteractionActivity" android:label="Voice Interaction Target">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
diff --git a/tests/VoiceInteraction/res/xml/interaction_service.xml b/tests/VoiceInteraction/res/xml/interaction_service.xml
new file mode 100644
index 0000000..45bd994d
--- /dev/null
+++ b/tests/VoiceInteraction/res/xml/interaction_service.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2014, 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.
+ */
+-->
+
+<voice-interaction-service xmlns:android="http://schemas.android.com/apk/res/android"
+ android:sessionService="com.android.test.voiceinteraction.MainInteractionSessionService" />
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
index 35702f1..008d97b 100644
--- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
@@ -31,8 +31,7 @@ public class MainInteractionService extends VoiceInteractionService {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
- startVoiceActivity(new Intent(this, TestInteractionActivity.class),
- new MainInteractionSession(this));
+ startVoiceActivity(new Intent(this, TestInteractionActivity.class), null);
stopSelf(startId);
return START_NOT_STICKY;
}
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java
index adc0df4..0fc563b 100644
--- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java
@@ -24,8 +24,11 @@ import android.util.Log;
public class MainInteractionSession extends VoiceInteractionSession {
static final String TAG = "MainInteractionSession";
- MainInteractionSession(Context context) {
+ final Bundle mArgs;
+
+ MainInteractionSession(Context context, Bundle args) {
super(context);
+ mArgs = args;
}
@Override
@@ -42,7 +45,7 @@ public class MainInteractionSession extends VoiceInteractionSession {
@Override
public void onCommand(Caller caller, Request request, String command, Bundle extras) {
Log.i(TAG, "onCommand: command=" + command + " extras=" + extras);
- request.sendCommandResult(null);
+ request.sendCommandResult(true, null);
}
@Override
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSessionService.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSessionService.java
new file mode 100644
index 0000000..8864d71
--- /dev/null
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSessionService.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2014 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 com.android.test.voiceinteraction;
+
+import android.os.Bundle;
+import android.service.voice.VoiceInteractionSession;
+import android.service.voice.VoiceInteractionSessionService;
+
+public class MainInteractionSessionService extends VoiceInteractionSessionService {
+ @Override
+ public VoiceInteractionSession onNewSession(Bundle args) {
+ return new MainInteractionSession(this, args);
+ }
+}
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java
index 016a80e..9c772ff 100644
--- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java
@@ -30,28 +30,30 @@ public class TestInteractionActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setContentView(R.layout.test_interaction);
if (!isVoiceInteraction()) {
Log.w(TAG, "Not running as a voice interaction!");
finish();
return;
}
+ setContentView(R.layout.test_interaction);
+
mInteractor = getVoiceInteractor();
- mInteractor.startConfirmation(new VoiceInteractor.Callback() {
+ VoiceInteractor.ConfirmationRequest req = new VoiceInteractor.ConfirmationRequest(
+ "This is a confirmation", null) {
@Override
- public void onConfirmationResult(VoiceInteractor.Request request, boolean confirmed,
- Bundle result) {
- Log.i(TAG, "Confirmation result: confirmed=" + confirmed + " result=" + result);
- finish();
+ public void onCancel() {
+ Log.i(TAG, "Canceled!");
+ getActivity().finish();
}
@Override
- public void onCancel(VoiceInteractor.Request request) {
- Log.i(TAG, "Canceled!");
- finish();
+ public void onConfirmationResult(boolean confirmed, Bundle result) {
+ Log.i(TAG, "Confirmation result: confirmed=" + confirmed + " result=" + result);
+ getActivity().finish();
}
- }, "This is a confirmation", null);
+ };
+ mInteractor.submitRequest(req);
}
@Override