Add ability to start voice interaction session directly
Add internal API's for SystemUI to start a voice interaction session
directly, without using an intent.
Make the assist gesture use that ability, if available.
Change-Id: I88ce3c7514714eb45666884847193585a07417a9
diff --git a/api/current.txt b/api/current.txt
index fb487ee..912f08f 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -28170,6 +28170,7 @@
method public void showSession(android.os.Bundle, int);
field public static final java.lang.String SERVICE_INTERFACE = "android.service.voice.VoiceInteractionService";
field public static final java.lang.String SERVICE_META_DATA = "android.voice_interaction";
+ field public static final int START_SOURCE_SYSTEM = 4; // 0x4
field public static final int START_WITH_ASSIST = 1; // 0x1
field public static final int START_WITH_SCREENSHOT = 2; // 0x2
}
diff --git a/api/system-current.txt b/api/system-current.txt
index 84f9dbf..5f795de 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -30241,6 +30241,7 @@
method public void showSession(android.os.Bundle, int);
field public static final java.lang.String SERVICE_INTERFACE = "android.service.voice.VoiceInteractionService";
field public static final java.lang.String SERVICE_META_DATA = "android.voice_interaction";
+ field public static final int START_SOURCE_SYSTEM = 4; // 0x4
field public static final int START_WITH_ASSIST = 1; // 0x1
field public static final int START_WITH_SCREENSHOT = 2; // 0x2
}
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index 419b92b..bc020e9 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -82,6 +82,12 @@
*/
public static final int START_WITH_SCREENSHOT = 1<<1;
+ /**
+ * Flag for use with {@link #showSession}: indicate that the session has been started from the
+ * system.
+ */
+ public static final int START_SOURCE_SYSTEM = 1<<2;
+
IVoiceInteractionService mInterface = new IVoiceInteractionService.Stub() {
@Override public void ready() {
mHandler.sendEmptyMessage(MSG_READY);
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 6450d52..144dca4 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -80,4 +80,20 @@
*/
int stopRecognition(in IVoiceInteractionService service, int keyphraseId,
in IRecognitionStatusCallback callback);
+
+ /**
+ * Indicates whether any voice interaction service is currently active.
+ */
+ boolean isServiceActive();
+
+ /**
+ * Shows the session for the currently active service. Used to start a new session from system
+ * affordances.
+ */
+ void showSessionForActiveService();
+
+ /**
+ * Indicates whether there is a voice session running (but not necessarily showing).
+ */
+ boolean isSessionRunning();
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index b0771dd..f427f2b 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3009,6 +3009,14 @@
android:description="@string/permdesc_bindCarrierMessagingService"
android:protectionLevel="signature|system" />
+ <!-- Allows an application to interact with the currently active
+ {@link android.service.voice.VoiceInteractionService}.
+ @hide -->
+ <permission android:name="android.permission.ACCESS_VOICE_INTERACTION_SERVICE"
+ android:protectionLevel="signature"
+ android:description="@string/permdesc_accessVoiceInteractionService"
+ android:label="@string/permlab_accessVoiceInteractionService" />
+
<!-- The system process is explicitly the only one allowed to launch the
confirmation UI for full backup/restore -->
<uses-permission android:name="android.permission.CONFIRM_FULL_BACKUP"/>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 6cd3139..4d90932 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2462,6 +2462,11 @@
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_bindCarrierMessagingService">Allows the holder to bind to the top-level interface of a carrier messaging service. Should never be needed for normal apps.</string>
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_accessVoiceInteractionService">interact with voice interaction service</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_accessVoiceInteractionService">Allows the holder to interact with the currently active voice interaction service. Should never be needed for normal apps.</string>
+
<!-- Policy administration -->
<!-- Title of policy access to limiting the user's password choices -->
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 7a58c87..6a3e89e 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -120,6 +120,9 @@
<!-- Screen Capturing -->
<uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION" />
+ <!-- Assist -->
+ <uses-permission android:name="android.permission.ACCESS_VOICE_INTERACTION_SERVICE" />
+
<application
android:name=".SystemUIApplication"
android:persistent="true"
diff --git a/packages/SystemUI/src/com/android/systemui/SearchPanelView.java b/packages/SystemUI/src/com/android/systemui/SearchPanelView.java
index 445b499..f0e6c43 100644
--- a/packages/SystemUI/src/com/android/systemui/SearchPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/SearchPanelView.java
@@ -27,16 +27,20 @@
import android.media.AudioAttributes;
import android.os.AsyncTask;
import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.Vibrator;
import android.provider.Settings;
import android.util.AttributeSet;
import android.util.Log;
+import android.util.Slog;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
+import com.android.internal.app.IVoiceInteractionManagerService;
import com.android.systemui.statusbar.BaseStatusBar;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusBarPanel;
@@ -70,6 +74,8 @@
private float mStartDrag;
private boolean mLaunchPending;
+ private IVoiceInteractionManagerService mVoiceInteractionManagerService;
+
public SearchPanelView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
@@ -80,6 +86,14 @@
mThreshold = context.getResources().getDimensionPixelSize(R.dimen.search_panel_threshold);
}
+ private void startAssist() {
+ if (isVoiceInteractorActive()) {
+ startVoiceInteractor();
+ } else {
+ startAssistActivity();
+ }
+ }
+
private void startAssistActivity() {
if (!mBar.isDeviceProvisioned()) return;
@@ -106,6 +120,23 @@
}
}
+ private void startVoiceInteractor() {
+ try {
+ mVoiceInteractionManagerService.showSessionForActiveService();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to call showSessionForActiveService", e);
+ }
+ }
+
+ private boolean isVoiceInteractorActive() {
+ try {
+ return mVoiceInteractionManagerService.isServiceActive();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to call isServiceActive", e);
+ return false;
+ }
+ }
+
@Override
protected void onFinishInflate() {
super.onFinishInflate();
@@ -113,6 +144,9 @@
mCircle = (SearchPanelCircleView) findViewById(R.id.search_panel_circle);
mLogo = (ImageView) findViewById(R.id.search_logo);
mScrim = findViewById(R.id.search_panel_scrim);
+ mVoiceInteractionManagerService = (IVoiceInteractionManagerService)
+ IVoiceInteractionManagerService.Stub.asInterface(
+ ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
}
private void maybeSwapSearchIcon() {
@@ -320,7 +354,7 @@
return;
}
mLaunching = true;
- startAssistActivity();
+ startAssist();
vibrate();
mCircle.setAnimatingOut(true);
mCircle.startExitAnimation(new Runnable() {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index f032ccf..d8b9140 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -707,6 +707,60 @@
}
@Override
+ public boolean isServiceActive() {
+ synchronized (this) {
+ if (mContext.checkCallingPermission(
+ Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Caller does not hold the permission "
+ + Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE);
+ }
+ return mImpl != null;
+ }
+ }
+
+ @Override
+ public void showSessionForActiveService() {
+ synchronized (this) {
+ if (mContext.checkCallingPermission(
+ Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Caller does not hold the permission "
+ + Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE);
+ }
+ if (mImpl == null) {
+ Slog.w(TAG, "showSessionForActiveService without running voice interaction"
+ + "service");
+ return;
+ }
+ final int callingPid = Binder.getCallingPid();
+ final int callingUid = Binder.getCallingUid();
+ final long caller = Binder.clearCallingIdentity();
+ try {
+ mImpl.showSessionLocked(callingPid, callingUid, new Bundle() /* sessionArgs */,
+ VoiceInteractionService.START_SOURCE_SYSTEM
+ | VoiceInteractionService.START_WITH_ASSIST
+ | VoiceInteractionService.START_WITH_SCREENSHOT);
+ } finally {
+ Binder.restoreCallingIdentity(caller);
+ }
+ }
+ }
+
+ @Override
+ public boolean isSessionRunning() {
+ synchronized (this) {
+ if (mContext.checkCallingPermission(
+ Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Caller does not hold the permission "
+ + Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE);
+ }
+ return mImpl != null && mImpl.mActiveSession != null;
+ }
+ }
+
+ @Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {