VoiceInteractor.java revision dcff99598f16737e75278e8ea9dbfe92579ea234
1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.app; 18 19import android.content.Context; 20import android.os.Bundle; 21import android.os.IBinder; 22import android.os.Looper; 23import android.os.Message; 24import android.os.RemoteException; 25import android.util.ArrayMap; 26import android.util.Log; 27import com.android.internal.app.IVoiceInteractor; 28import com.android.internal.app.IVoiceInteractorCallback; 29import com.android.internal.app.IVoiceInteractorRequest; 30import com.android.internal.os.HandlerCaller; 31import com.android.internal.os.SomeArgs; 32 33import java.util.WeakHashMap; 34 35/** 36 * Interface for an {@link Activity} to interact with the user through voice. 37 * @hide 38 */ 39public class VoiceInteractor { 40 static final String TAG = "VoiceInteractor"; 41 static final boolean DEBUG = true; 42 43 final Context mContext; 44 final Activity mActivity; 45 final IVoiceInteractor mInteractor; 46 final HandlerCaller mHandlerCaller; 47 final HandlerCaller.Callback mHandlerCallerCallback = new HandlerCaller.Callback() { 48 @Override 49 public void executeMessage(Message msg) { 50 SomeArgs args = (SomeArgs)msg.obj; 51 Request request; 52 switch (msg.what) { 53 case MSG_CONFIRMATION_RESULT: 54 request = pullRequest((IVoiceInteractorRequest)args.arg1, true); 55 if (DEBUG) Log.d(TAG, "onConfirmResult: req=" 56 + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request 57 + " confirmed=" + msg.arg1 + " result=" + args.arg2); 58 if (request != null) { 59 ((ConfirmationRequest)request).onConfirmationResult(msg.arg1 != 0, 60 (Bundle) args.arg2); 61 request.clear(); 62 } 63 break; 64 case MSG_COMMAND_RESULT: 65 request = pullRequest((IVoiceInteractorRequest)args.arg1, msg.arg1 != 0); 66 if (DEBUG) Log.d(TAG, "onCommandResult: req=" 67 + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request 68 + " result=" + args.arg2); 69 if (request != null) { 70 ((CommandRequest)request).onCommandResult((Bundle) args.arg2); 71 if (msg.arg1 != 0) { 72 request.clear(); 73 } 74 } 75 break; 76 case MSG_CANCEL_RESULT: 77 request = pullRequest((IVoiceInteractorRequest)args.arg1, true); 78 if (DEBUG) Log.d(TAG, "onCancelResult: req=" 79 + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request); 80 if (request != null) { 81 request.onCancel(); 82 request.clear(); 83 } 84 break; 85 } 86 } 87 }; 88 89 final IVoiceInteractorCallback.Stub mCallback = new IVoiceInteractorCallback.Stub() { 90 @Override 91 public void deliverConfirmationResult(IVoiceInteractorRequest request, boolean confirmed, 92 Bundle result) { 93 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOO( 94 MSG_CONFIRMATION_RESULT, confirmed ? 1 : 0, request, result)); 95 } 96 97 @Override 98 public void deliverCommandResult(IVoiceInteractorRequest request, boolean complete, 99 Bundle result) { 100 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOO( 101 MSG_COMMAND_RESULT, complete ? 1 : 0, request, result)); 102 } 103 104 @Override 105 public void deliverCancel(IVoiceInteractorRequest request) throws RemoteException { 106 mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO( 107 MSG_CANCEL_RESULT, request)); 108 } 109 }; 110 111 final ArrayMap<IBinder, Request> mActiveRequests = new ArrayMap<IBinder, Request>(); 112 113 static final int MSG_CONFIRMATION_RESULT = 1; 114 static final int MSG_COMMAND_RESULT = 2; 115 static final int MSG_CANCEL_RESULT = 3; 116 117 public static abstract class Request { 118 IVoiceInteractorRequest mRequestInterface; 119 Context mContext; 120 Activity mActivity; 121 122 public Request() { 123 } 124 125 public void cancel() { 126 try { 127 mRequestInterface.cancel(); 128 } catch (RemoteException e) { 129 Log.w(TAG, "Voice interactor has died", e); 130 } 131 } 132 133 public Context getContext() { 134 return mContext; 135 } 136 137 public Activity getActivity() { 138 return mActivity; 139 } 140 141 public void onCancel() { 142 } 143 144 void clear() { 145 mRequestInterface = null; 146 mContext = null; 147 mActivity = null; 148 } 149 150 abstract IVoiceInteractorRequest submit(IVoiceInteractor interactor, 151 String packageName, IVoiceInteractorCallback callback) throws RemoteException; 152 } 153 154 public static class ConfirmationRequest extends Request { 155 final CharSequence mPrompt; 156 final Bundle mExtras; 157 158 /** 159 * Confirms an operation with the user via the trusted system 160 * VoiceInteractionService. This allows an Activity to complete an unsafe operation that 161 * would require the user to touch the screen when voice interaction mode is not enabled. 162 * The result of the confirmation will be returned through an asynchronous call to 163 * either {@link #onConfirmationResult(boolean, android.os.Bundle)} or 164 * {@link #onCancel()}. 165 * 166 * <p>In some cases this may be a simple yes / no confirmation or the confirmation could 167 * include context information about how the action will be completed 168 * (e.g. booking a cab might include details about how long until the cab arrives) 169 * so the user can give a confirmation. 170 * @param prompt Optional confirmation text to read to the user as the action being 171 * confirmed. 172 * @param extras Additional optional information. 173 */ 174 public ConfirmationRequest(CharSequence prompt, Bundle extras) { 175 mPrompt = prompt; 176 mExtras = extras; 177 } 178 179 public void onConfirmationResult(boolean confirmed, Bundle result) { 180 } 181 182 IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName, 183 IVoiceInteractorCallback callback) throws RemoteException { 184 return interactor.startConfirmation(packageName, callback, mPrompt.toString(), mExtras); 185 } 186 } 187 188 public static class CommandRequest extends Request { 189 final String mCommand; 190 final Bundle mArgs; 191 192 /** 193 * Execute a command using the trusted system VoiceInteractionService. 194 * This allows an Activity to request additional information from the user needed to 195 * complete an action (e.g. booking a table might have several possible times that the 196 * user could select from or an app might need the user to agree to a terms of service). 197 * The result of the confirmation will be returned through an asynchronous call to 198 * either {@link #onCommandResult(android.os.Bundle)} or 199 * {@link #onCancel()}. 200 * 201 * <p>The command is a string that describes the generic operation to be performed. 202 * The command will determine how the properties in extras are interpreted and the set of 203 * available commands is expected to grow over time. An example might be 204 * "com.google.voice.commands.REQUEST_NUMBER_BAGS" to request the number of bags as part of 205 * airline check-in. (This is not an actual working example.) 206 * 207 * @param command The desired command to perform. 208 * @param args Additional arguments to control execution of the command. 209 */ 210 public CommandRequest(String command, Bundle args) { 211 mCommand = command; 212 mArgs = args; 213 } 214 215 public void onCommandResult(Bundle result) { 216 } 217 218 IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName, 219 IVoiceInteractorCallback callback) throws RemoteException { 220 return interactor.startConfirmation(packageName, callback, mCommand, mArgs); 221 } 222 } 223 224 VoiceInteractor(Context context, Activity activity, IVoiceInteractor interactor, 225 Looper looper) { 226 mContext = context; 227 mActivity = activity; 228 mInteractor = interactor; 229 mHandlerCaller = new HandlerCaller(context, looper, mHandlerCallerCallback, true); 230 } 231 232 Request pullRequest(IVoiceInteractorRequest request, boolean complete) { 233 synchronized (mActiveRequests) { 234 Request req = mActiveRequests.get(request.asBinder()); 235 if (req != null && complete) { 236 mActiveRequests.remove(request.asBinder()); 237 } 238 return req; 239 } 240 } 241 242 public boolean submitRequest(Request request) { 243 try { 244 IVoiceInteractorRequest ireq = request.submit(mInteractor, 245 mContext.getOpPackageName(), mCallback); 246 request.mRequestInterface = ireq; 247 request.mContext = mContext; 248 request.mActivity = mActivity; 249 synchronized (mActiveRequests) { 250 mActiveRequests.put(ireq.asBinder(), request); 251 } 252 return true; 253 } catch (RemoteException e) { 254 Log.w(TAG, "Remove voice interactor service died", e); 255 return false; 256 } 257 } 258 259 /** 260 * Queries the supported commands available from the VoiceinteractionService. 261 * The command is a string that describes the generic operation to be performed. 262 * An example might be "com.google.voice.commands.REQUEST_NUMBER_BAGS" to request the number 263 * of bags as part of airline check-in. (This is not an actual working example.) 264 * 265 * @param commands 266 */ 267 public boolean[] supportsCommands(String[] commands) { 268 try { 269 boolean[] res = mInteractor.supportsCommands(mContext.getOpPackageName(), commands); 270 if (DEBUG) Log.d(TAG, "supportsCommands: cmds=" + commands + " res=" + res); 271 return res; 272 } catch (RemoteException e) { 273 throw new RuntimeException("Voice interactor has died", e); 274 } 275 } 276} 277