Voice Interaction from within an Activity

This allows an app to show a voice search button
and invoke a voice interaction session for use
within the activity. Once the activity exits, the
session is stopped.

Test application has a new activity that
demonstrates it with the test voice interaction
service.

This initial version is functional enough for
an integration test, with some more tests
and improvements to come later.

Bug: 22791070
Change-Id: Ib1e5bc8cae1fde40570c999b9cf4bb29efe4916d
diff --git a/tests/VoiceInteraction/AndroidManifest.xml b/tests/VoiceInteraction/AndroidManifest.xml
index fe17c6e..cbc6c76 100644
--- a/tests/VoiceInteraction/AndroidManifest.xml
+++ b/tests/VoiceInteraction/AndroidManifest.xml
@@ -61,5 +61,13 @@
                 <category android:name="android.intent.category.VOICE" />
             </intent-filter>
         </activity>
+        <activity android:name="StartVoiceInteractionActivity" android:label="In-Activity Voice"
+                  android:theme="@android:style/Theme.Material.Light">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
     </application>
 </manifest>
diff --git a/tests/VoiceInteraction/res/layout/local_interaction_app.xml b/tests/VoiceInteraction/res/layout/local_interaction_app.xml
new file mode 100644
index 0000000..9694133
--- /dev/null
+++ b/tests/VoiceInteraction/res/layout/local_interaction_app.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:padding="8dp"
+    >
+
+    <TextView android:id="@+id/log"
+        android:layout_width="match_parent"
+        android:layout_height="0px"
+        android:layout_weight="1"
+        android:layout_marginTop="16dp"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        />
+
+    <LinearLayout android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="16dp"
+        android:orientation="horizontal">
+
+        <Button android:id="@+id/start"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/startFromActivity"
+            />
+
+    </LinearLayout>
+
+    <LinearLayout android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="16dp"
+        android:orientation="horizontal">
+
+        <Button android:id="@+id/stop"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/stopFromActivity"
+            android:enabled="false"
+            />
+
+    </LinearLayout>
+
+    <LinearLayout android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="16dp"
+        android:orientation="horizontal">
+
+        <Button android:id="@+id/command"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/commandVoice"
+            />
+
+        <Button android:id="@+id/pick"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/pickVoice"
+            />
+
+    </LinearLayout>
+
+    <LinearLayout android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="16dp"
+        android:layout_marginBottom="16dp"
+        android:orientation="horizontal">
+
+        <Button android:id="@+id/cancel"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/cancelVoice"
+        />
+
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/tests/VoiceInteraction/res/values/strings.xml b/tests/VoiceInteraction/res/values/strings.xml
index c665c23..64f8bc5 100644
--- a/tests/VoiceInteraction/res/values/strings.xml
+++ b/tests/VoiceInteraction/res/values/strings.xml
@@ -31,6 +31,8 @@
     <string name="pickVoice">Pick Voice</string>
     <string name="cancelVoice">Cancel</string>
     <string name="jumpOut">Jump out</string>
+    <string name="startFromActivity">Start voice interaction</string>
+    <string name="stopFromActivity">Stop voice interaction</string>
 
     <string name="largetext">This is a bunch of text that we will use to show how we handle it
 when reporting it for assist data.  We need many many lines of text, like\n
diff --git a/tests/VoiceInteraction/res/xml/interaction_service.xml b/tests/VoiceInteraction/res/xml/interaction_service.xml
index c015ad2..f0c88a2 100644
--- a/tests/VoiceInteraction/res/xml/interaction_service.xml
+++ b/tests/VoiceInteraction/res/xml/interaction_service.xml
@@ -21,4 +21,5 @@
     android:sessionService="com.android.test.voiceinteraction.MainInteractionSessionService"
     android:recognitionService="com.android.test.voiceinteraction.MainRecognitionService"
     android:settingsActivity="com.android.test.voiceinteraction.SettingsActivity"
-    android:supportsAssist="true" />
+    android:supportsAssist="true" 
+    android:supportsLocalInteraction="true" />
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java
index 6e3694b..450334c 100644
--- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java
@@ -73,6 +73,7 @@
     CharSequence mPendingPrompt;
     Request mPendingRequest;
     int mCurrentTask = -1;
