1 /* 2 * Copyright (C) 2015 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 17 package android.voiceinteraction.service; 18 19 import android.app.VoiceInteractor; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.pm.LauncherApps; 23 import android.graphics.Bitmap; 24 import android.os.AsyncTask; 25 import android.os.Bundle; 26 import android.service.voice.VoiceInteractionSession; 27 import android.util.Log; 28 import android.voiceinteraction.common.Utils; 29 30 import androidx.annotation.NonNull; 31 import androidx.annotation.Nullable; 32 33 import java.util.ArrayList; 34 import java.util.List; 35 36 public class MainInteractionSession extends VoiceInteractionSession { 37 static final String TAG = "MainInteractionSession"; 38 public static final String ACTION_SESSION_STARTED = "session_started"; 39 public static final String ACTION_SCREENSHOT_RECEIVED = "screenshot_received"; 40 public static final String ACTION_ASSIST_DATA_RECEIVED = "assist_data_received"; 41 public static final String ACTION_ON_SHOW_RECEIVED = "on_show_received"; 42 public static final String EXTRA_RECEIVED = "received"; 43 44 Intent mStartIntent; 45 List<MyTask> mUsedTasks = new ArrayList<MyTask>(); 46 MainInteractionSession(Context context)47 MainInteractionSession(Context context) { 48 super(context); 49 } 50 51 @Override onCreate()52 public void onCreate() { 53 super.onCreate(); 54 55 Bundle extras = new Bundle(); 56 if (!getContext().getSystemService(LauncherApps.class).hasShortcutHostPermission()) { 57 extras.putString("error", "Does not have shortcut permission"); 58 } 59 notifyTestReceiver(ACTION_SESSION_STARTED, extras); 60 } 61 62 @Override onDestroy()63 public void onDestroy() { 64 Log.i(TAG, "Canceling the Asynctask in onDestroy()"); 65 for (MyTask t : mUsedTasks) { 66 t.cancel(true); 67 } 68 super.onDestroy(); 69 } 70 71 @Override onShow(Bundle args, int showFlags)72 public void onShow(Bundle args, int showFlags) { 73 if (args == null) { 74 Log.e(TAG, "onshow() received null args"); 75 return; 76 } 77 mStartIntent = args.getParcelable("intent"); 78 notifyTestReceiver(ACTION_ON_SHOW_RECEIVED, args); 79 if (mStartIntent != null) { 80 startVoiceActivity(mStartIntent); 81 } else if ((showFlags & SHOW_SOURCE_ACTIVITY) == SHOW_SOURCE_ACTIVITY) { 82 // Verify args 83 if (args == null 84 || !Utils.PRIVATE_OPTIONS_VALUE.equals( 85 args.getString(Utils.PRIVATE_OPTIONS_KEY))) { 86 throw new IllegalArgumentException("Incorrect arguments for SHOW_SOURCE_ACTIVITY"); 87 } 88 } 89 } 90 assertPromptFromTestApp(CharSequence prompt, Bundle extras)91 void assertPromptFromTestApp(CharSequence prompt, Bundle extras) { 92 String str = prompt.toString(); 93 if (str.equals(Utils.TEST_PROMPT)) { 94 Log.i(TAG, "prompt received ok from TestApp in Session"); 95 } else { 96 Utils.addErrorResult(extras, "Invalid prompt received: " + str); 97 } 98 } 99 newTask()100 synchronized MyTask newTask() { 101 MyTask t = new MyTask(); 102 mUsedTasks.add(t); 103 return t; 104 } 105 106 @Override onGetSupportedCommands(String[] commands)107 public boolean[] onGetSupportedCommands(String[] commands) { 108 boolean[] results = new boolean[commands.length]; 109 Log.i(TAG, "in onGetSupportedCommands"); 110 for (int idx = 0; idx < commands.length; idx++) { 111 results[idx] = Utils.TEST_COMMAND.equals(commands[idx]); 112 Log.i(TAG, "command " + commands[idx] + ", support = " + results[idx]); 113 } 114 return results; 115 } 116 117 @Override onRequestConfirmation(ConfirmationRequest request)118 public void onRequestConfirmation(ConfirmationRequest request) { 119 Bundle extras = request.getExtras(); 120 CharSequence prompt = request.getVoicePrompt().getVoicePromptAt(0); 121 Log.i(TAG, "in Session onRequestConfirmation recvd. prompt=" + prompt + 122 ", extras=" + Utils.toBundleString(extras)); 123 assertPromptFromTestApp(prompt, extras); 124 AsyncTaskArg asyncTaskArg = new AsyncTaskArg().setRequest(request).setExtras(extras); 125 if (isTestTypeCancel(extras)) { 126 Log.i(TAG, "Sending Cancel."); 127 newTask().execute( 128 asyncTaskArg.setTestType(Utils.TestCaseType.CONFIRMATION_REQUEST_CANCEL_TEST)); 129 } else { 130 Log.i(TAG, "in Session sending sendConfirmationResult. " + 131 Utils.toBundleString(extras)); 132 newTask().execute( 133 asyncTaskArg.setTestType(Utils.TestCaseType.CONFIRMATION_REQUEST_TEST)); 134 } 135 } 136 137 @Override onRequestCompleteVoice(CompleteVoiceRequest request)138 public void onRequestCompleteVoice(CompleteVoiceRequest request) { 139 Bundle extras = request.getExtras(); 140 CharSequence prompt = request.getVoicePrompt().getVoicePromptAt(0); 141 Log.i(TAG, "in Session onRequestCompleteVoice recvd. message=" + 142 prompt + ", extras=" + Utils.toBundleString(extras)); 143 assertPromptFromTestApp(prompt, extras); 144 AsyncTaskArg asyncTaskArg = new AsyncTaskArg().setRequest(request).setExtras(extras); 145 if (isTestTypeCancel(extras)) { 146 Log.i(TAG, "Sending Cancel."); 147 newTask().execute( 148 asyncTaskArg.setTestType(Utils.TestCaseType.COMPLETION_REQUEST_CANCEL_TEST)); 149 } else { 150 Log.i(TAG, "in Session sending sendConfirmationResult. " + 151 Utils.toBundleString(extras)); 152 newTask().execute( 153 asyncTaskArg.setTestType(Utils.TestCaseType.COMPLETION_REQUEST_TEST)); 154 } 155 } 156 157 @Override onRequestAbortVoice(AbortVoiceRequest request)158 public void onRequestAbortVoice(AbortVoiceRequest request) { 159 Bundle extras = request.getExtras(); 160 CharSequence prompt = request.getVoicePrompt().getVoicePromptAt(0); 161 Log.i(TAG, "in Session onRequestAbortVoice recvd. message=" + 162 prompt + ", extras=" + Utils.toBundleString(extras)); 163 assertPromptFromTestApp(prompt, extras); 164 AsyncTaskArg asyncTaskArg = new AsyncTaskArg().setRequest(request).setExtras(extras); 165 if (isTestTypeCancel(extras)) { 166 Log.i(TAG, "Sending Cancel."); 167 newTask().execute( 168 asyncTaskArg.setTestType(Utils.TestCaseType.ABORT_REQUEST_CANCEL_TEST)); 169 } else { 170 Log.i(TAG, "in Session sending sendAbortResult. " + 171 Utils.toBundleString(extras)); 172 newTask().execute(asyncTaskArg.setTestType(Utils.TestCaseType.ABORT_REQUEST_TEST)); 173 } 174 } 175 176 @Override onRequestCommand(CommandRequest request)177 public void onRequestCommand(CommandRequest request) { 178 Bundle extras = request.getExtras(); 179 Log.i(TAG, "in Session onRequestCommand recvd. Bundle = " + 180 Utils.toBundleString(extras)); 181 182 // Make sure that the input request has Utils.TEST_COMMAND sent by TestApp 183 String command = request.getCommand(); 184 if (command.equals(Utils.TEST_COMMAND)) { 185 Log.i(TAG, "command received ok from TestApp in Session"); 186 } else { 187 Utils.addErrorResult(extras, "Invalid TEST_COMMAND received: " + command); 188 } 189 // Add a field and value in the bundle to be sent to TestApp. 190 // TestApp will ensure that these are transmitted correctly. 191 extras.putString(Utils.TEST_ONCOMMAND_RESULT, Utils.TEST_ONCOMMAND_RESULT_VALUE); 192 AsyncTaskArg asyncTaskArg = new AsyncTaskArg().setRequest(request).setExtras(extras); 193 if (isTestTypeCancel(extras)) { 194 Log.i(TAG, "Sending Cancel."); 195 newTask().execute( 196 asyncTaskArg.setTestType(Utils.TestCaseType.COMMANDREQUEST_CANCEL_TEST)); 197 } else { 198 Log.i(TAG, "in Session sending sendResult. " + 199 Utils.toBundleString(extras) + ", string_in_bundle: " + 200 Utils.TEST_ONCOMMAND_RESULT + " = " + Utils.TEST_ONCOMMAND_RESULT_VALUE); 201 newTask().execute(asyncTaskArg.setTestType(Utils.TestCaseType.COMMANDREQUEST_TEST)); 202 } 203 } 204 assertPickOptionsFromTestApp(VoiceInteractor.PickOptionRequest.Option[] options, Bundle extras)205 void assertPickOptionsFromTestApp(VoiceInteractor.PickOptionRequest.Option[] options, 206 Bundle extras) { 207 if ((options.length != 2) || 208 !options[0].getLabel().toString().equals(Utils.PICKOPTON_1) || 209 !options[1].getLabel().toString().equals(Utils.PICKOPTON_2)) { 210 Utils.addErrorResult(extras, "Pickoptions Not received correctly in Session."); 211 } else { 212 Log.i(TAG, "Pickoptions received ok from TestApp in Session"); 213 } 214 } 215 216 @Override onRequestPickOption(PickOptionRequest request)217 public void onRequestPickOption(PickOptionRequest request) { 218 Bundle extras = request.getExtras(); 219 CharSequence prompt = request.getVoicePrompt().getVoicePromptAt(0); 220 Log.i(TAG, "in Session onRequestPickOption recvd. message=" + 221 prompt + ", options = " + Utils.toOptionsString(request.getOptions()) + 222 ", extras=" + Utils.toBundleString(extras)); 223 VoiceInteractor.PickOptionRequest.Option[] picked 224 = new VoiceInteractor.PickOptionRequest.Option[1]; 225 assertPromptFromTestApp(prompt, extras); 226 assertPickOptionsFromTestApp(request.getOptions(), extras); 227 picked[0] = new VoiceInteractor.PickOptionRequest.Option(Utils.PICKOPTON_3, 0); 228 AsyncTaskArg asyncTaskArg = new AsyncTaskArg().setRequest(request) 229 .setExtras(extras) 230 .setPickedOptions(picked); 231 if (isTestTypeCancel(extras)) { 232 Log.i(TAG, "in MainInteractionSession, Sending Cancel."); 233 newTask().execute( 234 asyncTaskArg.setTestType(Utils.TestCaseType.PICKOPTION_REQUEST_CANCEL_TEST)); 235 } else { 236 Log.i(TAG, "in MainInteractionSession sending sendPickOptionResult. " + 237 Utils.toBundleString(extras)); 238 newTask().execute(asyncTaskArg.setTestType(Utils.TestCaseType.PICKOPTION_REQUEST_TEST)); 239 } 240 } 241 242 @Override onHandleScreenshot(@ullable Bitmap screenshot)243 public void onHandleScreenshot(@Nullable Bitmap screenshot) { 244 super.onHandleScreenshot(screenshot); 245 Log.d(TAG, "onHandleScreenshot: " + screenshot); 246 247 Bundle extras = new Bundle(); 248 extras.putBoolean(EXTRA_RECEIVED, screenshot != null); 249 notifyTestReceiver(ACTION_SCREENSHOT_RECEIVED, extras); 250 } 251 252 @Override onHandleAssist(@onNull AssistState state)253 public void onHandleAssist(@NonNull AssistState state) { 254 super.onHandleAssist(state); 255 Log.d(TAG, "onHandleAssist: " + state); 256 257 Bundle extras = new Bundle(); 258 extras.putBoolean(EXTRA_RECEIVED, state.getAssistStructure() != null); 259 notifyTestReceiver(ACTION_ASSIST_DATA_RECEIVED, extras); 260 } 261 notifyTestReceiver(String action, Bundle extras)262 private void notifyTestReceiver(String action, Bundle extras) { 263 Intent intent = new Intent(action); 264 intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 265 intent.putExtras(extras); 266 intent.setClassName("android.voiceinteraction.cts", 267 "android.voiceinteraction.cts.VoiceInteractionTestReceiver"); 268 Log.i(TAG, "notifyTestReceiver: broadcast intent=" + intent); 269 getContext().sendBroadcast(intent); 270 } 271 isTestTypeCancel(Bundle extras)272 public static final boolean isTestTypeCancel(Bundle extras) { 273 Utils.TestCaseType testCaseType; 274 try { 275 testCaseType = Utils.TestCaseType.valueOf(extras.getString(Utils.TESTCASE_TYPE)); 276 } catch (IllegalArgumentException | NullPointerException e) { 277 Log.wtf(TAG, "unexpected testCaseType value in Bundle received", e); 278 return true; 279 } 280 return testCaseType == Utils.TestCaseType.COMPLETION_REQUEST_CANCEL_TEST || 281 testCaseType == Utils.TestCaseType.COMMANDREQUEST_CANCEL_TEST || 282 testCaseType == Utils.TestCaseType.CONFIRMATION_REQUEST_CANCEL_TEST || 283 testCaseType == Utils.TestCaseType.PICKOPTION_REQUEST_CANCEL_TEST || 284 testCaseType == Utils.TestCaseType.ABORT_REQUEST_CANCEL_TEST; 285 } 286 287 private class AsyncTaskArg { 288 ConfirmationRequest confReq; 289 CommandRequest commandReq; 290 CompleteVoiceRequest compReq; 291 AbortVoiceRequest abortReq; 292 PickOptionRequest pickReq; 293 Bundle extras; 294 VoiceInteractor.PickOptionRequest.Option[] picked; 295 Utils.TestCaseType testType; 296 setTestType(Utils.TestCaseType t)297 AsyncTaskArg setTestType(Utils.TestCaseType t) {testType = t; return this;} setRequest(CommandRequest r)298 AsyncTaskArg setRequest(CommandRequest r) {commandReq = r; return this;} setRequest(ConfirmationRequest r)299 AsyncTaskArg setRequest(ConfirmationRequest r) {confReq = r; return this;} setRequest(CompleteVoiceRequest r)300 AsyncTaskArg setRequest(CompleteVoiceRequest r) {compReq = r; return this;} setRequest(AbortVoiceRequest r)301 AsyncTaskArg setRequest(AbortVoiceRequest r) {abortReq = r; return this;} setRequest(PickOptionRequest r)302 AsyncTaskArg setRequest(PickOptionRequest r) {pickReq = r; return this;} setExtras(Bundle e)303 AsyncTaskArg setExtras(Bundle e) {extras = e; return this;} setPickedOptions(VoiceInteractor.PickOptionRequest.Option[] p)304 AsyncTaskArg setPickedOptions(VoiceInteractor.PickOptionRequest.Option[] p) { 305 picked = p; 306 return this; 307 } 308 } 309 310 private class MyTask extends AsyncTask<AsyncTaskArg, Void, Void> { 311 @Override doInBackground(AsyncTaskArg... params)312 protected Void doInBackground(AsyncTaskArg... params) { 313 AsyncTaskArg arg = params[0]; 314 Log.i(TAG, "in MyTask - doInBackground: requestType = " + 315 arg.testType.toString()); 316 switch (arg.testType) { 317 case ABORT_REQUEST_CANCEL_TEST: 318 arg.abortReq.cancel(); 319 break; 320 case ABORT_REQUEST_TEST: 321 arg.abortReq.sendAbortResult(arg.extras); 322 break; 323 case COMMANDREQUEST_CANCEL_TEST: 324 arg.commandReq.cancel(); 325 break; 326 case COMMANDREQUEST_TEST: 327 Log.i(TAG, "in MyTask sendResult. " + 328 Utils.toBundleString(arg.extras) + ", string_in_bundle: " + 329 Utils.TEST_ONCOMMAND_RESULT + " = " + 330 Utils.TEST_ONCOMMAND_RESULT_VALUE); 331 arg.commandReq.sendResult(arg.extras); 332 break; 333 case COMPLETION_REQUEST_CANCEL_TEST: 334 arg.compReq.cancel(); 335 break; 336 case COMPLETION_REQUEST_TEST: 337 arg.compReq.sendCompleteResult(arg.extras); 338 break; 339 case CONFIRMATION_REQUEST_CANCEL_TEST: 340 arg.confReq.cancel(); 341 break; 342 case CONFIRMATION_REQUEST_TEST: 343 arg.confReq.sendConfirmationResult(true, arg.extras); 344 break; 345 case PICKOPTION_REQUEST_CANCEL_TEST: 346 arg.pickReq.cancel(); 347 break; 348 case PICKOPTION_REQUEST_TEST: 349 StringBuilder buf = new StringBuilder(); 350 for (VoiceInteractor.PickOptionRequest.Option s : arg.picked) { 351 buf.append("option: " + s.toString() + ", "); 352 } 353 Log.i(TAG, "******** Sending PickoptionResult: " + 354 "picked: size = " + arg.picked.length + 355 ", Options = " + buf.toString() + 356 ", Bundle: " + Utils.toBundleString(arg.extras)); 357 arg.pickReq.sendPickOptionResult(arg.picked, arg.extras); 358 break; 359 default: 360 Log.i(TAG, "Doing nothing for the testcase type: " + arg.testType); 361 break; 362 } 363 return null; 364 } 365 } 366 } 367