diff options
author | Dianne Hackborn <hackbod@google.com> | 2014-04-04 18:02:06 -0700 |
---|---|---|
committer | Dianne Hackborn <hackbod@google.com> | 2014-04-24 17:48:58 -0700 |
commit | 91097de49b0f683b00e26a75dbc0ac6082344137 (patch) | |
tree | 82c3185634a71233ce2e81a3645b07b1ba55f412 /core/java/android/service | |
parent | 23af77a3cd1febc740d885ff03ead09837df269c (diff) | |
download | frameworks_base-91097de49b0f683b00e26a75dbc0ac6082344137.zip frameworks_base-91097de49b0f683b00e26a75dbc0ac6082344137.tar.gz frameworks_base-91097de49b0f683b00e26a75dbc0ac6082344137.tar.bz2 |
Initial implementation of new voice interaction API.
This gives a basic working implementation of a persist
running service that can start a voice interaction when
it wants, with the target activity(s) able to go through
the protocol to interact with it. It may even work when
the screen is off by putting the activity manager in the
correct state to act like the screen is on.
Includes a sample app that is a voice interation service
and also has an activity it can launch.
Now that I have this initial implementation, I think I
want to rework some aspects of the API.
Change-Id: I7646d0af8fb4ac768c63a18fe3de43f8091f60e9
Diffstat (limited to 'core/java/android/service')
4 files changed, 323 insertions, 0 deletions
diff --git a/core/java/android/service/voice/IVoiceInteractionService.aidl b/core/java/android/service/voice/IVoiceInteractionService.aidl new file mode 100644 index 0000000..e9e2f4c --- /dev/null +++ b/core/java/android/service/voice/IVoiceInteractionService.aidl @@ -0,0 +1,23 @@ +/* + * 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; + +/** + * @hide + */ +oneway interface IVoiceInteractionService { +} diff --git a/core/java/android/service/voice/IVoiceInteractionSession.aidl b/core/java/android/service/voice/IVoiceInteractionSession.aidl new file mode 100644 index 0000000..7dbf66b --- /dev/null +++ b/core/java/android/service/voice/IVoiceInteractionSession.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 com.android.internal.app.IVoiceInteractorCallback; +import com.android.internal.app.IVoiceInteractorRequest; + +/** + * @hide + */ +interface IVoiceInteractionSession { +} diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java new file mode 100644 index 0000000..ed93b74 --- /dev/null +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -0,0 +1,77 @@ +/** + * 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.annotation.SdkConstant; +import android.app.Instrumentation; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import com.android.internal.app.IVoiceInteractionManagerService; + +public class VoiceInteractionService extends Service { + /** + * The {@link Intent} that must be declared as handled by the service. + * To be supported, the service must also require the + * {@link android.Manifest.permission#BIND_VOICE_INTERACTION} permission so + * that other applications can not abuse it. + */ + @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) + public static final String SERVICE_INTERFACE = + "android.service.voice.VoiceInteractionService"; + + /** + * Name under which a VoiceInteractionService component publishes information about itself. + * This meta-data should reference an XML resource containing a + * <code><{@link + * android.R.styleable#VoiceInteractionService voice-interaction-service}></code> tag. + */ + public static final String SERVICE_META_DATA = "android.voice_interaction"; + + IVoiceInteractionService mInterface = new IVoiceInteractionService.Stub() { + }; + + IVoiceInteractionManagerService mSystemService; + + public void startVoiceActivity(Intent intent, VoiceInteractionSession session) { + try { + int res = mSystemService.startVoiceActivity(intent, + intent.resolveType(getContentResolver()), + mInterface, session.mSession, session.mInteractor); + Instrumentation.checkStartActivityResult(res, intent); + } catch (RemoteException e) { + } + } + + @Override + public void onCreate() { + super.onCreate(); + mSystemService = IVoiceInteractionManagerService.Stub.asInterface( + ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE)); + } + + @Override + public IBinder onBind(Intent intent) { + if (SERVICE_INTERFACE.equals(intent.getAction())) { + return mInterface.asBinder(); + } + return null; + } +} diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java new file mode 100644 index 0000000..59544be --- /dev/null +++ b/core/java/android/service/voice/VoiceInteractionSession.java @@ -0,0 +1,195 @@ +/** + * 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.content.Context; +import android.os.Binder; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +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; +import com.android.internal.app.IVoiceInteractorRequest; +import com.android.internal.os.HandlerCaller; +import com.android.internal.os.SomeArgs; + +public abstract class VoiceInteractionSession { + static final String TAG = "VoiceInteractionSession"; + static final boolean DEBUG = true; + + final IVoiceInteractor mInteractor = new IVoiceInteractor.Stub() { + @Override + public IVoiceInteractorRequest startConfirmation(String callingPackage, + IVoiceInteractorCallback callback, String prompt, Bundle extras) { + Request request = findRequest(callback, true); + mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOO(MSG_START_CONFIRMATION, + new Caller(callingPackage, Binder.getCallingUid()), request, + prompt, extras)); + return request.mInterface; + } + + @Override + public IVoiceInteractorRequest startCommand(String callingPackage, + IVoiceInteractorCallback callback, String command, Bundle extras) { + Request request = findRequest(callback, true); + mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOO(MSG_START_COMMAND, + new Caller(callingPackage, Binder.getCallingUid()), request, + command, extras)); + return request.mInterface; + } + + @Override + public boolean[] supportsCommands(String callingPackage, String[] commands) { + Message msg = mHandlerCaller.obtainMessageIOO(MSG_SUPPORTS_COMMANDS, + 0, new Caller(callingPackage, Binder.getCallingUid()), commands); + SomeArgs args = mHandlerCaller.sendMessageAndWait(msg); + if (args != null) { + boolean[] res = (boolean[])args.arg1; + args.recycle(); + return res; + } + return new boolean[commands.length]; + } + }; + + final IVoiceInteractionSession mSession = new IVoiceInteractionSession.Stub() { + }; + + public static class Request { + final IVoiceInteractorRequest mInterface = new IVoiceInteractorRequest.Stub() { + @Override + public void cancel() throws RemoteException { + mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(MSG_CANCEL, Request.this)); + } + }; + final IVoiceInteractorCallback mCallback; + final HandlerCaller mHandlerCaller; + Request(IVoiceInteractorCallback callback, HandlerCaller handlerCaller) { + mCallback = callback; + mHandlerCaller = handlerCaller; + } + + public void sendConfirmResult(boolean confirmed, Bundle result) { + try { + if (DEBUG) Log.d(TAG, "sendConfirmResult: req=" + mInterface + + " confirmed=" + confirmed + " result=" + result); + mCallback.deliverConfirmationResult(mInterface, confirmed, result); + } catch (RemoteException e) { + } + } + + public void sendCommandResult(Bundle result) { + try { + if (DEBUG) Log.d(TAG, "sendCommandResult: req=" + mInterface + + " result=" + result); + mCallback.deliverCommandResult(mInterface, result); + } catch (RemoteException e) { + } + } + + public void sendCancelResult() { + try { + if (DEBUG) Log.d(TAG, "sendCancelResult: req=" + mInterface); + mCallback.deliverCancel(mInterface); + } catch (RemoteException e) { + } + } + } + + public static class Caller { + final String packageName; + final int uid; + + Caller(String _packageName, int _uid) { + packageName = _packageName; + uid = _uid; + } + } + + static final int MSG_START_CONFIRMATION = 1; + static final int MSG_START_COMMAND = 2; + static final int MSG_SUPPORTS_COMMANDS = 3; + static final int MSG_CANCEL = 4; + + final Context mContext; + final 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_START_CONFIRMATION: + if (DEBUG) Log.d(TAG, "onConfirm: req=" + ((Request) args.arg2).mInterface + + " prompt=" + args.arg3 + " extras=" + args.arg4); + onConfirm((Caller)args.arg1, (Request)args.arg2, (String)args.arg3, + (Bundle)args.arg4); + break; + case MSG_START_COMMAND: + if (DEBUG) Log.d(TAG, "onCommand: req=" + ((Request) args.arg2).mInterface + + " command=" + args.arg3 + " extras=" + args.arg4); + onCommand((Caller) args.arg1, (Request) args.arg2, (String) args.arg3, + (Bundle) args.arg4); + break; + case MSG_SUPPORTS_COMMANDS: + if (DEBUG) Log.d(TAG, "onGetSupportedCommands: cmds=" + args.arg2); + args.arg1 = onGetSupportedCommands((Caller) args.arg1, (String[]) args.arg2); + break; + case MSG_CANCEL: + if (DEBUG) Log.d(TAG, "onCancel: req=" + ((Request) args.arg1).mInterface); + onCancel((Request)args.arg1); + break; + } + } + }; + + final ArrayMap<IBinder, Request> mActiveRequests = new ArrayMap<IBinder, Request>(); + + public VoiceInteractionSession(Context context) { + this(context, new Handler()); + } + + public VoiceInteractionSession(Context context, Handler handler) { + mContext = context; + mHandlerCaller = new HandlerCaller(context, handler.getLooper(), + mHandlerCallerCallback, true); + } + + Request findRequest(IVoiceInteractorCallback callback, boolean newRequest) { + synchronized (this) { + Request req = mActiveRequests.get(callback.asBinder()); + if (req != null) { + if (newRequest) { + throw new IllegalArgumentException("Given request callback " + callback + + " is already active"); + } + return req; + } + req = new Request(callback, mHandlerCaller); + mActiveRequests.put(callback.asBinder(), req); + return req; + } + } + + public abstract boolean[] onGetSupportedCommands(Caller caller, String[] commands); + public abstract void onConfirm(Caller caller, Request request, String prompt, Bundle extras); + public abstract void onCommand(Caller caller, Request request, String command, Bundle extras); + public abstract void onCancel(Request request); +} |