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
This commit is contained in:
Dianne Hackborn
2015-02-25 11:08:11 -08:00
parent 6e53931f49
commit ffeecb1bfb
17 changed files with 538 additions and 213 deletions

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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));
}
};

View File

@ -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);

View File

@ -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.
*/

View File

@ -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.
*
* <p>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();
}
/**

View File

@ -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);

View File

@ -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) {

View File

@ -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);
}
}
}

View File

@ -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);
}
}
};

View File

@ -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="">
<intent-filter>
<action android:name="android.intent.action.ASSIST" />
<category android:name="android.intent.category.DEFAULT" />

View File

@ -48,4 +48,11 @@
android:text="@string/abortVoice"
/>
<Button android:id="@+id/cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/cancelVoice"
/>
</LinearLayout>

View File

@ -22,6 +22,7 @@
<string name="complete">Complete</string>
<string name="abortVoice">Abort Voice</string>
<string name="completeVoice">Complete Voice</string>
<string name="cancelVoice">Cancel</string>
</resources>

View File

@ -29,6 +29,6 @@ public class AssistProxyActivity extends Activity {
Intent intent = new Intent(this, MainInteractionService.class);
intent.setAction(Intent.ACTION_ASSIST);
intent.putExtras(getIntent());
startService(new Intent(this, MainInteractionService.class));
startService(intent);
}
}

View File

@ -57,6 +57,11 @@ public class AssistVisualizer extends View {
}
}
public void clearAssistData() {
mAssistData = null;
mTextRects.clear();
}
void buildTextRects(AssistData.ViewNode root, int parentLeft, int parentTop) {
if (root.getVisibility() != View.VISIBLE) {
return;

View File

@ -49,6 +49,7 @@ public class MainInteractionSession extends VoiceInteractionSession
static final int STATE_COMMAND = 3;
static final int STATE_ABORT_VOICE = 4;
static final int STATE_COMPLETE_VOICE = 5;
static final int STATE_DONE=6;
int mState = STATE_IDLE;
Request mPendingRequest;
@ -60,12 +61,26 @@ public class MainInteractionSession extends VoiceInteractionSession
@Override
public void onCreate(Bundle args, int startFlags) {
super.onCreate(args);
showWindow();
}
@Override
public void onShow(Bundle args, int showFlags) {
super.onShow(args, showFlags);
mState = STATE_IDLE;
mStartIntent = args.getParcelable("intent");
Bundle assist = args.getBundle("assist");
if (assist != null) {
parseAssistData(assist);
parseAssistData(assist);
updateState();
}
@Override
public void onHide() {
super.onHide();
if (mAssistVisualizer != null) {
mAssistVisualizer.clearAssistData();
}
mState = STATE_DONE;
updateState();
}
@Override
@ -86,7 +101,6 @@ public class MainInteractionSession extends VoiceInteractionSession
mCompleteButton.setOnClickListener(this);
mAbortButton = (Button)mContentView.findViewById(R.id.abort);
mAbortButton.setOnClickListener(this);
updateState();
return mContentView;
}
@ -100,23 +114,35 @@ public class MainInteractionSession extends VoiceInteractionSession
}
void parseAssistData(Bundle assistBundle) {
Bundle assistContext = assistBundle.getBundle(Intent.EXTRA_ASSIST_CONTEXT);
if (assistContext != null) {
mAssistData = AssistData.getAssistData(assistContext);
mAssistData.dump();
if (mAssistVisualizer != null) {
mAssistVisualizer.setAssistData(mAssistData);
if (assistBundle != null) {
Bundle assistContext = assistBundle.getBundle(Intent.EXTRA_ASSIST_CONTEXT);
if (assistContext != null) {
mAssistData = AssistData.getAssistData(assistContext);
mAssistData.dump();
if (mAssistVisualizer != null) {
mAssistVisualizer.setAssistData(mAssistData);
}
return;
}
}
if (mAssistVisualizer != null) {
mAssistVisualizer.clearAssistData();
}
}
void updateState() {
if (mState == STATE_IDLE) {
mTopContent.setVisibility(View.VISIBLE);
mBottomContent.setVisibility(View.GONE);
mAssistVisualizer.setVisibility(View.VISIBLE);
} else if (mState == STATE_DONE) {
mTopContent.setVisibility(View.GONE);
mBottomContent.setVisibility(View.GONE);
mAssistVisualizer.setVisibility(View.GONE);
} else {
mTopContent.setVisibility(View.GONE);
mBottomContent.setVisibility(View.VISIBLE);
mAssistVisualizer.setVisibility(View.GONE);
}
mStartButton.setEnabled(mState == STATE_IDLE);
mConfirmButton.setEnabled(mState == STATE_CONFIRM || mState == STATE_COMMAND);
@ -136,18 +162,12 @@ public class MainInteractionSession extends VoiceInteractionSession
mPendingRequest.sendCommandResult(true, null);
}
mPendingRequest = null;
mState = STATE_IDLE;
updateState();
} else if (v == mAbortButton) {
mPendingRequest.sendAbortVoiceResult(null);
mPendingRequest = null;
mState = STATE_IDLE;
updateState();
} else if (v== mCompleteButton) {
mPendingRequest.sendCompleteVoiceResult(null);
mPendingRequest = null;
mState = STATE_IDLE;
updateState();
}
}

View File

@ -31,8 +31,10 @@ public class TestInteractionActivity extends Activity implements View.OnClickLis
static final String TAG = "TestInteractionActivity";
VoiceInteractor mInteractor;
VoiceInteractor.Request mCurrentRequest = null;
Button mAbortButton;
Button mCompleteButton;
Button mCancelButton;
@Override
public void onCreate(Bundle savedInstanceState) {
@ -56,9 +58,11 @@ public class TestInteractionActivity extends Activity implements View.OnClickLis
mAbortButton.setOnClickListener(this);
mCompleteButton = (Button)findViewById(R.id.complete);
mCompleteButton.setOnClickListener(this);
mCancelButton = (Button)findViewById(R.id.cancel);
mCancelButton.setOnClickListener(this);
mInteractor = getVoiceInteractor();
VoiceInteractor.ConfirmationRequest req = new VoiceInteractor.ConfirmationRequest(
mCurrentRequest = new VoiceInteractor.ConfirmationRequest(
"This is a confirmation", null) {
@Override
public void onCancel() {
@ -72,7 +76,7 @@ public class TestInteractionActivity extends Activity implements View.OnClickLis
getActivity().finish();
}
};
mInteractor.submitRequest(req);
mInteractor.submitRequest(mCurrentRequest);
}
@Override
@ -112,6 +116,9 @@ public class TestInteractionActivity extends Activity implements View.OnClickLis
}
};
mInteractor.submitRequest(req);
} else if (v == mCancelButton && mCurrentRequest != null) {
Log.i(TAG, "Cancel request");
mCurrentRequest.cancel();
}
}