diff options
author | Dianne Hackborn <hackbod@google.com> | 2014-04-04 18:02:06 -0700 |
---|---|---|
committer | Dianne Hackborn <hackbod@google.com> | 2014-04-24 17:48:58 -0700 |
commit | 91097de49b0f683b00e26a75dbc0ac6082344137 (patch) | |
tree | 82c3185634a71233ce2e81a3645b07b1ba55f412 | |
parent | 23af77a3cd1febc740d885ff03ead09837df269c (diff) | |
download | frameworks_base-91097de49b0f683b00e26a75dbc0ac6082344137.zip frameworks_base-91097de49b0f683b00e26a75dbc0ac6082344137.tar.gz frameworks_base-91097de49b0f683b00e26a75dbc0ac6082344137.tar.bz2 |
Initial implementation of new voice interaction API.
This gives a basic working implementation of a persist
running service that can start a voice interaction when
it wants, with the target activity(s) able to go through
the protocol to interact with it. It may even work when
the screen is off by putting the activity manager in the
correct state to act like the screen is on.
Includes a sample app that is a voice interation service
and also has an activity it can launch.
Now that I have this initial implementation, I think I
want to rework some aspects of the API.
Change-Id: I7646d0af8fb4ac768c63a18fe3de43f8091f60e9
52 files changed, 1992 insertions, 69 deletions
@@ -197,6 +197,8 @@ LOCAL_SRC_FILES += \ core/java/android/service/dreams/IDreamService.aidl \ core/java/android/service/trust/ITrustAgentService.aidl \ core/java/android/service/trust/ITrustAgentServiceCallback.aidl \ + core/java/android/service/voice/IVoiceInteractionService.aidl \ + core/java/android/service/voice/IVoiceInteractionSession.aidl \ core/java/android/service/wallpaper/IWallpaperConnection.aidl \ core/java/android/service/wallpaper/IWallpaperEngine.aidl \ core/java/android/service/wallpaper/IWallpaperService.aidl \ @@ -230,6 +232,10 @@ LOCAL_SRC_FILES += \ core/java/com/android/internal/app/IBatteryStats.aidl \ core/java/com/android/internal/app/IProcessStats.aidl \ core/java/com/android/internal/app/IUsageStats.aidl \ + core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl \ + core/java/com/android/internal/app/IVoiceInteractor.aidl \ + core/java/com/android/internal/app/IVoiceInteractorCallback.aidl \ + core/java/com/android/internal/app/IVoiceInteractorRequest.aidl \ core/java/com/android/internal/app/IMediaContainerService.aidl \ core/java/com/android/internal/appwidget/IAppWidgetService.aidl \ core/java/com/android/internal/appwidget/IAppWidgetHost.aidl \ diff --git a/api/current.txt b/api/current.txt index 37d2345..e485d67 100644 --- a/api/current.txt +++ b/api/current.txt @@ -31,6 +31,7 @@ package android { field public static final java.lang.String BIND_TEXT_SERVICE = "android.permission.BIND_TEXT_SERVICE"; field public static final java.lang.String BIND_TRUST_AGENT_SERVICE = "android.permission.BIND_TRUST_AGENT_SERVICE"; field public static final java.lang.String BIND_TV_INPUT = "android.permission.BIND_TV_INPUT"; + field public static final java.lang.String BIND_VOICE_INTERACTION = "android.permission.BIND_VOICE_INTERACTION"; field public static final java.lang.String BIND_VPN_SERVICE = "android.permission.BIND_VPN_SERVICE"; field public static final java.lang.String BIND_WALLPAPER = "android.permission.BIND_WALLPAPER"; field public static final java.lang.String BLUETOOTH = "android.permission.BLUETOOTH"; @@ -3202,6 +3203,7 @@ package android.app { method public int getTaskId(); method public final java.lang.CharSequence getTitle(); method public final int getTitleColor(); + method public android.app.VoiceInteractor getVoiceInteractor(); method public final int getVolumeControlStream(); method public android.view.Window getWindow(); method public android.view.WindowManager getWindowManager(); @@ -3213,6 +3215,7 @@ package android.app { method public boolean isFinishing(); method public boolean isImmersive(); method public boolean isTaskRoot(); + method public boolean isVoiceInteraction(); method public final deprecated android.database.Cursor managedQuery(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String); method public boolean moveTaskToBack(boolean); method public boolean navigateUpTo(android.content.Intent); @@ -4838,6 +4841,23 @@ package android.app { field public static final int MODE_NIGHT_YES = 2; // 0x2 } + public class VoiceInteractor { + method public android.app.VoiceInteractor.Request startCommand(android.app.VoiceInteractor.Callback, java.lang.String, android.os.Bundle); + method public android.app.VoiceInteractor.Request startConfirmation(android.app.VoiceInteractor.Callback, java.lang.String, android.os.Bundle); + method public boolean[] supportsCommands(java.lang.String[]); + } + + public static class VoiceInteractor.Callback { + ctor public VoiceInteractor.Callback(); + method public void onCancel(android.app.VoiceInteractor.Request); + method public void onCommandResult(android.app.VoiceInteractor.Request, android.os.Bundle); + method public void onConfirmationResult(android.app.VoiceInteractor.Request, boolean, android.os.Bundle); + } + + public static class VoiceInteractor.Request { + method public void cancel(); + } + public final class WallpaperInfo implements android.os.Parcelable { ctor public WallpaperInfo(android.content.Context, android.content.pm.ResolveInfo) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; method public int describeContents(); @@ -6997,6 +7017,7 @@ package android.content { field public static final java.lang.String CATEGORY_TAB = "android.intent.category.TAB"; field public static final java.lang.String CATEGORY_TEST = "android.intent.category.TEST"; field public static final java.lang.String CATEGORY_UNIT_TEST = "android.intent.category.UNIT_TEST"; + field public static final java.lang.String CATEGORY_VOICE = "android.intent.category.VOICE"; field public static final android.os.Parcelable.Creator CREATOR; field public static final java.lang.String EXTRA_ALARM_COUNT = "android.intent.extra.ALARM_COUNT"; field public static final java.lang.String EXTRA_ALLOW_MULTIPLE = "android.intent.extra.ALLOW_MULTIPLE"; @@ -24866,6 +24887,36 @@ package android.service.trust { } +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.service.voice.VoiceInteractionSession); + 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 { + ctor public VoiceInteractionSession(android.content.Context); + ctor public VoiceInteractionSession(android.content.Context, android.os.Handler); + method public abstract void onCancel(android.service.voice.VoiceInteractionSession.Request); + method public abstract void onCommand(android.service.voice.VoiceInteractionSession.Caller, android.service.voice.VoiceInteractionSession.Request, java.lang.String, android.os.Bundle); + method public abstract void onConfirm(android.service.voice.VoiceInteractionSession.Caller, android.service.voice.VoiceInteractionSession.Request, java.lang.String, android.os.Bundle); + method public abstract boolean[] onGetSupportedCommands(android.service.voice.VoiceInteractionSession.Caller, java.lang.String[]); + } + + public static class VoiceInteractionSession.Caller { + } + + public static class VoiceInteractionSession.Request { + method public void sendCancelResult(); + method public void sendCommandResult(android.os.Bundle); + method public void sendConfirmResult(boolean, android.os.Bundle); + } + +} + package android.service.wallpaper { public abstract class WallpaperService extends android.app.Service { diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java index 7df55a5..6b55b7b 100644 --- a/cmds/am/src/com/android/commands/am/Am.java +++ b/cmds/am/src/com/android/commands/am/Am.java @@ -749,6 +749,11 @@ public class Am extends BaseCommand { "Error: Activity not started, you do not " + "have permission to access it."); break; + case ActivityManager.START_NOT_VOICE_COMPATIBLE: + out.println( + "Error: Activity not started, voice control not allowed for: " + + intent); + break; default: out.println( "Error: Activity not started, unknown error code " + res); diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 599a608..197eaf8 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -22,6 +22,7 @@ import android.transition.TransitionManager; import android.util.ArrayMap; import android.util.SuperNotCalledException; import android.widget.Toolbar; +import com.android.internal.app.IVoiceInteractor; import com.android.internal.app.WindowDecorActionBar; import com.android.internal.app.ToolbarActionBar; import com.android.internal.policy.PolicyManager; @@ -726,6 +727,8 @@ public class Activity extends ContextThemeWrapper /*package*/ ActionBar mActionBar = null; private boolean mEnableDefaultActionBarUp; + private VoiceInteractor mVoiceInteractor; + private CharSequence mTitle; private int mTitleColor = 0; @@ -1134,6 +1137,23 @@ public class Activity extends ContextThemeWrapper } /** + * Check whether this activity is running as part of a voice interaction with the user. + * If true, it should perform its interaction with the user through the + * {@link VoiceInteractor} returned by {@link #getVoiceInteractor}. + */ + public boolean isVoiceInteraction() { + return mVoiceInteractor != null; + } + + /** + * Retrieve the active {@link VoiceInteractor} that the user is going through to + * interact with this activity. + */ + public VoiceInteractor getVoiceInteractor() { + return mVoiceInteractor; + } + + /** * This is called for activities that set launchMode to "singleTop" in * their package, or if a client used the {@link Intent#FLAG_ACTIVITY_SINGLE_TOP} * flag when calling {@link #startActivity}. In either case, when the @@ -5397,7 +5417,7 @@ public class Activity extends ContextThemeWrapper NonConfigurationInstances lastNonConfigurationInstances, Configuration config) { attach(context, aThread, instr, token, ident, application, intent, info, title, parent, id, - lastNonConfigurationInstances, config, null); + lastNonConfigurationInstances, config, null, null); } final void attach(Context context, ActivityThread aThread, @@ -5405,7 +5425,7 @@ public class Activity extends ContextThemeWrapper Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances, - Configuration config, Bundle options) { + Configuration config, Bundle options, IVoiceInteractor voiceInteractor) { attachBaseContext(context); mFragments.attachActivity(this, mContainer, null); @@ -5433,6 +5453,8 @@ public class Activity extends ContextThemeWrapper mParent = parent; mEmbeddedID = id; mLastNonConfigurationInstances = lastNonConfigurationInstances; + mVoiceInteractor = voiceInteractor != null + ? new VoiceInteractor(this, voiceInteractor, Looper.myLooper()) : null; mWindow.setWindowManager( (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index c027e99..018e949 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -76,6 +76,13 @@ public class ActivityManager { public static final String META_HOME_ALTERNATE = "android.app.home.alternate"; /** + * Result for IActivityManager.startActivity: trying to start an activity under voice + * control when that activity does not support the VOICE category. + * @hide + */ + public static final int START_NOT_VOICE_COMPATIBLE = -7; + + /** * Result for IActivityManager.startActivity: an error where the * start had to be canceled. * @hide diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index 10831f2..b1c37de 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -43,9 +43,11 @@ import android.os.Parcelable; import android.os.RemoteException; import android.os.ServiceManager; import android.os.StrictMode; +import android.service.voice.IVoiceInteractionSession; import android.text.TextUtils; import android.util.Log; import android.util.Singleton; +import com.android.internal.app.IVoiceInteractor; import java.util.ArrayList; import java.util.List; @@ -242,6 +244,33 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case START_VOICE_ACTIVITY_TRANSACTION: + { + data.enforceInterface(IActivityManager.descriptor); + String callingPackage = data.readString(); + int callingPid = data.readInt(); + int callingUid = data.readInt(); + Intent intent = Intent.CREATOR.createFromParcel(data); + String resolvedType = data.readString(); + IVoiceInteractionSession session = IVoiceInteractionSession.Stub.asInterface( + data.readStrongBinder()); + IVoiceInteractor interactor = IVoiceInteractor.Stub.asInterface( + data.readStrongBinder()); + int startFlags = data.readInt(); + String profileFile = data.readString(); + ParcelFileDescriptor profileFd = data.readInt() != 0 + ? ParcelFileDescriptor.CREATOR.createFromParcel(data) : null; + Bundle options = data.readInt() != 0 + ? Bundle.CREATOR.createFromParcel(data) : null; + int userId = data.readInt(); + int result = startVoiceActivity(callingPackage, callingPid, callingUid, + intent, resolvedType, session, interactor, startFlags, + profileFile, profileFd, options, userId); + reply.writeNoException(); + reply.writeInt(result); + return true; + } + case START_NEXT_MATCHING_ACTIVITY_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); @@ -2323,6 +2352,42 @@ class ActivityManagerProxy implements IActivityManager data.recycle(); return result; } + public int startVoiceActivity(String callingPackage, int callingPid, int callingUid, + Intent intent, String resolvedType, IVoiceInteractionSession session, + IVoiceInteractor interactor, int startFlags, String profileFile, + ParcelFileDescriptor profileFd, Bundle options, int userId) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeString(callingPackage); + data.writeInt(callingPid); + data.writeInt(callingUid); + intent.writeToParcel(data, 0); + data.writeString(resolvedType); + data.writeStrongBinder(session.asBinder()); + data.writeStrongBinder(interactor.asBinder()); + data.writeInt(startFlags); + data.writeString(profileFile); + if (profileFd != null) { + data.writeInt(1); + profileFd.writeToParcel(data, Parcelable.PARCELABLE_WRITE_RETURN_VALUE); + } else { + data.writeInt(0); + } + if (options != null) { + data.writeInt(1); + options.writeToParcel(data, 0); + } else { + data.writeInt(0); + } + data.writeInt(userId); + mRemote.transact(START_VOICE_ACTIVITY_TRANSACTION, data, reply, 0); + reply.readException(); + int result = reply.readInt(); + reply.recycle(); + data.recycle(); + return result; + } public boolean startNextMatchingActivity(IBinder callingActivity, Intent intent, Bundle options) throws RemoteException { Parcel data = Parcel.obtain(); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 4562d6e..7dc21b4 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -94,6 +94,7 @@ import android.view.WindowManagerGlobal; import android.renderscript.RenderScript; import android.security.AndroidKeyStoreProvider; +import com.android.internal.app.IVoiceInteractor; import com.android.internal.os.BinderInternal; import com.android.internal.os.RuntimeInit; import com.android.internal.os.SamplingProfilerIntegration; @@ -265,6 +266,7 @@ public final class ActivityThread { IBinder token; int ident; Intent intent; + IVoiceInteractor voiceInteractor; Bundle state; Activity activity; Window window; @@ -603,6 +605,7 @@ public final class ActivityThread { // activity itself back to the activity manager. (matters more with ipc) public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident, ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo, + IVoiceInteractor voiceInteractor, int procState, Bundle state, List<ResultInfo> pendingResults, List<Intent> pendingNewIntents, boolean notResumed, boolean isForward, String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler, @@ -615,6 +618,7 @@ public final class ActivityThread { r.token = token; r.ident = ident; r.intent = intent; + r.voiceInteractor = voiceInteractor; r.activityInfo = info; r.compatInfo = compatInfo; r.state = state; @@ -2197,7 +2201,8 @@ public final class ActivityThread { + r.activityInfo.name + " with config " + config); activity.attach(appContext, this, getInstrumentation(), r.token, r.ident, app, r.intent, r.activityInfo, title, r.parent, - r.embeddedID, r.lastNonConfigurationInstances, config, options); + r.embeddedID, r.lastNonConfigurationInstances, config, options, + r.voiceInteractor); if (customIntent != null) { activity.mIntent = customIntent; diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java index f1c632e..fcc7f8e 100644 --- a/core/java/android/app/ApplicationThreadNative.java +++ b/core/java/android/app/ApplicationThreadNative.java @@ -33,6 +33,7 @@ import android.os.RemoteException; import android.os.IBinder; import android.os.Parcel; import android.os.ParcelFileDescriptor; +import com.android.internal.app.IVoiceInteractor; import java.io.FileDescriptor; import java.io.IOException; @@ -136,6 +137,8 @@ public abstract class ApplicationThreadNative extends Binder ActivityInfo info = ActivityInfo.CREATOR.createFromParcel(data); Configuration curConfig = Configuration.CREATOR.createFromParcel(data); CompatibilityInfo compatInfo = CompatibilityInfo.CREATOR.createFromParcel(data); + IVoiceInteractor voiceInteractor = IVoiceInteractor.Stub.asInterface( + data.readStrongBinder()); int procState = data.readInt(); Bundle state = data.readBundle(); List<ResultInfo> ri = data.createTypedArrayList(ResultInfo.CREATOR); @@ -147,7 +150,8 @@ public abstract class ApplicationThreadNative extends Binder ? ParcelFileDescriptor.CREATOR.createFromParcel(data) : null; boolean autoStopProfiler = data.readInt() != 0; Bundle resumeArgs = data.readBundle(); - scheduleLaunchActivity(intent, b, ident, info, curConfig, compatInfo, procState, state, + scheduleLaunchActivity(intent, b, ident, info, curConfig, compatInfo, + voiceInteractor, procState, state, ri, pi, notResumed, isForward, profileName, profileFd, autoStopProfiler, resumeArgs); return true; @@ -735,6 +739,7 @@ class ApplicationThreadProxy implements IApplicationThread { public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident, ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo, + IVoiceInteractor voiceInteractor, int procState, Bundle state, List<ResultInfo> pendingResults, List<Intent> pendingNewIntents, boolean notResumed, boolean isForward, String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler, @@ -748,6 +753,7 @@ class ApplicationThreadProxy implements IApplicationThread { info.writeToParcel(data, 0); curConfig.writeToParcel(data, 0); compatInfo.writeToParcel(data, 0); + data.writeStrongBinder(voiceInteractor != null ? voiceInteractor.asBinder() : null); data.writeInt(procState); data.writeBundle(state); data.writeTypedList(pendingResults); diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index 52003f1..6b94c4e 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -47,6 +47,8 @@ import android.os.ParcelFileDescriptor; import android.os.Parcelable; import android.os.RemoteException; import android.os.StrictMode; +import android.service.voice.IVoiceInteractionSession; +import com.android.internal.app.IVoiceInteractor; import java.util.List; @@ -77,6 +79,10 @@ public interface IActivityManager extends IInterface { IntentSender intent, Intent fillInIntent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int flagsMask, int flagsValues, Bundle options) throws RemoteException; + public int startVoiceActivity(String callingPackage, int callingPid, int callingUid, + Intent intent, String resolvedType, IVoiceInteractionSession session, + IVoiceInteractor interactor, int flags, String profileFile, + ParcelFileDescriptor profileFd, Bundle options, int userId) throws RemoteException; public boolean startNextMatchingActivity(IBinder callingActivity, Intent intent, Bundle options) throws RemoteException; public boolean finishActivity(IBinder token, int code, Intent data, boolean finishTask) @@ -733,4 +739,5 @@ public interface IActivityManager extends IInterface { int STOP_LOCK_TASK_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+215; int IS_IN_LOCK_TASK_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+216; int SET_ACTIVITY_LABEL_ICON_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+217; + int START_VOICE_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+218; } diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java index ac8ac8f..f290e94 100644 --- a/core/java/android/app/IApplicationThread.java +++ b/core/java/android/app/IApplicationThread.java @@ -31,6 +31,8 @@ import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.IBinder; import android.os.IInterface; +import android.service.voice.IVoiceInteractionSession; +import com.android.internal.app.IVoiceInteractor; import java.io.FileDescriptor; import java.util.List; @@ -55,8 +57,9 @@ public interface IApplicationThread extends IInterface { void scheduleSendResult(IBinder token, List<ResultInfo> results) throws RemoteException; void scheduleLaunchActivity(Intent intent, IBinder token, int ident, ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo, - int procState, Bundle state, List<ResultInfo> pendingResults, - List<Intent> pendingNewIntents, boolean notResumed, boolean isForward, + IVoiceInteractor voiceInteractor, int procState, Bundle state, + List<ResultInfo> pendingResults, List<Intent> pendingNewIntents, boolean notResumed, + boolean isForward, String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler, Bundle resumeArgs) throws RemoteException; diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index 028fa68..e58ccb8 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -1428,7 +1428,7 @@ public class Instrumentation { } /** - * Like {@link #execStartActivity(Context, IBinder, IBinder, Activity, Intent, int)}, + * Like {@link #execStartActivity}, * but accepts an array of activities to be started. Note that active * {@link ActivityMonitor} objects only match against the first activity in * the array. @@ -1442,7 +1442,7 @@ public class Instrumentation { } /** - * Like {@link #execStartActivity(Context, IBinder, IBinder, Activity, Intent, int)}, + * Like {@link #execStartActivity}, * but accepts an array of activities to be started. Note that active * {@link ActivityMonitor} objects only match against the first activity in * the array. @@ -1545,8 +1545,7 @@ public class Instrumentation { } /** - * Like {@link #execStartActivity(Context, IBinder, IBinder, Activity, Intent, int)}, - * but for starting as a particular user. + * Like {@link #execStartActivity}, but for starting as a particular user. * * @param who The Context from which the activity is being started. * @param contextThread The main thread of the Context from which the activity @@ -1616,7 +1615,8 @@ public class Instrumentation { mUiAutomationConnection = uiAutomationConnection; } - /*package*/ static void checkStartActivityResult(int res, Object intent) { + /** @hide */ + public static void checkStartActivityResult(int res, Object intent) { if (res >= ActivityManager.START_SUCCESS) { return; } @@ -1640,6 +1640,9 @@ public class Instrumentation { case ActivityManager.START_NOT_ACTIVITY: throw new IllegalArgumentException( "PendingIntent is not an activity"); + case ActivityManager.START_NOT_VOICE_COMPATIBLE: + throw new SecurityException( + "Starting under voice control not allowed for: " + intent); default: throw new AndroidRuntimeException("Unknown error code " + res + " when starting " + intent); diff --git a/core/java/android/app/VoiceInteractor.java b/core/java/android/app/VoiceInteractor.java new file mode 100644 index 0000000..6820dfd --- /dev/null +++ b/core/java/android/app/VoiceInteractor.java @@ -0,0 +1,235 @@ +/* + * 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. + */ + +package android.app; + +import android.content.Context; +import android.os.Bundle; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.util.Log; +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; + +import java.util.WeakHashMap; + +/** + * Interface for an {@link Activity} to interact with the user through voice. + */ +public class VoiceInteractor { + static final String TAG = "VoiceInteractor"; + static final boolean DEBUG = true; + + final Context mContext; + final IVoiceInteractor mInteractor; + final HandlerCaller mHandlerCaller; + final HandlerCaller.Callback mHandlerCallerCallback = new HandlerCaller.Callback() { + @Override + public void executeMessage(Message msg) { + SomeArgs args = (SomeArgs)msg.obj; + switch (msg.what) { + case MSG_CONFIRMATION_RESULT: + if (DEBUG) Log.d(TAG, "onConfirmResult: req=" + + ((IVoiceInteractorRequest)args.arg2).asBinder() + + " confirmed=" + msg.arg1 + " result=" + args.arg3); + ((Callback)args.arg1).onConfirmationResult( + findRequest((IVoiceInteractorRequest)args.arg2), + msg.arg1 != 0, (Bundle)args.arg3); + break; + case MSG_COMMAND_RESULT: + if (DEBUG) Log.d(TAG, "onCommandResult: req=" + + ((IVoiceInteractorRequest)args.arg2).asBinder() + + " result=" + args.arg2); + ((Callback)args.arg1).onCommandResult( + findRequest((IVoiceInteractorRequest) args.arg2), + (Bundle) args.arg3); + break; + case MSG_CANCEL_RESULT: + if (DEBUG) Log.d(TAG, "onCancelResult: req=" + + ((IVoiceInteractorRequest)args.arg2).asBinder()); + ((Callback)args.arg1).onCancel( + findRequest((IVoiceInteractorRequest) args.arg2)); + break; + } + } + }; + + final WeakHashMap<IBinder, Request> mActiveRequests = new WeakHashMap<IBinder, Request>(); + + static final int MSG_CONFIRMATION_RESULT = 1; + static final int MSG_COMMAND_RESULT = 2; + static final int MSG_CANCEL_RESULT = 3; + + public static class Request { + final IVoiceInteractorRequest mRequestInterface; + + Request(IVoiceInteractorRequest requestInterface) { + mRequestInterface = requestInterface; + } + + public void cancel() { + try { + mRequestInterface.cancel(); + } catch (RemoteException e) { + Log.w(TAG, "Voice interactor has died", e); + } + } + } + + public static class Callback { + VoiceInteractor mInteractor; + + final IVoiceInteractorCallback.Stub mWrapper = new IVoiceInteractorCallback.Stub() { + @Override + public void deliverConfirmationResult(IVoiceInteractorRequest request, boolean confirmed, + Bundle result) { + mInteractor.mHandlerCaller.sendMessage(mInteractor.mHandlerCaller.obtainMessageIOOO( + MSG_CONFIRMATION_RESULT, confirmed ? 1 : 0, Callback.this, request, + result)); + } + + @Override + public void deliverCommandResult(IVoiceInteractorRequest request, Bundle result) { + mInteractor.mHandlerCaller.sendMessage(mInteractor.mHandlerCaller.obtainMessageOOO( + MSG_COMMAND_RESULT, Callback.this, request, result)); + } + + @Override + public void deliverCancel(IVoiceInteractorRequest request) throws RemoteException { + mInteractor.mHandlerCaller.sendMessage(mInteractor.mHandlerCaller.obtainMessageOO( + MSG_CANCEL_RESULT, Callback.this, request)); + } + }; + + public void onConfirmationResult(Request request, boolean confirmed, Bundle result) { + } + + public void onCommandResult(Request request, Bundle result) { + } + + public void onCancel(Request request) { + } + } + + VoiceInteractor(Context context, IVoiceInteractor interactor, Looper looper) { + mContext = context; + mInteractor = interactor; + mHandlerCaller = new HandlerCaller(context, looper, mHandlerCallerCallback, true); + } + + Request storeRequest(IVoiceInteractorRequest request) { + synchronized (mActiveRequests) { + Request req = new Request(request); + mActiveRequests.put(request.asBinder(), req); + return req; + } + } + + Request findRequest(IVoiceInteractorRequest request) { + synchronized (mActiveRequests) { + Request req = mActiveRequests.get(request.asBinder()); + if (req == null) { + throw new IllegalStateException("Received callback without active request: " + + request); + } + return req; + } + } + + /** + * Asynchronously confirms an operation with the user via the trusted system + * VoiceinteractionService. This allows an Activity to complete an unsafe operation that + * would require the user to touch the screen when voice interaction mode is not enabled. + * The result of the confirmation will be returned by calling the + * {@link Callback#onConfirmationResult Callback.onConfirmationResult} method. + * + * In some cases this may be a simple yes / no confirmation or the confirmation could + * include context information about how the action will be completed + * (e.g. booking a cab might include details about how long until the cab arrives) so the user + * can give informed consent. + * @param callback Required callback target for interaction results. + * @param prompt Optional confirmation text to read to the user as the action being confirmed. + * @param extras Additional optional information. + * @return Returns a new {@link Request} object representing this operation. + */ + public Request startConfirmation(Callback callback, String prompt, Bundle extras) { + try { + callback.mInteractor = this; + Request req = storeRequest(mInteractor.startConfirmation( + mContext.getOpPackageName(), callback.mWrapper, prompt, extras)); + if (DEBUG) Log.d(TAG, "startConfirmation: req=" + req.mRequestInterface.asBinder() + + " prompt=" + prompt + " extras=" + extras); + return req; + } catch (RemoteException e) { + throw new RuntimeException("Voice interactor has died", e); + } + } + + /** + * Asynchronously executes a command using the trusted system VoiceinteractionService. + * This allows an Activity to request additional information from the user needed to + * complete an action (e.g. booking a table might have several possible times that the + * user could select from or an app might need the user to agree to a terms of service). + * + * The command is a string that describes the generic operation to be performed. + * The command will determine how the properties in extras are interpreted and the set of + * available commands is expected to grow over time. An example might be + * "com.google.voice.commands.REQUEST_NUMBER_BAGS" to request the number of bags as part of + * airline check-in. (This is not an actual working example.) + * The result of the command will be returned by calling the + * {@link Callback#onCommandResult Callback.onCommandResult} method. + * + * @param callback Required callback target for interaction results. + * @param command + * @param extras + * @return Returns a new {@link Request} object representing this operation. + */ + public Request startCommand(Callback callback, String command, Bundle extras) { + try { + callback.mInteractor = this; + Request req = storeRequest(mInteractor.startCommand( + mContext.getOpPackageName(), callback.mWrapper, command, extras)); + if (DEBUG) Log.d(TAG, "startCommand: req=" + req.mRequestInterface.asBinder() + + " command=" + command + " extras=" + extras); + return req; + } catch (RemoteException e) { + throw new RuntimeException("Voice interactor has died", e); + } + } + + /** + * Queries the supported commands available from the VoiceinteractionService. + * The command is a string that describes the generic operation to be performed. + * An example might be "com.google.voice.commands.REQUEST_NUMBER_BAGS" to request the number + * of bags as part of airline check-in. (This is not an actual working example.) + * + * @param commands + */ + public boolean[] supportsCommands(String[] commands) { + try { + boolean[] res = mInteractor.supportsCommands(mContext.getOpPackageName(), commands); + if (DEBUG) Log.d(TAG, "supportsCommands: cmds=" + commands + " res=" + res); + return res; + } catch (RemoteException e) { + throw new RuntimeException("Voice interactor has died", e); + } + } +} diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index cbb6cf5..de223a3 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -2442,6 +2442,14 @@ public abstract class Context { public static final String APPWIDGET_SERVICE = "appwidget"; /** + * Official published name of the (internal) voice interaction manager service. + * + * @hide + * @see #getSystemService + */ + public static final String VOICE_INTERACTION_MANAGER_SERVICE = "voiceinteraction"; + + /** * Use with {@link #getSystemService} to retrieve an * {@link android.app.backup.IBackupManager IBackupManager} for communicating * with the backup mechanism. diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index c0f04af..ae5437b 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -2804,6 +2804,14 @@ public class Intent implements Parcelable, Cloneable { @SdkConstant(SdkConstantType.INTENT_CATEGORY) public static final String CATEGORY_BROWSABLE = "android.intent.category.BROWSABLE"; /** + * Categories for activities that can participate in voice interaction. + * An activity that supports this category must be prepared to run with + * no UI shown at all (though in some case it may have a UI shown), and + * rely on {@link android.app.VoiceInteractor} to interact with the user. + */ + @SdkConstant(SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_VOICE = "android.intent.category.VOICE"; + /** * Set if the activity should be considered as an alternative action to * the data the user is currently viewing. See also * {@link #CATEGORY_SELECTED_ALTERNATIVE} for an alternative action that diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index ae0899f..488e25f 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -74,6 +74,9 @@ interface IPackageManager { ActivityInfo getActivityInfo(in ComponentName className, int flags, int userId); + boolean activitySupportsIntent(in ComponentName className, in Intent intent, + String resolvedType); + ActivityInfo getReceiverInfo(in ComponentName className, int flags, int userId); ServiceInfo getServiceInfo(in ComponentName className, int flags, int userId); diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index d981cc1..484a2a1 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -1235,7 +1235,6 @@ public abstract class PackageManager { */ @SdkConstant(SdkConstantType.FEATURE) public static final String FEATURE_LIVE_WALLPAPER = "android.software.live_wallpaper"; - /** * Feature for {@link #getSystemAvailableFeatures} and * {@link #hasSystemFeature}: The device supports app widgets. @@ -1244,6 +1243,17 @@ public abstract class PackageManager { public static final String FEATURE_APP_WIDGETS = "android.software.app_widgets"; /** + * @hide + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device supports + * {@link android.service.voice.VoiceInteractionService} and + * {@link android.app.VoiceInteractor}. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_VOICE_RECOGNIZERS = "android.software.voice_recognizers"; + + + /** * Feature for {@link #getSystemAvailableFeatures} and * {@link #hasSystemFeature}: The device supports a home screen that is replaceable * by third party applications. diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 7f9f862..691317b 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -3311,6 +3311,12 @@ public final class Settings { "input_method_selector_visibility"; /** + * The currently selected voice interaction service flattened ComponentName. + * @hide + */ + public static final String VOICE_INTERACTION_SERVICE = "voice_interaction_service"; + + /** * bluetooth HCI snoop log configuration * @hide */ diff --git a/core/java/android/service/voice/IVoiceInteractionService.aidl b/core/java/android/service/voice/IVoiceInteractionService.aidl new file mode 100644 index 0000000..e9e2f4c --- /dev/null +++ b/core/java/android/service/voice/IVoiceInteractionService.aidl @@ -0,0 +1,23 @@ +/* + * 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. + */ + +package android.service.voice; + +/** + * @hide + */ +oneway interface IVoiceInteractionService { +} diff --git a/core/java/android/service/voice/IVoiceInteractionSession.aidl b/core/java/android/service/voice/IVoiceInteractionSession.aidl new file mode 100644 index 0000000..7dbf66b --- /dev/null +++ b/core/java/android/service/voice/IVoiceInteractionSession.aidl @@ -0,0 +1,28 @@ +/* + * 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. + */ + +package android.service.voice; + +import android.os.Bundle; + +import com.android.internal.app.IVoiceInteractorCallback; +import com.android.internal.app.IVoiceInteractorRequest; + +/** + * @hide + */ +interface IVoiceInteractionSession { +} diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java new file mode 100644 index 0000000..ed93b74 --- /dev/null +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -0,0 +1,77 @@ +/** + * 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. + */ + +package android.service.voice; + +import android.annotation.SdkConstant; +import android.app.Instrumentation; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import com.android.internal.app.IVoiceInteractionManagerService; + +public class VoiceInteractionService extends Service { + /** + * The {@link Intent} that must be declared as handled by the service. + * To be supported, the service must also require the + * {@link android.Manifest.permission#BIND_VOICE_INTERACTION} permission so + * that other applications can not abuse it. + */ + @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) + public static final String SERVICE_INTERFACE = + "android.service.voice.VoiceInteractionService"; + + /** + * Name under which a VoiceInteractionService component publishes information about itself. + * This meta-data should reference an XML resource containing a + * <code><{@link + * android.R.styleable#VoiceInteractionService voice-interaction-service}></code> tag. + */ + public static final String SERVICE_META_DATA = "android.voice_interaction"; + + IVoiceInteractionService mInterface = new IVoiceInteractionService.Stub() { + }; + + IVoiceInteractionManagerService mSystemService; + + public void startVoiceActivity(Intent intent, VoiceInteractionSession session) { + try { + int res = mSystemService.startVoiceActivity(intent, + intent.resolveType(getContentResolver()), + mInterface, session.mSession, session.mInteractor); + Instrumentation.checkStartActivityResult(res, intent); + } catch (RemoteException e) { + } + } + + @Override + public void onCreate() { + super.onCreate(); + mSystemService = IVoiceInteractionManagerService.Stub.asInterface( + ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE)); + } + + @Override + public IBinder onBind(Intent intent) { + if (SERVICE_INTERFACE.equals(intent.getAction())) { + return mInterface.asBinder(); + } + return null; + } +} diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java new file mode 100644 index 0000000..59544be --- /dev/null +++ b/core/java/android/service/voice/VoiceInteractionSession.java @@ -0,0 +1,195 @@ +/** + * 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. + */ + +package android.service.voice; + +import android.content.Context; +import android.os.Binder; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.RemoteException; +import android.util.ArrayMap; +import android.util.Log; +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 { + static final String TAG = "VoiceInteractionSession"; + static final boolean DEBUG = true; + + final IVoiceInteractor mInteractor = new IVoiceInteractor.Stub() { + @Override + public IVoiceInteractorRequest startConfirmation(String callingPackage, + IVoiceInteractorCallback callback, String prompt, Bundle extras) { + Request request = findRequest(callback, true); + mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOO(MSG_START_CONFIRMATION, + new Caller(callingPackage, Binder.getCallingUid()), request, + prompt, extras)); + return request.mInterface; + } + + @Override + public IVoiceInteractorRequest startCommand(String callingPackage, + IVoiceInteractorCallback callback, String command, Bundle extras) { + Request request = findRequest(callback, true); + mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOO(MSG_START_COMMAND, + new Caller(callingPackage, Binder.getCallingUid()), request, + command, extras)); + return request.mInterface; + } + + @Override + public boolean[] supportsCommands(String callingPackage, String[] commands) { + Message msg = mHandlerCaller.obtainMessageIOO(MSG_SUPPORTS_COMMANDS, + 0, new Caller(callingPackage, Binder.getCallingUid()), commands); + SomeArgs args = mHandlerCaller.sendMessageAndWait(msg); + if (args != null) { + boolean[] res = (boolean[])args.arg1; + args.recycle(); + return res; + } + return new boolean[commands.length]; + } + }; + + final IVoiceInteractionSession mSession = new IVoiceInteractionSession.Stub() { + }; + + public static class Request { + final IVoiceInteractorRequest mInterface = new IVoiceInteractorRequest.Stub() { + @Override + public void cancel() throws RemoteException { + mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(MSG_CANCEL, Request.this)); + } + }; + final IVoiceInteractorCallback mCallback; + final HandlerCaller mHandlerCaller; + Request(IVoiceInteractorCallback callback, HandlerCaller handlerCaller) { + mCallback = callback; + mHandlerCaller = handlerCaller; + } + + public void sendConfirmResult(boolean confirmed, Bundle result) { + try { + if (DEBUG) Log.d(TAG, "sendConfirmResult: req=" + mInterface + + " confirmed=" + confirmed + " result=" + result); + mCallback.deliverConfirmationResult(mInterface, confirmed, result); + } catch (RemoteException e) { + } + } + + public void sendCommandResult(Bundle result) { + try { + if (DEBUG) Log.d(TAG, "sendCommandResult: req=" + mInterface + + " result=" + result); + mCallback.deliverCommandResult(mInterface, result); + } catch (RemoteException e) { + } + } + + public void sendCancelResult() { + try { + if (DEBUG) Log.d(TAG, "sendCancelResult: req=" + mInterface); + mCallback.deliverCancel(mInterface); + } catch (RemoteException e) { + } + } + } + + public static class Caller { + final String packageName; + final int uid; + + Caller(String _packageName, int _uid) { + packageName = _packageName; + uid = _uid; + } + } + + static final int MSG_START_CONFIRMATION = 1; + static final int MSG_START_COMMAND = 2; + 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() { + @Override + public void executeMessage(Message msg) { + SomeArgs args = (SomeArgs)msg.obj; + switch (msg.what) { + case MSG_START_CONFIRMATION: + 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: + 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: + if (DEBUG) Log.d(TAG, "onGetSupportedCommands: cmds=" + args.arg2); + args.arg1 = onGetSupportedCommands((Caller) args.arg1, (String[]) args.arg2); + break; + case MSG_CANCEL: + if (DEBUG) Log.d(TAG, "onCancel: req=" + ((Request) args.arg1).mInterface); + onCancel((Request)args.arg1); + break; + } + } + }; + + final ArrayMap<IBinder, Request> mActiveRequests = new ArrayMap<IBinder, Request>(); + + public VoiceInteractionSession(Context context) { + this(context, new Handler()); + } + + public VoiceInteractionSession(Context context, Handler handler) { + mContext = context; + mHandlerCaller = new HandlerCaller(context, handler.getLooper(), + mHandlerCallerCallback, true); + } + + Request findRequest(IVoiceInteractorCallback callback, boolean newRequest) { + synchronized (this) { + Request req = mActiveRequests.get(callback.asBinder()); + if (req != null) { + if (newRequest) { + throw new IllegalArgumentException("Given request callback " + callback + + " is already active"); + } + return req; + } + req = new Request(callback, mHandlerCaller); + mActiveRequests.put(callback.asBinder(), req); + return req; + } + } + + 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); + public abstract void onCancel(Request request); +} diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl new file mode 100644 index 0000000..e3c0728 --- /dev/null +++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl @@ -0,0 +1,28 @@ +/* + * 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. + */ + +package com.android.internal.app; + +import android.content.Intent; + +import com.android.internal.app.IVoiceInteractor; +import android.service.voice.IVoiceInteractionService; +import android.service.voice.IVoiceInteractionSession; + +interface IVoiceInteractionManagerService { + int startVoiceActivity(in Intent intent, String resolvedType, IVoiceInteractionService service, + IVoiceInteractionSession session, IVoiceInteractor interactor); +} diff --git a/core/java/com/android/internal/app/IVoiceInteractor.aidl b/core/java/com/android/internal/app/IVoiceInteractor.aidl new file mode 100644 index 0000000..737906a --- /dev/null +++ b/core/java/com/android/internal/app/IVoiceInteractor.aidl @@ -0,0 +1,33 @@ +/* + * 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. + */ + +package com.android.internal.app; + +import android.os.Bundle; + +import com.android.internal.app.IVoiceInteractorCallback; +import com.android.internal.app.IVoiceInteractorRequest; + +/** + * IPC interface for an application to perform calls through a VoiceInteractor. + */ +interface IVoiceInteractor { + IVoiceInteractorRequest startConfirmation(String callingPackage, + IVoiceInteractorCallback callback, String prompt, in Bundle extras); + IVoiceInteractorRequest startCommand(String callingPackage, + IVoiceInteractorCallback callback, String command, in Bundle extras); + boolean[] supportsCommands(String callingPackage, in String[] commands); +} diff --git a/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl b/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl new file mode 100644 index 0000000..f5392e9 --- /dev/null +++ b/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl @@ -0,0 +1,31 @@ +/* + * 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. + */ + +package com.android.internal.app; + +import android.os.Bundle; + +import com.android.internal.app.IVoiceInteractorRequest; + +/** + * IPC interface for an application to receive callbacks from the voice system. + */ +oneway interface IVoiceInteractorCallback { + void deliverConfirmationResult(IVoiceInteractorRequest request, boolean confirmed, + in Bundle result); + void deliverCommandResult(IVoiceInteractorRequest request, in Bundle result); + void deliverCancel(IVoiceInteractorRequest request); +} diff --git a/core/java/com/android/internal/app/IVoiceInteractorRequest.aidl b/core/java/com/android/internal/app/IVoiceInteractorRequest.aidl new file mode 100644 index 0000000..ce2902d --- /dev/null +++ b/core/java/com/android/internal/app/IVoiceInteractorRequest.aidl @@ -0,0 +1,24 @@ +/* + * 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. + */ + +package com.android.internal.app; + +/** + * IPC interface identifying a request from an application calling through an IVoiceInteractor. + */ +interface IVoiceInteractorRequest { + void cancel(); +} diff --git a/core/java/com/android/internal/os/HandlerCaller.java b/core/java/com/android/internal/os/HandlerCaller.java index d9e3ef6..40834ba 100644 --- a/core/java/com/android/internal/os/HandlerCaller.java +++ b/core/java/com/android/internal/os/HandlerCaller.java @@ -85,7 +85,27 @@ public class HandlerCaller { public void sendMessage(Message msg) { mH.sendMessage(msg); } - + + public SomeArgs sendMessageAndWait(Message msg) { + if (Looper.myLooper() == mH.getLooper()) { + throw new IllegalStateException("Can't wait on same thread as looper"); + } + SomeArgs args = (SomeArgs)msg.obj; + args.mWaitState = SomeArgs.WAIT_WAITING; + mH.sendMessage(msg); + synchronized (args) { + while (args.mWaitState == SomeArgs.WAIT_WAITING) { + try { + args.wait(); + } catch (InterruptedException e) { + return null; + } + } + } + args.mWaitState = SomeArgs.WAIT_NONE; + return args; + } + public Message obtainMessage(int what) { return mH.obtainMessage(what); } @@ -136,6 +156,14 @@ public class HandlerCaller { return mH.obtainMessage(what, arg1, 0, args); } + public Message obtainMessageIOOO(int what, int arg1, Object arg2, Object arg3, Object arg4) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = arg2; + args.arg2 = arg3; + args.arg3 = arg4; + return mH.obtainMessage(what, arg1, 0, args); + } + public Message obtainMessageOO(int what, Object arg1, Object arg2) { SomeArgs args = SomeArgs.obtain(); args.arg1 = arg1; @@ -161,6 +189,17 @@ public class HandlerCaller { return mH.obtainMessage(what, 0, 0, args); } + public Message obtainMessageOOOOO(int what, Object arg1, Object arg2, + Object arg3, Object arg4, Object arg5) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = arg1; + args.arg2 = arg2; + args.arg3 = arg3; + args.arg4 = arg4; + args.arg5 = arg5; + return mH.obtainMessage(what, 0, 0, args); + } + public Message obtainMessageIIII(int what, int arg1, int arg2, int arg3, int arg4) { SomeArgs args = SomeArgs.obtain(); diff --git a/core/java/com/android/internal/os/SomeArgs.java b/core/java/com/android/internal/os/SomeArgs.java index 6fb72f1..7edf4cc 100644 --- a/core/java/com/android/internal/os/SomeArgs.java +++ b/core/java/com/android/internal/os/SomeArgs.java @@ -35,6 +35,11 @@ public final class SomeArgs { private boolean mInPool; + static final int WAIT_NONE = 0; + static final int WAIT_WAITING = 1; + static final int WAIT_FINISHED = 2; + int mWaitState = WAIT_NONE; + public Object arg1; public Object arg2; public Object arg3; @@ -70,6 +75,9 @@ public final class SomeArgs { if (mInPool) { throw new IllegalStateException("Already recycled."); } + if (mWaitState != WAIT_NONE) { + return; + } synchronized (sPoolLock) { clear(); if (sPoolSize < MAX_POOL_SIZE) { diff --git a/core/java/com/android/server/SystemService.java b/core/java/com/android/server/SystemService.java index e374563..bf36bb1 100644 --- a/core/java/com/android/server/SystemService.java +++ b/core/java/com/android/server/SystemService.java @@ -123,6 +123,38 @@ public abstract class SystemService { public void onBootPhase(int phase) {} /** + * Called when a new user is starting, for system services to initialize any per-user + * state they maintain for running users. + * @param userHandle The identifier of the user. + */ + public void onStartUser(int userHandle) {} + + /** + * Called when switching to a different foreground user, for system services that have + * special behavior for whichever user is currently in the foreground. This is called + * before any application processes are aware of the new user. + * @param userHandle The identifier of the user. + */ + public void onSwitchUser(int userHandle) {} + + /** + * Called when an existing user is stopping, for system services to finalize any per-user + * state they maintain for running users. This is called prior to sending the SHUTDOWN + * broadcast to the user; it is a good place to stop making use of any resources of that + * user (such as binding to a service running in the user). + * @param userHandle The identifier of the user. + */ + public void onStopUser(int userHandle) {} + + /** + * Called when an existing user is stopping, for system services to finalize any per-user + * state they maintain for running users. This is called after all application process + * teardown of the user is complete. + * @param userHandle The identifier of the user. + */ + public void onCleanupUser(int userHandle) {} + + /** * Publish the service so it is accessible to other services and apps. */ protected final void publishBinderService(String name, IBinder service) { diff --git a/core/java/com/android/server/SystemServiceManager.java b/core/java/com/android/server/SystemServiceManager.java index eb8df0e..87a50a9 100644 --- a/core/java/com/android/server/SystemServiceManager.java +++ b/core/java/com/android/server/SystemServiceManager.java @@ -131,6 +131,58 @@ public class SystemServiceManager { } } + public void startUser(final int userHandle) { + final int serviceLen = mServices.size(); + for (int i = 0; i < serviceLen; i++) { + final SystemService service = mServices.get(i); + try { + service.onStartUser(userHandle); + } catch (Exception ex) { + Slog.wtf(TAG, "Failure reporting start of user " + userHandle + + " to service " + service.getClass().getName(), ex); + } + } + } + + public void switchUser(final int userHandle) { + final int serviceLen = mServices.size(); + for (int i = 0; i < serviceLen; i++) { + final SystemService service = mServices.get(i); + try { + service.onSwitchUser(userHandle); + } catch (Exception ex) { + Slog.wtf(TAG, "Failure reporting switch of user " + userHandle + + " to service " + service.getClass().getName(), ex); + } + } + } + + public void stopUser(final int userHandle) { + final int serviceLen = mServices.size(); + for (int i = 0; i < serviceLen; i++) { + final SystemService service = mServices.get(i); + try { + service.onStopUser(userHandle); + } catch (Exception ex) { + Slog.wtf(TAG, "Failure reporting stop of user " + userHandle + + " to service " + service.getClass().getName(), ex); + } + } + } + + public void cleanupUser(final int userHandle) { + final int serviceLen = mServices.size(); + for (int i = 0; i < serviceLen; i++) { + final SystemService service = mServices.get(i); + try { + service.onCleanupUser(userHandle); + } catch (Exception ex) { + Slog.wtf(TAG, "Failure reporting cleanup of user " + userHandle + + " to service " + service.getClass().getName(), ex); + } + } + } + /** Sets the safe mode flag for services to query. */ public void setSafeMode(boolean safeMode) { mSafeMode = safeMode; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 57e845f..8dfce64 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2056,6 +2056,13 @@ android:description="@string/permdesc_bindWallpaper" android:protectionLevel="signature|system" /> + <!-- Must be required by a {@link android.service.voice.VoiceInteractionService}, + to ensure that only the system can bind to it. --> + <permission android:name="android.permission.BIND_VOICE_INTERACTION" + android:label="@string/permlab_bindVoiceInteraction" + android:description="@string/permdesc_bindVoiceInteraction" + android:protectionLevel="signature" /> + <!-- Must be required by a {@link com.android.media.remotedisplay.RemoteDisplayProvider}, to ensure that only the system can bind to it. @hide --> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 508a557..326485d 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -6267,13 +6267,21 @@ </declare-styleable> <!-- Use <code>recognition-service</code> as the root tag of the XML resource that - describes a {@link android.speech.RecognitionService}, which is reference from + describes a {@link android.speech.RecognitionService}, which is referenced from its {@link android.speech.RecognitionService#SERVICE_META_DATA} meta-data entry. Described here are the attributes that can be included in that tag. --> <declare-styleable name="RecognitionService"> <attr name="settingsActivity" /> </declare-styleable> + <!-- Use <code>voice-interaction-service</code> as the root tag of the XML resource that + describes a {@link android.service.voice.VoiceInteractionService}, which is referenced from + its {@link android.service.voice.VoiceInteractionService#SERVICE_META_DATA} meta-data entry. + Described here are the attributes that can be included in that tag. --> + <declare-styleable name="VoiceInteractionService"> + <attr name="settingsActivity" /> + </declare-styleable> + <!-- Attributes used to style the Action Bar. --> <declare-styleable name="ActionBar"> <!-- The type of navigation to use. --> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 6d4ceef..9b89eaa 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1068,6 +1068,12 @@ interface of a wallpaper. Should never be needed for normal apps.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permlab_bindVoiceInteraction">bind to a voice interactor</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permdesc_bindVoiceInteraction">Allows the holder to bind to the top-level + interface of a voice interaction service. Should never be needed for normal apps.</string> + + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_bindRemoteDisplay">bind to a remote display</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permdesc_bindRemoteDisplay">Allows the holder to bind to the top-level diff --git a/services/Android.mk b/services/Android.mk index 165f456..5fcef64 100644 --- a/services/Android.mk +++ b/services/Android.mk @@ -25,7 +25,8 @@ services := \ backup \ devicepolicy \ print \ - usb + usb \ + voiceinteraction # The convention is to name each service module 'services.$(module_name)' LOCAL_STATIC_JAVA_LIBRARIES := $(addprefix services.,$(services)) diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index ed007e9..75bf5df 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -28,17 +28,20 @@ import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; import static org.xmlpull.v1.XmlPullParser.START_TAG; import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID; +import android.Manifest; import android.app.AppOpsManager; import android.app.IActivityContainer; import android.app.IActivityContainerCallback; import android.appwidget.AppWidgetManager; import android.graphics.Rect; import android.os.BatteryStats; +import android.service.voice.IVoiceInteractionSession; import android.util.ArrayMap; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IAppOpsService; +import com.android.internal.app.IVoiceInteractor; import com.android.internal.app.ProcessMap; import com.android.internal.app.ProcessStats; import com.android.internal.os.BackgroundThread; @@ -56,6 +59,7 @@ import com.android.server.IntentResolver; import com.android.server.LocalServices; import com.android.server.ServiceThread; import com.android.server.SystemService; +import com.android.server.SystemServiceManager; import com.android.server.Watchdog; import com.android.server.am.ActivityStack.ActivityState; import com.android.server.firewall.IntentFirewall; @@ -333,6 +337,9 @@ public final class ActivityManagerService extends ActivityManagerNative // How many bytes to write into the dropbox log before truncating static final int DROPBOX_MAX_SIZE = 256 * 1024; + /** All system services */ + SystemServiceManager mSystemServiceManager; + /** Run all ActivityStacks through this */ ActivityStackSupervisor mStackSupervisor; @@ -399,7 +406,7 @@ public final class ActivityManagerService extends ActivityManagerNative /** * List of intents that were used to start the most recent tasks. */ - private final ArrayList<TaskRecord> mRecentTasks = new ArrayList<TaskRecord>(); + final ArrayList<TaskRecord> mRecentTasks = new ArrayList<TaskRecord>(); public class PendingAssistExtras extends Binder implements Runnable { public final ActivityRecord activity; @@ -879,17 +886,23 @@ public final class ActivityManagerService extends ActivityManagerNative * Set while we are wanting to sleep, to prevent any * activities from being started/resumed. */ - boolean mSleeping = false; + private boolean mSleeping = false; + + /** + * Set while we are running a voice interaction. This overrides + * sleeping while it is active. + */ + private boolean mRunningVoice = false; /** * State of external calls telling us if the device is asleep. */ - boolean mWentToSleep = false; + private boolean mWentToSleep = false; /** * State of external call telling us if the lock screen is shown. */ - boolean mLockScreenShown = false; + private boolean mLockScreenShown = false; /** * Set if we are shutting down the system, similar to sleeping. @@ -1117,6 +1130,8 @@ public final class ActivityManagerService extends ActivityManagerNative static final int REQUEST_ALL_PSS_MSG = 39; static final int START_PROFILES_MSG = 40; static final int UPDATE_TIME = 41; + static final int SYSTEM_USER_START_MSG = 42; + static final int SYSTEM_USER_CURRENT_MSG = 43; static final int FIRST_ACTIVITY_STACK_MSG = 100; static final int FIRST_BROADCAST_QUEUE_MSG = 200; @@ -1696,15 +1711,15 @@ public final class ActivityManagerService extends ActivityManagerNative break; } case REPORT_USER_SWITCH_MSG: { - dispatchUserSwitch((UserStartedState)msg.obj, msg.arg1, msg.arg2); + dispatchUserSwitch((UserStartedState) msg.obj, msg.arg1, msg.arg2); break; } case CONTINUE_USER_SWITCH_MSG: { - continueUserSwitch((UserStartedState)msg.obj, msg.arg1, msg.arg2); + continueUserSwitch((UserStartedState) msg.obj, msg.arg1, msg.arg2); break; } case USER_SWITCH_TIMEOUT_MSG: { - timeoutUserSwitch((UserStartedState)msg.obj, msg.arg1, msg.arg2); + timeoutUserSwitch((UserStartedState) msg.obj, msg.arg1, msg.arg2); break; } case IMMERSIVE_MODE_LOCK_MSG: { @@ -1751,6 +1766,14 @@ public final class ActivityManagerService extends ActivityManagerNative } break; } + case SYSTEM_USER_START_MSG: { + mSystemServiceManager.startUser(msg.arg1); + break; + } + case SYSTEM_USER_CURRENT_MSG: { + mSystemServiceManager.switchUser(msg.arg1); + break; + } } } }; @@ -2059,6 +2082,10 @@ public final class ActivityManagerService extends ActivityManagerNative Watchdog.getInstance().addThread(mHandler); } + public void setSystemServiceManager(SystemServiceManager mgr) { + mSystemServiceManager = mgr; + } + private void start() { mProcessCpuThread.start(); @@ -2254,6 +2281,11 @@ public final class ActivityManagerService extends ActivityManagerNative if (mFocusedActivity != r) { if (DEBUG_FOCUS) Slog.d(TAG, "setFocusedActivityLocked: r=" + r); mFocusedActivity = r; + if (r.task != null && r.task.voiceInteractor != null) { + startRunningVoiceLocked(); + } else { + finishRunningVoiceLocked(); + } mStackSupervisor.setFocusedStack(r); if (r != null) { mWindowManager.setFocusedApp(r.appToken, true); @@ -2262,6 +2294,12 @@ public final class ActivityManagerService extends ActivityManagerNative } } + final void clearFocusedActivity(ActivityRecord r) { + if (mFocusedActivity == r) { + mFocusedActivity = null; + } + } + @Override public void setFocusedStack(int stackId) { if (DEBUG_FOCUS) Slog.d(TAG, "setFocusedStack: stackId=" + stackId); @@ -2990,7 +3028,7 @@ public final class ActivityManagerService extends ActivityManagerNative intent.setComponent(new ComponentName( ri.activityInfo.packageName, ri.activityInfo.name)); mStackSupervisor.startActivityLocked(null, intent, null, ri.activityInfo, - null, null, 0, 0, 0, null, 0, null, false, null, null); + null, null, null, null, 0, 0, 0, null, 0, null, false, null, null); } } } @@ -3121,7 +3159,7 @@ public final class ActivityManagerService extends ActivityManagerNative } for (int i=0; i<N; i++) { PendingActivityLaunch pal = mPendingActivityLaunches.get(i); - mStackSupervisor.startActivityUncheckedLocked(pal.r, pal.sourceRecord, pal.startFlags, + mStackSupervisor.startActivityUncheckedLocked(pal.r, pal.sourceRecord, null, null, pal.startFlags, doResume && i == (N-1), null); } mPendingActivityLaunches.clear(); @@ -3147,7 +3185,7 @@ public final class ActivityManagerService extends ActivityManagerNative false, true, "startActivity", null); // TODO: Switch to user app stacks here. return mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType, - resultTo, resultWho, requestCode, startFlags, profileFile, profileFd, + null, null, resultTo, resultWho, requestCode, startFlags, profileFile, profileFd, null, null, options, userId, null); } @@ -3162,7 +3200,7 @@ public final class ActivityManagerService extends ActivityManagerNative WaitResult res = new WaitResult(); // TODO: Switch to user app stacks here. mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType, - resultTo, resultWho, requestCode, startFlags, profileFile, profileFd, + null, null, resultTo, resultWho, requestCode, startFlags, profileFile, profileFd, res, null, options, UserHandle.getCallingUserId(), null); return res; } @@ -3177,7 +3215,7 @@ public final class ActivityManagerService extends ActivityManagerNative false, true, "startActivityWithConfig", null); // TODO: Switch to user app stacks here. int ret = mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent, - resolvedType, resultTo, resultWho, requestCode, startFlags, + resolvedType, null, null, resultTo, resultWho, requestCode, startFlags, null, null, null, config, options, userId, null); return ret; } @@ -3215,6 +3253,31 @@ public final class ActivityManagerService extends ActivityManagerNative } @Override + public int startVoiceActivity(String callingPackage, int callingPid, int callingUid, + Intent intent, String resolvedType, IVoiceInteractionSession session, + IVoiceInteractor interactor, int startFlags, String profileFile, + ParcelFileDescriptor profileFd, Bundle options, int userId) { + if (checkCallingPermission(Manifest.permission.BIND_VOICE_INTERACTION) + != PackageManager.PERMISSION_GRANTED) { + String msg = "Permission Denial: startVoiceActivity() from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid() + + " requires " + android.Manifest.permission.BIND_VOICE_INTERACTION; + Slog.w(TAG, msg); + throw new SecurityException(msg); + } + if (session == null || interactor == null) { + throw new NullPointerException("null session or interactor"); + } + userId = handleIncomingUser(callingPid, callingUid, userId, + false, true, "startVoiceActivity", null); + // TODO: Switch to user app stacks here. + return mStackSupervisor.startActivityMayWait(null, callingUid, callingPackage, intent, + resolvedType, session, interactor, null, null, 0, startFlags, + profileFile, profileFd, null, null, options, userId, null); + } + + @Override public boolean startNextMatchingActivity(IBinder callingActivity, Intent intent, Bundle options) { // Refuse possible leaked file descriptors @@ -3307,7 +3370,7 @@ public final class ActivityManagerService extends ActivityManagerNative final long origId = Binder.clearCallingIdentity(); int res = mStackSupervisor.startActivityLocked(r.app.thread, intent, - r.resolvedType, aInfo, resultTo != null ? resultTo.appToken : null, + r.resolvedType, aInfo, null, null, resultTo != null ? resultTo.appToken : null, resultWho, requestCode, -1, r.launchedFromUid, r.launchedFromPackage, 0, options, false, null, null); Binder.restoreCallingIdentity(origId); @@ -3330,7 +3393,7 @@ public final class ActivityManagerService extends ActivityManagerNative // TODO: Switch to user app stacks here. int ret = mStackSupervisor.startActivityMayWait(null, uid, callingPackage, intent, resolvedType, - resultTo, resultWho, requestCode, startFlags, + null, null, resultTo, resultWho, requestCode, startFlags, null, null, null, null, options, userId, container); return ret; } @@ -3366,6 +3429,10 @@ public final class ActivityManagerService extends ActivityManagerNative if (N > 0 && mRecentTasks.get(0) == task) { return; } + // Another quick case: never add voice sessions. + if (task.voiceSession != null) { + return; + } // Remove any existing entries that are the same kind of task. final Intent intent = task.intent; final boolean document = intent != null && intent.isDocument(); @@ -8509,11 +8576,27 @@ public final class ActivityManagerService extends ActivityManagerNative return mSleeping || mShuttingDown; } + public boolean isSleeping() { + return mSleeping; + } + void goingToSleep() { synchronized(this) { mWentToSleep = true; updateEventDispatchingLocked(); + goToSleepIfNeededLocked(); + } + } + + void finishRunningVoiceLocked() { + if (mRunningVoice) { + mRunningVoice = false; + goToSleepIfNeededLocked(); + } + } + void goToSleepIfNeededLocked() { + if (mWentToSleep && !mRunningVoice) { if (!mSleeping) { mSleeping = true; mStackSupervisor.goingToSleepLocked(); @@ -8576,7 +8659,7 @@ public final class ActivityManagerService extends ActivityManagerNative } private void comeOutOfSleepIfNeededLocked() { - if (!mWentToSleep && !mLockScreenShown) { + if ((!mWentToSleep && !mLockScreenShown) || mRunningVoice) { if (mSleeping) { mSleeping = false; mStackSupervisor.comeOutOfSleepIfNeededLocked(); @@ -8592,6 +8675,13 @@ public final class ActivityManagerService extends ActivityManagerNative } } + void startRunningVoiceLocked() { + if (!mRunningVoice) { + mRunningVoice = true; + comeOutOfSleepIfNeededLocked(); + } + } + private void updateEventDispatchingLocked() { mWindowManager.setEventDispatching(mBooted && !mWentToSleep && !mShuttingDown); } @@ -9298,7 +9388,7 @@ public final class ActivityManagerService extends ActivityManagerNative proc.notCachedSinceIdle = true; proc.initialIdlePss = 0; proc.nextPssTime = ProcessList.computeNextPssTime(proc.curProcState, true, - mSleeping, now); + isSleeping(), now); } } @@ -9597,6 +9687,8 @@ public final class ActivityManagerService extends ActivityManagerNative if (goingCallback != null) goingCallback.run(); + mSystemServiceManager.startUser(mCurrentUserId); + synchronized (this) { if (mFactoryTest != FactoryTest.FACTORY_TEST_LOW_LEVEL) { try { @@ -9653,6 +9745,8 @@ public final class ActivityManagerService extends ActivityManagerNative }, 0, null, null, android.Manifest.permission.INTERACT_ACROSS_USERS, AppOpsManager.OP_NONE, true, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL); + } catch (Throwable t) { + Slog.wtf(TAG, "Failed sending first user broadcasts", t); } finally { Binder.restoreCallingIdentity(ident); } @@ -11152,8 +11246,8 @@ public final class ActivityManagerService extends ActivityManagerNative pw.println(" mSleeping=" + mSleeping + " mWentToSleep=" + mWentToSleep + " mLockScreenShown " + mLockScreenShown); } - if (mShuttingDown) { - pw.println(" mShuttingDown=" + mShuttingDown); + if (mShuttingDown || mRunningVoice) { + pw.print(" mShuttingDown=" + mShuttingDown + " mRunningVoice=" + mRunningVoice); } } if (mDebugApp != null || mOrigDebugApp != null || mDebugTransient @@ -15273,7 +15367,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (memLowered || now > (app.lastStateTime+ProcessList.PSS_ALL_INTERVAL)) { app.pssProcState = app.setProcState; app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState, true, - mSleeping, now); + isSleeping(), now); mPendingPssProcesses.add(app); } } @@ -15310,7 +15404,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } return !processingBroadcasts - && (mSleeping || mStackSupervisor.allResumedActivitiesIdle()); + && (isSleeping() || mStackSupervisor.allResumedActivitiesIdle()); } /** @@ -15585,7 +15679,7 @@ public final class ActivityManagerService extends ActivityManagerNative app.setProcState)) { app.lastStateTime = now; app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState, true, - mSleeping, now); + isSleeping(), now); if (DEBUG_PSS) Slog.d(TAG, "Process state change from " + ProcessList.makeProcStateString(app.setProcState) + " to " + ProcessList.makeProcStateString(app.curProcState) + " next pss in " @@ -15595,7 +15689,7 @@ public final class ActivityManagerService extends ActivityManagerNative && now > (app.lastStateTime+ProcessList.PSS_MIN_TIME_FROM_STATE_CHANGE))) { requestPssLocked(app, app.setProcState); app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState, false, - mSleeping, now); + isSleeping(), now); } else if (false && DEBUG_PSS) { Slog.d(TAG, "Not requesting PSS of " + app + ": next=" + (app.nextPssTime-now)); } @@ -15932,7 +16026,7 @@ public final class ActivityManagerService extends ActivityManagerNative } mLastMemoryLevel = memFactor; mLastNumProcesses = mLruProcesses.size(); - boolean allChanged = mProcessStats.setMemFactorLocked(memFactor, !mSleeping, now); + boolean allChanged = mProcessStats.setMemFactorLocked(memFactor, !isSleeping(), now); final int trackerMemFactor = mProcessStats.getMemFactorLocked(); if (memFactor != ProcessStats.ADJ_MEM_FACTOR_NORMAL) { if (mLowRamStartTime == 0) { @@ -16475,7 +16569,15 @@ public final class ActivityManagerService extends ActivityManagerNative needStart = true; } + if (uss.mState == UserStartedState.STATE_BOOTING) { + // Booting up a new user, need to tell system services about it. + // Note that this is on the same handler as scheduling of broadcasts, + // which is important because it needs to go first. + mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_START_MSG, userId)); + } + if (foreground) { + mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_CURRENT_MSG, userId)); mHandler.removeMessages(REPORT_USER_SWITCH_MSG); mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG); mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_MSG, @@ -16830,6 +16932,7 @@ public final class ActivityManagerService extends ActivityManagerNative } uss.mState = UserStartedState.STATE_SHUTDOWN; } + mSystemServiceManager.stopUser(userId); broadcastIntentLocked(null, null, shutdownIntent, null, shutdownReceiver, 0, null, null, null, AppOpsManager.OP_NONE, true, false, MY_PID, Process.SYSTEM_UID, userId); @@ -16879,7 +16982,12 @@ public final class ActivityManagerService extends ActivityManagerNative } } - mStackSupervisor.removeUserLocked(userId); + if (stopped) { + mSystemServiceManager.cleanupUser(userId); + synchronized (this) { + mStackSupervisor.removeUserLocked(userId); + } + } } @Override diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index 3e59def..7a44473 100755 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -633,7 +633,7 @@ final class ActivityRecord { // case we will deliver it if this is the current top activity on its // stack. boolean unsent = true; - if ((state == ActivityState.RESUMED || (service.mSleeping + if ((state == ActivityState.RESUMED || (service.isSleeping() && task.stack.topRunningActivityLocked(null) == this)) && app != null && app.thread != null) { try { diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index a2a9336..bea926f 100755 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -38,6 +38,8 @@ import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID; import static com.android.server.am.ActivityStackSupervisor.ActivityContainer.CONTAINER_STATE_HAS_SURFACE; +import android.service.voice.IVoiceInteractionSession; +import com.android.internal.app.IVoiceInteractor; import com.android.internal.os.BatteryStatsImpl; import com.android.server.Watchdog; import com.android.server.am.ActivityManagerService.ItemMatcher; @@ -501,6 +503,11 @@ final class ActivityStack { if (DEBUG_TASKS) Slog.d(TAG, "Looking for task of " + target + " in " + this); for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { final TaskRecord task = mTaskHistory.get(taskNdx); + if (task.voiceSession != null) { + // We never match voice sessions; those always run independently. + if (DEBUG_TASKS) Slog.d(TAG, "Skipping " + task + ": voice session"); + continue; + } if (task.userId != userId) { // Looking for a different task. if (DEBUG_TASKS) Slog.d(TAG, "Skipping " + task + ": different user"); @@ -1503,7 +1510,7 @@ final class ActivityStack { // If the most recent activity was noHistory but was only stopped rather // than stopped+finished because the device went to sleep, we need to make // sure to finish it as we're making a new activity topmost. - if (mService.mSleeping && mLastNoHistoryActivity != null && + if (mService.isSleeping() && mLastNoHistoryActivity != null && !mLastNoHistoryActivity.finishing) { if (DEBUG_STATES) Slog.d(TAG, "no-history finish of " + mLastNoHistoryActivity + " on new resume"); @@ -2034,7 +2041,7 @@ final class ActivityStack { + " out to bottom task " + bottom.task); } else { targetTask = createTaskRecord(mStackSupervisor.getNextTaskId(), target.info, - null, false); + null, null, null, false); newThumbHolder = targetTask; targetTask.affinityIntent = target.intent; if (DEBUG_TASKS) Slog.v(TAG, "Start pushing activity " + target @@ -2331,7 +2338,7 @@ final class ActivityStack { if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_HISTORY) != 0 || (r.info.flags&ActivityInfo.FLAG_NO_HISTORY) != 0) { if (!r.finishing) { - if (!mService.mSleeping) { + if (!mService.isSleeping()) { if (DEBUG_STATES) { Slog.d(TAG, "no-history finish of " + r); } @@ -2710,7 +2717,7 @@ final class ActivityStack { ActivityInfo aInfo = AppGlobals.getPackageManager().getActivityInfo( destIntent.getComponent(), 0, srec.userId); int res = mStackSupervisor.startActivityLocked(srec.app.thread, destIntent, - null, aInfo, parent.appToken, null, + null, aInfo, null, null, parent.appToken, null, 0, -1, parent.launchedFromUid, parent.launchedFromPackage, 0, null, true, null, null); foundParentInTask = res == ActivityManager.START_SUCCESS; @@ -2739,9 +2746,7 @@ final class ActivityStack { if (mPausingActivity == r) { mPausingActivity = null; } - if (mService.mFocusedActivity == r) { - mService.mFocusedActivity = null; - } + mService.clearFocusedActivity(r); r.configDestroy = false; r.frozenBeforeDestroy = false; @@ -3723,6 +3728,11 @@ final class ActivityStack { mTaskHistory.get(taskNdx + 1).mOnTopOfHome = true; } mTaskHistory.remove(task); + if (task.voiceInteractor != null) { + // This task was a voice interaction, so it should not remain on the + // recent tasks list. + mService.mRecentTasks.remove(task); + } if (mTaskHistory.isEmpty()) { if (DEBUG_STACK) Slog.i(TAG, "removeTask: moving to back stack=" + this); @@ -3736,8 +3746,10 @@ final class ActivityStack { } } - TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent, boolean toTop) { - TaskRecord task = new TaskRecord(taskId, info, intent); + TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent, + IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, + boolean toTop) { + TaskRecord task = new TaskRecord(taskId, info, intent, voiceSession, voiceInteractor); addTask(task, toTop); return task; } diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index e044d3f..274e011 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -17,7 +17,6 @@ package com.android.server.am; import static android.Manifest.permission.START_ANY_ACTIVITY; -import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME; import static android.content.pm.PackageManager.PERMISSION_GRANTED; @@ -77,6 +76,7 @@ import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; +import android.service.voice.IVoiceInteractionSession; import android.util.EventLog; import android.util.Slog; import android.util.SparseArray; @@ -87,6 +87,7 @@ import android.view.DisplayInfo; import android.view.InputEvent; import android.view.Surface; import com.android.internal.app.HeavyWeightSwitcherActivity; +import com.android.internal.app.IVoiceInteractor; import com.android.internal.os.TransferPipe; import com.android.server.LocalServices; import com.android.server.am.ActivityManagerService.PendingActivityLaunch; @@ -687,13 +688,14 @@ public final class ActivityStackSupervisor implements DisplayListener { void startHomeActivity(Intent intent, ActivityInfo aInfo) { moveHomeToTop(); - startActivityLocked(null, intent, null, aInfo, null, null, 0, 0, 0, null, 0, + startActivityLocked(null, intent, null, aInfo, null, null, null, null, 0, 0, 0, null, 0, null, false, null, null); } final int startActivityMayWait(IApplicationThread caller, int callingUid, - String callingPackage, Intent intent, String resolvedType, IBinder resultTo, - String resultWho, int requestCode, int startFlags, String profileFile, + String callingPackage, Intent intent, String resolvedType, + IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, + IBinder resultTo, String resultWho, int requestCode, int startFlags, String profileFile, ParcelFileDescriptor profileFd, WaitResult outResult, Configuration config, Bundle options, int userId, IActivityContainer iContainer) { // Refuse possible leaked file descriptors @@ -802,7 +804,8 @@ public final class ActivityStackSupervisor implements DisplayListener { } } - int res = startActivityLocked(caller, intent, resolvedType, aInfo, resultTo, resultWho, + int res = startActivityLocked(caller, intent, resolvedType, aInfo, + voiceSession, voiceInteractor, resultTo, resultWho, requestCode, callingPid, callingUid, callingPackage, startFlags, options, componentSpecified, null, container); @@ -918,7 +921,7 @@ public final class ActivityStackSupervisor implements DisplayListener { theseOptions = null; } int res = startActivityLocked(caller, intent, resolvedTypes[i], - aInfo, resultTo, null, -1, callingPid, callingUid, callingPackage, + aInfo, null, null, resultTo, null, -1, callingPid, callingUid, callingPackage, 0, theseOptions, componentSpecified, outActivity, null); if (res < 0) { return res; @@ -1034,8 +1037,8 @@ public final class ActivityStackSupervisor implements DisplayListener { app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken, System.identityHashCode(r), r.info, new Configuration(mService.mConfiguration), r.compat, - app.repProcState, r.icicle, results, newIntents, !andResume, - mService.isNextTransitionForward(), profileFile, profileFd, + r.task.voiceInteractor, app.repProcState, r.icicle, results, newIntents, + !andResume, mService.isNextTransitionForward(), profileFile, profileFd, profileAutoStop, options); if ((app.info.flags&ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) { @@ -1143,8 +1146,9 @@ public final class ActivityStackSupervisor implements DisplayListener { } final int startActivityLocked(IApplicationThread caller, - Intent intent, String resolvedType, ActivityInfo aInfo, IBinder resultTo, - String resultWho, int requestCode, + Intent intent, String resolvedType, ActivityInfo aInfo, + IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, + IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid, String callingPackage, int startFlags, Bundle options, boolean componentSpecified, ActivityRecord[] outActivity, ActivityContainer container) { int err = ActivityManager.START_SUCCESS; @@ -1187,7 +1191,7 @@ public final class ActivityStackSupervisor implements DisplayListener { } ActivityStack resultStack = resultRecord == null ? null : resultRecord.task.stack; - int launchFlags = intent.getFlags(); + final int launchFlags = intent.getFlags(); if ((launchFlags&Intent.FLAG_ACTIVITY_FORWARD_RESULT) != 0 && sourceRecord != null) { @@ -1232,6 +1236,38 @@ public final class ActivityStackSupervisor implements DisplayListener { err = ActivityManager.START_CLASS_NOT_FOUND; } + if (err == ActivityManager.START_SUCCESS && sourceRecord != null + && sourceRecord.task.voiceSession != null) { + // If this activity is being launched as part of a voice session, we need + // to ensure that it is safe to do so. If the upcoming activity will also + // be part of the voice session, we can only launch it if it has explicitly + // said it supports the VOICE category, or it is a part of the calling app. + if ((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) == 0 + && sourceRecord.info.applicationInfo.uid != aInfo.applicationInfo.uid) { + try { + if (!AppGlobals.getPackageManager().activitySupportsIntent(intent.getComponent(), + intent, resolvedType)) { + err = ActivityManager.START_NOT_VOICE_COMPATIBLE; + } + } catch (RemoteException e) { + err = ActivityManager.START_NOT_VOICE_COMPATIBLE; + } + } + } + + if (err == ActivityManager.START_SUCCESS && voiceSession != null) { + // If the caller is starting a new voice session, just make sure the target + // is actually allowing it to run this way. + try { + if (!AppGlobals.getPackageManager().activitySupportsIntent(intent.getComponent(), + intent, resolvedType)) { + err = ActivityManager.START_NOT_VOICE_COMPATIBLE; + } + } catch (RemoteException e) { + err = ActivityManager.START_NOT_VOICE_COMPATIBLE; + } + } + if (err != ActivityManager.START_SUCCESS) { if (resultRecord != null) { resultStack.sendActivityResultLocked(-1, @@ -1305,8 +1341,8 @@ public final class ActivityStackSupervisor implements DisplayListener { } final ActivityStack stack = getFocusedStack(); - if (stack.mResumedActivity == null - || stack.mResumedActivity.info.applicationInfo.uid != callingUid) { + if (voiceSession == null && (stack.mResumedActivity == null + || stack.mResumedActivity.info.applicationInfo.uid != callingUid)) { if (!mService.checkAppSwitchAllowedLocked(callingPid, callingUid, "Activity start")) { PendingActivityLaunch pal = new PendingActivityLaunch(r, sourceRecord, startFlags, stack); @@ -1330,7 +1366,8 @@ public final class ActivityStackSupervisor implements DisplayListener { mService.doPendingActivityLaunchesLocked(false); - err = startActivityUncheckedLocked(r, sourceRecord, startFlags, true, options); + err = startActivityUncheckedLocked(r, sourceRecord, voiceSession, voiceInteractor, + startFlags, true, options); if (allPausedActivitiesComplete()) { // If someone asked to have the keyguard dismissed on the next @@ -1410,8 +1447,9 @@ public final class ActivityStackSupervisor implements DisplayListener { } final int startActivityUncheckedLocked(ActivityRecord r, - ActivityRecord sourceRecord, int startFlags, boolean doResume, - Bundle options) { + ActivityRecord sourceRecord, + IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, int startFlags, + boolean doResume, Bundle options) { final Intent intent = r.intent; final int callingUid = r.launchedFromUid; @@ -1755,7 +1793,7 @@ public final class ActivityStackSupervisor implements DisplayListener { r.setTask(targetStack.createTaskRecord(getNextTaskId(), newTaskInfo != null ? newTaskInfo : r.info, newTaskIntent != null ? newTaskIntent : intent, - true), null, true); + voiceSession, voiceInteractor, true), null, true); if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + " in new task " + r.task); } else { @@ -1833,7 +1871,7 @@ public final class ActivityStackSupervisor implements DisplayListener { targetStack.moveToFront(); ActivityRecord prev = targetStack.topActivity(); r.setTask(prev != null ? prev.task - : targetStack.createTaskRecord(getNextTaskId(), r.info, intent, true), + : targetStack.createTaskRecord(getNextTaskId(), r.info, intent, null, null, true), null, true); mWindowManager.moveTaskToTop(r.task.taskId); if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r @@ -3104,7 +3142,7 @@ public final class ActivityStackSupervisor implements DisplayListener { && "content".equals(intent.getData().getScheme())) { mimeType = mService.getProviderMimeType(intent.getData(), userId); } - return startActivityMayWait(null, -1, null, intent, mimeType, null, null, 0, 0, null, + return startActivityMayWait(null, -1, null, intent, mimeType, null, null, null, null, 0, 0, null, null, null, null, null, userId, this); } diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java index 80a219d..68da54d 100644 --- a/services/core/java/com/android/server/am/TaskRecord.java +++ b/services/core/java/com/android/server/am/TaskRecord.java @@ -28,7 +28,9 @@ import android.content.Intent; import android.content.pm.ActivityInfo; import android.graphics.Bitmap; import android.os.UserHandle; +import android.service.voice.IVoiceInteractionSession; import android.util.Slog; +import com.android.internal.app.IVoiceInteractor; import java.io.PrintWriter; import java.util.ArrayList; @@ -36,6 +38,8 @@ import java.util.ArrayList; final class TaskRecord extends ThumbnailHolder { final int taskId; // Unique identifier for this task. final String affinity; // The affinity name for this task, or null. + final IVoiceInteractionSession voiceSession; // Voice interaction session driving task + final IVoiceInteractor voiceInteractor; // Associated interactor to provide to app Intent intent; // The original intent that started the task. Intent affinityIntent; // Intent of affinity-moved activity that started this task. ComponentName origActivity; // The non-alias activity component of the intent. @@ -64,9 +68,12 @@ final class TaskRecord extends ThumbnailHolder { * Display.DEFAULT_DISPLAY. */ boolean mOnTopOfHome = false; - TaskRecord(int _taskId, ActivityInfo info, Intent _intent) { + TaskRecord(int _taskId, ActivityInfo info, Intent _intent, + IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor) { taskId = _taskId; affinity = info.taskAffinity; + voiceSession = _voiceSession; + voiceInteractor = _voiceInteractor; setIntent(_intent, info); } @@ -473,6 +480,12 @@ final class TaskRecord extends ThumbnailHolder { if (affinity != null) { pw.print(prefix); pw.print("affinity="); pw.println(affinity); } + if (voiceSession != null || voiceInteractor != null) { + pw.print(prefix); pw.print("VOICE: session=0x"); + pw.print(Integer.toHexString(System.identityHashCode(voiceSession))); + pw.print(" interactor=0x"); + pw.println(Integer.toHexString(System.identityHashCode(voiceInteractor))); + } if (intent != null) { StringBuilder sb = new StringBuilder(128); sb.append(prefix); sb.append("intent={"); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 7683205..ad07084 100755 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -2160,6 +2160,24 @@ public class PackageManagerService extends IPackageManager.Stub { } @Override + public boolean activitySupportsIntent(ComponentName component, Intent intent, + String resolvedType) { + synchronized (mPackages) { + PackageParser.Activity a = mActivities.mActivities.get(component); + if (a == null) { + return false; + } + for (int i=0; i<a.intents.size(); i++) { + if (a.intents.get(i).match(intent.getAction(), resolvedType, intent.getScheme(), + intent.getData(), intent.getCategories(), TAG) >= 0) { + return true; + } + } + return false; + } + } + + @Override public ActivityInfo getReceiverInfo(ComponentName component, int flags, int userId) { if (!sUserManager.exists(userId)) return null; enforceCrossUserPermission(Binder.getCallingUid(), userId, false, "get receiver info"); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index f08d69f..2497466 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -114,6 +114,8 @@ public final class SystemServer { "com.android.server.devicepolicy.DevicePolicyManagerService$Lifecycle"; private static final String APPWIDGET_SERVICE_CLASS = "com.android.server.appwidget.AppWidgetService"; + private static final String VOICE_RECOGNITION_MANAGER_SERVICE_CLASS = + "com.android.server.voiceinteraction.VoiceInteractionManagerService"; private static final String PRINT_MANAGER_SERVICE_CLASS = "com.android.server.print.PrintManagerService"; private static final String USB_SERVICE_CLASS = @@ -290,6 +292,7 @@ public final class SystemServer { // Activity manager runs the show. mActivityManagerService = mSystemServiceManager.startService( ActivityManagerService.Lifecycle.class).getService(); + mActivityManagerService.setSystemServiceManager(mSystemServiceManager); } private void startCoreServices() { @@ -826,6 +829,15 @@ public final class SystemServer { } catch (Throwable e) { reportWtf("starting Recognition Service", e); } + + try { + if (pm.hasSystemFeature(PackageManager.FEATURE_VOICE_RECOGNIZERS)) { + Slog.i(TAG, "Voice Recognition Service"); + mSystemServiceManager.startService(VOICE_RECOGNITION_MANAGER_SERVICE_CLASS); + } + } catch (Throwable e) { + reportWtf("starting Voice Recognition Service", e); + } } try { diff --git a/services/voiceinteraction/Android.mk b/services/voiceinteraction/Android.mk new file mode 100644 index 0000000..c9e5dd0 --- /dev/null +++ b/services/voiceinteraction/Android.mk @@ -0,0 +1,12 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := services.voiceinteraction + +LOCAL_SRC_FILES += \ + $(call all-java-files-under,java) + +LOCAL_JAVA_LIBRARIES := services.core + +include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java new file mode 100644 index 0000000..9e2bcab --- /dev/null +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -0,0 +1,209 @@ +/* + * 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. + */ + +package com.android.server.voiceinteraction; + +import android.app.ActivityManager; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.database.ContentObserver; +import android.os.Binder; +import android.os.Handler; +import android.os.UserHandle; +import android.provider.Settings; +import android.service.voice.IVoiceInteractionService; +import android.service.voice.IVoiceInteractionSession; +import android.util.Slog; +import com.android.internal.app.IVoiceInteractionManagerService; +import com.android.internal.app.IVoiceInteractor; +import com.android.internal.content.PackageMonitor; +import com.android.internal.os.BackgroundThread; +import com.android.server.SystemService; +import com.android.server.UiThread; + +import java.io.FileDescriptor; +import java.io.PrintWriter; + + +/** + * SystemService that publishes an IVoiceInteractionManagerService. + */ +public class VoiceInteractionManagerService extends SystemService { + + static final String TAG = "VoiceInteractionManagerService"; + + final Context mContext; + final ContentResolver mResolver; + + public VoiceInteractionManagerService(Context context) { + super(context); + mContext = context; + mResolver = context.getContentResolver(); + } + + @Override + public void onStart() { + publishBinderService(Context.VOICE_INTERACTION_MANAGER_SERVICE, mServiceStub); + } + + @Override + public void onBootPhase(int phase) { + if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) { + mServiceStub.systemRunning(isSafeMode()); + } + } + + @Override + public void onSwitchUser(int userHandle) { + mServiceStub.switchUser(userHandle); + } + + // implementation entry point and binder service + private final VoiceInteractionManagerServiceStub mServiceStub + = new VoiceInteractionManagerServiceStub(); + + class VoiceInteractionManagerServiceStub extends IVoiceInteractionManagerService.Stub { + + VoiceInteractionManagerServiceImpl mImpl; + + private boolean mSafeMode; + private int mCurUser; + + public void systemRunning(boolean safeMode) { + mSafeMode = safeMode; + + mPackageMonitor.register(mContext, BackgroundThread.getHandler().getLooper(), + UserHandle.ALL, true); + new SettingsObserver(UiThread.getHandler()); + + synchronized (this) { + mCurUser = ActivityManager.getCurrentUser(); + switchImplementationIfNeededLocked(); + } + } + + public void switchUser(int userHandle) { + synchronized (this) { + mCurUser = userHandle; + switchImplementationIfNeededLocked(); + } + } + + void switchImplementationIfNeededLocked() { + if (!mSafeMode) { + String curService = Settings.Secure.getStringForUser( + mResolver, Settings.Secure.VOICE_INTERACTION_SERVICE, mCurUser); + ComponentName serviceComponent = null; + if (curService != null && !curService.isEmpty()) { + try { + serviceComponent = ComponentName.unflattenFromString(curService); + } catch (RuntimeException e) { + Slog.wtf(TAG, "Bad voice interaction service name " + curService, e); + serviceComponent = null; + } + } + if (mImpl == null || mImpl.mUser != mCurUser + || !mImpl.mComponent.equals(serviceComponent)) { + if (mImpl != null) { + mImpl.shutdownLocked(); + } + if (serviceComponent != null) { + mImpl = new VoiceInteractionManagerServiceImpl(mContext, + UiThread.getHandler(), this, mCurUser, serviceComponent); + mImpl.startLocked(); + } else { + mImpl = null; + } + } + } + } + + @Override + public int startVoiceActivity(Intent intent, String resolvedType, + IVoiceInteractionService service, + IVoiceInteractionSession session, IVoiceInteractor interactor) { + synchronized (this) { + if (mImpl == null || service.asBinder() != mImpl.mService.asBinder()) { + throw new SecurityException( + "Caller is not the current voice interaction service"); + } + final int callingPid = Binder.getCallingPid(); + final int callingUid = Binder.getCallingUid(); + final long caller = Binder.clearCallingIdentity(); + try { + return mImpl.startVoiceActivityLocked(callingPid, callingUid, + intent, resolvedType, session, interactor); + } finally { + Binder.restoreCallingIdentity(caller); + } + } + } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); + } + + class SettingsObserver extends ContentObserver { + SettingsObserver(Handler handler) { + super(handler); + ContentResolver resolver = mContext.getContentResolver(); + resolver.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.VOICE_INTERACTION_SERVICE), false, this); + } + + @Override public void onChange(boolean selfChange) { + synchronized (VoiceInteractionManagerServiceStub.this) { + switchImplementationIfNeededLocked(); + } + } + } + + PackageMonitor mPackageMonitor = new PackageMonitor() { + @Override + public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) { + return super.onHandleForceStop(intent, packages, uid, doit); + } + + @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); + } + + @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 new file mode 100644 index 0000000..af8ae1e --- /dev/null +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java @@ -0,0 +1,125 @@ +/* + * 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. + */ + +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.content.pm.PackageManager; +import android.content.pm.ServiceInfo; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.UserHandle; +import android.service.voice.IVoiceInteractionService; +import android.service.voice.IVoiceInteractionSession; +import android.service.voice.VoiceInteractionService; +import android.util.Slog; +import com.android.internal.app.IVoiceInteractor; + +class VoiceInteractionManagerServiceImpl { + final static String TAG = "VoiceInteractionServiceManager"; + + final Context mContext; + final Handler mHandler; + final Object mLock; + final int mUser; + final ComponentName mComponent; + final IActivityManager mAm; + boolean mBound = false; + IVoiceInteractionService mService; + IVoiceInteractionSession mActiveSession; + IVoiceInteractor mActiveInteractor; + + final ServiceConnection mConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + synchronized (mLock) { + mService = IVoiceInteractionService.Stub.asInterface(service); + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + mService = null; + } + }; + + VoiceInteractionManagerServiceImpl(Context context, Handler handler, Object lock, + int userHandle, ComponentName service) { + mContext = context; + mHandler = handler; + mLock = lock; + mUser = userHandle; + mComponent = service; + mAm = ActivityManagerNative.getDefault(); + } + + public int startVoiceActivityLocked(int callingPid, int callingUid, Intent intent, + String resolvedType, IVoiceInteractionSession session, IVoiceInteractor interactor) { + if (session == null) { + throw new NullPointerException("session is null"); + } + if (interactor == null) { + throw new NullPointerException("interactor is null"); + } + if (mActiveSession != null) { + // XXX cancel current session. + } + intent.addCategory(Intent.CATEGORY_VOICE); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK); + mActiveSession = session; + mActiveInteractor = interactor; + try { + return mAm.startVoiceActivity(mComponent.getPackageName(), callingPid, callingUid, + intent, resolvedType, mActiveSession, mActiveInteractor, + 0, null, null, null, mUser); + } catch (RemoteException e) { + throw new IllegalStateException("Unexpected remote error", e); + } + } + + void startLocked() { + Intent intent = new Intent(VoiceInteractionService.SERVICE_INTERFACE); + intent.setComponent(mComponent); + try { + ServiceInfo si = mContext.getPackageManager().getServiceInfo(mComponent, 0); + if (!android.Manifest.permission.BIND_VOICE_INTERACTION.equals(si.permission)) { + Slog.w(TAG, "Not using voice interaction service " + mComponent + + ": does not require permission " + + android.Manifest.permission.BIND_VOICE_INTERACTION); + return; + } + } catch (PackageManager.NameNotFoundException e) { + Slog.w(TAG, "Unable to find voice interaction service: " + mComponent, e); + return; + } + mContext.bindServiceAsUser(intent, mConnection, + Context.BIND_AUTO_CREATE, new UserHandle(mUser)); + mBound = true; + } + + void shutdownLocked() { + if (mBound) { + mContext.unbindService(mConnection); + mBound = false; + } + } +} diff --git a/tests/VoiceInteraction/Android.mk b/tests/VoiceInteraction/Android.mk new file mode 100644 index 0000000..8decca7 --- /dev/null +++ b/tests/VoiceInteraction/Android.mk @@ -0,0 +1,10 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests + +LOCAL_SRC_FILES := $(call all-subdir-java-files) + +LOCAL_PACKAGE_NAME := VoiceInteraction + +include $(BUILD_PACKAGE) diff --git a/tests/VoiceInteraction/AndroidManifest.xml b/tests/VoiceInteraction/AndroidManifest.xml new file mode 100644 index 0000000..9c5acf9 --- /dev/null +++ b/tests/VoiceInteraction/AndroidManifest.xml @@ -0,0 +1,27 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.test.voiceinteraction"> + + <application> + <activity android:name="VoiceInteractionMain" android:label="Voice Interaction"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + <service android:name="MainInteractionService" + android:permission="android.permission.BIND_VOICE_INTERACTION" + android:process=":interactor"> + <intent-filter> + <action android:name="android.service.voice.VoiceInteractionService" /> + </intent-filter> + </service> + <activity android:name="TestInteractionActivity" android:label="Voice Interaction Target"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.VOICE" /> + </intent-filter> + </activity> + </application> +</manifest> diff --git a/tests/VoiceInteraction/res/layout/main.xml b/tests/VoiceInteraction/res/layout/main.xml new file mode 100644 index 0000000..3d7a418 --- /dev/null +++ b/tests/VoiceInteraction/res/layout/main.xml @@ -0,0 +1,31 @@ +<?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="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + > + + <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/res/layout/test_interaction.xml b/tests/VoiceInteraction/res/layout/test_interaction.xml new file mode 100644 index 0000000..2abf651 --- /dev/null +++ b/tests/VoiceInteraction/res/layout/test_interaction.xml @@ -0,0 +1,37 @@ +<?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="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + > + + <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="We are interacting!" + /> + + <TextView android:id="@+id/log" + android:layout_width="match_parent" + android:layout_height="0px" + android:layout_weight="1" + android:layout_marginTop="10dp" + android:textSize="12sp" + android:textColor="#ffffffff" + /> + +</LinearLayout> diff --git a/tests/VoiceInteraction/res/values/strings.xml b/tests/VoiceInteraction/res/values/strings.xml new file mode 100644 index 0000000..12edb31 --- /dev/null +++ b/tests/VoiceInteraction/res/values/strings.xml @@ -0,0 +1,22 @@ +<?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. +--> + +<resources> + + <string name="start">Start!</string> + +</resources> + diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java new file mode 100644 index 0000000..35702f1 --- /dev/null +++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java @@ -0,0 +1,39 @@ +/* + * 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. + */ + +package com.android.test.voiceinteraction; + +import android.content.Intent; +import android.service.voice.VoiceInteractionService; +import android.util.Log; + +public class MainInteractionService extends VoiceInteractionService { + static final String TAG = "MainInteractionService"; + + @Override + public void onCreate() { + super.onCreate(); + Log.i(TAG, "Creating " + this); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + startVoiceActivity(new Intent(this, TestInteractionActivity.class), + new MainInteractionSession(this)); + 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 new file mode 100644 index 0000000..adc0df4 --- /dev/null +++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java @@ -0,0 +1,53 @@ +/* + * 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. + */ + +package com.android.test.voiceinteraction; + +import android.content.Context; +import android.os.Bundle; +import android.service.voice.VoiceInteractionSession; +import android.util.Log; + +public class MainInteractionSession extends VoiceInteractionSession { + static final String TAG = "MainInteractionSession"; + + MainInteractionSession(Context context) { + super(context); + } + + @Override + public boolean[] onGetSupportedCommands(Caller caller, String[] commands) { + return new boolean[commands.length]; + } + + @Override + public void onConfirm(Caller caller, Request request, String prompt, Bundle extras) { + Log.i(TAG, "onConform: prompt=" + prompt + " extras=" + extras); + request.sendConfirmResult(true, null); + } + + @Override + public void onCommand(Caller caller, Request request, String command, Bundle extras) { + Log.i(TAG, "onCommand: command=" + command + " extras=" + extras); + request.sendCommandResult(null); + } + + @Override + public void onCancel(Request request) { + Log.i(TAG, "onCancel"); + request.sendCancelResult(); + } +} diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java new file mode 100644 index 0000000..016a80e --- /dev/null +++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java @@ -0,0 +1,66 @@ +/* + * 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. + */ + +package com.android.test.voiceinteraction; + +import android.app.Activity; +import android.app.VoiceInteractor; +import android.os.Bundle; +import android.util.Log; + +public class TestInteractionActivity extends Activity { + static final String TAG = "TestInteractionActivity"; + + VoiceInteractor mInteractor; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.test_interaction); + if (!isVoiceInteraction()) { + Log.w(TAG, "Not running as a voice interaction!"); + finish(); + return; + } + + mInteractor = getVoiceInteractor(); + mInteractor.startConfirmation(new VoiceInteractor.Callback() { + @Override + public void onConfirmationResult(VoiceInteractor.Request request, boolean confirmed, + Bundle result) { + Log.i(TAG, "Confirmation result: confirmed=" + confirmed + " result=" + result); + finish(); + } + + @Override + public void onCancel(VoiceInteractor.Request request) { + Log.i(TAG, "Canceled!"); + finish(); + } + }, "This is a confirmation", null); + } + + @Override + public void onResume() { + super.onResume(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + } +} diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/VoiceInteractionMain.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/VoiceInteractionMain.java new file mode 100644 index 0000000..5d212a4 --- /dev/null +++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/VoiceInteractionMain.java @@ -0,0 +1,49 @@ +/* + * 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. + */ + +package com.android.test.voiceinteraction; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; + +public class VoiceInteractionMain extends Activity { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.main); + findViewById(R.id.start).setOnClickListener(mStartListener); + } + + @Override + public void onResume() { + super.onResume(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + } + + View.OnClickListener mStartListener = new View.OnClickListener() { + public void onClick(View v) { + startService(new Intent(VoiceInteractionMain.this, MainInteractionService.class)); + } + }; +} |