diff options
24 files changed, 812 insertions, 90 deletions
diff --git a/api/current.txt b/api/current.txt index 67c9276..3c4832d 100644 --- a/api/current.txt +++ b/api/current.txt @@ -25130,23 +25130,53 @@ package android.service.voice { public class VoiceInteractionService extends android.app.Service { ctor public VoiceInteractionService(); method public android.os.IBinder onBind(android.content.Intent); - method public void startVoiceActivity(android.content.Intent, android.os.Bundle); + method public void startSession(android.os.Bundle); field public static final java.lang.String SERVICE_INTERFACE = "android.service.voice.VoiceInteractionService"; field public static final java.lang.String SERVICE_META_DATA = "android.voice_interaction"; } - public abstract class VoiceInteractionSession { + public abstract class VoiceInteractionSession implements android.view.KeyEvent.Callback { ctor public VoiceInteractionSession(android.content.Context); ctor public VoiceInteractionSession(android.content.Context, android.os.Handler); + method public void finish(); + method public android.view.LayoutInflater getLayoutInflater(); + method public android.app.Dialog getWindow(); + method public void hideWindow(); + method public void onBackPressed(); method public abstract void onCancel(android.service.voice.VoiceInteractionSession.Request); + method public void onCloseSystemDialogs(); method public abstract void onCommand(android.service.voice.VoiceInteractionSession.Caller, android.service.voice.VoiceInteractionSession.Request, java.lang.String, android.os.Bundle); + method public void onComputeInsets(android.service.voice.VoiceInteractionSession.Insets); method public abstract void onConfirm(android.service.voice.VoiceInteractionSession.Caller, android.service.voice.VoiceInteractionSession.Request, java.lang.String, android.os.Bundle); + method public void onCreate(android.os.Bundle); + method public android.view.View onCreateContentView(); + method public void onDestroy(); method public abstract boolean[] onGetSupportedCommands(android.service.voice.VoiceInteractionSession.Caller, java.lang.String[]); + method public boolean onKeyDown(int, android.view.KeyEvent); + method public boolean onKeyLongPress(int, android.view.KeyEvent); + method public boolean onKeyMultiple(int, int, android.view.KeyEvent); + method public boolean onKeyUp(int, android.view.KeyEvent); + method public void onTaskFinished(android.content.Intent, int); + method public void onTaskStarted(android.content.Intent, int); + method public void setContentView(android.view.View); + method public void setTheme(int); + method public void showWindow(); + method public void startVoiceActivity(android.content.Intent); } public static class VoiceInteractionSession.Caller { } + public static final class VoiceInteractionSession.Insets { + ctor public VoiceInteractionSession.Insets(); + field public static final int TOUCHABLE_INSETS_CONTENT = 1; // 0x1 + field public static final int TOUCHABLE_INSETS_FRAME = 0; // 0x0 + field public static final int TOUCHABLE_INSETS_REGION = 3; // 0x3 + field public int contentTopInsets; + field public int touchableInsets; + field public final android.graphics.Region touchableRegion; + } + public static class VoiceInteractionSession.Request { method public void sendCancelResult(); method public void sendCommandResult(boolean, android.os.Bundle); diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index e6dbcd0..4160633 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -664,7 +664,8 @@ public class InputMethodService extends AbstractInputMethodService { mImm = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE); mInflater = (LayoutInflater)getSystemService( Context.LAYOUT_INFLATER_SERVICE); - mWindow = new SoftInputWindow(this, mTheme, mDispatcherState); + mWindow = new SoftInputWindow(this, "InputMethod", mTheme, null, null, mDispatcherState, + false); if (mHardwareAccelerated) { mWindow.getWindow().addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); } diff --git a/core/java/android/inputmethodservice/SoftInputWindow.java b/core/java/android/inputmethodservice/SoftInputWindow.java index df1afee..a9bace1 100644 --- a/core/java/android/inputmethodservice/SoftInputWindow.java +++ b/core/java/android/inputmethodservice/SoftInputWindow.java @@ -30,11 +30,20 @@ import android.view.WindowManager; * method window. It will be displayed along the edge of the screen, moving * the application user interface away from it so that the focused item is * always visible. + * @hide */ -class SoftInputWindow extends Dialog { +public class SoftInputWindow extends Dialog { + final String mName; + final Callback mCallback; + final KeyEvent.Callback mKeyEventCallback; final KeyEvent.DispatcherState mDispatcherState; + final boolean mTakesFocus; private final Rect mBounds = new Rect(); - + + public interface Callback { + public void onBackPressed(); + } + public void setToken(IBinder token) { WindowManager.LayoutParams lp = getWindow().getAttributes(); lp.token = token; @@ -53,10 +62,15 @@ class SoftInputWindow extends Dialog { * using styles. This theme is applied on top of the current theme in * <var>context</var>. If 0, the default dialog theme will be used. */ - public SoftInputWindow(Context context, int theme, - KeyEvent.DispatcherState dispatcherState) { + public SoftInputWindow(Context context, String name, int theme, Callback callback, + KeyEvent.Callback keyEventCallback, KeyEvent.DispatcherState dispatcherState, + boolean takesFocus) { super(context, theme); + mName = name; + mCallback = callback; + mKeyEventCallback = keyEventCallback; mDispatcherState = dispatcherState; + mTakesFocus = takesFocus; initDockWindow(); } @@ -148,11 +162,47 @@ class SoftInputWindow extends Dialog { } } + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (mKeyEventCallback != null && mKeyEventCallback.onKeyDown(keyCode, event)) { + return true; + } + return super.onKeyDown(keyCode, event); + } + + public boolean onKeyLongPress(int keyCode, KeyEvent event) { + if (mKeyEventCallback != null && mKeyEventCallback.onKeyLongPress(keyCode, event)) { + return true; + } + return super.onKeyLongPress(keyCode, event); + } + + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (mKeyEventCallback != null && mKeyEventCallback.onKeyUp(keyCode, event)) { + return true; + } + return super.onKeyUp(keyCode, event); + } + + public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) { + if (mKeyEventCallback != null && mKeyEventCallback.onKeyMultiple(keyCode, count, event)) { + return true; + } + return super.onKeyMultiple(keyCode, count, event); + } + + public void onBackPressed() { + if (mCallback != null) { + mCallback.onBackPressed(); + } else { + super.onBackPressed(); + } + } + private void initDockWindow() { WindowManager.LayoutParams lp = getWindow().getAttributes(); lp.type = WindowManager.LayoutParams.TYPE_INPUT_METHOD; - lp.setTitle("InputMethod"); + lp.setTitle(mName); lp.gravity = Gravity.BOTTOM; lp.width = -1; @@ -161,11 +211,19 @@ class SoftInputWindow extends Dialog { //lp.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_USER; getWindow().setAttributes(lp); - getWindow().setFlags( - WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | - WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, - WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | + + int windowSetFlags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; + int windowModFlags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | - WindowManager.LayoutParams.FLAG_DIM_BEHIND); + WindowManager.LayoutParams.FLAG_DIM_BEHIND; + + if (!mTakesFocus) { + windowSetFlags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; + } else { + windowSetFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; + windowModFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; + } + + getWindow().setFlags(windowSetFlags, windowModFlags); } } diff --git a/core/java/android/service/voice/IVoiceInteractionSession.aidl b/core/java/android/service/voice/IVoiceInteractionSession.aidl index 7dbf66b..9f9c312 100644 --- a/core/java/android/service/voice/IVoiceInteractionSession.aidl +++ b/core/java/android/service/voice/IVoiceInteractionSession.aidl @@ -16,13 +16,14 @@ package android.service.voice; -import android.os.Bundle; - -import com.android.internal.app.IVoiceInteractorCallback; -import com.android.internal.app.IVoiceInteractorRequest; +import android.content.Intent; /** * @hide */ -interface IVoiceInteractionSession { +oneway interface IVoiceInteractionSession { + void taskStarted(in Intent intent, int taskId); + void taskFinished(in Intent intent, int taskId); + void closeSystemDialogs(); + void destroy(); } diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java index d005890..e15489b 100644 --- a/core/java/android/service/voice/VoiceInteractionService.java +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -27,6 +27,19 @@ import android.os.RemoteException; import android.os.ServiceManager; import com.android.internal.app.IVoiceInteractionManagerService; +/** + * Top-level service of the current global voice interactor, which is providing + * support for hotwording, the back-end of a {@link android.app.VoiceInteractor}, etc. + * The current VoiceInteractionService that has been selected by the user is kept + * always running by the system, to allow it to do things like listen for hotwords + * in the background to instigate voice interactions. + * + * <p>Because this service is always running, it should be kept as lightweight as + * possible. Heavy-weight operations (including showing UI) should be implemented + * in the associated {@link android.service.voice.VoiceInteractionSessionService} when + * an actual voice interaction is taking place, and that service should run in a + * separate process from this one. + */ public class VoiceInteractionService extends Service { /** * The {@link Intent} that must be declared as handled by the service. @@ -51,11 +64,9 @@ public class VoiceInteractionService extends Service { IVoiceInteractionManagerService mSystemService; - public void startVoiceActivity(Intent intent, Bundle sessionArgs) { + public void startSession(Bundle args) { try { - mSystemService.startVoiceActivity(intent, - intent.resolveType(getContentResolver()), - mInterface, sessionArgs); + mSystemService.startSession(mInterface, args); } catch (RemoteException e) { } } diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java index 963b6b4..a83544d 100644 --- a/core/java/android/service/voice/VoiceInteractionSession.java +++ b/core/java/android/service/voice/VoiceInteractionSession.java @@ -16,7 +16,14 @@ package android.service.voice; +import android.app.Dialog; +import android.app.Instrumentation; import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Region; +import android.inputmethodservice.SoftInputWindow; import android.os.Binder; import android.os.Bundle; import android.os.Handler; @@ -25,16 +32,53 @@ import android.os.Message; import android.os.RemoteException; import android.util.ArrayMap; import android.util.Log; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +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.IVoiceInteractor; import com.android.internal.app.IVoiceInteractorCallback; import com.android.internal.app.IVoiceInteractorRequest; import com.android.internal.os.HandlerCaller; import com.android.internal.os.SomeArgs; -public abstract class VoiceInteractionSession { +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; + +public abstract class VoiceInteractionSession implements KeyEvent.Callback { static final String TAG = "VoiceInteractionSession"; static final boolean DEBUG = true; + final Context mContext; + final HandlerCaller mHandlerCaller; + + final KeyEvent.DispatcherState mDispatcherState = new KeyEvent.DispatcherState(); + + IVoiceInteractionManagerService mSystemService; + IBinder mToken; + + int mTheme = 0; + LayoutInflater mInflater; + TypedArray mThemeAttrs; + View mRootView; + FrameLayout mContentFrame; + SoftInputWindow mWindow; + + boolean mInitialized; + boolean mWindowAdded; + boolean mWindowVisible; + boolean mWindowWasVisible; + boolean mInShowWindow; + + final ArrayMap<IBinder, Request> mActiveRequests = new ArrayMap<IBinder, Request>(); + + final Insets mTmpInsets = new Insets(); + final int[] mTmpLocation = new int[2]; + final IVoiceInteractor mInteractor = new IVoiceInteractor.Stub() { @Override public IVoiceInteractorRequest startConfirmation(String callingPackage, @@ -71,6 +115,27 @@ public abstract class VoiceInteractionSession { }; final IVoiceInteractionSession mSession = new IVoiceInteractionSession.Stub() { + @Override + public void taskStarted(Intent intent, int taskId) { + mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIO(MSG_TASK_STARTED, + taskId, intent)); + } + + @Override + public void taskFinished(Intent intent, int taskId) { + mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIO(MSG_TASK_FINISHED, + taskId, intent)); + } + + @Override + public void closeSystemDialogs() { + mHandlerCaller.sendMessage(mHandlerCaller.obtainMessage(MSG_CLOSE_SYSTEM_DIALOGS)); + } + + @Override + public void destroy() { + mHandlerCaller.sendMessage(mHandlerCaller.obtainMessage(MSG_DESTROY)); + } }; public static class Request { @@ -129,38 +194,128 @@ public abstract class VoiceInteractionSession { static final int MSG_SUPPORTS_COMMANDS = 3; static final int MSG_CANCEL = 4; - final Context mContext; - final HandlerCaller mHandlerCaller; - final HandlerCaller.Callback mHandlerCallerCallback = new HandlerCaller.Callback() { + static final int MSG_TASK_STARTED = 100; + static final int MSG_TASK_FINISHED = 101; + static final int MSG_CLOSE_SYSTEM_DIALOGS = 102; + static final int MSG_DESTROY = 103; + + class MyCallbacks implements HandlerCaller.Callback, SoftInputWindow.Callback { @Override public void executeMessage(Message msg) { - SomeArgs args = (SomeArgs)msg.obj; + SomeArgs args; switch (msg.what) { case MSG_START_CONFIRMATION: + args = (SomeArgs)msg.obj; if (DEBUG) Log.d(TAG, "onConfirm: req=" + ((Request) args.arg2).mInterface + " prompt=" + args.arg3 + " extras=" + args.arg4); onConfirm((Caller)args.arg1, (Request)args.arg2, (String)args.arg3, (Bundle)args.arg4); break; case MSG_START_COMMAND: + args = (SomeArgs)msg.obj; if (DEBUG) Log.d(TAG, "onCommand: req=" + ((Request) args.arg2).mInterface + " command=" + args.arg3 + " extras=" + args.arg4); onCommand((Caller) args.arg1, (Request) args.arg2, (String) args.arg3, (Bundle) args.arg4); break; case MSG_SUPPORTS_COMMANDS: + args = (SomeArgs)msg.obj; if (DEBUG) Log.d(TAG, "onGetSupportedCommands: cmds=" + args.arg2); args.arg1 = onGetSupportedCommands((Caller) args.arg1, (String[]) args.arg2); break; case MSG_CANCEL: + args = (SomeArgs)msg.obj; if (DEBUG) Log.d(TAG, "onCancel: req=" + ((Request) args.arg1).mInterface); onCancel((Request)args.arg1); break; + case MSG_TASK_STARTED: + if (DEBUG) Log.d(TAG, "onTaskStarted: intent=" + msg.obj + + " taskId=" + msg.arg1); + onTaskStarted((Intent) msg.obj, msg.arg1); + break; + case MSG_TASK_FINISHED: + if (DEBUG) Log.d(TAG, "onTaskFinished: intent=" + msg.obj + + " taskId=" + msg.arg1); + onTaskFinished((Intent) msg.obj, msg.arg1); + break; + case MSG_CLOSE_SYSTEM_DIALOGS: + if (DEBUG) Log.d(TAG, "onCloseSystemDialogs"); + onCloseSystemDialogs(); + break; + case MSG_DESTROY: + if (DEBUG) Log.d(TAG, "doDestroy"); + doDestroy(); + break; } } - }; - final ArrayMap<IBinder, Request> mActiveRequests = new ArrayMap<IBinder, Request>(); + @Override + public void onBackPressed() { + VoiceInteractionSession.this.onBackPressed(); + } + } + + final MyCallbacks mCallbacks = new MyCallbacks(); + + /** + * Information about where interesting parts of the input method UI appear. + */ + public static final class Insets { + /** + * This is the top part of the UI that is the main content. It is + * used to determine the basic space needed, to resize/pan the + * application behind. It is assumed that this inset does not + * change very much, since any change will cause a full resize/pan + * of the application behind. This value is relative to the top edge + * of the input method window. + */ + public int contentTopInsets; + + /** + * This is the region of the UI that is touchable. It is used when + * {@link #touchableInsets} is set to {@link #TOUCHABLE_INSETS_REGION}. + * The region should be specified relative to the origin of the window frame. + */ + public final Region touchableRegion = new Region(); + + /** + * Option for {@link #touchableInsets}: the entire window frame + * can be touched. + */ + public static final int TOUCHABLE_INSETS_FRAME + = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME; + + /** + * Option for {@link #touchableInsets}: the area inside of + * the content insets can be touched. + */ + public static final int TOUCHABLE_INSETS_CONTENT + = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT; + + /** + * Option for {@link #touchableInsets}: the region specified by + * {@link #touchableRegion} can be touched. + */ + public static final int TOUCHABLE_INSETS_REGION + = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION; + + /** + * Determine which area of the window is touchable by the user. May + * be one of: {@link #TOUCHABLE_INSETS_FRAME}, + * {@link #TOUCHABLE_INSETS_CONTENT}, or {@link #TOUCHABLE_INSETS_REGION}. + */ + public int touchableInsets; + } + + final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer = + new ViewTreeObserver.OnComputeInternalInsetsListener() { + public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) { + onComputeInsets(mTmpInsets); + info.contentInsets.top = info.visibleInsets.top = mTmpInsets.contentTopInsets; + info.touchableRegion.set(mTmpInsets.touchableRegion); + info.setTouchableInsets(mTmpInsets.touchableInsets); + } + }; public VoiceInteractionSession(Context context) { this(context, new Handler()); @@ -169,7 +324,7 @@ public abstract class VoiceInteractionSession { public VoiceInteractionSession(Context context, Handler handler) { mContext = context; mHandlerCaller = new HandlerCaller(context, handler.getLooper(), - mHandlerCallerCallback, true); + mCallbacks, true); } Request findRequest(IVoiceInteractorCallback callback, boolean newRequest) { @@ -188,6 +343,192 @@ public abstract class VoiceInteractionSession { } } + void doCreate(IVoiceInteractionManagerService service, IBinder token, Bundle args) { + mSystemService = service; + mToken = token; + onCreate(args); + } + + void doDestroy() { + if (mInitialized) { + mRootView.getViewTreeObserver().removeOnComputeInternalInsetsListener( + mInsetsComputer); + if (mWindowAdded) { + mWindow.dismiss(); + mWindowAdded = false; + } + mInitialized = false; + } + } + + void initViews() { + mInitialized = true; + + mThemeAttrs = mContext.obtainStyledAttributes(android.R.styleable.VoiceInteractionSession); + mRootView = mInflater.inflate( + com.android.internal.R.layout.voice_interaction_session, null); + mRootView.setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); + mWindow.setContentView(mRootView); + mRootView.getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsComputer); + + mContentFrame = (FrameLayout)mRootView.findViewById(android.R.id.content); + } + + public void showWindow() { + if (DEBUG) Log.v(TAG, "Showing window: mWindowAdded=" + mWindowAdded + + " mWindowVisible=" + mWindowVisible); + + if (mInShowWindow) { + Log.w(TAG, "Re-entrance in to showWindow"); + return; + } + + try { + mInShowWindow = true; + if (!mWindowVisible) { + mWindowVisible = true; + if (!mWindowAdded) { + mWindowAdded = true; + View v = onCreateContentView(); + if (v != null) { + setContentView(v); + } + } + mWindow.show(); + } + } finally { + mWindowWasVisible = true; + mInShowWindow = false; + } + } + + public void hideWindow() { + if (mWindowVisible) { + mWindow.hide(); + mWindowVisible = false; + } + } + + /** + * You can call this to customize the theme used by your IME's window. + * This must be set before {@link #onCreate}, so you + * will typically call it in your constructor with the resource ID + * of your custom theme. + */ + public void setTheme(int theme) { + if (mWindow != null) { + throw new IllegalStateException("Must be called before onCreate()"); + } + mTheme = theme; + } + + public void startVoiceActivity(Intent intent) { + if (mToken == null) { + throw new IllegalStateException("Can't call before onCreate()"); + } + try { + int res = mSystemService.startVoiceActivity(mToken, intent, + intent.resolveType(mContext.getContentResolver())); + Instrumentation.checkStartActivityResult(res, intent); + } catch (RemoteException e) { + } + } + + public LayoutInflater getLayoutInflater() { + return mInflater; + } + + public Dialog getWindow() { + return mWindow; + } + + public void finish() { + if (mToken == null) { + throw new IllegalStateException("Can't call before onCreate()"); + } + hideWindow(); + try { + mSystemService.finish(mToken); + } catch (RemoteException e) { + } + } + + public void onCreate(Bundle args) { + mTheme = mTheme != 0 ? mTheme + : com.android.internal.R.style.Theme_DeviceDefault_VoiceInteractionSession; + mInflater = (LayoutInflater)mContext.getSystemService( + Context.LAYOUT_INFLATER_SERVICE); + mWindow = new SoftInputWindow(mContext, "VoiceInteractionSession", mTheme, + mCallbacks, this, mDispatcherState, true); + mWindow.getWindow().addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); + initViews(); + mWindow.getWindow().setLayout(MATCH_PARENT, WRAP_CONTENT); + mWindow.setToken(mToken); + } + + public void onDestroy() { + } + + public View onCreateContentView() { + return null; + } + + public void setContentView(View view) { + mContentFrame.removeAllViews(); + mContentFrame.addView(view, new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT)); + + } + + public boolean onKeyDown(int keyCode, KeyEvent event) { + return false; + } + + public boolean onKeyLongPress(int keyCode, KeyEvent event) { + return false; + } + + public boolean onKeyUp(int keyCode, KeyEvent event) { + return false; + } + + public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) { + return false; + } + + public void onBackPressed() { + finish(); + } + + public void onCloseSystemDialogs() { + finish(); + } + + /** + * Compute the interesting insets into your UI. The default implementation + * uses the entire window frame as the insets. The default touchable + * insets are {@link Insets#TOUCHABLE_INSETS_FRAME}. + * + * @param outInsets Fill in with the current UI insets. + */ + public void onComputeInsets(Insets outInsets) { + int[] loc = mTmpLocation; + View decor = getWindow().getWindow().getDecorView(); + decor.getLocationInWindow(loc); + outInsets.contentTopInsets = loc[1]; + outInsets.touchableInsets = Insets.TOUCHABLE_INSETS_FRAME; + outInsets.touchableRegion.setEmpty(); + } + + public void onTaskStarted(Intent intent, int taskId) { + } + + public void onTaskFinished(Intent intent, int taskId) { + finish(); + } + public abstract boolean[] onGetSupportedCommands(Caller caller, String[] commands); public abstract void onConfirm(Caller caller, Request request, String prompt, Bundle extras); public abstract void onCommand(Caller caller, Request request, String command, Bundle extras); diff --git a/core/java/android/service/voice/VoiceInteractionSessionService.java b/core/java/android/service/voice/VoiceInteractionSessionService.java index 40e5bba..e793849 100644 --- a/core/java/android/service/voice/VoiceInteractionSessionService.java +++ b/core/java/android/service/voice/VoiceInteractionSessionService.java @@ -29,11 +29,15 @@ import com.android.internal.app.IVoiceInteractionManagerService; import com.android.internal.os.HandlerCaller; import com.android.internal.os.SomeArgs; +/** + * An active voice interaction session, initiated by a {@link VoiceInteractionService}. + */ public abstract class VoiceInteractionSessionService extends Service { static final int MSG_NEW_SESSION = 1; IVoiceInteractionManagerService mSystemService; + VoiceInteractionSession mSession; IVoiceInteractionSessionService mInterface = new IVoiceInteractionSessionService.Stub() { public void newSession(IBinder token, Bundle args) { @@ -73,9 +77,14 @@ public abstract class VoiceInteractionSessionService extends Service { } void doNewSession(IBinder token, Bundle args) { - VoiceInteractionSession session = onNewSession(args); + if (mSession != null) { + mSession.doDestroy(); + mSession = null; + } + mSession = onNewSession(args); try { - mSystemService.deliverNewSession(token, session.mSession, session.mInteractor); + mSystemService.deliverNewSession(token, mSession.mSession, mSession.mInteractor); + mSession.doCreate(mSystemService, token, args); } catch (RemoteException e) { } } diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl index 3219ddd..98e35dd 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl @@ -24,8 +24,9 @@ import android.service.voice.IVoiceInteractionService; import android.service.voice.IVoiceInteractionSession; interface IVoiceInteractionManagerService { - void startVoiceActivity(in Intent intent, String resolvedType, IVoiceInteractionService service, - in Bundle sessionArgs); - int deliverNewSession(IBinder token, IVoiceInteractionSession session, + void startSession(IVoiceInteractionService service, in Bundle sessionArgs); + boolean deliverNewSession(IBinder token, IVoiceInteractionSession session, IVoiceInteractor interactor); + int startVoiceActivity(IBinder token, in Intent intent, String resolvedType); + void finish(IBinder token); } diff --git a/core/res/res/layout/voice_interaction_session.xml b/core/res/res/layout/voice_interaction_session.xml new file mode 100644 index 0000000..48b6579 --- /dev/null +++ b/core/res/res/layout/voice_interaction_session.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* //device/apps/common/res/layout/alert_dialog.xml +** +** Copyright 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. +*/ +--> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@android:id/content" + android:layout_width="match_parent" + android:layout_height="wrap_content"> +</FrameLayout> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index e07ebd4..7036224 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -5810,6 +5810,9 @@ <attr name="imeExtractExitAnimation" format="reference" /> </declare-styleable> + <declare-styleable name="VoiceInteractionSession"> + </declare-styleable> + <declare-styleable name="KeyboardView"> <!-- Default KeyboardView style. --> <attr name="keyboardViewStyle" format="reference" /> diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index 37716f7..891265f 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -169,6 +169,12 @@ please see styles_device_defaults.xml. <item name="windowExitAnimation">@anim/input_method_exit</item> </style> + <!-- Window animations that are applied to voice interaction overlay windows. --> + <style name="Animation.VoiceInteractionSession"> + <item name="windowEnterAnimation">@anim/input_method_enter</item> + <item name="windowExitAnimation">@anim/input_method_exit</item> + </style> + <!-- Special optional fancy IM animations. @hide --> <style name="Animation.InputMethodFancy"> <item name="windowEnterAnimation">@anim/input_method_fancy_enter</item> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 7c6a91a..6bcbbce 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1198,6 +1198,7 @@ <java-symbol type="layout" name="transient_notification" /> <java-symbol type="layout" name="volume_adjust" /> <java-symbol type="layout" name="volume_adjust_item" /> + <java-symbol type="layout" name="voice_interaction_session" /> <java-symbol type="layout" name="web_text_view_dropdown" /> <java-symbol type="layout" name="webview_find" /> <java-symbol type="layout" name="webview_select_singlechoice" /> @@ -1269,6 +1270,7 @@ <java-symbol type="style" name="TextAppearance.SlidingTabNormal" /> <java-symbol type="style" name="Theme.DeviceDefault.Dialog.NoFrame" /> <java-symbol type="style" name="Theme.IconMenu" /> + <java-symbol type="style" name="Theme.DeviceDefault.VoiceInteractionSession" /> <java-symbol type="attr" name="mediaRouteButtonStyle" /> <java-symbol type="attr" name="externalRouteEnabledDrawable" /> diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml index 6f4e7d0..e99f64f 100644 --- a/core/res/res/values/themes.xml +++ b/core/res/res/values/themes.xml @@ -805,6 +805,14 @@ please see themes_device_defaults.xml. <item name="android:imeExtractExitAnimation">@android:anim/input_method_extract_exit</item> </style> + <!-- Default theme for voice interaction, which is used by the + {@link android.service.voice.VoiceInteractionSession} class. + this inherits from Theme.Panel, but sets up appropriate animations + and a few custom attributes. --> + <style name="Theme.VoiceInteractionSession" parent="Theme.Panel"> + <item name="android:windowAnimationStyle">@android:style/Animation.VoiceInteractionSession</item> + </style> + <!-- Default theme for holo style input methods, which is used by the {@link android.inputmethodservice.InputMethodService} class. this inherits from Theme.Panel, but sets up IME appropriate animations diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml index 80c10dd..dbc3d9e 100644 --- a/core/res/res/values/themes_device_defaults.xml +++ b/core/res/res/values/themes_device_defaults.xml @@ -299,6 +299,11 @@ easier. {@link android.inputmethodservice.InputMethodService} class.--> <style name="Theme.DeviceDefault.InputMethod" parent="Theme.Quantum.InputMethod" /> + <!-- DeviceDefault style for input methods, which is used by the + {@link android.service.voice.VoiceInteractionSession} class.--> + <style name="Theme.DeviceDefault.VoiceInteractionSession" parent="Theme.Quantum.VoiceInteractionSession" > + + </style> <style name="Theme.DeviceDefault.Dialog.Alert" parent="Theme.Quantum.Dialog.Alert"> <item name="windowTitleStyle">@style/DialogWindowTitle.DeviceDefault</item> </style> diff --git a/core/res/res/values/themes_quantum.xml b/core/res/res/values/themes_quantum.xml index c0bd18b..6115fa7 100644 --- a/core/res/res/values/themes_quantum.xml +++ b/core/res/res/values/themes_quantum.xml @@ -853,6 +853,14 @@ please see themes_device_defaults.xml. <item name="imeExtractExitAnimation">@anim/input_method_extract_exit</item> </style> + <!-- Default theme for quantum style voice interaction, which is used by the + {@link android.service.voice.VoiceInteractionSession} class. + this inherits from Theme.Panel, but sets up appropriate animations + and a few custom attributes. --> + <style name="Theme.Quantum.VoiceInteractionSession" parent="Theme.Quantum.Light.Panel"> + <item name="android:windowAnimationStyle">@android:style/Animation.VoiceInteractionSession</item> + </style> + <!-- Theme for the search input bar. --> <style name="Theme.Quantum.SearchBar" parent="Theme.Quantum.Panel"> diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index f506eab..7c3f288 100755 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -480,7 +480,7 @@ final class ActivityRecord { void setTask(TaskRecord newTask, ThumbnailHolder newThumbHolder, boolean isRoot) { if (task != null && task.removeActivity(this)) { if (task != newTask) { - task.stack.removeTask(task); + task.stack.removeTask(task, false); } else { Slog.d(TAG, "!!! REMOVE THIS LOG !!! setTask: nearly removed stack=" + (newTask == null ? null : newTask.stack)); diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index d5ab277..ee39b67 100755 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -2841,7 +2841,7 @@ final class ActivityStack { if (mStackSupervisor.isFrontStack(this) && task == topTask() && task.mOnTopOfHome) { mStackSupervisor.moveHomeToTop(); } - removeTask(task); + removeTask(task, false); } cleanUpActivityServicesLocked(r); r.removeUriPermissionsLocked(); @@ -3717,7 +3717,7 @@ final class ActivityStack { return starting; } - void removeTask(TaskRecord task) { + void removeTask(TaskRecord task, boolean moving) { mStackSupervisor.endLockTaskModeIfTaskEnding(task); mWindowManager.removeTask(task.taskId); final ActivityRecord r = mResumedActivity; @@ -3731,9 +3731,13 @@ final class ActivityStack { mTaskHistory.get(taskNdx + 1).mOnTopOfHome = true; } mTaskHistory.remove(task); - if (task.voiceInteractor != null) { + if (!moving && task.voiceSession != null) { // This task was a voice interaction, so it should not remain on the // recent tasks list. + try { + task.voiceSession.taskFinished(task.intent, task.taskId); + } catch (RemoteException e) { + } mService.mRecentTasks.remove(task); } @@ -3753,7 +3757,7 @@ final class ActivityStack { IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, boolean toTop) { TaskRecord task = new TaskRecord(taskId, info, intent, voiceSession, voiceInteractor); - addTask(task, toTop); + addTask(task, toTop, false); return task; } @@ -3761,13 +3765,19 @@ final class ActivityStack { return new ArrayList<TaskRecord>(mTaskHistory); } - void addTask(final TaskRecord task, final boolean toTop) { + void addTask(final TaskRecord task, final boolean toTop, boolean moving) { task.stack = this; if (toTop) { insertTaskAtTop(task); } else { mTaskHistory.add(0, task); } + if (!moving && task.voiceSession != null) { + try { + task.voiceSession.taskStarted(task.intent, task.taskId); + } catch (RemoteException e) { + } + } } public int getStackId() { diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 9107cb6..8829b5f 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -2237,8 +2237,8 @@ public final class ActivityStackSupervisor implements DisplayListener { Slog.w(TAG, "moveTaskToStack: no stack for id=" + stackId); return; } - task.stack.removeTask(task); - stack.addTask(task, toTop); + task.stack.removeTask(task, true); + stack.addTask(task, toTop, true); mWindowManager.addTask(taskId, stackId, toTop); resumeTopActivitiesLocked(); } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 045c0f6..16afc8f 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -28,6 +28,8 @@ import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; +import android.os.Parcel; +import android.os.RemoteException; import android.os.UserHandle; import android.provider.Settings; import android.service.voice.IVoiceInteractionService; @@ -88,6 +90,21 @@ public class VoiceInteractionManagerService extends SystemService { private boolean mSafeMode; private int mCurUser; + @Override + public boolean onTransact(int code, Parcel data, Parcel reply, int flags) + throws RemoteException { + try { + return super.onTransact(code, data, reply, flags); + } catch (RuntimeException e) { + // The activity manager only throws security exceptions, so let's + // log all others. + if (!(e instanceof SecurityException)) { + Slog.wtf(TAG, "VoiceInteractionManagerService Crash", e); + } + throw e; + } + } + public void systemRunning(boolean safeMode) { mSafeMode = safeMode; @@ -97,18 +114,18 @@ public class VoiceInteractionManagerService extends SystemService { synchronized (this) { mCurUser = ActivityManager.getCurrentUser(); - switchImplementationIfNeededLocked(); + switchImplementationIfNeededLocked(false); } } public void switchUser(int userHandle) { synchronized (this) { mCurUser = userHandle; - switchImplementationIfNeededLocked(); + switchImplementationIfNeededLocked(false); } } - void switchImplementationIfNeededLocked() { + void switchImplementationIfNeededLocked(boolean force) { if (!mSafeMode) { String curService = Settings.Secure.getStringForUser( mResolver, Settings.Secure.VOICE_INTERACTION_SERVICE, mCurUser); @@ -121,7 +138,7 @@ public class VoiceInteractionManagerService extends SystemService { serviceComponent = null; } } - if (mImpl == null || mImpl.mUser != mCurUser + if (force || mImpl == null || mImpl.mUser != mCurUser || !mImpl.mComponent.equals(serviceComponent)) { if (mImpl != null) { mImpl.shutdownLocked(); @@ -138,10 +155,10 @@ public class VoiceInteractionManagerService extends SystemService { } @Override - public void startVoiceActivity(Intent intent, String resolvedType, - IVoiceInteractionService service, Bundle args) { + public void startSession(IVoiceInteractionService service, Bundle args) { synchronized (this) { - if (mImpl == null || service.asBinder() != mImpl.mService.asBinder()) { + if (mImpl == null || mImpl.mService == null + || service.asBinder() != mImpl.mService.asBinder()) { throw new SecurityException( "Caller is not the current voice interaction service"); } @@ -149,8 +166,7 @@ public class VoiceInteractionManagerService extends SystemService { final int callingUid = Binder.getCallingUid(); final long caller = Binder.clearCallingIdentity(); try { - mImpl.startVoiceActivityLocked(callingPid, callingUid, - intent, resolvedType, args); + mImpl.startSessionLocked(callingPid, callingUid, args); } finally { Binder.restoreCallingIdentity(caller); } @@ -158,12 +174,12 @@ public class VoiceInteractionManagerService extends SystemService { } @Override - public int deliverNewSession(IBinder token, IVoiceInteractionSession session, + public boolean deliverNewSession(IBinder token, IVoiceInteractionSession session, IVoiceInteractor interactor) { synchronized (this) { if (mImpl == null) { - Slog.w(TAG, "deliverNewSession without running voice interaction service"); - return ActivityManager.START_CANCELED; + throw new SecurityException( + "deliverNewSession without running voice interaction service"); } final int callingPid = Binder.getCallingPid(); final int callingUid = Binder.getCallingUid(); @@ -175,7 +191,43 @@ public class VoiceInteractionManagerService extends SystemService { Binder.restoreCallingIdentity(caller); } } + } + @Override + public int startVoiceActivity(IBinder token, Intent intent, String resolvedType) { + synchronized (this) { + if (mImpl == null) { + Slog.w(TAG, "startVoiceActivity without running voice interaction service"); + return ActivityManager.START_CANCELED; + } + final int callingPid = Binder.getCallingPid(); + final int callingUid = Binder.getCallingUid(); + final long caller = Binder.clearCallingIdentity(); + try { + return mImpl.startVoiceActivityLocked(callingPid, callingUid, token, + intent, resolvedType); + } finally { + Binder.restoreCallingIdentity(caller); + } + } + } + + @Override + public void finish(IBinder token) { + synchronized (this) { + if (mImpl == null) { + Slog.w(TAG, "finish without running voice interaction service"); + return; + } + final int callingPid = Binder.getCallingPid(); + final int callingUid = Binder.getCallingUid(); + final long caller = Binder.clearCallingIdentity(); + try { + mImpl.finishLocked(callingPid, callingUid, token); + } finally { + Binder.restoreCallingIdentity(caller); + } + } } @Override @@ -207,7 +259,7 @@ public class VoiceInteractionManagerService extends SystemService { @Override public void onChange(boolean selfChange) { synchronized (VoiceInteractionManagerServiceStub.this) { - switchImplementationIfNeededLocked(); + switchImplementationIfNeededLocked(false); } } } @@ -220,27 +272,25 @@ public class VoiceInteractionManagerService extends SystemService { @Override public void onHandleUserStop(Intent intent, int userHandle) { - super.onHandleUserStop(intent, userHandle); } @Override public void onPackageDisappeared(String packageName, int reason) { - super.onPackageDisappeared(packageName, reason); } @Override public void onPackageAppeared(String packageName, int reason) { - super.onPackageAppeared(packageName, reason); + if (mImpl != null && packageName.equals(mImpl.mComponent.getPackageName())) { + switchImplementationIfNeededLocked(true); + } } @Override public void onPackageModified(String packageName) { - super.onPackageModified(packageName); } @Override public void onSomePackagesChanged() { - super.onSomePackagesChanged(); } }; } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java index 6bbd1c1..9b6daad 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java @@ -19,9 +19,11 @@ package com.android.server.voiceinteraction; import android.app.ActivityManager; import android.app.ActivityManagerNative; import android.app.IActivityManager; +import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; @@ -30,6 +32,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; +import android.os.ServiceManager; import android.os.UserHandle; import android.service.voice.IVoiceInteractionService; import android.service.voice.IVoiceInteractionSession; @@ -37,6 +40,8 @@ import android.service.voice.IVoiceInteractionSessionService; import android.service.voice.VoiceInteractionService; import android.service.voice.VoiceInteractionServiceInfo; import android.util.Slog; +import android.view.IWindowManager; +import android.view.WindowManager; import com.android.internal.app.IVoiceInteractor; import java.io.FileDescriptor; @@ -55,11 +60,28 @@ class VoiceInteractionManagerServiceImpl { final IActivityManager mAm; final VoiceInteractionServiceInfo mInfo; final ComponentName mSessionComponentName; + final IWindowManager mIWindowManager; boolean mBound = false; IVoiceInteractionService mService; SessionConnection mActiveSession; + final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) { + synchronized (mLock) { + if (mActiveSession != null && mActiveSession.mSession != null) { + try { + mActiveSession.mSession.closeSystemDialogs(); + } catch (RemoteException e) { + } + } + } + } + } + }; + final ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { @@ -76,23 +98,26 @@ class VoiceInteractionManagerServiceImpl { final class SessionConnection implements ServiceConnection { final IBinder mToken = new Binder(); - final Intent mIntent; - final String mResolvedType; final Bundle mArgs; boolean mBound; IVoiceInteractionSessionService mService; IVoiceInteractionSession mSession; IVoiceInteractor mInteractor; - SessionConnection(Intent intent, String resolvedType, Bundle args) { - mIntent = intent; - mResolvedType = resolvedType; + SessionConnection(Bundle args) { mArgs = args; Intent serviceIntent = new Intent(VoiceInteractionService.SERVICE_INTERFACE); serviceIntent.setComponent(mSessionComponentName); mBound = mContext.bindServiceAsUser(serviceIntent, this, Context.BIND_AUTO_CREATE, new UserHandle(mUser)); - if (!mBound) { + if (mBound) { + try { + mIWindowManager.addWindowToken(mToken, + WindowManager.LayoutParams.TYPE_INPUT_METHOD); + } catch (RemoteException e) { + Slog.w(TAG, "Failed adding window token", e); + } + } else { Slog.w(TAG, "Failed binding to voice interaction session service " + mComponent); } } @@ -105,7 +130,7 @@ class VoiceInteractionManagerServiceImpl { try { mService.newSession(mToken, mArgs); } catch (RemoteException e) { - Slog.w(TAG, "Failed making new session", e); + Slog.w(TAG, "Failed adding window token", e); } } } @@ -118,7 +143,19 @@ class VoiceInteractionManagerServiceImpl { public void cancel() { if (mBound) { + if (mSession != null) { + try { + mSession.destroy(); + } catch (RemoteException e) { + Slog.w(TAG, "Voice interation session already dead"); + } + } mContext.unbindService(this); + try { + mIWindowManager.removeWindowToken(mToken); + } catch (RemoteException e) { + Slog.w(TAG, "Failed removing window token", e); + } mBound = false; mService = null; mSession = null; @@ -128,8 +165,6 @@ class VoiceInteractionManagerServiceImpl { public void dump(String prefix, PrintWriter pw) { pw.print(prefix); pw.print("mToken="); pw.println(mToken); - pw.print(prefix); pw.print("mIntent="); pw.println(mIntent); - pw.print(" mResolvedType="); pw.println(mResolvedType); pw.print(prefix); pw.print("mArgs="); pw.println(mArgs); pw.print(prefix); pw.print("mBound="); pw.println(mBound); if (mBound) { @@ -155,6 +190,7 @@ class VoiceInteractionManagerServiceImpl { Slog.w(TAG, "Voice interaction service not found: " + service); mInfo = null; mSessionComponentName = null; + mIWindowManager = null; mValid = false; return; } @@ -162,43 +198,67 @@ class VoiceInteractionManagerServiceImpl { if (mInfo.getParseError() != null) { Slog.w(TAG, "Bad voice interaction service: " + mInfo.getParseError()); mSessionComponentName = null; + mIWindowManager = null; mValid = false; return; } mValid = true; mSessionComponentName = new ComponentName(service.getPackageName(), mInfo.getSessionService()); + mIWindowManager = IWindowManager.Stub.asInterface( + ServiceManager.getService(Context.WINDOW_SERVICE)); + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); + mContext.registerReceiver(mBroadcastReceiver, filter, null, handler); } - public void startVoiceActivityLocked(int callingPid, int callingUid, Intent intent, - String resolvedType, Bundle args) { + public void startSessionLocked(int callingPid, int callingUid, Bundle args) { if (mActiveSession != null) { mActiveSession.cancel(); mActiveSession = null; } - mActiveSession = new SessionConnection(intent, resolvedType, args); - intent.addCategory(Intent.CATEGORY_VOICE); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK); + mActiveSession = new SessionConnection(args); } - public int deliverNewSessionLocked(int callingPid, int callingUid, IBinder token, + public boolean deliverNewSessionLocked(int callingPid, int callingUid, IBinder token, IVoiceInteractionSession session, IVoiceInteractor interactor) { + if (mActiveSession == null || token != mActiveSession.mToken) { + Slog.w(TAG, "deliverNewSession does not match active session"); + return false; + } + mActiveSession.mSession = session; + mActiveSession.mInteractor = interactor; + return true; + } + + public int startVoiceActivityLocked(int callingPid, int callingUid, IBinder token, + Intent intent, String resolvedType) { try { if (mActiveSession == null || token != mActiveSession.mToken) { - Slog.w(TAG, "deliverNewSession does not match active session"); + Slog.w(TAG, "startVoiceActivity does not match active session"); return ActivityManager.START_CANCELED; } - mActiveSession.mSession = session; - mActiveSession.mInteractor = interactor; + intent = new Intent(intent); + intent.addCategory(Intent.CATEGORY_VOICE); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK); return mAm.startVoiceActivity(mComponent.getPackageName(), callingPid, callingUid, - mActiveSession.mIntent, mActiveSession.mResolvedType, - mActiveSession.mSession, mActiveSession.mInteractor, + intent, resolvedType, mActiveSession.mSession, mActiveSession.mInteractor, 0, null, null, null, mUser); } catch (RemoteException e) { throw new IllegalStateException("Unexpected remote error", e); } } + + public void finishLocked(int callingPid, int callingUid, IBinder token) { + if (mActiveSession == null || token != mActiveSession.mToken) { + Slog.w(TAG, "finish does not match active session"); + return; + } + mActiveSession.cancel(); + mActiveSession = null; + } + public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args) { if (!mValid) { pw.print(" NOT VALID: "); @@ -234,5 +294,8 @@ class VoiceInteractionManagerServiceImpl { mContext.unbindService(mConnection); mBound = false; } + if (mValid) { + mContext.unregisterReceiver(mBroadcastReceiver); + } } } diff --git a/tests/VoiceInteraction/res/layout/voice_interaction_session.xml b/tests/VoiceInteraction/res/layout/voice_interaction_session.xml new file mode 100644 index 0000000..9fcbf3e --- /dev/null +++ b/tests/VoiceInteraction/res/layout/voice_interaction_session.xml @@ -0,0 +1,38 @@ +<?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="fill_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:background="#ffffffff" + > + + <TextView android:id="@+id/text" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="32dp" + /> + + <Button android:id="@+id/start" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/start" + /> + +</LinearLayout> + + diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java index 008d97b..d40b05f 100644 --- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java +++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java @@ -17,6 +17,7 @@ package com.android.test.voiceinteraction; import android.content.Intent; +import android.os.Bundle; import android.service.voice.VoiceInteractionService; import android.util.Log; @@ -31,7 +32,9 @@ public class MainInteractionService extends VoiceInteractionService { @Override public int onStartCommand(Intent intent, int flags, int startId) { - startVoiceActivity(new Intent(this, TestInteractionActivity.class), null); + Bundle args = new Bundle(); + args.putParcelable("intent", new Intent(this, TestInteractionActivity.class)); + startSession(args); stopSelf(startId); return START_NOT_STICKY; } diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java index 0fc563b..a3af284 100644 --- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java +++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java @@ -17,18 +17,59 @@ package com.android.test.voiceinteraction; import android.content.Context; +import android.content.Intent; import android.os.Bundle; import android.service.voice.VoiceInteractionSession; import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.TextView; -public class MainInteractionSession extends VoiceInteractionSession { +public class MainInteractionSession extends VoiceInteractionSession + implements View.OnClickListener { static final String TAG = "MainInteractionSession"; - final Bundle mArgs; + Intent mStartIntent; + View mContentView; + TextView mText; + Button mStartButton; - MainInteractionSession(Context context, Bundle args) { + Request mPendingRequest; + boolean mPendingConfirm; + + MainInteractionSession(Context context) { super(context); - mArgs = args; + } + + @Override + public void onCreate(Bundle args) { + super.onCreate(args); + showWindow(); + mStartIntent = args.getParcelable("intent"); + } + + @Override + public View onCreateContentView() { + mContentView = getLayoutInflater().inflate(R.layout.voice_interaction_session, null); + mText = (TextView)mContentView.findViewById(R.id.text); + mStartButton = (Button)mContentView.findViewById(R.id.start); + mStartButton.setOnClickListener(this); + return mContentView; + } + + public void onClick(View v) { + if (mPendingRequest == null) { + mStartButton.setEnabled(false); + startVoiceActivity(mStartIntent); + } else { + if (mPendingConfirm) { + mPendingRequest.sendConfirmResult(true, null); + } else { + mPendingRequest.sendCommandResult(true, null); + } + mPendingRequest = null; + mStartButton.setText("Start"); + } } @Override @@ -38,14 +79,22 @@ public class MainInteractionSession extends VoiceInteractionSession { @Override public void onConfirm(Caller caller, Request request, String prompt, Bundle extras) { - Log.i(TAG, "onConform: prompt=" + prompt + " extras=" + extras); - request.sendConfirmResult(true, null); + Log.i(TAG, "onConfirm: prompt=" + prompt + " extras=" + extras); + mText.setText(prompt); + mStartButton.setEnabled(true); + mStartButton.setText("Confirm"); + mPendingRequest = request; + mPendingConfirm = true; } @Override public void onCommand(Caller caller, Request request, String command, Bundle extras) { Log.i(TAG, "onCommand: command=" + command + " extras=" + extras); - request.sendCommandResult(true, null); + mText.setText("Command: " + command); + mStartButton.setEnabled(true); + mStartButton.setText("Finish Command"); + mPendingRequest = request; + mPendingConfirm = false; } @Override diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSessionService.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSessionService.java index 8864d71..7cf8178 100644 --- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSessionService.java +++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSessionService.java @@ -23,6 +23,6 @@ import android.service.voice.VoiceInteractionSessionService; public class MainInteractionSessionService extends VoiceInteractionSessionService { @Override public VoiceInteractionSession onNewSession(Bundle args) { - return new MainInteractionSession(this, args); + return new MainInteractionSession(this); } } |