diff options
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()); + } + } +} |