diff options
Diffstat (limited to 'core/java/android')
6 files changed, 451 insertions, 30 deletions
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) { } } |