diff options
| author | 2016-01-17 15:36:19 -0800 | |
|---|---|---|
| committer | 2016-01-23 21:25:50 -0800 | |
| commit | 0af6fa7015cd9da08bf52c1efb13641d30fd6bd7 (patch) | |
| tree | e73062130b029fc260b14728c77a99e37b02d93b | |
| parent | b9dd234a6c30921a04d16dbd73826eb919537111 (diff) | |
Voice Interaction from within an Activity
This allows an app to show a voice search button
and invoke a voice interaction session for use
within the activity. Once the activity exits, the
session is stopped.
Test application has a new activity that
demonstrates it with the test voice interaction
service.
This initial version is functional enough for
an integration test, with some more tests
and improvements to come later.
Bug: 22791070
Change-Id: Ib1e5bc8cae1fde40570c999b9cf4bb29efe4916d
29 files changed, 811 insertions, 16 deletions
diff --git a/api/current.txt b/api/current.txt index c700501a89bd..e379c444ca01 100644 --- a/api/current.txt +++ b/api/current.txt @@ -1183,6 +1183,7 @@ package android { field public static final int summaryOn = 16843247; // 0x10101ef field public static final int supportsAssist = 16844016; // 0x10104f0 field public static final int supportsLaunchVoiceAssistFromKeyguard = 16844017; // 0x10104f1 + field public static final int supportsLocalInteraction = 16844048; // 0x1010510 field public static final int supportsPictureInPicture = 16844024; // 0x10104f8 field public static final int supportsRtl = 16843695; // 0x10103af field public static final int supportsSwitchingToNextInputMethod = 16843755; // 0x10103eb @@ -3436,6 +3437,7 @@ package android.app { method public boolean isDestroyed(); method public boolean isFinishing(); method public boolean isImmersive(); + method public boolean isLocalVoiceInteractionSupported(); method public boolean isTaskRoot(); method public boolean isVoiceInteraction(); method public boolean isVoiceInteractionRoot(); @@ -3477,6 +3479,8 @@ package android.app { method public boolean onKeyMultiple(int, int, android.view.KeyEvent); method public boolean onKeyShortcut(int, android.view.KeyEvent); method public boolean onKeyUp(int, android.view.KeyEvent); + method public void onLocalVoiceInteractionStarted(); + method public void onLocalVoiceInteractionStopped(); method public void onLowMemory(); method public boolean onMenuItemSelected(int, android.view.MenuItem); method public boolean onMenuOpened(int, android.view.Menu); @@ -3591,12 +3595,14 @@ package android.app { method public void startIntentSenderForResult(android.content.IntentSender, int, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException; method public void startIntentSenderFromChild(android.app.Activity, android.content.IntentSender, int, android.content.Intent, int, int, int) throws android.content.IntentSender.SendIntentException; method public void startIntentSenderFromChild(android.app.Activity, android.content.IntentSender, int, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException; + method public void startLocalVoiceInteraction(android.os.Bundle); method public void startLockTask(); method public deprecated void startManagingCursor(android.database.Cursor); method public boolean startNextMatchingActivity(android.content.Intent); method public boolean startNextMatchingActivity(android.content.Intent, android.os.Bundle); method public void startPostponedEnterTransition(); method public void startSearch(java.lang.String, boolean, android.os.Bundle, boolean); + method public void stopLocalVoiceInteraction(); method public void stopLockTask(); method public deprecated void stopManagingCursor(android.database.Cursor); method public void takeKeyEvents(boolean); @@ -34005,6 +34011,7 @@ package android.service.voice { method public void setTheme(int); method public void show(android.os.Bundle, int); method public void startVoiceActivity(android.content.Intent); + field public static final int SHOW_SOURCE_ACTIVITY = 16; // 0x10 field public static final int SHOW_SOURCE_APPLICATION = 8; // 0x8 field public static final int SHOW_SOURCE_ASSIST_GESTURE = 4; // 0x4 field public static final int SHOW_WITH_ASSIST = 1; // 0x1 diff --git a/api/system-current.txt b/api/system-current.txt index 7444db100a59..2fd54b3c1b01 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -1282,6 +1282,7 @@ package android { field public static final int summaryOn = 16843247; // 0x10101ef field public static final int supportsAssist = 16844016; // 0x10104f0 field public static final int supportsLaunchVoiceAssistFromKeyguard = 16844017; // 0x10104f1 + field public static final int supportsLocalInteraction = 16844048; // 0x1010510 field public static final int supportsPictureInPicture = 16844024; // 0x10104f8 field public static final int supportsRtl = 16843695; // 0x10103af field public static final int supportsSwitchingToNextInputMethod = 16843755; // 0x10103eb @@ -3551,6 +3552,7 @@ package android.app { method public boolean isDestroyed(); method public boolean isFinishing(); method public boolean isImmersive(); + method public boolean isLocalVoiceInteractionSupported(); method public boolean isTaskRoot(); method public boolean isVoiceInteraction(); method public boolean isVoiceInteractionRoot(); @@ -3593,6 +3595,8 @@ package android.app { method public boolean onKeyMultiple(int, int, android.view.KeyEvent); method public boolean onKeyShortcut(int, android.view.KeyEvent); method public boolean onKeyUp(int, android.view.KeyEvent); + method public void onLocalVoiceInteractionStarted(); + method public void onLocalVoiceInteractionStopped(); method public void onLowMemory(); method public boolean onMenuItemSelected(int, android.view.MenuItem); method public boolean onMenuOpened(int, android.view.Menu); @@ -3707,12 +3711,14 @@ package android.app { method public void startIntentSenderForResult(android.content.IntentSender, int, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException; method public void startIntentSenderFromChild(android.app.Activity, android.content.IntentSender, int, android.content.Intent, int, int, int) throws android.content.IntentSender.SendIntentException; method public void startIntentSenderFromChild(android.app.Activity, android.content.IntentSender, int, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException; + method public void startLocalVoiceInteraction(android.os.Bundle); method public void startLockTask(); method public deprecated void startManagingCursor(android.database.Cursor); method public boolean startNextMatchingActivity(android.content.Intent); method public boolean startNextMatchingActivity(android.content.Intent, android.os.Bundle); method public void startPostponedEnterTransition(); method public void startSearch(java.lang.String, boolean, android.os.Bundle, boolean); + method public void stopLocalVoiceInteraction(); method public void stopLockTask(); method public deprecated void stopManagingCursor(android.database.Cursor); method public void takeKeyEvents(boolean); @@ -36248,6 +36254,7 @@ package android.service.voice { method public void setTheme(int); method public void show(android.os.Bundle, int); method public void startVoiceActivity(android.content.Intent); + field public static final int SHOW_SOURCE_ACTIVITY = 16; // 0x10 field public static final int SHOW_SOURCE_APPLICATION = 8; // 0x8 field public static final int SHOW_SOURCE_ASSIST_GESTURE = 4; // 0x4 field public static final int SHOW_WITH_ASSIST = 1; // 0x1 diff --git a/api/test-current.txt b/api/test-current.txt index 80674804188e..2eb8d7540c17 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -1183,6 +1183,7 @@ package android { field public static final int summaryOn = 16843247; // 0x10101ef field public static final int supportsAssist = 16844016; // 0x10104f0 field public static final int supportsLaunchVoiceAssistFromKeyguard = 16844017; // 0x10104f1 + field public static final int supportsLocalInteraction = 16844048; // 0x1010510 field public static final int supportsPictureInPicture = 16844024; // 0x10104f8 field public static final int supportsRtl = 16843695; // 0x10103af field public static final int supportsSwitchingToNextInputMethod = 16843755; // 0x10103eb @@ -3436,6 +3437,7 @@ package android.app { method public boolean isDestroyed(); method public boolean isFinishing(); method public boolean isImmersive(); + method public boolean isLocalVoiceInteractionSupported(); method public boolean isTaskRoot(); method public boolean isVoiceInteraction(); method public boolean isVoiceInteractionRoot(); @@ -3477,6 +3479,8 @@ package android.app { method public boolean onKeyMultiple(int, int, android.view.KeyEvent); method public boolean onKeyShortcut(int, android.view.KeyEvent); method public boolean onKeyUp(int, android.view.KeyEvent); + method public void onLocalVoiceInteractionStarted(); + method public void onLocalVoiceInteractionStopped(); method public void onLowMemory(); method public boolean onMenuItemSelected(int, android.view.MenuItem); method public boolean onMenuOpened(int, android.view.Menu); @@ -3591,12 +3595,14 @@ package android.app { method public void startIntentSenderForResult(android.content.IntentSender, int, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException; method public void startIntentSenderFromChild(android.app.Activity, android.content.IntentSender, int, android.content.Intent, int, int, int) throws android.content.IntentSender.SendIntentException; method public void startIntentSenderFromChild(android.app.Activity, android.content.IntentSender, int, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException; + method public void startLocalVoiceInteraction(android.os.Bundle); method public void startLockTask(); method public deprecated void startManagingCursor(android.database.Cursor); method public boolean startNextMatchingActivity(android.content.Intent); method public boolean startNextMatchingActivity(android.content.Intent, android.os.Bundle); method public void startPostponedEnterTransition(); method public void startSearch(java.lang.String, boolean, android.os.Bundle, boolean); + method public void stopLocalVoiceInteraction(); method public void stopLockTask(); method public deprecated void stopManagingCursor(android.database.Cursor); method public void takeKeyEvents(boolean); @@ -34019,6 +34025,7 @@ package android.service.voice { method public void setTheme(int); method public void show(android.os.Bundle, int); method public void startVoiceActivity(android.content.Intent); + field public static final int SHOW_SOURCE_ACTIVITY = 16; // 0x10 field public static final int SHOW_SOURCE_APPLICATION = 8; // 0x8 field public static final int SHOW_SOURCE_ASSIST_GESTURE = 4; // 0x4 field public static final int SHOW_WITH_ASSIST = 1; // 0x1 diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index e31259692501..e36a4275736c 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -1267,6 +1267,15 @@ public class Activity extends ContextThemeWrapper mCalled = true; } + void setVoiceInteractor(IVoiceInteractor voiceInteractor) { + if (voiceInteractor == null) { + mVoiceInteractor = null; + } else { + mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this, + Looper.myLooper()); + } + } + /** * Check whether this activity is running as part of a voice interaction with the user. * If true, it should perform its interaction with the user through the @@ -1301,6 +1310,62 @@ public class Activity extends ContextThemeWrapper } /** + * Queries whether the currently enabled voice interaction service supports returning + * a voice interactor for use by the activity. This is valid only for the duration of the + * activity. + * + * @return whether the current voice interaction service supports local voice interaction + */ + public boolean isLocalVoiceInteractionSupported() { + try { + return ActivityManagerNative.getDefault().supportsLocalVoiceInteraction(); + } catch (RemoteException re) { + } + return false; + } + + /** + * Starts a local voice interaction session. When ready, + * {@link #onLocalVoiceInteractionStarted()} is called. You can pass a bundle of private options + * to the registered voice interaction service. + * @param privateOptions a Bundle of private arguments to the current voice interaction service + */ + public void startLocalVoiceInteraction(Bundle privateOptions) { + try { + ActivityManagerNative.getDefault().startLocalVoiceInteraction(mToken, privateOptions); + } catch (RemoteException re) { + } + } + + /** + * Callback to indicate that {@link #startLocalVoiceInteraction(Bundle)} has resulted in a + * voice interaction session being started. You can now retrieve a voice interactor using + * {@link #getVoiceInteractor()}. + */ + public void onLocalVoiceInteractionStarted() { + Log.i(TAG, "onLocalVoiceInteractionStarted! " + getVoiceInteractor()); + } + + /** + * Callback to indicate that the local voice interaction has stopped for some + * reason. + */ + public void onLocalVoiceInteractionStopped() { + Log.i(TAG, "onLocalVoiceInteractionStopped :( " + getVoiceInteractor()); + } + + /** + * Request to terminate the current voice interaction that was previously started + * using {@link #startLocalVoiceInteraction(Bundle)}. + */ + public void stopLocalVoiceInteraction() { + try { + ActivityManagerNative.getDefault().stopLocalVoiceInteraction(mToken); + } catch (RemoteException re) { + } + } + + /** * This is called for activities that set launchMode to "singleTop" in * their package, or if a client used the {@link Intent#FLAG_ACTIVITY_SINGLE_TOP} * flag when calling {@link #startActivity}. In either case, when the diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index 373a23fa7422..4fa654f0db5a 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -18,6 +18,10 @@ package android.app; import android.annotation.NonNull; import android.content.ComponentName; +import android.os.IBinder; +import android.service.voice.IVoiceInteractionSession; + +import com.android.internal.app.IVoiceInteractor; /** * Activity manager local system service interface. @@ -64,4 +68,8 @@ public abstract class ActivityManagerInternal { * @param userId The user being cleaned up. */ public abstract void onUserRemoved(int userId); + + public abstract void onLocalVoiceInteractionStarted(IBinder callingActivity, + IVoiceInteractionSession mSession, + IVoiceInteractor mInteractor); } diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index 63b6825dca34..42ff8e83862d 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -315,6 +315,34 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case START_LOCAL_VOICE_INTERACTION_TRANSACTION: + { + data.enforceInterface(IActivityManager.descriptor); + IBinder token = data.readStrongBinder(); + Bundle options = data.readBundle(); + startLocalVoiceInteraction(token, options); + reply.writeNoException(); + return true; + } + + case STOP_LOCAL_VOICE_INTERACTION_TRANSACTION: + { + data.enforceInterface(IActivityManager.descriptor); + IBinder token = data.readStrongBinder(); + stopLocalVoiceInteraction(token); + reply.writeNoException(); + return true; + } + + case SUPPORTS_LOCAL_VOICE_INTERACTION_TRANSACTION: + { + data.enforceInterface(IActivityManager.descriptor); + boolean result = supportsLocalVoiceInteraction(); + reply.writeNoException(); + reply.writeInt(result? 1 : 0); + return true; + } + case START_NEXT_MATCHING_ACTIVITY_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); @@ -3136,6 +3164,43 @@ class ActivityManagerProxy implements IActivityManager data.recycle(); return result; } + + public void startLocalVoiceInteraction(IBinder callingActivity, Bundle options) + throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(callingActivity); + data.writeBundle(options); + mRemote.transact(START_LOCAL_VOICE_INTERACTION_TRANSACTION, data, reply, 0); + reply.readException(); + reply.recycle(); + data.recycle(); + } + + public void stopLocalVoiceInteraction(IBinder callingActivity) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(callingActivity); + mRemote.transact(STOP_LOCAL_VOICE_INTERACTION_TRANSACTION, data, reply, 0); + reply.readException(); + reply.recycle(); + data.recycle(); + } + + public boolean supportsLocalVoiceInteraction() throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + mRemote.transact(SUPPORTS_LOCAL_VOICE_INTERACTION_TRANSACTION, data, reply, 0); + reply.readException(); + int result = reply.readInt(); + reply.recycle(); + data.recycle(); + return result != 0; + } + public boolean startNextMatchingActivity(IBinder callingActivity, Intent intent, Bundle options) throws RemoteException { Parcel data = Parcel.obtain(); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 37b861e45c6d..f3e1fc3a9615 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -1254,6 +1254,15 @@ public final class ActivityThread { throws RemoteException { sendMessage(H.PICTURE_IN_PICTURE_MODE_CHANGED, token, pipMode ? 1 : 0); } + + @Override + public void scheduleLocalVoiceInteractionStarted(IBinder token, + IVoiceInteractor voiceInteractor) throws RemoteException { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = token; + args.arg2 = voiceInteractor; + sendMessage(H.LOCAL_VOICE_INTERACTION_STARTED, args); + } } private int getLifecycleSeq() { @@ -1317,6 +1326,7 @@ public final class ActivityThread { public static final int STOP_BINDER_TRACKING_AND_DUMP = 151; public static final int MULTI_WINDOW_MODE_CHANGED = 152; public static final int PICTURE_IN_PICTURE_MODE_CHANGED = 153; + public static final int LOCAL_VOICE_INTERACTION_STARTED = 154; String codeToString(int code) { if (DEBUG_MESSAGES) { @@ -1372,6 +1382,7 @@ public final class ActivityThread { case ENTER_ANIMATION_COMPLETE: return "ENTER_ANIMATION_COMPLETE"; case MULTI_WINDOW_MODE_CHANGED: return "MULTI_WINDOW_MODE_CHANGED"; case PICTURE_IN_PICTURE_MODE_CHANGED: return "PICTURE_IN_PICTURE_MODE_CHANGED"; + case LOCAL_VOICE_INTERACTION_STARTED: return "LOCAL_VOICE_INTERACTION_STARTED"; } } return Integer.toString(code); @@ -1621,6 +1632,10 @@ public final class ActivityThread { case PICTURE_IN_PICTURE_MODE_CHANGED: handlePictureInPictureModeChanged((IBinder) msg.obj, msg.arg1 == 1); break; + case LOCAL_VOICE_INTERACTION_STARTED: + handleLocalVoiceInteractionStarted((IBinder) ((SomeArgs) msg.obj).arg1, + (IVoiceInteractor) ((SomeArgs) msg.obj).arg2); + break; } Object obj = msg.obj; if (obj instanceof SomeArgs) { @@ -2878,6 +2893,19 @@ public final class ActivityThread { } } + private void handleLocalVoiceInteractionStarted(IBinder token, IVoiceInteractor interactor) { + final ActivityClientRecord r = mActivities.get(token); + if (r != null) { + r.voiceInteractor = interactor; + r.activity.setVoiceInteractor(interactor); + if (interactor == null) { + r.activity.onLocalVoiceInteractionStopped(); + } else { + r.activity.onLocalVoiceInteractionStarted(); + } + } + } + private static final ThreadLocal<Intent> sCurrentBroadcastIntent = new ThreadLocal<Intent>(); /** diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java index 5951c8d9c958..9be7f23d3a63 100644 --- a/core/java/android/app/ApplicationThreadNative.java +++ b/core/java/android/app/ApplicationThreadNative.java @@ -425,6 +425,16 @@ public abstract class ApplicationThreadNative extends Binder return true; } + case SCHEDULE_LOCAL_VOICE_INTERACTION_STARTED_TRANSACTION: + { + data.enforceInterface(IApplicationThread.descriptor); + IBinder token = data.readStrongBinder(); + IVoiceInteractor voiceInteractor = IVoiceInteractor.Stub.asInterface( + data.readStrongBinder()); + scheduleLocalVoiceInteractionStarted(token, voiceInteractor); + return true; + } + case PROFILER_CONTROL_TRANSACTION: { data.enforceInterface(IApplicationThread.descriptor); @@ -1101,6 +1111,17 @@ class ApplicationThreadProxy implements IApplicationThread { data.recycle(); } + public final void scheduleLocalVoiceInteractionStarted(IBinder token, + IVoiceInteractor voiceInteractor) throws RemoteException { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(IApplicationThread.descriptor); + data.writeStrongBinder(token); + data.writeStrongBinder(voiceInteractor != null ? voiceInteractor.asBinder() : null); + mRemote.transact(SCHEDULE_LOCAL_VOICE_INTERACTION_STARTED_TRANSACTION, data, null, + IBinder.FLAG_ONEWAY); + data.recycle(); + } + public void updateTimeZone() throws RemoteException { Parcel data = Parcel.obtain(); data.writeInterfaceToken(IApplicationThread.descriptor); diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index 5bb2cf5014d4..22de2ff7bc6e 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -591,6 +591,12 @@ public interface IActivityManager extends IInterface { public boolean isAppForeground(int uid) throws RemoteException; + public void startLocalVoiceInteraction(IBinder token, Bundle options) throws RemoteException; + + public void stopLocalVoiceInteraction(IBinder token) throws RemoteException; + + public boolean supportsLocalVoiceInteraction() throws RemoteException; + /* * Private non-Binder interfaces */ @@ -963,4 +969,7 @@ public interface IActivityManager extends IInterface { int GET_GRANTED_URI_PERMISSIONS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 360; int CLEAR_GRANTED_URI_PERMISSIONS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 361; int IS_APP_FOREGROUND_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 362; + int START_LOCAL_VOICE_INTERACTION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 363; + int STOP_LOCAL_VOICE_INTERACTION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 364; + int SUPPORTS_LOCAL_VOICE_INTERACTION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 365; } diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java index dc67026c0f87..6d64bd0d87ec 100644 --- a/core/java/android/app/IApplicationThread.java +++ b/core/java/android/app/IApplicationThread.java @@ -154,6 +154,7 @@ public interface IApplicationThread extends IInterface { void stopBinderTrackingAndDump(FileDescriptor fd) throws RemoteException; void scheduleMultiWindowModeChanged(IBinder token, boolean multiWindowMode) throws RemoteException; void schedulePictureInPictureModeChanged(IBinder token, boolean multiWindowMode) throws RemoteException; + void scheduleLocalVoiceInteractionStarted(IBinder token, IVoiceInteractor voiceInteractor) throws RemoteException; String descriptor = "android.app.IApplicationThread"; @@ -216,4 +217,5 @@ public interface IApplicationThread extends IInterface { int STOP_BINDER_TRACKING_AND_DUMP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+57; int SCHEDULE_MULTI_WINDOW_MODE_CHANGED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+58; int SCHEDULE_PICTURE_IN_PICTURE_MODE_CHANGED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+59; + int SCHEDULE_LOCAL_VOICE_INTERACTION_STARTED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+60; } diff --git a/core/java/android/service/voice/VoiceInteractionManagerInternal.java b/core/java/android/service/voice/VoiceInteractionManagerInternal.java new file mode 100644 index 000000000000..b38067b9975a --- /dev/null +++ b/core/java/android/service/voice/VoiceInteractionManagerInternal.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2016 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.os.IBinder; + + +/** + * @hide + * Private interface to the VoiceInteractionManagerService for use by ActivityManagerService. + */ +public abstract class VoiceInteractionManagerInternal { + + /** + * Start a new voice interaction session when requested from within an activity + * by Activity.startLocalVoiceInteraction() + * @param callingActivity The binder token representing the calling activity. + * @param options + */ + public abstract void startLocalVoiceInteraction(IBinder callingActivity, Bundle options); + + /** + * Returns whether the currently selected voice interaction service supports local voice + * interaction for launching from an Activity. + */ + public abstract boolean supportsLocalVoiceInteraction(); + + public abstract void stopLocalVoiceInteraction(IBinder callingActivity); +}
\ No newline at end of file diff --git a/core/java/android/service/voice/VoiceInteractionServiceInfo.java b/core/java/android/service/voice/VoiceInteractionServiceInfo.java index 4f58626b03e3..a9db32b703fa 100644 --- a/core/java/android/service/voice/VoiceInteractionServiceInfo.java +++ b/core/java/android/service/voice/VoiceInteractionServiceInfo.java @@ -45,6 +45,7 @@ public class VoiceInteractionServiceInfo { private String mSettingsActivity; private boolean mSupportsAssist; private boolean mSupportsLaunchFromKeyguard; + private boolean mSupportsLocalInteraction; public VoiceInteractionServiceInfo(PackageManager pm, ComponentName comp) throws PackageManager.NameNotFoundException { @@ -118,6 +119,8 @@ public class VoiceInteractionServiceInfo { mSupportsLaunchFromKeyguard = array.getBoolean(com.android.internal. R.styleable.VoiceInteractionService_supportsLaunchVoiceAssistFromKeyguard, false); + mSupportsLocalInteraction = array.getBoolean(com.android.internal. + R.styleable.VoiceInteractionService_supportsLocalInteraction, false); array.recycle(); if (mSessionService == null) { mParseError = "No sessionService specified"; @@ -172,4 +175,8 @@ public class VoiceInteractionServiceInfo { public boolean getSupportsLaunchFromKeyguard() { return mSupportsLaunchFromKeyguard; } + + public boolean getSupportsLocalInteraction() { + return mSupportsLocalInteraction; + } } diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java index ec14740bd815..0c6a0c6e1d26 100644 --- a/core/java/android/service/voice/VoiceInteractionSession.java +++ b/core/java/android/service/voice/VoiceInteractionSession.java @@ -17,6 +17,7 @@ package android.service.voice; import android.annotation.Nullable; +import android.app.Activity; import android.app.Dialog; import android.app.Instrumentation; import android.app.VoiceInteractor; @@ -49,6 +50,7 @@ import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.WindowManager; import android.widget.FrameLayout; + import com.android.internal.app.IVoiceInteractionManagerService; import com.android.internal.app.IVoiceInteractionSessionShowCallback; import com.android.internal.app.IVoiceInteractor; @@ -75,7 +77,7 @@ import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; */ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCallbacks2 { static final String TAG = "VoiceInteractionSession"; - static final boolean DEBUG = false; + static final boolean DEBUG = true; /** * Flag received in {@link #onShow}: originator requested that the session be started with @@ -101,6 +103,13 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall */ public static final int SHOW_SOURCE_APPLICATION = 1<<3; + /** + * Flag for use with {@link #onShow}: indicates that an Activity has invoked the voice + * interaction service for a local interaction using + * {@link Activity#startLocalVoiceInteraction(Bundle)}. + */ + public static final int SHOW_SOURCE_ACTIVITY = 1<<4; + final Context mContext; final HandlerCaller mHandlerCaller; diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index e0f9eca0077d..80c6060a8893 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -7512,6 +7512,10 @@ i <!-- Flag indicating whether this voice interaction service is capable of being launched from the keyguard. --> <attr name="supportsLaunchVoiceAssistFromKeyguard" format="boolean" /> + <!-- Flag indicating whether this voice interaction service can handle local voice + interaction requests from an Activity. This flag is new in + {@link android.os.Build.VERSION_CODES#N} and not used in previous versions. --> + <attr name="supportsLocalInteraction" format="boolean" /> </declare-styleable> <!-- Use <code>voice-enrollment-application</code> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index c883b1f8a7db..57132ea82977 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2690,6 +2690,7 @@ <public type="attr" name="tickMarkTintMode" /> <public type="attr" name="canPerformGestures" /> <public type="attr" name="externalService" /> + <public type="attr" name="supportsLocalInteraction" /> <public type="style" name="Theme.Material.Light.DialogWhenLarge.DarkActionBar" /> <public type="style" name="Widget.Material.SeekBar.Discrete" /> diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 8c2090e6b669..25ee38ee09be 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -187,6 +187,7 @@ import android.os.storage.MountServiceInternal; import android.os.storage.StorageManager; import android.provider.Settings; import android.service.voice.IVoiceInteractionSession; +import android.service.voice.VoiceInteractionManagerInternal; import android.service.voice.VoiceInteractionSession; import android.text.format.DateUtils; import android.text.format.Time; @@ -2803,16 +2804,21 @@ public final class ActivityManagerService extends ActivityManagerNative } else { r.appTimeTracker = null; } + // TODO: VI Maybe r.task.voiceInteractor || r.voiceInteractor != null + // TODO: Probably not, because we don't want to resume voice on switching + // back to this activity if (r.task.voiceInteractor != null) { startRunningVoiceLocked(r.task.voiceSession, r.info.applicationInfo.uid); } else { finishRunningVoiceLocked(); - if (last != null && last.task.voiceSession != null) { + IVoiceInteractionSession session; + if (last != null && ((session = last.task.voiceSession) != null + || (session = last.voiceSession) != null)) { // We had been in a voice interaction session, but now focused has // move to something different. Just finish the session, we can't // return to it and retain the proper state and synchronization with // the voice interaction service. - finishVoiceTask(last.task.voiceSession); + finishVoiceTask(session); } } if (mStackSupervisor.moveActivityStackToFront(r, reason + " setFocusedActivity")) { @@ -4256,6 +4262,66 @@ public final class ActivityManagerService extends ActivityManagerNative } @Override + public void startLocalVoiceInteraction(IBinder callingActivity, Bundle options) + throws RemoteException { + Slog.i(TAG, "Activity tried to startVoiceInteraction"); + synchronized (this) { + ActivityRecord activity = getFocusedStack().topActivity(); + if (ActivityRecord.forTokenLocked(callingActivity) != activity) { + throw new SecurityException("Only focused activity can call startVoiceInteraction"); + } + if (mRunningVoice != null || activity.task.voiceSession != null + || activity.voiceSession != null) { + Slog.w(TAG, "Already in a voice interaction, cannot start new voice interaction"); + return; + } + if (activity.pendingVoiceInteractionStart) { + Slog.w(TAG, "Pending start of voice interaction already."); + return; + } + activity.pendingVoiceInteractionStart = true; + } + LocalServices.getService(VoiceInteractionManagerInternal.class) + .startLocalVoiceInteraction(callingActivity, options); + } + + @Override + public void stopLocalVoiceInteraction(IBinder callingActivity) throws RemoteException { + LocalServices.getService(VoiceInteractionManagerInternal.class) + .stopLocalVoiceInteraction(callingActivity); + } + + @Override + public boolean supportsLocalVoiceInteraction() throws RemoteException { + return LocalServices.getService(VoiceInteractionManagerInternal.class) + .supportsLocalVoiceInteraction(); + } + + void onLocalVoiceInteractionStartedLocked(IBinder activity, + IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor) { + ActivityRecord activityToCallback = ActivityRecord.forTokenLocked(activity); + if (activityToCallback == null) return; + activityToCallback.setVoiceSessionLocked(voiceSession); + + // Inform the activity + try { + activityToCallback.app.thread.scheduleLocalVoiceInteractionStarted(activity, + voiceInteractor); + long token = Binder.clearCallingIdentity(); + try { + startRunningVoiceLocked(voiceSession, activityToCallback.appInfo.uid); + } finally { + Binder.restoreCallingIdentity(token); + } + // TODO: VI Should we cache the activity so that it's easier to find later + // rather than scan through all the stacks and activities? + } catch (RemoteException re) { + activityToCallback.clearVoiceSessionLocked(); + // TODO: VI Should this terminate the voice session? + } + } + + @Override public void setVoiceKeepAwake(IVoiceInteractionSession session, boolean keepAwake) { synchronized (this) { if (mRunningVoice != null && mRunningVoice.asBinder() == session.asBinder()) { @@ -4747,9 +4813,11 @@ public final class ActivityManagerService extends ActivityManagerNative @Override public void finishVoiceTask(IVoiceInteractionSession session) { - synchronized(this) { + synchronized (this) { final long origId = Binder.clearCallingIdentity(); try { + // TODO: VI Consider treating local voice interactions and voice tasks + // differently here mStackSupervisor.finishVoiceTask(session); } finally { Binder.restoreCallingIdentity(origId); @@ -11107,6 +11175,7 @@ public final class ActivityManagerService extends ActivityManagerNative } void finishRunningVoiceLocked() { + Slog.d(TAG, "finishRunningVoiceLocked() >>>>"); if (mRunningVoice != null) { mRunningVoice = null; mVoiceWakeLock.release(); @@ -11250,6 +11319,7 @@ public final class ActivityManagerService extends ActivityManagerNative } void startRunningVoiceLocked(IVoiceInteractionSession session, int targetUid) { + Slog.d(TAG, "<<< startRunningVoiceLocked()"); mVoiceWakeLock.setWorkSource(new WorkSource(targetUid)); if (mRunningVoice == null || mRunningVoice.asBinder() != session.asBinder()) { boolean wasRunningVoice = mRunningVoice != null; @@ -21059,6 +21129,15 @@ public final class ActivityManagerService extends ActivityManagerNative ActivityManagerService.this.onUserStoppedLocked(userId); } } + + @Override + public void onLocalVoiceInteractionStarted(IBinder activity, + IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor) { + synchronized (ActivityManagerService.this) { + ActivityManagerService.this.onLocalVoiceInteractionStartedLocked(activity, + voiceSession, voiceInteractor); + } + } } private final class SleepTokenImpl extends SleepToken { diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index b16bd2b1c279..71008a9e8141 100755 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -54,6 +54,7 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.Trace; import android.os.UserHandle; +import android.service.voice.IVoiceInteractionSession; import android.util.EventLog; import android.util.Log; import android.util.Slog; @@ -209,6 +210,9 @@ final class ActivityRecord { private int[] mHorizontalSizeConfigurations; private int[] mSmallestSizeConfigurations; + boolean pendingVoiceInteractionStart; // Waiting for activity-invoked voice session + IVoiceInteractionSession voiceSession; // Voice interaction session for this activity + void dump(PrintWriter pw, String prefix) { final long now = SystemClock.uptimeMillis(); pw.print(prefix); pw.print("packageName="); pw.print(packageName); @@ -1274,6 +1278,16 @@ final class ActivityRecord { taskDescription = _taskDescription; } + void setVoiceSessionLocked(IVoiceInteractionSession session) { + voiceSession = session; + pendingVoiceInteractionStart = false; + } + + void clearVoiceSessionLocked() { + voiceSession = null; + pendingVoiceInteractionStart = false; + } + void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException { out.attribute(null, ATTR_ID, String.valueOf(createTime)); out.attribute(null, ATTR_LAUNCHEDFROMUID, String.valueOf(launchedFromUid)); diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index fcea6251d3cb..4bac2d6fd69a 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -3093,8 +3093,29 @@ final class ActivityStack { didOne = true; } } + } else { + // Check if any of the activities are using voice + for (int activityNdx = tr.mActivities.size() - 1; activityNdx >= 0; --activityNdx) { + ActivityRecord r = tr.mActivities.get(activityNdx); + if (r.voiceSession != null + && r.voiceSession.asBinder() == sessionBinder) { + // Inform of cancellation + r.clearVoiceSessionLocked(); + try { + r.app.thread.scheduleLocalVoiceInteractionStarted((IBinder) r.appToken, + null); + } catch (RemoteException re) { + // Ok + } + // TODO: VI This is redundant in some cases + mService.finishRunningVoiceLocked(); + break; + } + } } } + Slog.d(TAG, "ActivityStack.finishVoiceTask()"); + if (didOne) { mService.updateOomAdjLocked(); } @@ -4686,6 +4707,7 @@ final class ActivityStack { updateTaskMovement(task, true); if (!moving && task.mActivities.isEmpty()) { + // TODO: VI what about activity? final boolean isVoiceSession = task.voiceSession != null; if (isVoiceSession) { try { @@ -4790,6 +4812,7 @@ final class ActivityStack { void addConfigOverride(ActivityRecord r, TaskRecord task) { final Rect bounds = task.updateOverrideConfigurationFromLaunchBounds(); + // TODO: VI deal with activity mWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken, r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen, (r.info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0, r.userId, r.info.configChanges, diff --git a/services/core/java/com/android/server/am/RecentTasks.java b/services/core/java/com/android/server/am/RecentTasks.java index 05702af634ed..3f0674d99939 100644 --- a/services/core/java/com/android/server/am/RecentTasks.java +++ b/services/core/java/com/android/server/am/RecentTasks.java @@ -405,6 +405,8 @@ class RecentTasks extends ArrayList<TaskRecord> { int recentsCount = size(); // Quick case: never add voice sessions. + // TODO: VI what about if it's just an activity? + // Probably nothing to do here if (task.voiceSession != null) { if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: not adding voice interaction " + task); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index f05e45f0a6bb..d11da792e9d6 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -3517,7 +3517,7 @@ public class PackageManagerService extends IPackageManager.Stub { /** * Checks if the request is from the system or an app that has INTERACT_ACROSS_USERS * or INTERACT_ACROSS_USERS_FULL permissions, if the userid is not for the caller. - * @param checkShell TODO(yamasani): + * @param checkShell whether to prevent shell from access if there's a debugging restriction * @param message the message to log on security exception */ void enforceCrossUserPermission(int callingUid, int userId, boolean requireFullPermission, diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 8fee91fdca7e..2aef1091ab7e 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -18,6 +18,8 @@ package com.android.server.voiceinteraction; import android.Manifest; import android.app.ActivityManager; +import android.app.ActivityManagerInternal; +import android.app.ActivityManagerNative; import android.app.AppGlobals; import android.content.ComponentName; import android.content.ContentResolver; @@ -45,6 +47,7 @@ import android.os.UserHandle; import android.provider.Settings; import android.service.voice.IVoiceInteractionService; import android.service.voice.IVoiceInteractionSession; +import android.service.voice.VoiceInteractionManagerInternal; import android.service.voice.VoiceInteractionService; import android.service.voice.VoiceInteractionServiceInfo; import android.service.voice.VoiceInteractionSession; @@ -71,12 +74,13 @@ import java.util.List; */ public class VoiceInteractionManagerService extends SystemService { static final String TAG = "VoiceInteractionManagerService"; - static final boolean DEBUG = false; + static final boolean DEBUG = true; final Context mContext; final ContentResolver mResolver; final DatabaseHelper mDbHelper; final SoundTriggerHelper mSoundTriggerHelper; + final ActivityManagerInternal mAmInternal; public VoiceInteractionManagerService(Context context) { super(context); @@ -85,6 +89,7 @@ public class VoiceInteractionManagerService extends SystemService { mDbHelper = new DatabaseHelper(context); mSoundTriggerHelper = new SoundTriggerHelper(context); mServiceStub = new VoiceInteractionManagerServiceStub(); + mAmInternal = LocalServices.getService(ActivityManagerInternal.class); PackageManagerInternal packageManagerInternal = LocalServices.getService( PackageManagerInternal.class); @@ -105,6 +110,7 @@ public class VoiceInteractionManagerService extends SystemService { @Override public void onStart() { publishBinderService(Context.VOICE_INTERACTION_MANAGER_SERVICE, mServiceStub); + publishLocalService(VoiceInteractionManagerInternal.class, new LocalService()); } @Override @@ -124,6 +130,31 @@ public class VoiceInteractionManagerService extends SystemService { mServiceStub.switchUser(userHandle); } + class LocalService extends VoiceInteractionManagerInternal { + @Override + public void startLocalVoiceInteraction(IBinder callingActivity, Bundle options) { + if (DEBUG) { + Slog.i(TAG, "startLocalVoiceInteraction " + callingActivity); + } + VoiceInteractionManagerService.this.mServiceStub.startLocalVoiceInteraction( + callingActivity, options); + } + + @Override + public boolean supportsLocalVoiceInteraction() { + return VoiceInteractionManagerService.this.mServiceStub.supportsLocalVoiceInteraction(); + } + + @Override + public void stopLocalVoiceInteraction(IBinder callingActivity) { + if (DEBUG) { + Slog.i(TAG, "stopLocalVoiceInteraction " + callingActivity); + } + VoiceInteractionManagerService.this.mServiceStub.stopLocalVoiceInteraction( + callingActivity); + } + } + // implementation entry point and binder service private final VoiceInteractionManagerServiceStub mServiceStub; @@ -139,6 +170,49 @@ public class VoiceInteractionManagerService extends SystemService { mEnableService = shouldEnableService(mContext.getResources()); } + // TODO: VI Make sure the caller is the current user or profile + void startLocalVoiceInteraction(final IBinder token, Bundle options) { + if (mImpl == null) return; + + final long caller = Binder.clearCallingIdentity(); + try { + mImpl.showSessionLocked(options, + VoiceInteractionSession.SHOW_SOURCE_ACTIVITY, + new IVoiceInteractionSessionShowCallback.Stub() { + @Override + public void onFailed() { + } + + @Override + public void onShown() { + mAmInternal.onLocalVoiceInteractionStarted(token, + mImpl.mActiveSession.mSession, + mImpl.mActiveSession.mInteractor); + } + }, + token); + } finally { + Binder.restoreCallingIdentity(caller); + } + } + + public void stopLocalVoiceInteraction(IBinder callingActivity) { + if (mImpl == null) return; + + final long caller = Binder.clearCallingIdentity(); + try { + mImpl.finishLocked(callingActivity, true); + } finally { + Binder.restoreCallingIdentity(caller); + } + } + + public boolean supportsLocalVoiceInteraction() { + if (mImpl == null) return false; + + return mImpl.supportsLocalVoiceInteraction(); + } + @Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { @@ -568,7 +642,7 @@ public class VoiceInteractionManagerService extends SystemService { } final long caller = Binder.clearCallingIdentity(); try { - mImpl.finishLocked(token); + mImpl.finishLocked(token, false); } finally { Binder.restoreCallingIdentity(caller); } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java index 3efd0fb7ad7a..154472336fd2 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java @@ -215,12 +215,12 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne } } - public void finishLocked(IBinder token) { - if (mActiveSession == null || token != mActiveSession.mToken) { + public void finishLocked(IBinder token, boolean finishTask) { + if (mActiveSession == null || (!finishTask && token != mActiveSession.mToken)) { Slog.w(TAG, "finish does not match active session"); return; } - mActiveSession.cancelLocked(); + mActiveSession.cancelLocked(finishTask); mActiveSession = null; } @@ -251,6 +251,10 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne return mActiveSession != null ? mActiveSession.getUserDisabledShowContextLocked() : 0; } + public boolean supportsLocalVoiceInteraction() { + return mInfo.getSupportsLocalInteraction(); + } + public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args) { if (!mValid) { pw.print(" NOT VALID: "); @@ -308,7 +312,7 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne // If there is an active session, cancel it to allow it to clean up its window and other // state. if (mActiveSession != null) { - mActiveSession.cancelLocked(); + mActiveSession.cancelLocked(false); mActiveSession = null; } try { @@ -343,7 +347,7 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne @Override public void sessionConnectionGone(VoiceInteractionSessionConnection connection) { synchronized (mLock) { - finishLocked(connection.mToken); + finishLocked(connection.mToken, false); } } } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java index 1788e88c82fd..e04f312ad7b4 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java @@ -418,7 +418,7 @@ final class VoiceInteractionSessionConnection implements ServiceConnection { return false; } - public void cancelLocked() { + public void cancelLocked(boolean finishTask) { hideLocked(); mCanceled = true; if (mBound) { @@ -429,7 +429,7 @@ final class VoiceInteractionSessionConnection implements ServiceConnection { Slog.w(TAG, "Voice interation session already dead"); } } - if (mSession != null) { + if (finishTask && mSession != null) { try { mAm.finishVoiceTask(mSession); } catch (RemoteException e) { diff --git a/tests/VoiceInteraction/AndroidManifest.xml b/tests/VoiceInteraction/AndroidManifest.xml index fe17c6e18601..cbc6c76aa934 100644 --- a/tests/VoiceInteraction/AndroidManifest.xml +++ b/tests/VoiceInteraction/AndroidManifest.xml @@ -61,5 +61,13 @@ <category android:name="android.intent.category.VOICE" /> </intent-filter> </activity> + <activity android:name="StartVoiceInteractionActivity" android:label="In-Activity Voice" + android:theme="@android:style/Theme.Material.Light"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> </application> </manifest> diff --git a/tests/VoiceInteraction/res/layout/local_interaction_app.xml b/tests/VoiceInteraction/res/layout/local_interaction_app.xml new file mode 100644 index 000000000000..969413303d82 --- /dev/null +++ b/tests/VoiceInteraction/res/layout/local_interaction_app.xml @@ -0,0 +1,92 @@ +<?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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:padding="8dp" + > + + <TextView android:id="@+id/log" + android:layout_width="match_parent" + android:layout_height="0px" + android:layout_weight="1" + android:layout_marginTop="16dp" + android:textAppearance="?android:attr/textAppearanceMedium" + /> + + <LinearLayout android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:orientation="horizontal"> + + <Button android:id="@+id/start" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/startFromActivity" + /> + + </LinearLayout> + + <LinearLayout android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:orientation="horizontal"> + + <Button android:id="@+id/stop" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/stopFromActivity" + android:enabled="false" + /> + + </LinearLayout> + + <LinearLayout android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:orientation="horizontal"> + + <Button android:id="@+id/command" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/commandVoice" + /> + + <Button android:id="@+id/pick" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/pickVoice" + /> + + </LinearLayout> + + <LinearLayout android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:layout_marginBottom="16dp" + android:orientation="horizontal"> + + <Button android:id="@+id/cancel" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/cancelVoice" + /> + + </LinearLayout> + +</LinearLayout> diff --git a/tests/VoiceInteraction/res/values/strings.xml b/tests/VoiceInteraction/res/values/strings.xml index c665c23faa63..64f8bc5f1e1b 100644 --- a/tests/VoiceInteraction/res/values/strings.xml +++ b/tests/VoiceInteraction/res/values/strings.xml @@ -31,6 +31,8 @@ <string name="pickVoice">Pick Voice</string> <string name="cancelVoice">Cancel</string> <string name="jumpOut">Jump out</string> + <string name="startFromActivity">Start voice interaction</string> + <string name="stopFromActivity">Stop voice interaction</string> <string name="largetext">This is a bunch of text that we will use to show how we handle it when reporting it for assist data. We need many many lines of text, like\n diff --git a/tests/VoiceInteraction/res/xml/interaction_service.xml b/tests/VoiceInteraction/res/xml/interaction_service.xml index c015ad232fae..f0c88a2f28cf 100644 --- a/tests/VoiceInteraction/res/xml/interaction_service.xml +++ b/tests/VoiceInteraction/res/xml/interaction_service.xml @@ -21,4 +21,5 @@ android:sessionService="com.android.test.voiceinteraction.MainInteractionSessionService" android:recognitionService="com.android.test.voiceinteraction.MainRecognitionService" android:settingsActivity="com.android.test.voiceinteraction.SettingsActivity" - android:supportsAssist="true" /> + android:supportsAssist="true" + android:supportsLocalInteraction="true" /> diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java index 6e3694b345e9..450334cb2d86 100644 --- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java +++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java @@ -73,6 +73,7 @@ public class MainInteractionSession extends VoiceInteractionSession CharSequence mPendingPrompt; Request mPendingRequest; int mCurrentTask = -1; + int mShowFlags; MainInteractionSession(Context context) { super(context); @@ -88,6 +89,7 @@ public class MainInteractionSession extends VoiceInteractionSession @Override public void onShow(Bundle args, int showFlags) { super.onShow(args, showFlags); + mShowFlags = showFlags; Log.i(TAG, "onShow: flags=0x" + Integer.toHexString(showFlags) + " args=" + args); mState = STATE_IDLE; mStartIntent = args != null ? (Intent)args.getParcelable("intent") : null; @@ -311,6 +313,8 @@ public class MainInteractionSession extends VoiceInteractionSession if (mState != STATE_IDLE) { outInsets.contentInsets.top = mBottomContent.getTop(); outInsets.touchableInsets = Insets.TOUCHABLE_INSETS_CONTENT; + } else if ((mShowFlags & SHOW_SOURCE_ACTIVITY) != 0) { + outInsets.touchableInsets = Insets.TOUCHABLE_INSETS_CONTENT; } } @@ -355,7 +359,7 @@ public class MainInteractionSession extends VoiceInteractionSession mPendingPrompt = prompt.getVisualPrompt(); } } - + @Override public void onRequestConfirmation(ConfirmationRequest request) { Log.i(TAG, "onConfirm: prompt=" + request.getVoicePrompt() + " extras=" diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/StartVoiceInteractionActivity.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/StartVoiceInteractionActivity.java new file mode 100644 index 000000000000..41058c9ba8a4 --- /dev/null +++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/StartVoiceInteractionActivity.java @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2016 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.app.Activity; +import android.app.VoiceInteractor; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.TextView; + +public class StartVoiceInteractionActivity extends Activity implements View.OnClickListener { + static final String TAG = "LocalVoiceInteractionActivity"; + + static final String REQUEST_ABORT = "abort"; + static final String REQUEST_COMPLETE = "complete"; + static final String REQUEST_COMMAND = "command"; + static final String REQUEST_PICK = "pick"; + static final String REQUEST_CONFIRM = "confirm"; + + VoiceInteractor mInteractor; + VoiceInteractor.Request mCurrentRequest = null; + TextView mLog; + Button mCommandButton; + Button mPickButton; + Button mCancelButton; + Button mStartButton; + Button mStopButton; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.local_interaction_app); + + mLog = (TextView)findViewById(R.id.log); + mCommandButton = (Button)findViewById(R.id.command); + mCommandButton.setOnClickListener(this); + mPickButton = (Button)findViewById(R.id.pick); + mPickButton.setOnClickListener(this); + mCancelButton = (Button)findViewById(R.id.cancel); + mCancelButton.setOnClickListener(this); + mStartButton = (Button) findViewById(R.id.start); + mStartButton.setOnClickListener(this); + mStopButton = (Button) findViewById(R.id.stop); + mStopButton.setOnClickListener(this); + + mLog.append("Local Voice Interaction Supported = " + isLocalVoiceInteractionSupported()); + } + + @Override + public void onResume() { + super.onResume(); + } + + @Override + public void onClick(View v) { + if (v == mCommandButton) { + VoiceInteractor.CommandRequest req = new TestCommand("Some arg"); + mInteractor.submitRequest(req, REQUEST_COMMAND); + } else if (v == mPickButton) { + VoiceInteractor.PickOptionRequest.Option[] options = + new VoiceInteractor.PickOptionRequest.Option[5]; + options[0] = new VoiceInteractor.PickOptionRequest.Option("One"); + options[1] = new VoiceInteractor.PickOptionRequest.Option("Two"); + options[2] = new VoiceInteractor.PickOptionRequest.Option("Three"); + options[3] = new VoiceInteractor.PickOptionRequest.Option("Four"); + options[4] = new VoiceInteractor.PickOptionRequest.Option("Five"); + VoiceInteractor.PickOptionRequest req = new TestPickOption(options); + mInteractor.submitRequest(req, REQUEST_PICK); + } else if (v == mCancelButton && mCurrentRequest != null) { + Log.i(TAG, "Cancel request"); + mCurrentRequest.cancel(); + } else if (v == mStartButton) { + Bundle args = new Bundle(); + args.putString("Foo", "Bar"); + startLocalVoiceInteraction(args); + } else if (v == mStopButton) { + stopLocalVoiceInteraction(); + } + } + + @Override + public void onLocalVoiceInteractionStarted() { + mInteractor = getVoiceInteractor(); + mLog.append("\nLocalVoiceInteraction started!"); + mStopButton.setEnabled(true); + } + + @Override + public void onLocalVoiceInteractionStopped() { + mInteractor = getVoiceInteractor(); + mLog.append("\nLocalVoiceInteraction stopped!"); + mStopButton.setEnabled(false); + } + + @Override + public void onDestroy() { + super.onDestroy(); + } + + static class TestAbortVoice extends VoiceInteractor.AbortVoiceRequest { + public TestAbortVoice() { + super(new VoiceInteractor.Prompt("Dammit, we suck :("), null); + } + @Override public void onCancel() { + Log.i(TAG, "Canceled!"); + ((StartVoiceInteractionActivity)getActivity()).mLog.append("Canceled abort\n"); + } + @Override public void onAbortResult(Bundle result) { + Log.i(TAG, "Abort result: result=" + result); + ((StartVoiceInteractionActivity)getActivity()).mLog.append( + "Abort: result=" + result + "\n"); + getActivity().finish(); + } + } + + static class TestCompleteVoice extends VoiceInteractor.CompleteVoiceRequest { + public TestCompleteVoice() { + super(new VoiceInteractor.Prompt("Woohoo, completed!"), null); + } + @Override public void onCancel() { + Log.i(TAG, "Canceled!"); + ((StartVoiceInteractionActivity)getActivity()).mLog.append("Canceled complete\n"); + } + @Override public void onCompleteResult(Bundle result) { + Log.i(TAG, "Complete result: result=" + result); + ((StartVoiceInteractionActivity)getActivity()).mLog.append("Complete: result=" + + result + "\n"); + getActivity().finish(); + } + } + + static class TestCommand extends VoiceInteractor.CommandRequest { + public TestCommand(String arg) { + super("com.android.test.voiceinteraction.COMMAND", makeBundle(arg)); + } + @Override public void onCancel() { + Log.i(TAG, "Canceled!"); + ((StartVoiceInteractionActivity)getActivity()).mLog.append("Canceled command\n"); + } + @Override + public void onCommandResult(boolean finished, Bundle result) { + Log.i(TAG, "Command result: finished=" + finished + " result=" + result); + StringBuilder sb = new StringBuilder(); + if (finished) { + sb.append("Command final result: "); + } else { + sb.append("Command intermediate result: "); + } + if (result != null) { + result.getString("key"); + } + sb.append(result); + sb.append("\n"); + ((StartVoiceInteractionActivity)getActivity()).mLog.append(sb.toString()); + } + static Bundle makeBundle(String arg) { + Bundle b = new Bundle(); + b.putString("key", arg); + return b; + } + } + + static class TestPickOption extends VoiceInteractor.PickOptionRequest { + public TestPickOption(Option[] options) { + super(new VoiceInteractor.Prompt("Need to pick something"), options, null); + } + @Override public void onCancel() { + Log.i(TAG, "Canceled!"); + ((StartVoiceInteractionActivity)getActivity()).mLog.append("Canceled pick\n"); + } + @Override + public void onPickOptionResult(boolean finished, Option[] selections, Bundle result) { + Log.i(TAG, "Pick result: finished=" + finished + " selections=" + selections + + " result=" + result); + StringBuilder sb = new StringBuilder(); + if (finished) { + sb.append("Pick final result: "); + } else { + sb.append("Pick intermediate result: "); + } + for (int i=0; i<selections.length; i++) { + if (i >= 1) { + sb.append(", "); + } + sb.append(selections[i].getLabel()); + } + sb.append("\n"); + ((StartVoiceInteractionActivity)getActivity()).mLog.append(sb.toString()); + } + } +} |