summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDianne Hackborn <hackbod@google.com>2014-05-02 10:45:59 -0700
committerDianne Hackborn <hackbod@google.com>2014-05-05 11:18:08 -0700
commitc03c9167c2d9a1e22fb2b176b00a0524177fb037 (patch)
treebc8045725f3384dff1c53a508041fa63f78e5ce8
parent6b003c9e3c0d3e5e31d9578e6d15facc6553e45e (diff)
downloadframeworks_base-c03c9167c2d9a1e22fb2b176b00a0524177fb037.zip
frameworks_base-c03c9167c2d9a1e22fb2b176b00a0524177fb037.tar.gz
frameworks_base-c03c9167c2d9a1e22fb2b176b00a0524177fb037.tar.bz2
Further work on voice interaction services.
This makes VoiceInteractionSession a more first-class concept. Now the flow is that a VoiceInteractionService calls startSession() when it wants to begin a session. This will result in a new VoiceInteractionSession via the VoiceInteractionSessionService containing it, and the session at that point an decide what to do. It can now show UI, and it is what has access to the startVoiceActivity API. Change-Id: Ie2b85b3020ef1206d3f44b335b128d064e8f9935
-rw-r--r--api/current.txt34
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java3
-rw-r--r--core/java/android/inputmethodservice/SoftInputWindow.java78
-rw-r--r--core/java/android/service/voice/IVoiceInteractionSession.aidl11
-rw-r--r--core/java/android/service/voice/VoiceInteractionService.java19
-rw-r--r--core/java/android/service/voice/VoiceInteractionSession.java357
-rw-r--r--core/java/android/service/voice/VoiceInteractionSessionService.java13
-rw-r--r--core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl7
-rw-r--r--core/res/res/layout/voice_interaction_session.xml25
-rw-r--r--core/res/res/values/attrs.xml3
-rw-r--r--core/res/res/values/styles.xml6
-rw-r--r--core/res/res/values/symbols.xml2
-rw-r--r--core/res/res/values/themes.xml8
-rw-r--r--core/res/res/values/themes_device_defaults.xml5
-rw-r--r--core/res/res/values/themes_quantum.xml8
-rwxr-xr-xservices/core/java/com/android/server/am/ActivityRecord.java2
-rwxr-xr-xservices/core/java/com/android/server/am/ActivityStack.java20
-rw-r--r--services/core/java/com/android/server/am/ActivityStackSupervisor.java4
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java86
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java103
-rw-r--r--tests/VoiceInteraction/res/layout/voice_interaction_session.xml38
-rw-r--r--tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java5
-rw-r--r--tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java63
-rw-r--r--tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSessionService.java2
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);
}
}