From ffeecb1bfb9b71f4b62c9ef1fbf7b58a7a63f655 Mon Sep 17 00:00:00 2001 From: Dianne Hackborn Date: Wed, 25 Feb 2015 11:08:11 -0800 Subject: [PATCH] Rework voice interaction session lifecycle. We now have a formal concept of the session being shown and hidden, with it being able to continue running while hidden as long as there is enough RAM. This changes the flow that a VoiceInteractionSession will see: onCreate() is when it is first created, onCreateContentView() is when its UI first needs to be built, onShow() is called each time it needs to be shown and has the arguments given when the show request was made (which has been renamed from startSession to showSession), and then onHide() will be called when the UI is no longer shown. The methods show() and hide() now allow a VoiceInteractionSession subclass to control when it is shown and hidden, working with the shown state being maintained by the system. Change-Id: Ic4a430ec7e8bf76a5441fd0425e2932806170fcc --- api/current.txt | 6 +- api/system-current.txt | 6 +- core/java/android/app/VoiceInteractor.java | 4 +- .../voice/IVoiceInteractionSession.aidl | 2 + .../voice/VoiceInteractionService.java | 15 +- .../voice/VoiceInteractionSession.java | 152 +++++++--- .../app/IVoiceInteractionManagerService.aidl | 4 +- .../VoiceInteractionManagerService.java | 40 ++- .../VoiceInteractionManagerServiceImpl.java | 156 ++-------- .../VoiceInteractionSessionConnection.java | 285 ++++++++++++++++++ tests/VoiceInteraction/AndroidManifest.xml | 3 +- .../res/layout/test_interaction.xml | 7 + tests/VoiceInteraction/res/values/strings.xml | 1 + .../voiceinteraction/AssistProxyActivity.java | 2 +- .../voiceinteraction/AssistVisualizer.java | 5 + .../MainInteractionSession.java | 52 +++- .../TestInteractionActivity.java | 11 +- 17 files changed, 538 insertions(+), 213 deletions(-) create mode 100644 services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java diff --git a/api/current.txt b/api/current.txt index de5b1e6b89f00..00ee7a8dd36f4 100644 --- a/api/current.txt +++ b/api/current.txt @@ -27656,7 +27656,7 @@ package android.service.voice { method public android.os.IBinder onBind(android.content.Intent); method public void onReady(); method public void onShutdown(); - method public void startSession(android.os.Bundle, int); + method public void showSession(android.os.Bundle, int); 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"; field public static final int START_WITH_ASSIST = 1; // 0x1 @@ -27668,6 +27668,7 @@ package android.service.voice { method public void finish(); method public android.view.LayoutInflater getLayoutInflater(); method public android.app.Dialog getWindow(); + method public void hide(); method public void hideWindow(); method public void onAbortVoice(android.service.voice.VoiceInteractionSession.Caller, android.service.voice.VoiceInteractionSession.Request, java.lang.CharSequence, android.os.Bundle); method public void onBackPressed(); @@ -27682,14 +27683,17 @@ package android.service.voice { method public void onDestroy(); method public boolean[] onGetSupportedCommands(android.service.voice.VoiceInteractionSession.Caller, java.lang.String[]); method public void onHandleAssist(android.os.Bundle); + method public void onHide(); 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 onShow(android.os.Bundle, int); 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 show(); method public void showWindow(); method public void startVoiceActivity(android.content.Intent); } diff --git a/api/system-current.txt b/api/system-current.txt index 92f0f4d61a803..626dfd08f9b01 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -29345,7 +29345,7 @@ package android.service.voice { method public android.os.IBinder onBind(android.content.Intent); method public void onReady(); method public void onShutdown(); - method public void startSession(android.os.Bundle, int); + method public void showSession(android.os.Bundle, int); 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"; field public static final int START_WITH_ASSIST = 1; // 0x1 @@ -29357,6 +29357,7 @@ package android.service.voice { method public void finish(); method public android.view.LayoutInflater getLayoutInflater(); method public android.app.Dialog getWindow(); + method public void hide(); method public void hideWindow(); method public void onAbortVoice(android.service.voice.VoiceInteractionSession.Caller, android.service.voice.VoiceInteractionSession.Request, java.lang.CharSequence, android.os.Bundle); method public void onBackPressed(); @@ -29371,14 +29372,17 @@ package android.service.voice { method public void onDestroy(); method public boolean[] onGetSupportedCommands(android.service.voice.VoiceInteractionSession.Caller, java.lang.String[]); method public void onHandleAssist(android.os.Bundle); + method public void onHide(); 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 onShow(android.os.Bundle, int); 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 show(); method public void showWindow(); method public void startVoiceActivity(android.content.Intent); } diff --git a/core/java/android/app/VoiceInteractor.java b/core/java/android/app/VoiceInteractor.java index 7f9693fbe62ba..7b84cb4c82ff5 100644 --- a/core/java/android/app/VoiceInteractor.java +++ b/core/java/android/app/VoiceInteractor.java @@ -156,8 +156,8 @@ public class VoiceInteractor { @Override public void deliverCancel(IVoiceInteractorRequest request) throws RemoteException { - mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO( - MSG_CANCEL_RESULT, request)); + mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOO( + MSG_CANCEL_RESULT, request, null)); } }; diff --git a/core/java/android/service/voice/IVoiceInteractionSession.aidl b/core/java/android/service/voice/IVoiceInteractionSession.aidl index a8c0c4c04559e..797457a305361 100644 --- a/core/java/android/service/voice/IVoiceInteractionSession.aidl +++ b/core/java/android/service/voice/IVoiceInteractionSession.aidl @@ -23,6 +23,8 @@ import android.os.Bundle; * @hide */ oneway interface IVoiceInteractionSession { + void show(in Bundle sessionArgs, int flags); + void hide(); void handleAssist(in Bundle assistData); void taskStarted(in Intent intent, int taskId); void taskFinished(in Intent intent, int taskId); diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java index 54a3a0a65283f..0c01b259a33f8 100644 --- a/core/java/android/service/voice/VoiceInteractionService.java +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -71,7 +71,7 @@ public class VoiceInteractionService extends Service { public static final String SERVICE_META_DATA = "android.voice_interaction"; /** - * Flag for use with {@link #startSession}: request that the session be started with + * Flag for use with {@link #showSession: request that the session be started with * assist data from the currently focused activity. */ public static final int START_WITH_ASSIST = 1<<0; @@ -139,19 +139,24 @@ public class VoiceInteractionService extends Service { } /** - * Initiate the execution of a new {@link android.service.voice.VoiceInteractionSession}. + * Request that the associated {@link android.service.voice.VoiceInteractionSession} be + * shown to the user, starting it if necessary. * @param args Arbitrary arguments that will be propagated to the session. */ - public void startSession(Bundle args, int flags) { + public void showSession(Bundle args, int flags) { if (mSystemService == null) { throw new IllegalStateException("Not available until onReady() is called"); } try { - mSystemService.startSession(mInterface, args, flags); + mSystemService.showSession(mInterface, args, flags); } catch (RemoteException e) { } } + /** @hide */ + public void startSession(Bundle args, int flags) { + showSession(args, flags); + } /** @hide */ public void startSession(Bundle args) { startSession(args, 0); @@ -174,7 +179,7 @@ public class VoiceInteractionService extends Service { /** * Called during service initialization to tell you when the system is ready * to receive interaction from it. You should generally do initialization here - * rather than in {@link #onCreate}. Methods such as {@link #startSession} and + * rather than in {@link #onCreate}. Methods such as {@link #showSession} and * {@link #createAlwaysOnHotwordDetector} * will not be operational until this point. */ diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java index a3a2ca1f47e61..4cf0e4ca87652 100644 --- a/core/java/android/service/voice/VoiceInteractionSession.java +++ b/core/java/android/service/voice/VoiceInteractionSession.java @@ -53,10 +53,9 @@ import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; /** * An active voice interaction session, providing a facility for the implementation - * to interact with the user in the voice interaction layer. This interface is no shown - * by default, but you can request that it be shown with {@link #showWindow()}, which - * will result in a later call to {@link #onCreateContentView()} in which the UI can be - * built + * to interact with the user in the voice interaction layer. The user interface is + * initially shown by default, and can be created be overriding {@link #onCreateContentView()} + * in which the UI can be built. * *

A voice interaction session can be self-contained, ultimately calling {@link #finish} * when done. It can also initiate voice interactions with applications by calling @@ -150,6 +149,17 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { }; final IVoiceInteractionSession mSession = new IVoiceInteractionSession.Stub() { + @Override + public void show(Bundle sessionArgs, int flags) { + mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIO(MSG_SHOW, + flags, sessionArgs)); + } + + @Override + public void hide() { + mHandlerCaller.sendMessage(mHandlerCaller.obtainMessage(MSG_HIDE)); + } + @Override public void handleAssist(Bundle assistBundle) { mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(MSG_HANDLE_ASSIST, @@ -284,6 +294,8 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { static final int MSG_CLOSE_SYSTEM_DIALOGS = 102; static final int MSG_DESTROY = 103; static final int MSG_HANDLE_ASSIST = 104; + static final int MSG_SHOW = 105; + static final int MSG_HIDE = 106; class MyCallbacks implements HandlerCaller.Callback, SoftInputWindow.Callback { @Override @@ -324,9 +336,8 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { 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); + if (DEBUG) Log.d(TAG, "onCancel: req=" + ((Request)msg.obj)); + onCancel((Request)msg.obj); break; case MSG_TASK_STARTED: if (DEBUG) Log.d(TAG, "onTaskStarted: intent=" + msg.obj @@ -350,6 +361,15 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { if (DEBUG) Log.d(TAG, "onHandleAssist: " + (Bundle)msg.obj); onHandleAssist((Bundle) msg.obj); break; + case MSG_SHOW: + if (DEBUG) Log.d(TAG, "doShow: args=" + msg.obj + + " flags=" + msg.arg1); + doShow((Bundle) msg.obj, msg.arg1); + break; + case MSG_HIDE: + if (DEBUG) Log.d(TAG, "doHide"); + doHide(); + break; } } @@ -457,6 +477,45 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { onCreate(args, startFlags); } + void doShow(Bundle args, int flags) { + 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) { + if (!mWindowAdded) { + mWindowAdded = true; + View v = onCreateContentView(); + if (v != null) { + setContentView(v); + } + } + } + onShow(args, flags); + if (!mWindowVisible) { + mWindowVisible = true; + mWindow.show(); + } + } finally { + mWindowWasVisible = true; + mInShowWindow = false; + } + } + + void doHide() { + if (mWindowVisible) { + mWindow.hide(); + mWindowVisible = false; + onHide(); + } + } + void doDestroy() { onDestroy(); if (mInitialized) { @@ -484,41 +543,28 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { 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; - } - + public void show() { 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; + mSystemService.showSessionFromSession(mToken, null, 0); + } catch (RemoteException e) { } } - public void hideWindow() { - if (mWindowVisible) { - mWindow.hide(); - mWindowVisible = false; + public void hide() { + try { + mSystemService.hideSessionFromSession(mToken); + } catch (RemoteException e) { } } + /** TODO: remove */ + public void showWindow() { + } + + /** TODO: remove */ + public void hideWindow() { + } + /** * You can call this to customize the theme used by your IME's window. * This must be set before {@link #onCreate}, so you @@ -611,15 +657,33 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { } /** - * Initiatize a new session. + * Initiatize a new session. The given args and showFlags are the initial values + * passed to {@link VoiceInteractionService#showSession VoiceInteractionService.showSession}, + * if possible. Normally you should handle these in {@link #onShow}. + */ + public void onCreate(Bundle args, int showFlags) { + onCreate(args); + } + + /** + * Called when the session UI is going to be shown. This is called after + * {@link #onCreateContentView} (if the session's content UI needed to be created) and + * immediately prior to the window being shown. This may be called while the window + * is already shown, if a show request has come in while it is shown, to allow you to + * update the UI to match the new show arguments. * * @param args The arguments that were supplied to - * {@link VoiceInteractionService#startSession VoiceInteractionService.startSession}. - * @param startFlags The start flags originally provided to - * {@link VoiceInteractionService#startSession VoiceInteractionService.startSession}. + * {@link VoiceInteractionService#showSession VoiceInteractionService.showSession}. + * @param showFlags The show flags originally provided to + * {@link VoiceInteractionService#showSession VoiceInteractionService.showSession}. */ - public void onCreate(Bundle args, int startFlags) { - onCreate(args); + public void onShow(Bundle args, int showFlags) { + } + + /** + * Called immediately after stopping to show the session UI. + */ + public void onHide() { } /** @@ -663,7 +727,7 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { } public void onBackPressed() { - finish(); + hide(); } /** @@ -672,7 +736,7 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { * calls {@link #finish}. */ public void onCloseSystemDialogs() { - finish(); + hide(); } /** @@ -717,7 +781,7 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { * @param taskId Unique ID of the finished task. */ public void onTaskFinished(Intent intent, int taskId) { - finish(); + hide(); } /** diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl index 6d904207d1b4e..8f549a6b5fc08 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl @@ -26,9 +26,11 @@ import android.service.voice.IVoiceInteractionService; import android.service.voice.IVoiceInteractionSession; interface IVoiceInteractionManagerService { - void startSession(IVoiceInteractionService service, in Bundle sessionArgs, int flags); + void showSession(IVoiceInteractionService service, in Bundle sessionArgs, int flags); boolean deliverNewSession(IBinder token, IVoiceInteractionSession session, IVoiceInteractor interactor); + boolean showSessionFromSession(IBinder token, in Bundle sessionArgs, int flags); + boolean hideSessionFromSession(IBinder token); int startVoiceActivity(IBinder token, in Intent intent, String resolvedType); void finish(IBinder token); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index fd990d798ea78..6b8c49cc82fc6 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -385,7 +385,7 @@ public class VoiceInteractionManagerService extends SystemService { } @Override - public void startSession(IVoiceInteractionService service, Bundle args, int flags) { + public void showSession(IVoiceInteractionService service, Bundle args, int flags) { synchronized (this) { if (mImpl == null || mImpl.mService == null || service.asBinder() != mImpl.mService.asBinder()) { @@ -396,7 +396,7 @@ public class VoiceInteractionManagerService extends SystemService { final int callingUid = Binder.getCallingUid(); final long caller = Binder.clearCallingIdentity(); try { - mImpl.startSessionLocked(callingPid, callingUid, args, flags); + mImpl.showSessionLocked(callingPid, callingUid, args, flags); } finally { Binder.restoreCallingIdentity(caller); } @@ -423,6 +423,42 @@ public class VoiceInteractionManagerService extends SystemService { } } + @Override + public boolean showSessionFromSession(IBinder token, Bundle sessionArgs, int flags) { + synchronized (this) { + if (mImpl == null) { + Slog.w(TAG, "showSessionFromSession without running voice interaction service"); + return false; + } + final int callingPid = Binder.getCallingPid(); + final int callingUid = Binder.getCallingUid(); + final long caller = Binder.clearCallingIdentity(); + try { + return mImpl.showSessionLocked(callingPid, callingUid, sessionArgs, flags); + } finally { + Binder.restoreCallingIdentity(caller); + } + } + } + + @Override + public boolean hideSessionFromSession(IBinder token) { + synchronized (this) { + if (mImpl == null) { + Slog.w(TAG, "hideSessionFromSession without running voice interaction service"); + return false; + } + final int callingPid = Binder.getCallingPid(); + final int callingUid = Binder.getCallingUid(); + final long caller = Binder.clearCallingIdentity(); + try { + return mImpl.hideSessionLocked(callingPid, callingUid); + } finally { + Binder.restoreCallingIdentity(caller); + } + } + } + @Override public int startVoiceActivity(IBinder token, Intent intent, String resolvedType) { synchronized (this) { diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java index e80f702d6b770..9e92867349ce1 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java @@ -26,7 +26,6 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.pm.PackageManager; -import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -35,20 +34,17 @@ import android.os.ServiceManager; import android.os.UserHandle; import android.service.voice.IVoiceInteractionService; import android.service.voice.IVoiceInteractionSession; -import android.service.voice.IVoiceInteractionSessionService; import android.service.voice.VoiceInteractionService; import android.service.voice.VoiceInteractionServiceInfo; import android.util.Slog; import android.view.IWindowManager; -import android.view.WindowManager; import com.android.internal.app.IVoiceInteractor; -import com.android.internal.os.IResultReceiver; import java.io.FileDescriptor; import java.io.PrintWriter; -class VoiceInteractionManagerServiceImpl { +class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConnection.Callback { final static String TAG = "VoiceInteractionServiceManager"; final boolean mValid; @@ -65,7 +61,7 @@ class VoiceInteractionManagerServiceImpl { boolean mBound = false; IVoiceInteractionService mService; - SessionConnection mActiveSession; + VoiceInteractionSessionConnection mActiveSession; final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override @@ -101,124 +97,6 @@ class VoiceInteractionManagerServiceImpl { } }; - final class SessionConnection implements ServiceConnection { - final IBinder mToken = new Binder(); - final Bundle mArgs; - final int mFlags; - boolean mBound; - IVoiceInteractionSessionService mService; - IVoiceInteractionSession mSession; - IVoiceInteractor mInteractor; - boolean mHaveAssistData; - Bundle mAssistData; - - final IResultReceiver mAssistReceiver = new IResultReceiver.Stub() { - @Override - public void send(int resultCode, Bundle resultData) throws RemoteException { - synchronized (mLock) { - mHaveAssistData = true; - mAssistData = resultData; - if (mSession != null) { - try { - mSession.handleAssist(resultData); - } catch (RemoteException e) { - } - } - } - } - }; - - SessionConnection(Bundle args, int flags) { - mArgs = args; - mFlags = flags; - Intent serviceIntent = new Intent(VoiceInteractionService.SERVICE_INTERFACE); - serviceIntent.setComponent(mSessionComponentName); - mBound = mContext.bindServiceAsUser(serviceIntent, this, - Context.BIND_AUTO_CREATE, new UserHandle(mUser)); - if (mBound) { - try { - mIWindowManager.addWindowToken(mToken, - WindowManager.LayoutParams.TYPE_VOICE_INTERACTION); - } catch (RemoteException e) { - Slog.w(TAG, "Failed adding window token", e); - } - if ((flags&VoiceInteractionService.START_WITH_ASSIST) != 0) { - try { - mAm.requestAssistContextExtras(0, mAssistReceiver); - } catch (RemoteException e) { - } - } else { - mHaveAssistData = true; - } - } else { - Slog.w(TAG, "Failed binding to voice interaction session service " + mComponent); - } - } - - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - synchronized (mLock) { - mService = IVoiceInteractionSessionService.Stub.asInterface(service); - if (mActiveSession == this) { - try { - mService.newSession(mToken, mArgs, mFlags); - } catch (RemoteException e) { - Slog.w(TAG, "Failed adding window token", e); - } - } - } - } - - @Override - public void onServiceDisconnected(ComponentName name) { - mService = null; - } - - public void cancel() { - if (mBound) { - if (mSession != null) { - try { - mSession.destroy(); - } catch (RemoteException e) { - Slog.w(TAG, "Voice interation session already dead"); - } - } - if (mSession != null) { - try { - mAm.finishVoiceTask(mSession); - } catch (RemoteException e) { - } - } - mContext.unbindService(this); - try { - mIWindowManager.removeWindowToken(mToken); - } catch (RemoteException e) { - Slog.w(TAG, "Failed removing window token", e); - } - mBound = false; - mService = null; - mSession = null; - mInteractor = null; - } - } - - public void dump(String prefix, PrintWriter pw) { - pw.print(prefix); pw.print("mToken="); pw.println(mToken); - pw.print(prefix); pw.print("mArgs="); pw.println(mArgs); - pw.print(prefix); pw.print("mFlags=0x"); pw.println(Integer.toHexString(mFlags)); - pw.print(prefix); pw.print("mBound="); pw.println(mBound); - if (mBound) { - pw.print(prefix); pw.print("mService="); pw.println(mService); - pw.print(prefix); pw.print("mSession="); pw.println(mSession); - pw.print(prefix); pw.print("mInteractor="); pw.println(mInteractor); - } - pw.print(prefix); pw.print("mHaveAssistData="); pw.println(mHaveAssistData); - if (mHaveAssistData) { - pw.print(prefix); pw.print("mAssistData="); pw.println(mAssistData); - } - } - }; - VoiceInteractionManagerServiceImpl(Context context, Handler handler, Object lock, int userHandle, ComponentName service) { mContext = context; @@ -256,12 +134,16 @@ class VoiceInteractionManagerServiceImpl { mContext.registerReceiver(mBroadcastReceiver, filter, null, handler); } - public void startSessionLocked(int callingPid, int callingUid, Bundle args, int flags) { - if (mActiveSession != null) { - mActiveSession.cancel(); - mActiveSession = null; + public boolean showSessionLocked(int callingPid, int callingUid, Bundle args, int flags) { + if (mActiveSession == null) { + mActiveSession = new VoiceInteractionSessionConnection(mLock, mSessionComponentName, + mUser, mContext, this, callingPid, callingUid); } - mActiveSession = new SessionConnection(args, flags); + return mActiveSession.showLocked(args, flags); + } + + public boolean hideSessionLocked(int callingPid, int callingUid) { + return mActiveSession.hideLocked(); } public boolean deliverNewSessionLocked(int callingPid, int callingUid, IBinder token, @@ -270,14 +152,7 @@ class VoiceInteractionManagerServiceImpl { Slog.w(TAG, "deliverNewSession does not match active session"); return false; } - mActiveSession.mSession = session; - mActiveSession.mInteractor = interactor; - if (mActiveSession.mHaveAssistData) { - try { - session.handleAssist(mActiveSession.mAssistData); - } catch (RemoteException e) { - } - } + mActiveSession.deliverNewSessionLocked(session, interactor); return true; } @@ -367,4 +242,11 @@ class VoiceInteractionManagerServiceImpl { Slog.w(TAG, "RemoteException while calling soundModelsChanged", e); } } + + @Override + public void sessionConnectionGone(VoiceInteractionSessionConnection connection) { + synchronized (mLock) { + finishLocked(connection.mCallingPid, connection.mCallingUid, connection.mToken); + } + } } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java new file mode 100644 index 0000000000000..e2b47c38a462c --- /dev/null +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2015 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.server.voiceinteraction; + +import android.app.ActivityManagerNative; +import android.app.IActivityManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Binder; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.UserHandle; +import android.service.voice.IVoiceInteractionSession; +import android.service.voice.IVoiceInteractionSessionService; +import android.service.voice.VoiceInteractionService; +import android.util.Slog; +import android.view.IWindowManager; +import android.view.WindowManager; +import com.android.internal.app.IVoiceInteractor; +import com.android.internal.os.IResultReceiver; + +import java.io.PrintWriter; + +final class VoiceInteractionSessionConnection implements ServiceConnection { + final static String TAG = "VoiceInteractionServiceManager"; + + final IBinder mToken = new Binder(); + final Object mLock; + final ComponentName mSessionComponentName; + final Intent mBindIntent; + final int mUser; + final Context mContext; + final Callback mCallback; + final int mCallingPid; + final int mCallingUid; + final IActivityManager mAm; + final IWindowManager mIWindowManager; + boolean mShown; + Bundle mShowArgs; + int mShowFlags; + boolean mBound; + boolean mFullyBound; + boolean mCanceled; + IVoiceInteractionSessionService mService; + IVoiceInteractionSession mSession; + IVoiceInteractor mInteractor; + boolean mHaveAssistData; + Bundle mAssistData; + + public interface Callback { + public void sessionConnectionGone(VoiceInteractionSessionConnection connection); + } + + final ServiceConnection mFullConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + } + @Override + public void onServiceDisconnected(ComponentName name) { + } + }; + + final IResultReceiver mAssistReceiver = new IResultReceiver.Stub() { + @Override + public void send(int resultCode, Bundle resultData) throws RemoteException { + synchronized (mLock) { + if (mShown) { + if (mSession != null) { + try { + mSession.handleAssist(resultData); + } catch (RemoteException e) { + } + } else { + mHaveAssistData = true; + mAssistData = resultData; + } + } + } + } + }; + + public VoiceInteractionSessionConnection(Object lock, ComponentName component, int user, + Context context, Callback callback, int callingPid, int callingUid) { + mLock = lock; + mSessionComponentName = component; + mUser = user; + mContext = context; + mCallback = callback; + mCallingPid = callingPid; + mCallingUid = callingUid; + mAm = ActivityManagerNative.getDefault(); + mIWindowManager = IWindowManager.Stub.asInterface( + ServiceManager.getService(Context.WINDOW_SERVICE)); + mBindIntent = new Intent(VoiceInteractionService.SERVICE_INTERFACE); + mBindIntent.setComponent(mSessionComponentName); + mBound = mContext.bindServiceAsUser(mBindIntent, this, + Context.BIND_AUTO_CREATE|Context.BIND_ALLOW_OOM_MANAGEMENT, new UserHandle(mUser)); + if (mBound) { + try { + mIWindowManager.addWindowToken(mToken, + WindowManager.LayoutParams.TYPE_VOICE_INTERACTION); + } catch (RemoteException e) { + Slog.w(TAG, "Failed adding window token", e); + } + } else { + Slog.w(TAG, "Failed binding to voice interaction session service " + + mSessionComponentName); + } + } + + public boolean showLocked(Bundle args, int flags) { + if (mBound) { + if (!mFullyBound) { + mFullyBound = mContext.bindServiceAsUser(mBindIntent, mFullConnection, + Context.BIND_AUTO_CREATE|Context.BIND_TREAT_LIKE_ACTIVITY, + new UserHandle(mUser)); + } + mShown = true; + mShowArgs = args; + mShowFlags = flags; + if ((flags&VoiceInteractionService.START_WITH_ASSIST) != 0) { + try { + mAm.requestAssistContextExtras(0, mAssistReceiver); + } catch (RemoteException e) { + } + } else { + mHaveAssistData = false; + mAssistData = null; + } + if (mSession != null) { + try { + mSession.show(mShowArgs, mShowFlags); + mShowArgs = null; + mShowFlags = 0; + } catch (RemoteException e) { + } + if (mHaveAssistData) { + try { + mSession.handleAssist(mAssistData); + mAssistData = null; + mHaveAssistData = false; + } catch (RemoteException e) { + } + } + } + return true; + } + return false; + } + + public boolean hideLocked() { + if (mBound) { + if (mShown) { + mShown = false; + mShowArgs = null; + mShowFlags = 0; + mHaveAssistData = false; + mAssistData = null; + if (mSession != null) { + try { + mSession.hide(); + } catch (RemoteException e) { + } + } + } + if (mFullyBound) { + mContext.unbindService(mFullConnection); + mFullyBound = false; + } + return true; + } + return false; + } + + public boolean deliverNewSessionLocked(IVoiceInteractionSession session, + IVoiceInteractor interactor) { + mSession = session; + mInteractor = interactor; + if (mShown) { + try { + session.show(mShowArgs, mShowFlags); + mShowArgs = null; + mShowFlags = 0; + } catch (RemoteException e) { + } + if (mHaveAssistData) { + try { + session.handleAssist(mAssistData); + mAssistData = null; + mHaveAssistData = false; + } catch (RemoteException e) { + } + } + } + return true; + } + + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + synchronized (mLock) { + mService = IVoiceInteractionSessionService.Stub.asInterface(service); + if (!mCanceled) { + try { + mService.newSession(mToken, mShowArgs, mShowFlags); + } catch (RemoteException e) { + Slog.w(TAG, "Failed adding window token", e); + } + } + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + mCallback.sessionConnectionGone(this); + mService = null; + } + + public void cancel() { + mCanceled = true; + if (mBound) { + if (mSession != null) { + try { + mSession.destroy(); + } catch (RemoteException e) { + Slog.w(TAG, "Voice interation session already dead"); + } + } + if (mSession != null) { + try { + mAm.finishVoiceTask(mSession); + } catch (RemoteException e) { + } + } + mContext.unbindService(this); + try { + mIWindowManager.removeWindowToken(mToken); + } catch (RemoteException e) { + Slog.w(TAG, "Failed removing window token", e); + } + mBound = false; + mService = null; + mSession = null; + mInteractor = null; + } + if (mFullyBound) { + mContext.unbindService(mFullConnection); + mFullyBound = false; + } + } + + public void dump(String prefix, PrintWriter pw) { + pw.print(prefix); pw.print("mToken="); pw.println(mToken); + pw.print(prefix); pw.print("mShown="); pw.println(mShown); + pw.print(prefix); pw.print("mShowArgs="); pw.println(mShowArgs); + pw.print(prefix); pw.print("mShowFlags=0x"); pw.println(Integer.toHexString(mShowFlags)); + pw.print(prefix); pw.print("mBound="); pw.println(mBound); + if (mBound) { + pw.print(prefix); pw.print("mService="); pw.println(mService); + pw.print(prefix); pw.print("mSession="); pw.println(mSession); + pw.print(prefix); pw.print("mInteractor="); pw.println(mInteractor); + } + pw.print(prefix); pw.print("mHaveAssistData="); pw.println(mHaveAssistData); + if (mHaveAssistData) { + pw.print(prefix); pw.print("mAssistData="); pw.println(mAssistData); + } + } +}; diff --git a/tests/VoiceInteraction/AndroidManifest.xml b/tests/VoiceInteraction/AndroidManifest.xml index adf572c8f2396..36d5d984ba42f 100644 --- a/tests/VoiceInteraction/AndroidManifest.xml +++ b/tests/VoiceInteraction/AndroidManifest.xml @@ -16,7 +16,8 @@ android:label="Test Assist Proxy" android:theme="@android:style/Theme.NoDisplay" android:excludeFromRecents="true" - android:noHistory="true"> + android:noHistory="true" + android:taskAffinity=""> diff --git a/tests/VoiceInteraction/res/layout/test_interaction.xml b/tests/VoiceInteraction/res/layout/test_interaction.xml index c4e280e3ba9e5..f4648b57dfabe 100644 --- a/tests/VoiceInteraction/res/layout/test_interaction.xml +++ b/tests/VoiceInteraction/res/layout/test_interaction.xml @@ -48,4 +48,11 @@ android:text="@string/abortVoice" /> +