diff options
author | Dianne Hackborn <hackbod@google.com> | 2015-06-08 17:07:40 -0700 |
---|---|---|
committer | Dianne Hackborn <hackbod@google.com> | 2015-06-09 14:15:49 -0700 |
commit | a3acdb33df7c7be7ff3d9f376ff833e4b0c1d897 (patch) | |
tree | 452c3cd2b7a2706fcbdcd1432e820c71e31b13bb | |
parent | ea7438e4a698945b3e3a7786cff726c337291a68 (diff) | |
download | frameworks_base-a3acdb33df7c7be7ff3d9f376ff833e4b0c1d897.zip frameworks_base-a3acdb33df7c7be7ff3d9f376ff833e4b0c1d897.tar.gz frameworks_base-a3acdb33df7c7be7ff3d9f376ff833e4b0c1d897.tar.bz2 |
Fix issue #21621920: VI: need mechanism to get current request
Add new APIs to associate a Request with a name, get all active
requests, and get active request by name.
Also add a new Activity.onProvideReferrer() which will allow
applications to propagate referrer information to the assistant
(and other apps they launch) in a consistent way.
Change-Id: I4ef74b5ed07447da9303a74a1bdf42e4966df363
13 files changed, 205 insertions, 40 deletions
diff --git a/api/current.txt b/api/current.txt index 3b40214..6f74e49 100644 --- a/api/current.txt +++ b/api/current.txt @@ -3449,6 +3449,7 @@ package android.app { method public boolean onPreparePanel(int, android.view.View, android.view.Menu); method public void onProvideAssistContent(android.app.assist.AssistContent); method public void onProvideAssistData(android.os.Bundle); + method public android.net.Uri onProvideReferrer(); method public void onRequestPermissionsResult(int, java.lang.String[], int[]); method protected void onRestart(); method protected void onRestoreInstanceState(android.os.Bundle); @@ -5489,8 +5490,11 @@ package android.app { field public static final int MODE_NIGHT_YES = 2; // 0x2 } - public class VoiceInteractor { + public final class VoiceInteractor { + method public android.app.VoiceInteractor.Request getActiveRequest(java.lang.String); + method public android.app.VoiceInteractor.Request[] getActiveRequests(); method public boolean submitRequest(android.app.VoiceInteractor.Request); + method public boolean submitRequest(android.app.VoiceInteractor.Request, java.lang.String); method public boolean[] supportsCommands(java.lang.String[]); } @@ -5553,6 +5557,7 @@ package android.app { method public void cancel(); method public android.app.Activity getActivity(); method public android.content.Context getContext(); + method public java.lang.String getName(); method public void onAttached(android.app.Activity); method public void onCancel(); method public void onDetached(); diff --git a/api/system-current.txt b/api/system-current.txt index 2dcb0a8..28a2372 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -3535,6 +3535,7 @@ package android.app { method public boolean onPreparePanel(int, android.view.View, android.view.Menu); method public void onProvideAssistContent(android.app.assist.AssistContent); method public void onProvideAssistData(android.os.Bundle); + method public android.net.Uri onProvideReferrer(); method public void onRequestPermissionsResult(int, java.lang.String[], int[]); method protected void onRestart(); method protected void onRestoreInstanceState(android.os.Bundle); @@ -5585,8 +5586,11 @@ package android.app { field public static final int MODE_NIGHT_YES = 2; // 0x2 } - public class VoiceInteractor { + public final class VoiceInteractor { + method public android.app.VoiceInteractor.Request getActiveRequest(java.lang.String); + method public android.app.VoiceInteractor.Request[] getActiveRequests(); method public boolean submitRequest(android.app.VoiceInteractor.Request); + method public boolean submitRequest(android.app.VoiceInteractor.Request, java.lang.String); method public boolean[] supportsCommands(java.lang.String[]); } @@ -5649,6 +5653,7 @@ package android.app { method public void cancel(); method public android.app.Activity getActivity(); method public android.content.Context getContext(); + method public java.lang.String getName(); method public void onAttached(android.app.Activity); method public void onCancel(); method public void onDetached(); diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 90567c7..2a76a19 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -4279,6 +4279,10 @@ public class Activity extends ContextThemeWrapper if (mParent == null) { int result = ActivityManager.START_RETURN_INTENT_TO_CALLER; try { + Uri referrer = onProvideReferrer(); + if (referrer != null) { + intent.putExtra(Intent.EXTRA_REFERRER, referrer); + } intent.migrateExtraStreamToClipData(); intent.prepareToLeaveProcess(); result = ActivityManagerNative.getDefault() @@ -4463,6 +4467,10 @@ public class Activity extends ContextThemeWrapper @Override public void startActivityForResult( String who, Intent intent, int requestCode, @Nullable Bundle options) { + Uri referrer = onProvideReferrer(); + if (referrer != null) { + intent.putExtra(Intent.EXTRA_REFERRER, referrer); + } Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity( this, mMainThread.getApplicationThread(), mToken, who, @@ -4616,6 +4624,16 @@ public class Activity extends ContextThemeWrapper } /** + * Override to generate the desired referrer for the content currently being shown + * by the app. The default implementation returns null, meaning the referrer will simply + * be the android-app: of the package name of this activity. Return a non-null Uri to + * have that supplied as the {@link Intent#EXTRA_REFERRER} of any activities started from it. + */ + public Uri onProvideReferrer() { + return null; + } + + /** * Return the name of the package that invoked this activity. This is who * the data in {@link #setResult setResult()} will be sent to. You can * use this information to validate that the recipient is allowed to diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index dabcc50c..6ae21eb 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -2197,7 +2197,8 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM Bundle extras = data.readBundle(); AssistStructure structure = AssistStructure.CREATOR.createFromParcel(data); AssistContent content = AssistContent.CREATOR.createFromParcel(data); - reportAssistContextExtras(token, extras, structure, content); + Uri referrer = data.readInt() != 0 ? Uri.CREATOR.createFromParcel(data) : null; + reportAssistContextExtras(token, extras, structure, content, referrer); reply.writeNoException(); return true; } @@ -5367,7 +5368,7 @@ class ActivityManagerProxy implements IActivityManager } public void reportAssistContextExtras(IBinder token, Bundle extras, AssistStructure structure, - AssistContent content) throws RemoteException { + AssistContent content, Uri referrer) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); @@ -5375,6 +5376,12 @@ class ActivityManagerProxy implements IActivityManager data.writeBundle(extras); structure.writeToParcel(data, 0); content.writeToParcel(data, 0); + if (referrer != null) { + data.writeInt(1); + referrer.writeToParcel(data, 0); + } else { + data.writeInt(0); + } mRemote.transact(REPORT_ASSIST_CONTEXT_EXTRAS_TRANSACTION, data, reply, 0); reply.readException(); data.recycle(); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index ffb3fb8..e21c04a 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -2572,9 +2572,11 @@ public final class ActivityThread { AssistStructure structure = null; AssistContent content = new AssistContent(); ActivityClientRecord r = mActivities.get(cmd.activityToken); + Uri referrer = null; if (r != null) { r.activity.getApplication().dispatchOnProvideAssistData(r.activity, data); r.activity.onProvideAssistData(data); + referrer = r.activity.onProvideReferrer(); if (cmd.requestType == ActivityManager.ASSIST_CONTEXT_FULL) { structure = new AssistStructure(r.activity); Intent activityIntent = r.activity.getIntent(); @@ -2597,7 +2599,7 @@ public final class ActivityThread { } IActivityManager mgr = ActivityManagerNative.getDefault(); try { - mgr.reportAssistContextExtras(cmd.requestToken, data, structure, content); + mgr.reportAssistContextExtras(cmd.requestToken, data, structure, content, referrer); } catch (RemoteException e) { } } diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index 0d5e1c7..9311e5e 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -436,7 +436,7 @@ public interface IActivityManager extends IInterface { throws RemoteException; public void reportAssistContextExtras(IBinder token, Bundle extras, - AssistStructure structure, AssistContent content) throws RemoteException; + AssistStructure structure, AssistContent content, Uri referrer) throws RemoteException; public boolean launchAssistIntent(Intent intent, int requestType, String hint, int userHandle, Bundle args) throws RemoteException; diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index 0cc57ba..653f1b6 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -24,6 +24,7 @@ import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.hardware.input.InputManager; +import android.net.Uri; import android.os.Bundle; import android.os.Debug; import android.os.IBinder; @@ -1439,7 +1440,7 @@ public class Instrumentation { * objects and dispatches this call to the system activity manager; you can * override this to watch for the application to start an activity, and * modify what happens when it does. - * + * * <p>This method returns an {@link ActivityResult} object, which you can * use when intercepting application calls to avoid performing the start * activity action but still return the result the application is @@ -1448,10 +1449,10 @@ public class Instrumentation { * you would like the application to see, and don't call up to the super * class. Note that an application is only expecting a result if * <var>requestCode</var> is >= 0. - * + * * <p>This method throws {@link android.content.ActivityNotFoundException} * if there was no Activity found to run the given Intent. - * + * * @param who The Context from which the activity is being started. * @param contextThread The main thread of the Context from which the activity * is being started. @@ -1464,23 +1465,27 @@ public class Instrumentation { * @param requestCode Identifier for this request's result; less than zero * if the caller is not expecting a result. * @param options Addition options. - * + * * @return To force the return of a particular result, return an * ActivityResult object containing the desired data; otherwise * return null. The default implementation always returns null. - * + * * @throws android.content.ActivityNotFoundException - * + * * @see Activity#startActivity(Intent) * @see Activity#startActivityForResult(Intent, int) * @see Activity#startActivityFromChild - * + * * {@hide} */ public ActivityResult execStartActivity( Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) { IApplicationThread whoThread = (IApplicationThread) contextThread; + Uri referrer = target != null ? target.onProvideReferrer() : null; + if (referrer != null) { + intent.putExtra(Intent.EXTRA_REFERRER, referrer); + } if (mActivityMonitors != null) { synchronized (mSync) { final int N = mActivityMonitors.size(); diff --git a/core/java/android/app/VoiceInteractor.java b/core/java/android/app/VoiceInteractor.java index eccd9dc..9cc399d 100644 --- a/core/java/android/app/VoiceInteractor.java +++ b/core/java/android/app/VoiceInteractor.java @@ -58,9 +58,11 @@ import java.util.ArrayList; * request, rather than holding on to the activity instance yourself, either explicitly * or implicitly through a non-static inner class. */ -public class VoiceInteractor { +public final class VoiceInteractor { static final String TAG = "VoiceInteractor"; - static final boolean DEBUG = true; + static final boolean DEBUG = false; + + static final Request[] NO_REQUESTS = new Request[0]; final IVoiceInteractor mInteractor; @@ -189,7 +191,7 @@ public class VoiceInteractor { } }; - final ArrayMap<IBinder, Request> mActiveRequests = new ArrayMap<IBinder, Request>(); + final ArrayMap<IBinder, Request> mActiveRequests = new ArrayMap<>(); static final int MSG_CONFIRMATION_RESULT = 1; static final int MSG_PICK_OPTION_RESULT = 2; @@ -206,10 +208,22 @@ public class VoiceInteractor { IVoiceInteractorRequest mRequestInterface; Context mContext; Activity mActivity; + String mName; Request() { } + /** + * Return the name this request was submitted through + * {@link #submitRequest(android.app.VoiceInteractor.Request, String)}. + */ + public String getName() { + return mName; + } + + /** + * Cancel this active request. + */ public void cancel() { try { mRequestInterface.cancel(); @@ -218,20 +232,39 @@ public class VoiceInteractor { } } + /** + * Return the current {@link Context} this request is associated with. May change + * if the activity hosting it goes through a configuration change. + */ public Context getContext() { return mContext; } + /** + * Return the current {@link Activity} this request is associated with. Will change + * if the activity is restarted such as through a configuration change. + */ public Activity getActivity() { return mActivity; } + /** + * Report from voice interaction service: this operation has been canceled, typically + * as a completion of a previous call to {@link #cancel}. + */ public void onCancel() { } + /** + * The request is now attached to an activity, or being re-attached to a new activity + * after a configuration change. + */ public void onAttached(Activity activity) { } + /** + * The request is being detached from an activity. + */ public void onDetached() { } @@ -239,6 +272,7 @@ public class VoiceInteractor { mRequestInterface = null; mContext = null; mActivity = null; + mName = null; } abstract IVoiceInteractorRequest submit(IVoiceInteractor interactor, @@ -761,12 +795,31 @@ public class VoiceInteractor { } public boolean submitRequest(Request request) { + return submitRequest(request, null); + } + + /** + * Submit a new {@link Request} to the voice interaction service. The request must be + * one of the available subclasses -- {@link ConfirmationRequest}, {@link PickOptionRequest}, + * {@link CompleteVoiceRequest}, {@link AbortVoiceRequest}, or {@link CommandRequest}. + * + * @param request The desired request to submit. + * @param name An optional name for this request, or null. This can be used later with + * {@link #getActiveRequests} and {@link #getActiveRequest} to find the request. + * + * @return Returns true of the request was successfully submitted, else false. + */ + public boolean submitRequest(Request request, String name) { try { + if (request.mRequestInterface != null) { + throw new IllegalStateException("Given " + request + " is already active"); + } IVoiceInteractorRequest ireq = request.submit(mInteractor, mContext.getOpPackageName(), mCallback); request.mRequestInterface = ireq; request.mContext = mContext; request.mActivity = mActivity; + request.mName = name; synchronized (mActiveRequests) { mActiveRequests.put(ireq.asBinder(), request); } @@ -778,6 +831,43 @@ public class VoiceInteractor { } /** + * Return all currently active requests. + */ + public Request[] getActiveRequests() { + synchronized (mActiveRequests) { + final int N = mActiveRequests.size(); + if (N <= 0) { + return NO_REQUESTS; + } + Request[] requests = new Request[N]; + for (int i=0; i<N; i++) { + requests[i] = mActiveRequests.valueAt(i); + } + return requests; + } + } + + /** + * Return any currently active request that was submitted with the given name. + * + * @param name The name used to submit the request, as per + * {@link #submitRequest(android.app.VoiceInteractor.Request, String)}. + * @return Returns the active request with that name, or null if there was none. + */ + public Request getActiveRequest(String name) { + synchronized (mActiveRequests) { + final int N = mActiveRequests.size(); + for (int i=0; i<N; i++) { + Request req = mActiveRequests.valueAt(i); + if (name == req.getName() || (name != null && name.equals(req.getName()))) { + return req; + } + } + } + return null; + } + + /** * Queries the supported commands available from the VoiceInteractionService. * The command is a string that describes the generic operation to be performed. * An example might be "org.example.commands.PICK_DATE" to ask the user to pick diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 5c23204..25be96a 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -1226,7 +1226,8 @@ public class Intent implements Parcelable, Cloneable { * <p> * Input: {@link #EXTRA_ASSIST_PACKAGE}, {@link #EXTRA_ASSIST_CONTEXT}, can provide * additional optional contextual information about where the user was when they - * requested the assist. + * requested the assist; {@link #EXTRA_REFERRER} may be set with additional referrer + * information. * Output: nothing. */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 5fedbe4..0d08c2a 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -10727,12 +10727,15 @@ public final class ActivityManagerService extends ActivityManagerNative } public void reportAssistContextExtras(IBinder token, Bundle extras, AssistStructure structure, - AssistContent content) { + AssistContent content, Uri referrer) { PendingAssistExtras pae = (PendingAssistExtras)token; synchronized (pae) { pae.result = extras; pae.structure = structure; pae.content = content; + if (referrer != null) { + pae.extras.putParcelable(Intent.EXTRA_REFERRER, referrer); + } pae.haveResult = true; pae.notifyAll(); if (pae.intent == null && pae.receiver == null) { diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java index dae1ac3..3090a11 100644 --- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java +++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java @@ -23,6 +23,7 @@ import android.app.AssistStructure; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; +import android.net.Uri; import android.os.Bundle; import android.service.voice.VoiceInteractionSession; import android.util.Log; @@ -79,6 +80,9 @@ public class MainInteractionSession extends VoiceInteractionSession super.onShow(args, showFlags); mState = STATE_IDLE; mStartIntent = args.getParcelable("intent"); + if (mStartIntent == null) { + mStartIntent = new Intent(getContext(), TestInteractionActivity.class); + } if (mAssistVisualizer != null) { mAssistVisualizer.clearAssistData(); } @@ -119,6 +123,7 @@ public class MainInteractionSession extends VoiceInteractionSession } public void onHandleAssist(Bundle assistBundle) { + boolean hasStructure = false; if (assistBundle != null) { Bundle assistContext = assistBundle.getBundle(Intent.EXTRA_ASSIST_CONTEXT); if (assistContext != null) { @@ -126,6 +131,7 @@ public class MainInteractionSession extends VoiceInteractionSession if (mAssistStructure != null) { if (mAssistVisualizer != null) { mAssistVisualizer.setAssistStructure(mAssistStructure); + hasStructure = true; } } AssistContent content = AssistContent.getAssistContent(assistContext); @@ -133,10 +139,13 @@ public class MainInteractionSession extends VoiceInteractionSession Log.i(TAG, "Assist intent: " + content.getIntent()); Log.i(TAG, "Assist clipdata: " + content.getClipData()); } - return; + } + Uri referrer = assistBundle.getParcelable(Intent.EXTRA_REFERRER); + if (referrer != null) { + Log.i(TAG, "Referrer: " + referrer); } } - if (mAssistVisualizer != null) { + if (!hasStructure && mAssistVisualizer != null) { mAssistVisualizer.clearAssistData(); } } diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java index 67db289..c038414 100644 --- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java +++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java @@ -23,15 +23,18 @@ import android.content.Intent; import android.os.Bundle; import android.service.voice.VoiceInteractionService; import android.util.Log; -import android.view.Gravity; import android.view.View; -import android.view.ViewGroup; import android.widget.Button; import android.widget.TextView; public class TestInteractionActivity extends Activity implements View.OnClickListener { static final String TAG = "TestInteractionActivity"; + static final String REQUEST_ABORT = "abort"; + static final String REQUEST_COMPLETE = "complete"; + static final String REQUEST_PICK = "pick"; + static final String REQUEST_CONFIRM = "confirm"; + VoiceInteractor mInteractor; VoiceInteractor.Request mCurrentRequest = null; TextView mLog; @@ -72,21 +75,32 @@ public class TestInteractionActivity extends Activity implements View.OnClickLis mCancelButton.setOnClickListener(this); mInteractor = getVoiceInteractor(); - mCurrentRequest = new VoiceInteractor.ConfirmationRequest( - new VoiceInteractor.Prompt("This is a confirmation"), null) { - @Override - public void onCancel() { - Log.i(TAG, "Canceled!"); - getActivity().finish(); - } - - @Override - public void onConfirmationResult(boolean confirmed, Bundle result) { - Log.i(TAG, "Confirmation result: confirmed=" + confirmed + " result=" + result); - getActivity().finish(); - } - }; - mInteractor.submitRequest(mCurrentRequest); + + VoiceInteractor.Request[] active = mInteractor.getActiveRequests(); + for (int i=0; i<active.length; i++) { + Log.i(TAG, "Active #" + i + " / " + active[i].getName() + ": " + active[i]); + } + + mCurrentRequest = mInteractor.getActiveRequest(REQUEST_CONFIRM); + if (mCurrentRequest == null) { + mCurrentRequest = new VoiceInteractor.ConfirmationRequest( + new VoiceInteractor.Prompt("This is a confirmation"), null) { + @Override + public void onCancel() { + Log.i(TAG, "Canceled!"); + getActivity().finish(); + } + + @Override + public void onConfirmationResult(boolean confirmed, Bundle result) { + Log.i(TAG, "Confirmation result: confirmed=" + confirmed + " result=" + result); + getActivity().finish(); + } + }; + mInteractor.submitRequest(mCurrentRequest, REQUEST_CONFIRM); + } else { + Log.i(TAG, "Restarting with active confirmation: " + mCurrentRequest); + } } @Override @@ -112,7 +126,7 @@ public class TestInteractionActivity extends Activity implements View.OnClickLis getActivity().finish(); } }; - mInteractor.submitRequest(req); + mInteractor.submitRequest(req, REQUEST_ABORT); } else if (v == mCompleteButton) { VoiceInteractor.CompleteVoiceRequest req = new VoiceInteractor.CompleteVoiceRequest( new VoiceInteractor.Prompt("Woohoo, completed!"), null) { @@ -129,7 +143,7 @@ public class TestInteractionActivity extends Activity implements View.OnClickLis getActivity().finish(); } }; - mInteractor.submitRequest(req); + mInteractor.submitRequest(req, REQUEST_COMPLETE); } else if (v == mPickButton) { VoiceInteractor.PickOptionRequest.Option[] options = new VoiceInteractor.PickOptionRequest.Option[5]; @@ -168,7 +182,7 @@ public class TestInteractionActivity extends Activity implements View.OnClickLis } } }; - mInteractor.submitRequest(req); + mInteractor.submitRequest(req, REQUEST_PICK); } else if (v == mJumpOutButton) { Log.i(TAG, "Jump out"); Intent intent = new Intent(Intent.ACTION_MAIN); diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/VoiceInteractionMain.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/VoiceInteractionMain.java index 5d212a4..a7636c3 100644 --- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/VoiceInteractionMain.java +++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/VoiceInteractionMain.java @@ -18,6 +18,7 @@ package com.android.test.voiceinteraction; import android.app.Activity; import android.content.Intent; +import android.net.Uri; import android.os.Bundle; import android.view.View; @@ -41,6 +42,11 @@ public class VoiceInteractionMain extends Activity { super.onDestroy(); } + @Override + public Uri onProvideReferrer() { + return Uri.parse("http://www.example.com/VoiceInteractionMain"); + } + View.OnClickListener mStartListener = new View.OnClickListener() { public void onClick(View v) { startService(new Intent(VoiceInteractionMain.this, MainInteractionService.class)); |