+    int mShowFlags;
 
     MainInteractionSession(Context context) {
         super(context);
@@ -88,6 +89,7 @@
     @Override
     public void onShow(Bundle args, int showFlags) {
         super.onShow(args, showFlags);
+        mShowFlags = showFlags;
         Log.i(TAG, "onShow: flags=0x" + Integer.toHexString(showFlags) + " args=" + args);
         mState = STATE_IDLE;
         mStartIntent = args != null ? (Intent)args.getParcelable("intent") : null;
@@ -311,6 +313,8 @@
         if (mState != STATE_IDLE) {
             outInsets.contentInsets.top = mBottomContent.getTop();
             outInsets.touchableInsets = Insets.TOUCHABLE_INSETS_CONTENT;
+        } else if ((mShowFlags & SHOW_SOURCE_ACTIVITY) != 0) {
+            outInsets.touchableInsets = Insets.TOUCHABLE_INSETS_CONTENT;
         }
     }
 
@@ -355,7 +359,7 @@
             mPendingPrompt = prompt.getVisualPrompt();
         }
     }
-      
+
     @Override
     public void onRequestConfirmation(ConfirmationRequest request) {
         Log.i(TAG, "onConfirm: prompt=" + request.getVoicePrompt() + " extras="
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/StartVoiceInteractionActivity.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/StartVoiceInteractionActivity.java
new file mode 100644
index 0000000..41058c9
--- /dev/null
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/StartVoiceInteractionActivity.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.voiceinteraction;
+
+import android.app.Activity;
+import android.app.VoiceInteractor;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+public class StartVoiceInteractionActivity extends Activity implements View.OnClickListener {
+    static final String TAG = "LocalVoiceInteractionActivity";
+
+    static final String REQUEST_ABORT = "abort";
+    static final String REQUEST_COMPLETE = "complete";
+    static final String REQUEST_COMMAND = "command";
+    static final String REQUEST_PICK = "pick";
+    static final String REQUEST_CONFIRM = "confirm";
+
+    VoiceInteractor mInteractor;
+    VoiceInteractor.Request mCurrentRequest = null;
+    TextView mLog;
+    Button mCommandButton;
+    Button mPickButton;
+    Button mCancelButton;
+    Button mStartButton;
+    Button mStopButton;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.local_interaction_app);
+
+        mLog = (TextView)findViewById(R.id.log);
+        mCommandButton = (Button)findViewById(R.id.command);
+        mCommandButton.setOnClickListener(this);
+        mPickButton = (Button)findViewById(R.id.pick);
+        mPickButton.setOnClickListener(this);
+        mCancelButton = (Button)findViewById(R.id.cancel);
+        mCancelButton.setOnClickListener(this);
+        mStartButton = (Button) findViewById(R.id.start);
+        mStartButton.setOnClickListener(this);
+        mStopButton = (Button) findViewById(R.id.stop);
+        mStopButton.setOnClickListener(this);
+
+        mLog.append("Local Voice Interaction Supported = " + isLocalVoiceInteractionSupported());
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+    }
+
+    @Override
+    public void onClick(View v) {
+        if (v == mCommandButton) {
+            VoiceInteractor.CommandRequest req = new TestCommand("Some arg");
+            mInteractor.submitRequest(req, REQUEST_COMMAND);
+        } else if (v == mPickButton) {
+            VoiceInteractor.PickOptionRequest.Option[] options =
+                    new VoiceInteractor.PickOptionRequest.Option[5];
+            options[0] = new VoiceInteractor.PickOptionRequest.Option("One");
+            options[1] = new VoiceInteractor.PickOptionRequest.Option("Two");
+            options[2] = new VoiceInteractor.PickOptionRequest.Option("Three");
+            options[3] = new VoiceInteractor.PickOptionRequest.Option("Four");
+            options[4] = new VoiceInteractor.PickOptionRequest.Option("Five");
+            VoiceInteractor.PickOptionRequest req = new TestPickOption(options);
+            mInteractor.submitRequest(req, REQUEST_PICK);
+        } else if (v == mCancelButton && mCurrentRequest != null) {
+            Log.i(TAG, "Cancel request");
+            mCurrentRequest.cancel();
+        } else if (v == mStartButton) {
+            Bundle args = new Bundle();
+            args.putString("Foo", "Bar");
+            startLocalVoiceInteraction(args);
+        } else if (v == mStopButton) {
+            stopLocalVoiceInteraction();
+        }
+    }
+
+    @Override
+    public void onLocalVoiceInteractionStarted() {
+        mInteractor = getVoiceInteractor();
+        mLog.append("\nLocalVoiceInteraction started!");
+        mStopButton.setEnabled(true);
+    }
+
+    @Override
+    public void onLocalVoiceInteractionStopped() {
+        mInteractor = getVoiceInteractor();
+        mLog.append("\nLocalVoiceInteraction stopped!");
+        mStopButton.setEnabled(false);
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+    }
+
+    static class TestAbortVoice extends VoiceInteractor.AbortVoiceRequest {
+        public TestAbortVoice() {
+            super(new VoiceInteractor.Prompt("Dammit, we suck :("), null);
+        }
+        @Override public void onCancel() {
+            Log.i(TAG, "Canceled!");
+            ((StartVoiceInteractionActivity)getActivity()).mLog.append("Canceled abort\n");
+        }
+        @Override public void onAbortResult(Bundle result) {
+            Log.i(TAG, "Abort result: result=" + result);
+            ((StartVoiceInteractionActivity)getActivity()).mLog.append(
+                    "Abort: result=" + result + "\n");
+            getActivity().finish();
+        }
+    }
+
+    static class TestCompleteVoice extends VoiceInteractor.CompleteVoiceRequest {
+        public TestCompleteVoice() {
+            super(new VoiceInteractor.Prompt("Woohoo, completed!"), null);
+        }
+        @Override public void onCancel() {
+            Log.i(TAG, "Canceled!");
+            ((StartVoiceInteractionActivity)getActivity()).mLog.append("Canceled complete\n");
+        }
+        @Override public void onCompleteResult(Bundle result) {
+            Log.i(TAG, "Complete result: result=" + result);
+            ((StartVoiceInteractionActivity)getActivity()).mLog.append("Complete: result="
+                    + result + "\n");
+            getActivity().finish();
+        }
+    }
+
+    static class TestCommand extends VoiceInteractor.CommandRequest {
+        public TestCommand(String arg) {
+            super("com.android.test.voiceinteraction.COMMAND", makeBundle(arg));
+        }
+        @Override public void onCancel() {
+            Log.i(TAG, "Canceled!");
+            ((StartVoiceInteractionActivity)getActivity()).mLog.append("Canceled command\n");
+        }
+        @Override
+        public void onCommandResult(boolean finished, Bundle result) {
+            Log.i(TAG, "Command result: finished=" + finished + " result=" + result);
+            StringBuilder sb = new StringBuilder();
+            if (finished) {
+                sb.append("Command final result: ");
+            } else {
+                sb.append("Command intermediate result: ");
+            }
+            if (result != null) {
+                result.getString("key");
+            }
+            sb.append(result);
+            sb.append("\n");
+            ((StartVoiceInteractionActivity)getActivity()).mLog.append(sb.toString());
+        }
+        static Bundle makeBundle(String arg) {
+            Bundle b = new Bundle();
+            b.putString("key", arg);
+            return b;
+        }
+    }
+
+    static class TestPickOption extends VoiceInteractor.PickOptionRequest {
+        public TestPickOption(Option[] options) {
+            super(new VoiceInteractor.Prompt("Need to pick something"), options, null);
+        }
+        @Override public void onCancel() {
+            Log.i(TAG, "Canceled!");
+            ((StartVoiceInteractionActivity)getActivity()).mLog.append("Canceled pick\n");
+        }
+        @Override
+        public void onPickOptionResult(boolean finished, Option[] selections, Bundle result) {
+            Log.i(TAG, "Pick result: finished=" + finished + " selections=" + selections
+                    + " result=" + result);
+            StringBuilder sb = new StringBuilder();
+            if (finished) {
+                sb.append("Pick final result: ");
+            } else {
+                sb.append("Pick intermediate result: ");
+            }
+            for (int i=0; i<selections.length; i++) {
+                if (i >= 1) {
+                    sb.append(", ");
+                }
+                sb.append(selections[i].getLabel());
+            }
+            sb.append("\n");
+            ((StartVoiceInteractionActivity)getActivity()).mLog.append(sb.toString());
+        }
+    }
+}