diff options
Diffstat (limited to 'core/java')
26 files changed, 971 insertions, 13 deletions
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; |
