diff options
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)); + } + }; +} |