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