content: /**
   * 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.
   */
  
  package android.service.voice;
  
  import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
  
  import android.annotation.CallbackExecutor;
  import android.annotation.IntRange;
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.app.Activity;
  import android.app.Dialog;
  import android.app.DirectAction;
  import android.app.Instrumentation;
  import android.app.VoiceInteractor;
  import android.app.assist.AssistContent;
  import android.app.assist.AssistStructure;
  import android.content.ComponentCallbacks2;
  import android.content.Context;
  import android.content.Intent;
  import android.content.pm.ParceledListSlice;
  import android.content.res.Configuration;
  import android.content.res.TypedArray;
  import android.graphics.Bitmap;
  import android.graphics.Rect;
  import android.graphics.Region;
  import android.inputmethodservice.SoftInputWindow;
  import android.os.Binder;
  import android.os.Bundle;
  import android.os.CancellationSignal;
  import android.os.Handler;
  import android.os.IBinder;
  import android.os.ICancellationSignal;
  import android.os.Message;
  import android.os.RemoteCallback;
  import android.os.RemoteException;
  import android.os.UserHandle;
  import android.util.ArrayMap;
  import android.util.DebugUtils;
  import android.util.Log;
  import android.view.Gravity;
  import android.view.KeyEvent;
  import android.view.LayoutInflater;
  import android.view.View;
  import android.view.ViewTreeObserver;
  import android.view.WindowManager;
  import android.widget.FrameLayout;
  
  import com.android.internal.annotations.Immutable;
  import com.android.internal.app.IVoiceInteractionManagerService;
  import com.android.internal.app.IVoiceInteractionSessionShowCallback;
  import com.android.internal.app.IVoiceInteractor;
  import com.android.internal.app.IVoiceInteractorCallback;
  import com.android.internal.app.IVoiceInteractorRequest;
  import com.android.internal.os.HandlerCaller;
  import com.android.internal.os.SomeArgs;
  import com.android.internal.util.Preconditions;
  import com.android.internal.util.function.pooled.PooledLambda;
  
  import java.io.FileDescriptor;
  import java.io.PrintWriter;
  import java.lang.ref.WeakReference;
  import java.util.Collections;
  import java.util.List;
  import java.util.Map;
  import java.util.concurrent.Executor;
  import java.util.function.Consumer;
  
  /**
   * An active voice interaction session, providing a facility for the implementation
   * to interact with the user in the voice interaction layer.  The user interface is
   * initially shown by default, and can be created be overriding {@link #onCreateContentView()}
   * in which the UI can be built.
   *
   * <p>A voice interaction session can be self-contained, ultimately calling {@link #finish}
   * when done.  It can also initiate voice interactions with applications by calling
   * {@link #startVoiceActivity}</p>.
   */
  public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCallbacks2 {
      static final String TAG = "VoiceInteractionSession";
      static final boolean DEBUG = false;
  
      /**
       * Flag received in {@link #onShow}: originator requested that the session be started with
       * assist data from the currently focused activity.
       */
      public static final int SHOW_WITH_ASSIST = 1<<0;
  
      /**
       * Flag received in {@link #onShow}: originator requested that the session be started with
       * a screen shot of the currently focused activity.
       */
      public static final int SHOW_WITH_SCREENSHOT = 1<<1;
  
      /**
       * Flag for use with {@link #onShow}: indicates that the session has been started from the
       * system assist gesture.
       */
      public static final int SHOW_SOURCE_ASSIST_GESTURE = 1<<2;
  
      /**
       * Flag for use with {@link #onShow}: indicates that the application itself has invoked
       * the assistant.
       */
      public static final int SHOW_SOURCE_APPLICATION = 1<<3;
  
      /**
       * Flag for use with {@link #onShow}: indicates that an Activity has invoked the voice
       * interaction service for a local interaction using
       * {@link Activity#startLocalVoiceInteraction(Bundle)}.
       */
      public static final int SHOW_SOURCE_ACTIVITY = 1<<4;
  
      /**
       * Flag for use with {@link #onShow}: indicates that the voice interaction service was invoked
       * from a physical button.
       */
      public static final int SHOW_SOURCE_PUSH_TO_TALK = 1 << 5;
  
      /**
       * Flag for use with {@link #onShow}: indicates that the voice interaction service was invoked
       * from a notification.
       */
      public static final int SHOW_SOURCE_NOTIFICATION = 1 << 6;
  
      /**
       * Flag for use with {@link #onShow}: indicates that the voice interaction service was invoked
       * from an Android automotive system UI.
       */
      public static final int SHOW_SOURCE_AUTOMOTIVE_SYSTEM_UI = 1 << 7;
  
      final Context mContext;
      final HandlerCaller mHandlerCaller;
  
      final KeyEvent.DispatcherState mDispatcherState = new KeyEvent.DispatcherState();
  
      IVoiceInteractionManagerService mSystemService;
      IBinder mToken;
  
      int mTheme = 0;
      LayoutInflater mInflater;
      TypedArray mThemeAttrs;
      View mRootView;
      FrameLayout mContentFrame;
      SoftInputWindow mWindow;
  
      boolean mUiEnabled = true;
      boolean mInitialized;
      boolean mWindowAdded;
      boolean mWindowVisible;
      boolean mWindowWasVisible;
      boolean mInShowWindow;
  
      final ArrayMap<IBinder, Request> mActiveRequests = new ArrayMap<IBinder, Request>();
  
      final Insets mTmpInsets = new Insets();
  
      final WeakReference<VoiceInteractionSession> mWeakRef
              = new WeakReference<VoiceInteractionSession>(this);
  
      // Registry of remote callbacks pending a reply with reply handles.
      final Map<SafeResultListener, Consumer<Bundle>> mRemoteCallbacks = new ArrayMap<>();
  
      ICancellationSignal mKillCallback;
  
      final IVoiceInteractor mInteractor = new IVoiceInteractor.Stub() {
          @Override
          public IVoiceInteractorRequest startConfirmation(String callingPackage,
                  IVoiceInteractorCallback callback, VoiceInteractor.Prompt prompt, Bundle extras) {
              ConfirmationRequest request = new ConfirmationRequest(callingPackage,
                      Binder.getCallingUid(), callback, VoiceInteractionSession.this,
                      prompt, extras);
              addRequest(request);
              mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(MSG_START_CONFIRMATION,
                      request));
              return request.mInterface;
          }
  
          @Override
          public IVoiceInteractorRequest startPickOption(String callingPackage,
                  IVoiceInteractorCallback callback, VoiceInteractor.Prompt prompt,
                  VoiceInteractor.PickOptionRequest.Option[] options, Bundle extras) {
              PickOptionRequest request = new PickOptionRequest(callingPackage,
                      Binder.getCallingUid(), callback, VoiceInteractionSession.this,
                      prompt, options, extras);
              addRequest(request);
              mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(MSG_START_PICK_OPTION,
                      request));
              return request.mInterface;
          }
  
          @Override
          public IVoiceInteractorRequest startCompleteVoice(String callingPackage,
                  IVoiceInteractorCallback callback, VoiceInteractor.Prompt message, Bundle extras) {
              CompleteVoiceRequest request = new CompleteVoiceRequest(callingPackage,
                      Binder.getCallingUid(), callback, VoiceInteractionSession.this,
                      message, extras);
              addRequest(request);
              mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(MSG_START_COMPLETE_VOICE,
                      request));
              return request.mInterface;
          }
  
          @Override
          public IVoiceInteractorRequest startAbortVoice(String callingPackage,
                  IVoiceInteractorCallback callback, VoiceInteractor.Prompt message, Bundle extras) {
              AbortVoiceRequest request = new AbortVoiceRequest(callingPackage,
                      Binder.getCallingUid(), callback, VoiceInteractionSession.this,
                      message, extras);
              addRequest(request);
              mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(MSG_START_ABORT_VOICE,
                      request));
              return request.mInterface;
          }
  
          @Override
          public IVoiceInteractorRequest startCommand(String callingPackage,
                  IVoiceInteractorCallback callback, String command, Bundle extras) {
              CommandRequest request = new CommandRequest(callingPackage,
                      Binder.getCallingUid(), callback, VoiceInteractionSession.this,
                      command, extras);
              addRequest(request);
              mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(MSG_START_COMMAND,
                      request));
              return request.mInterface;
          }
  
          @Override
          public boolean[] supportsCommands(String callingPackage, String[] commands) {
              Message msg = mHandlerCaller.obtainMessageIOO(MSG_SUPPORTS_COMMANDS,
                      0, commands, null);
              SomeArgs args = mHandlerCaller.sendMessageAndWait(msg);
              if (args != null) {
                  boolean[] res = (boolean[])args.arg1;
                  args.recycle();
                  return res;
              }
              return new boolean[commands.length];
          }
  
          @Override
          public void notifyDirectActionsChanged(int taskId, IBinder assistToken) {
              mHandlerCaller.getHandler().sendMessage(PooledLambda.obtainMessage(
                      VoiceInteractionSession::onDirectActionsInvalidated,
                      VoiceInteractionSession.this, new ActivityId(taskId, assistToken))
              );
          }
  
          @Override
          public void setKillCallback(ICancellationSignal callback) {
              mKillCallback = callback;
          }
      };
  
      final IVoiceInteractionSession mSession = new IVoiceInteractionSession.Stub() {
          @Override
          public void show(Bundle sessionArgs, int flags,
                  IVoiceInteractionSessionShowCallback showCallback) {
              mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOO(MSG_SHOW,
                      flags, sessionArgs, showCallback));
          }
  
          @Override
          public void hide() {
              // Remove any pending messages to show the session
              mHandlerCaller.removeMessages(MSG_SHOW);
              mHandlerCaller.sendMessage(mHandlerCaller.obtainMessage(MSG_HIDE));
          }
  
          @Override
          public void handleAssist(final int taskId, final IBinder assistToken, final Bundle data,
                  final AssistStructure structure, final AssistContent content, final int index,
                  final int count) {
              // We want to pre-warm the AssistStructure before handing it off to the main
              // thread.  We also want to do this on a separate thread, so that if the app
              // is for some reason slow (due to slow filling in of async children in the
              // structure), we don't block other incoming IPCs (such as the screenshot) to
              // us (since we are a oneway interface, they get serialized).  (Okay?)
              Thread retriever = new Thread("AssistStructure retriever") {
                  @Override
                  public void run() {
                      Throwable failure = null;
                      if (structure != null) {
                          try {
                              structure.ensureData();
                          } catch (Throwable e) {
                              Log.w(TAG, "Failure retrieving AssistStructure", e);
                              failure = e;
                          }
                      }
  
                      SomeArgs args = SomeArgs.obtain();
                      args.argi1 = taskId;
                      args.arg1 = data;
                      args.arg2 = (failure == null) ? structure : null;
                      args.arg3 = failure;
                      args.arg4 = content;
                      args.arg5 = assistToken;
                      args.argi5 = index;
                      args.argi6 = count;
  
                      mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(
                              MSG_HANDLE_ASSIST, args));
                  }
              };
              retriever.start();
          }
  
          @Override
          public void handleScreenshot(Bitmap screenshot) {
              mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(MSG_HANDLE_SCREENSHOT,
                      screenshot));
          }
  
          @Override
          public void taskStarted(Intent intent, int taskId) {
              mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIO(MSG_TASK_STARTED,
                      taskId, intent));
          }
  
          @Override
          public void taskFinished(Intent intent, int taskId) {
              mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIO(MSG_TASK_FINISHED,
                      taskId, intent));
          }
  
          @Override
          public void closeSystemDialogs() {
              mHandlerCaller.sendMessage(mHandlerCaller.obtainMessage(MSG_CLOSE_SYSTEM_DIALOGS));
          }
  
          @Override
          public void onLockscreenShown() {
              mHandlerCaller.sendMessage(mHandlerCaller.obtainMessage(MSG_ON_LOCKSCREEN_SHOWN));
          }
  
          @Override
          public void destroy() {
              mHandlerCaller.sendMessage(mHandlerCaller.obtainMessage(MSG_DESTROY));
          }
      };
  
      /**
       * Base class representing a request from a voice-driver app to perform a particular
       * voice operation with the user.  See related subclasses for the types of requests
       * that are possible.
       */
      public static class Request {
          final IVoiceInteractorRequest mInterface = new IVoiceInteractorRequest.Stub() {
              @Override
              public void cancel() throws RemoteException {
                  VoiceInteractionSession session = mSession.get();
                  if (session != null) {
                      session.mHandlerCaller.sendMessage(
                              session.mHandlerCaller.obtainMessageO(MSG_CANCEL, Request.this));
                  }
              }
          };
          final String mCallingPackage;
          final int mCallingUid;
          final IVoiceInteractorCallback mCallback;
          final WeakReference<VoiceInteractionSession> mSession;
          final Bundle mExtras;
  
          Request(String packageName, int uid, IVoiceInteractorCallback callback,
                  VoiceInteractionSession session, Bundle extras) {
              mCallingPackage = packageName;
              mCallingUid = uid;
              mCallback = callback;
              mSession = session.mWeakRef;
              mExtras = extras;
          }
  
          /**
           * Return the uid of the application that initiated the request.
           */
          public int getCallingUid() {
              return mCallingUid;
          }
  
          /**
           * Return the package name of the application that initiated the request.
           */
          public String getCallingPackage() {
              return mCallingPackage;
          }
  
          /**
           * Return any additional extra information that was supplied as part of the request.
           */
          public Bundle getExtras() {
              return mExtras;
          }
  
          /**
           * Check whether this request is currently active.  A request becomes inactive after
           * calling {@link #cancel} or a final result method that completes the request.  After
           * this point, further interactions with the request will result in
           * {@link java.lang.IllegalStateException} errors; you should not catch these errors,
           * but can use this method if you need to determine the state of the request.  Returns
           * true if the request is still active.
           */
          public boolean isActive() {
              VoiceInteractionSession session = mSession.get();
              if (session == null) {
                  return false;
              }
              return session.isRequestActive(mInterface.asBinder());
          }
  
          void finishRequest() {
              VoiceInteractionSession session = mSession.get();
              if (session == null) {
                  throw new IllegalStateException("VoiceInteractionSession has been destroyed");
              }
              Request req = session.removeRequest(mInterface.asBinder());
              if (req == null) {
                  throw new IllegalStateException("Request not active: " + this);
              } else if (req != this) {
                  throw new IllegalStateException("Current active request " + req
                          + " not same as calling request " + this);
              }
          }
  
          /**
           * Ask the app to cancel this current request.
           * This also finishes the request (it is no longer active).
           */
          public void cancel() {
              try {
                  if (DEBUG) Log.d(TAG, "sendCancelResult: req=" + mInterface);
                  finishRequest();
                  mCallback.deliverCancel(mInterface);
              } catch (RemoteException e) {
              }
          }
  
          @Override
          public String toString() {
              StringBuilder sb = new StringBuilder(128);
              DebugUtils.buildShortClassTag(this, sb);
              sb.append(" ");
              sb.append(mInterface.asBinder());
              sb.append(" pkg=");
              sb.append(mCallingPackage);
              sb.append(" uid=");
              UserHandle.formatUid(sb, mCallingUid);
              sb.append('}');
              return sb.toString();
          }
  
          void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
              writer.print(prefix); writer.print("mInterface=");
              writer.println(mInterface.asBinder());
              writer.print(prefix); writer.print("mCallingPackage="); writer.print(mCallingPackage);
              writer.print(" mCallingUid="); UserHandle.formatUid(writer, mCallingUid);
              writer.println();
              writer.print(prefix); writer.print("mCallback=");
              writer.println(mCallback.asBinder());
              if (mExtras != null) {
                  writer.print(prefix); writer.print("mExtras=");
                  writer.println(mExtras);
              }
          }
      }
  
      /**
       * A request for confirmation from the user of an operation, as per
       * {@link android.app.VoiceInteractor.ConfirmationRequest
       * VoiceInteractor.ConfirmationRequest}.
       */
      public static final class ConfirmationRequest extends Request {
          final VoiceInteractor.Prompt mPrompt;
  
          ConfirmationRequest(String packageName, int uid, IVoiceInteractorCallback callback,
                  VoiceInteractionSession session, VoiceInteractor.Prompt prompt, Bundle extras) {
              super(packageName, uid, callback, session, extras);
              mPrompt = prompt;
          }
  
          /**
           * Return the prompt informing the user of what will happen, as per
           * {@link android.app.VoiceInteractor.ConfirmationRequest
           * VoiceInteractor.ConfirmationRequest}.
           */
          @Nullable
          public VoiceInteractor.Prompt getVoicePrompt() {
              return mPrompt;
          }
  
          /**
           * Return the prompt informing the user of what will happen, as per
           * {@link android.app.VoiceInteractor.ConfirmationRequest
           * VoiceInteractor.ConfirmationRequest}.
           * @deprecated Prefer {@link #getVoicePrompt()} which allows multiple voice prompts.
           */
          @Deprecated
          @Nullable
          public CharSequence getPrompt() {
              return (mPrompt != null ? mPrompt.getVoicePromptAt(0) : null);
          }
  
          /**
           * Report that the voice interactor has confirmed the operation with the user, resulting
           * in a call to
           * {@link android.app.VoiceInteractor.ConfirmationRequest#onConfirmationResult
           * VoiceInteractor.ConfirmationRequest.onConfirmationResult}.
           * This finishes the request (it is no longer active).
           */
          public void sendConfirmationResult(boolean confirmed, Bundle result) {
              try {
                  if (DEBUG) Log.d(TAG, "sendConfirmationResult: req=" + mInterface
                          + " confirmed=" + confirmed + " result=" + result);
                  finishRequest();
                  mCallback.deliverConfirmationResult(mInterface, confirmed, result);
              } catch (RemoteException e) {
              }
          }
  
          void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
              super.dump(prefix, fd, writer, args);
              writer.print(prefix); writer.print("mPrompt=");
              writer.println(mPrompt);
          }
      }
  
      /**
       * A request for the user to pick from a set of option, as per
       * {@link android.app.VoiceInteractor.PickOptionRequest VoiceInteractor.PickOptionRequest}.
       */
      public static final class PickOptionRequest extends Request {
          final VoiceInteractor.Prompt mPrompt;
          final VoiceInteractor.PickOptionRequest.Option[] mOptions;
  
          PickOptionRequest(String packageName, int uid, IVoiceInteractorCallback callback,
                  VoiceInteractionSession session, VoiceInteractor.Prompt prompt,
                  VoiceInteractor.PickOptionRequest.Option[] options, Bundle extras) {
              super(packageName, uid, callback, session, extras);
              mPrompt = prompt;
              mOptions = options;
          }
  
          /**
           * Return the prompt informing the user of what they are picking, as per
           * {@link android.app.VoiceInteractor.PickOptionRequest VoiceInteractor.PickOptionRequest}.
           */
          @Nullable
          public VoiceInteractor.Prompt getVoicePrompt() {
              return mPrompt;
          }
  
          /**
           * Return the prompt informing the user of what they are picking, as per
           * {@link android.app.VoiceInteractor.PickOptionRequest VoiceInteractor.PickOptionRequest}.
           * @deprecated Prefer {@link #getVoicePrompt()} which allows multiple voice prompts.
           */
          @Deprecated
          @Nullable
          public CharSequence getPrompt() {
              return (mPrompt != null ? mPrompt.getVoicePromptAt(0) : null);
          }
  
          /**
           * Return the set of options the user is picking from, as per
           * {@link android.app.VoiceInteractor.PickOptionRequest VoiceInteractor.PickOptionRequest}.
           */
          public VoiceInteractor.PickOptionRequest.Option[] getOptions() {
              return mOptions;
          }
  
          void sendPickOptionResult(boolean finished,
                  VoiceInteractor.PickOptionRequest.Option[] selections, Bundle result) {
              try {
                  if (DEBUG) Log.d(TAG, "sendPickOptionResult: req=" + mInterface
                          + " finished=" + finished + " selections=" + selections
                          + " result=" + result);
                  if (finished) {
                      finishRequest();
                  }
                  mCallback.deliverPickOptionResult(mInterface, finished, selections, result);
              } catch (RemoteException e) {
              }
          }
  
          /**
           * Report an intermediate option selection from the request, without completing it (the
           * request is still active and the app is waiting for the final option selection),
           * resulting in a call to
           * {@link android.app.VoiceInteractor.PickOptionRequest#onPickOptionResult
           * VoiceInteractor.PickOptionRequest.onPickOptionResult} with false for finished.
           */
          public void sendIntermediatePickOptionResult(
                  VoiceInteractor.PickOptionRequest.Option[] selections, Bundle result) {
              sendPickOptionResult(false, selections, result);
          }
  
          /**
           * Report the final option selection for the request, completing the request
           * and resulting in a call to
           * {@link android.app.VoiceInteractor.PickOptionRequest#onPickOptionResult
           * VoiceInteractor.PickOptionRequest.onPickOptionResult} with false for finished.
           * This finishes the request (it is no longer active).
           */
          public void sendPickOptionResult(
                  VoiceInteractor.PickOptionRequest.Option[] selections, Bundle result) {
              sendPickOptionResult(true, selections, result);
          }
  
          void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
              super.dump(prefix, fd, writer, args);
              writer.print(prefix); writer.print("mPrompt=");
              writer.println(mPrompt);
              if (mOptions != null) {
                  writer.print(prefix); writer.println("Options:");
                  for (int i=0; i<mOptions.length; i++) {
                      VoiceInteractor.PickOptionRequest.Option op = mOptions[i];
                      writer.print(prefix); writer.print("  #"); writer.print(i); writer.println(":");
                      writer.print(prefix); writer.print("    mLabel=");
                      writer.println(op.getLabel());
                      writer.print(prefix); writer.print("    mIndex=");
                      writer.println(op.getIndex());
                      if (op.countSynonyms() > 0) {
                          writer.print(prefix); writer.println("    Synonyms:");
                          for (int j=0; j<op.countSynonyms(); j++) {
                              writer.print(prefix); writer.print("      #"); writer.print(j);
                              writer.print(": "); writer.println(op.getSynonymAt(j));
                          }
                      }
                      if (op.getExtras() != null) {
                          writer.print(prefix); writer.print("    mExtras=");
                          writer.println(op.getExtras());
                      }
                  }
              }
          }
      }
  
      /**
       * A request to simply inform the user that the voice operation has completed, as per
       * {@link android.app.VoiceInteractor.CompleteVoiceRequest
       * VoiceInteractor.CompleteVoiceRequest}.
       */
      public static final class CompleteVoiceRequest extends Request {
          final VoiceInteractor.Prompt mPrompt;
  
          CompleteVoiceRequest(String packageName, int uid, IVoiceInteractorCallback callback,
                  VoiceInteractionSession session, VoiceInteractor.Prompt prompt, Bundle extras) {
              super(packageName, uid, callback, session, extras);
              mPrompt = prompt;
          }
  
          /**
           * Return the message informing the user of the completion, as per
           * {@link android.app.VoiceInteractor.CompleteVoiceRequest
           * VoiceInteractor.CompleteVoiceRequest}.
           */
          @Nullable
          public VoiceInteractor.Prompt getVoicePrompt() {
              return mPrompt;
          }
  
          /**
           * Return the message informing the user of the completion, as per
           * {@link android.app.VoiceInteractor.CompleteVoiceRequest
           * VoiceInteractor.CompleteVoiceRequest}.
           * @deprecated Prefer {@link #getVoicePrompt()} which allows a separate visual message.
           */
          @Deprecated
          @Nullable
          public CharSequence getMessage() {
              return (mPrompt != null ? mPrompt.getVoicePromptAt(0) : null);
          }
  
          /**
           * Report that the voice interactor has finished completing the voice operation, resulting
           * in a call to
           * {@link android.app.VoiceInteractor.CompleteVoiceRequest#onCompleteResult
           * VoiceInteractor.CompleteVoiceRequest.onCompleteResult}.
           * This finishes the request (it is no longer active).
           */
          public void sendCompleteResult(Bundle result) {
              try {
                  if (DEBUG) Log.d(TAG, "sendCompleteVoiceResult: req=" + mInterface
                          + " result=" + result);
                  finishRequest();
                  mCallback.deliverCompleteVoiceResult(mInterface, result);
              } catch (RemoteException e) {
              }
          }
  
          void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
              super.dump(prefix, fd, writer, args);
              writer.print(prefix); writer.print("mPrompt=");
              writer.println(mPrompt);
          }
      }
  
      /**
       * A request to report that the current user interaction can not be completed with voice, as per
       * {@link android.app.VoiceInteractor.AbortVoiceRequest VoiceInteractor.AbortVoiceRequest}.
       */
      public static final class AbortVoiceRequest extends Request {
          final VoiceInteractor.Prompt mPrompt;
  
          AbortVoiceRequest(String packageName, int uid, IVoiceInteractorCallback callback,
                  VoiceInteractionSession session, VoiceInteractor.Prompt prompt, Bundle extras) {
              super(packageName, uid, callback, session, extras);
              mPrompt = prompt;
          }
  
          /**
           * Return the message informing the user of the problem, as per
           * {@link android.app.VoiceInteractor.AbortVoiceRequest VoiceInteractor.AbortVoiceRequest}.
           */
          @Nullable
          public VoiceInteractor.Prompt getVoicePrompt() {
              return mPrompt;
          }
  
          /**
           * Return the message informing the user of the problem, as per
           * {@link android.app.VoiceInteractor.AbortVoiceRequest VoiceInteractor.AbortVoiceRequest}.
           * @deprecated Prefer {@link #getVoicePrompt()} which allows a separate visual message.
           */
          @Deprecated
          @Nullable
          public CharSequence getMessage() {
              return (mPrompt != null ? mPrompt.getVoicePromptAt(0) : null);
          }
  
          /**
           * Report that the voice interactor has finished aborting the voice operation, resulting
           * in a call to
           * {@link android.app.VoiceInteractor.AbortVoiceRequest#onAbortResult
           * VoiceInteractor.AbortVoiceRequest.onAbortResult}.  This finishes the request (it
           * is no longer active).
           */
          public void sendAbortResult(Bundle result) {
              try {
                  if (DEBUG) Log.d(TAG, "sendConfirmResult: req=" + mInterface
                          + " result=" + result);
                  finishRequest();
                  mCallback.deliverAbortVoiceResult(mInterface, result);
              } catch (RemoteException e) {
              }
          }
  
          void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
              super.dump(prefix, fd, writer, args);
              writer.print(prefix); writer.print("mPrompt=");
              writer.println(mPrompt);
          }
      }
  
      /**
       * A generic vendor-specific request, as per
       * {@link android.app.VoiceInteractor.CommandRequest VoiceInteractor.CommandRequest}.
       */
      public static final class CommandRequest extends Request {
          final String mCommand;
  
          CommandRequest(String packageName, int uid, IVoiceInteractorCallback callback,
                  VoiceInteractionSession session, String command, Bundle extras) {
              super(packageName, uid, callback, session, extras);
              mCommand = command;
          }
  
          /**
           * Return the command that is being executed, as per
           * {@link android.app.VoiceInteractor.CommandRequest VoiceInteractor.CommandRequest}.
           */
          public String getCommand() {
              return mCommand;
          }
  
          void sendCommandResult(boolean finished, Bundle result) {
              try {
                  if (DEBUG) Log.d(TAG, "sendCommandResult: req=" + mInterface
                          + " result=" + result);
                  if (finished) {
                      finishRequest();
                  }
                  mCallback.deliverCommandResult(mInterface, finished, result);
              } catch (RemoteException e) {
              }
          }
  
          /**
           * Report an intermediate result of the request, without completing it (the request
           * is still active and the app is waiting for the final result), resulting in a call to
           * {@link android.app.VoiceInteractor.CommandRequest#onCommandResult
           * VoiceInteractor.CommandRequest.onCommandResult} with false for isCompleted.
           */
          public void sendIntermediateResult(Bundle result) {
              sendCommandResult(false, result);
          }
  
          /**
           * Report the final result of the request, completing the request and resulting in a call to
           * {@link android.app.VoiceInteractor.CommandRequest#onCommandResult
           * VoiceInteractor.CommandRequest.onCommandResult} with true for isCompleted.
           * This finishes the request (it is no longer active).
           */
          public void sendResult(Bundle result) {
              sendCommandResult(true, result);
          }
  
          void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
              super.dump(prefix, fd, writer, args);
              writer.print(prefix); writer.print("mCommand=");
              writer.println(mCommand);
          }
      }
  
      static final int MSG_START_CONFIRMATION = 1;
      static final int MSG_START_PICK_OPTION = 2;
      static final int MSG_START_COMPLETE_VOICE = 3;
      static final int MSG_START_ABORT_VOICE = 4;
      static final int MSG_START_COMMAND = 5;
      static final int MSG_SUPPORTS_COMMANDS = 6;
      static final int MSG_CANCEL = 7;
  
      static final int MSG_TASK_STARTED = 100;
      static final int MSG_TASK_FINISHED = 101;
      static final int MSG_CLOSE_SYSTEM_DIALOGS = 102;
      static final int MSG_DESTROY = 103;
      static final int MSG_HANDLE_ASSIST = 104;
      static final int MSG_HANDLE_SCREENSHOT = 105;
      static final int MSG_SHOW = 106;
      static final int MSG_HIDE = 107;
      static final int MSG_ON_LOCKSCREEN_SHOWN = 108;
  
      class MyCallbacks implements HandlerCaller.Callback, SoftInputWindow.Callback {
          @Override
          public void executeMessage(Message msg) {
              SomeArgs args = null;
              switch (msg.what) {
                  case MSG_START_CONFIRMATION:
                      if (DEBUG) Log.d(TAG, "onConfirm: req=" + msg.obj);
                      onRequestConfirmation((ConfirmationRequest) msg.obj);
                      break;
                  case MSG_START_PICK_OPTION:
                      if (DEBUG) Log.d(TAG, "onPickOption: req=" + msg.obj);
                      onRequestPickOption((PickOptionRequest) msg.obj);
                      break;
                  case MSG_START_COMPLETE_VOICE:
                      if (DEBUG) Log.d(TAG, "onCompleteVoice: req=" + msg.obj);
                      onRequestCompleteVoice((CompleteVoiceRequest) msg.obj);
                      break;
                  case MSG_START_ABORT_VOICE:
                      if (DEBUG) Log.d(TAG, "onAbortVoice: req=" + msg.obj);
                      onRequestAbortVoice((AbortVoiceRequest) msg.obj);
                      break;
                  case MSG_START_COMMAND:
                      if (DEBUG) Log.d(TAG, "onCommand: req=" + msg.obj);
                      onRequestCommand((CommandRequest) msg.obj);
                      break;
                  case MSG_SUPPORTS_COMMANDS:
                      args = (SomeArgs)msg.obj;
                      if (DEBUG) Log.d(TAG, "onGetSupportedCommands: cmds=" + args.arg1);
                      args.arg1 = onGetSupportedCommands((String[]) args.arg1);
                      args.complete();
                      args = null;
                      break;
                  case MSG_CANCEL:
                      if (DEBUG) Log.d(TAG, "onCancel: req=" + ((Request)msg.obj));
                      onCancelRequest((Request) msg.obj);
                      break;
                  case MSG_TASK_STARTED:
                      if (DEBUG) Log.d(TAG, "onTaskStarted: intent=" + msg.obj
                              + " taskId=" + msg.arg1);
                      onTaskStarted((Intent) msg.obj, msg.arg1);
                      break;
                  case MSG_TASK_FINISHED:
                      if (DEBUG) Log.d(TAG, "onTaskFinished: intent=" + msg.obj
                              + " taskId=" + msg.arg1);
                      onTaskFinished((Intent) msg.obj, msg.arg1);
                      break;
                  case MSG_CLOSE_SYSTEM_DIALOGS:
                      if (DEBUG) Log.d(TAG, "onCloseSystemDialogs");
                      onCloseSystemDialogs();
                      break;
                  case MSG_DESTROY:
                      if (DEBUG) Log.d(TAG, "doDestroy");
                      doDestroy();
                      break;
                  case MSG_HANDLE_ASSIST:
                      args = (SomeArgs)msg.obj;
                      if (DEBUG) Log.d(TAG, "onHandleAssist: taskId=" + args.argi1
                              + "assistToken=" + args.arg5 + " data=" + args.arg1
                              + " structure=" + args.arg2 + " content=" + args.arg3
                              + " activityIndex=" + args.argi5 + " activityCount=" + args.argi6);
                      doOnHandleAssist(args.argi1, (IBinder) args.arg5, (Bundle) args.arg1,
                              (AssistStructure) args.arg2, (Throwable) args.arg3,
                              (AssistContent) args.arg4, args.argi5, args.argi6);
                      break;
                  case MSG_HANDLE_SCREENSHOT:
                      if (DEBUG) Log.d(TAG, "onHandleScreenshot: " + msg.obj);
                      onHandleScreenshot((Bitmap) msg.obj);
                      break;
                  case MSG_SHOW:
                      args = (SomeArgs)msg.obj;
                      if (DEBUG) Log.d(TAG, "doShow: args=" + args.arg1
                              + " flags=" + msg.arg1
                              + " showCallback=" + args.arg2);
                      doShow((Bundle) args.arg1, msg.arg1,
                              (IVoiceInteractionSessionShowCallback) args.arg2);
                      break;
                  case MSG_HIDE:
                      if (DEBUG) Log.d(TAG, "doHide");
                      doHide();
                      break;
                  case MSG_ON_LOCKSCREEN_SHOWN:
                      if (DEBUG) Log.d(TAG, "onLockscreenShown");
                      onLockscreenShown();
                      break;
              }
              if (args != null) {
                  args.recycle();
              }
          }
  
          @Override
          public void onBackPressed() {
              VoiceInteractionSession.this.onBackPressed();
          }
      }
  
      final MyCallbacks mCallbacks = new MyCallbacks();
  
      /**
       * Information about where interesting parts of the input method UI appear.
       */
      public static final class Insets {
          /**
           * This is the part of the UI that is the main content.  It is
           * used to determine the basic space needed, to resize/pan the
           * application behind.  It is assumed that this inset does not
           * change very much, since any change will cause a full resize/pan
           * of the application behind.  This value is relative to the top edge
           * of the input method window.
           */
          public final Rect contentInsets = new Rect();
  
          /**
           * This is the region of the UI that is touchable.  It is used when
           * {@link #touchableInsets} is set to {@link #TOUCHABLE_INSETS_REGION}.
           * The region should be specified relative to the origin of the window frame.
           */
          public final Region touchableRegion = new Region();
  
          /**
           * Option for {@link #touchableInsets}: the entire window frame
           * can be touched.
           */
          public static final int TOUCHABLE_INSETS_FRAME
                  = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
  
          /**
           * Option for {@link #touchableInsets}: the area inside of
           * the content insets can be touched.
           */
          public static final int TOUCHABLE_INSETS_CONTENT
                  = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT;
  
          /**
           * Option for {@link #touchableInsets}: the region specified by
           * {@link #touchableRegion} can be touched.
           */
          public static final int TOUCHABLE_INSETS_REGION
                  = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
  
          /**
           * Determine which area of the window is touchable by the user.  May
           * be one of: {@link #TOUCHABLE_INSETS_FRAME},
           * {@link #TOUCHABLE_INSETS_CONTENT}, or {@link #TOUCHABLE_INSETS_REGION}.
           */
          public int touchableInsets;
      }
  
      final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer =
              new ViewTreeObserver.OnComputeInternalInsetsListener() {
          public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) {
              onComputeInsets(mTmpInsets);
              info.contentInsets.set(mTmpInsets.contentInsets);
              info.visibleInsets.set(mTmpInsets.contentInsets);
              info.touchableRegion.set(mTmpInsets.touchableRegion);
              info.setTouchableInsets(mTmpInsets.touchableInsets);
          }
      };
  
      public VoiceInteractionSession(Context context) {
          this(context, new Handler());
      }
  
      public VoiceInteractionSession(Context context, Handler handler) {
          mContext = context;
          mHandlerCaller = new HandlerCaller(context, handler.getLooper(),
                  mCallbacks, true);
      }
  
      public Context getContext() {
          return mContext;
      }
  
      void addRequest(Request req) {
          synchronized (this) {
              mActiveRequests.put(req.mInterface.asBinder(), req);
          }
      }
  
      boolean isRequestActive(IBinder reqInterface) {
          synchronized (this) {
              return mActiveRequests.containsKey(reqInterface);
          }
      }
  
      Request removeRequest(IBinder reqInterface) {
          synchronized (this) {
              return mActiveRequests.remove(reqInterface);
          }
      }
  
      void doCreate(IVoiceInteractionManagerService service, IBinder token) {
          mSystemService = service;
          mToken = token;
          onCreate();
      }
  
      void doShow(Bundle args, int flags, final IVoiceInteractionSessionShowCallback showCallback) {
          if (DEBUG) Log.v(TAG, "Showing window: mWindowAdded=" + mWindowAdded
                  + " mWindowVisible=" + mWindowVisible);
  
          if (mInShowWindow) {
              Log.w(TAG, "Re-entrance in to showWindow");
              return;
          }
  
          try {
              mInShowWindow = true;
              onPrepareShow(args, flags);
              if (!mWindowVisible) {
                  ensureWindowAdded();
              }
              onShow(args, flags);
              if (!mWindowVisible) {
                  mWindowVisible = true;
                  if (mUiEnabled) {
                      mWindow.show();
                  }
              }
              if (showCallback != null) {
                  if (mUiEnabled) {
                      mRootView.invalidate();
                      mRootView.getViewTreeObserver().addOnPreDrawListener(
                              new ViewTreeObserver.OnPreDrawListener() {
                                  @Override
                                  public boolean onPreDraw() {
                                      mRootView.getViewTreeObserver().removeOnPreDrawListener(this);
                                      try {
                                          showCallback.onShown();
                                      } catch (RemoteException e) {
                                          Log.w(TAG, "Error calling onShown", e);
                                      }
                                      return true;
                                  }
                              });
                  } else {
                      try {
                          showCallback.onShown();
                      } catch (RemoteException e) {
                          Log.w(TAG, "Error calling onShown", e);
                      }
                  }
              }
          } finally {
              mWindowWasVisible = true;
              mInShowWindow = false;
          }
      }
  
      void doHide() {
          if (mWindowVisible) {
              ensureWindowHidden();
              mWindowVisible = false;
              onHide();
          }
      }
  
      void doDestroy() {
          onDestroy();
          if (mKillCallback != null) {
              try {
                  mKillCallback.cancel();
              } catch (RemoteException e) {
                  /* ignore */
              }
              mKillCallback = null;
          }
          if (mInitialized) {
              mRootView.getViewTreeObserver().removeOnComputeInternalInsetsListener(
                      mInsetsComputer);
              if (mWindowAdded) {
                  mWindow.dismiss();
                  mWindowAdded = false;
              }
              mInitialized = false;
          }
      }
  
      void ensureWindowCreated() {
          if (mInitialized) {
              return;
          }
  
          if (!mUiEnabled) {
              throw new IllegalStateException("setUiEnabled is false");
          }
  
          mInitialized = true;
          mInflater = (LayoutInflater)mContext.getSystemService(
                  Context.LAYOUT_INFLATER_SERVICE);
          mWindow = new SoftInputWindow(mContext, "VoiceInteractionSession", mTheme,
                  mCallbacks, this, mDispatcherState,
                  WindowManager.LayoutParams.TYPE_VOICE_INTERACTION, Gravity.BOTTOM, true);
          mWindow.getWindow().addFlags(
                  WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED |
                          WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN |
                          WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
  
          mThemeAttrs = mContext.obtainStyledAttributes(android.R.styleable.VoiceInteractionSession);
          mRootView = mInflater.inflate(
                  com.android.internal.R.layout.voice_interaction_session, null);
          mRootView.setSystemUiVisibility(
                  View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                          | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
          mWindow.setContentView(mRootView);
          mRootView.getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsComputer);
  
          mContentFrame = (FrameLayout)mRootView.findViewById(android.R.id.content);
  
          mWindow.getWindow().setLayout(MATCH_PARENT, MATCH_PARENT);
          mWindow.setToken(mToken);
      }
  
      void ensureWindowAdded() {
          if (mUiEnabled && !mWindowAdded) {
              mWindowAdded = true;
              ensureWindowCreated();
              View v = onCreateContentView();
              if (v != null) {
                  setContentView(v);
              }
          }
      }
  
      void ensureWindowHidden() {
          if (mWindow != null) {
              mWindow.hide();
          }
      }
  
      /**
       * Equivalent to {@link VoiceInteractionService#setDisabledShowContext
       * VoiceInteractionService.setDisabledShowContext(int)}.
       */
      public void setDisabledShowContext(int flags) {
          try {
              mSystemService.setDisabledShowContext(flags);
          } catch (RemoteException e) {
          }
      }
  
      /**
       * Equivalent to {@link VoiceInteractionService#getDisabledShowContext
       * VoiceInteractionService.getDisabledShowContext}.
       */
      public int getDisabledShowContext() {
          try {
              return mSystemService.getDisabledShowContext();
          } catch (RemoteException e) {
              return 0;
          }
      }
  
      /**
       * Return which show context flags have been disabled by the user through the system
       * settings UI, so the session will never get this data.  Returned flags are any combination of
       * {@link VoiceInteractionSession#SHOW_WITH_ASSIST VoiceInteractionSession.SHOW_WITH_ASSIST} and
       * {@link VoiceInteractionSession#SHOW_WITH_SCREENSHOT
       * VoiceInteractionSession.SHOW_WITH_SCREENSHOT}.  Note that this only tells you about
       * global user settings, not about restrictions that may be applied contextual based on
       * the current application the user is in or other transient states.
       */
      public int getUserDisabledShowContext() {
          try {
              return mSystemService.getUserDisabledShowContext();
          } catch (RemoteException e) {
              return 0;
          }
      }
  
      /**
       * Show the UI for this session.  This asks the system to go through the process of showing
       * your UI, which will eventually culminate in {@link #onShow}.  This is similar to calling
       * {@link VoiceInteractionService#showSession VoiceInteractionService.showSession}.
       * @param args Arbitrary arguments that will be propagated {@link #onShow}.
       * @param flags Indicates additional optional behavior that should be performed.  May
       * be any combination of
       * {@link VoiceInteractionSession#SHOW_WITH_ASSIST VoiceInteractionSession.SHOW_WITH_ASSIST} and
       * {@link VoiceInteractionSession#SHOW_WITH_SCREENSHOT
       * VoiceInteractionSession.SHOW_WITH_SCREENSHOT}
       * to request that the system generate and deliver assist data on the current foreground
       * app as part of showing the session UI.
       */
      public void show(Bundle args, int flags) {
          if (mToken == null) {
              throw new IllegalStateException("Can't call before onCreate()");
          }
          try {
              mSystemService.showSessionFromSession(mToken, args, flags);
          } catch (RemoteException e) {
          }
      }
  
      /**
       * Hide the session's UI, if currently shown.  Call this when you are done with your
       * user interaction.
       */
      public void hide() {
          if (mToken == null) {
              throw new IllegalStateException("Can't call before onCreate()");
          }
          try {
              mSystemService.hideSessionFromSession(mToken);
          } catch (RemoteException e) {
          }
      }
  
      /**
       * Control whether the UI layer for this session is enabled.  It is enabled by default.
       * If set to false, you will not be able to provide a UI through {@link #onCreateContentView()}.
       */
      public void setUiEnabled(boolean enabled) {
          if (mUiEnabled != enabled) {
              mUiEnabled = enabled;
              if (mWindowVisible) {
                  if (enabled) {
                      ensureWindowAdded();
                      mWindow.show();
                  } else {
                      ensureWindowHidden();
                  }
              }
          }
      }
  
      /**
       * You can call this to customize the theme used by your IME's window.
       * This must be set before {@link #onCreate}, so you
       * will typically call it in your constructor with the resource ID
       * of your custom theme.
       */
      public void setTheme(int theme) {
          if (mWindow != null) {
              throw new IllegalStateException("Must be called before onCreate()");
          }
          mTheme = theme;
      }
  
      /**
       * Ask that a new activity be started for voice interaction.  This will create a
       * new dedicated task in the activity manager for this voice interaction session;
       * this means that {@link Intent#FLAG_ACTIVITY_NEW_TASK Intent.FLAG_ACTIVITY_NEW_TASK}
       * will be set for you to make it a new task.
       *
       * <p>The newly started activity will be displayed to the user in a special way, as
       * a layer under the voice interaction UI.</p>
       *
       * <p>As the voice activity runs, it can retrieve a {@link android.app.VoiceInteractor}
       * through which it can perform voice interactions through your session.  These requests
       * for voice interactions will appear as callbacks on {@link #onGetSupportedCommands},
       * {@link #onRequestConfirmation}, {@link #onRequestPickOption},
       * {@link #onRequestCompleteVoice}, {@link #onRequestAbortVoice},
       * or {@link #onRequestCommand}
       *
       * <p>You will receive a call to {@link #onTaskStarted} when the task starts up
       * and {@link #onTaskFinished} when the last activity has finished.
       *
       * @param intent The Intent to start this voice interaction.  The given Intent will
       * always have {@link Intent#CATEGORY_VOICE Intent.CATEGORY_VOICE} added to it, since
       * this is part of a voice interaction.
       */
      public void startVoiceActivity(Intent intent) {
          if (mToken == null) {
              throw new IllegalStateException("Can't call before onCreate()");
          }
          try {
              intent.migrateExtraStreamToClipData();
              intent.prepareToLeaveProcess(mContext);
              int res = mSystemService.startVoiceActivity(mToken, intent,
                      intent.resolveType(mContext.getContentResolver()));
              Instrumentation.checkStartActivityResult(res, intent);
          } catch (RemoteException e) {
          }
      }
  
      /**
       * <p>Ask that a new assistant activity be started.  This will create a new task in the
       * in activity manager: this means that
       * {@link Intent#FLAG_ACTIVITY_NEW_TASK Intent.FLAG_ACTIVITY_NEW_TASK}
       * will be set for you to make it a new task.</p>
       *
       * <p>The newly started activity will be displayed on top of other activities in the system
       * in a new layer that is not affected by multi-window mode.  Tasks started from this activity
       * will go into the normal activity layer and not this new layer.</p>
       *
       * <p>By default, the system will create a window for the UI for this session.  If you are using
       * an assistant activity instead, then you can disable the window creation by calling
       * {@link #setUiEnabled} in {@link #onPrepareShow(Bundle, int)}.</p>
       */
      public void startAssistantActivity(Intent intent) {
          if (mToken == null) {
              throw new IllegalStateException("Can't call before onCreate()");
          }
          try {
              intent.migrateExtraStreamToClipData();
              intent.prepareToLeaveProcess(mContext);
              int res = mSystemService.startAssistantActivity(mToken, intent,
                      intent.resolveType(mContext.getContentResolver()));
              Instrumentation.checkStartActivityResult(res, intent);
          } catch (RemoteException e) {
          }
      }
  
      /**
       * Requests a list of supported actions from an app.
       *
       * @param activityId Ths activity id of the app to get the actions from.
       * @param resultExecutor The handler to receive the callback
       * @param cancellationSignal A signal to cancel the operation in progress,
       *     or {@code null} if none.
       * @param callback The callback to receive the response
       */
      public final void requestDirectActions(@NonNull ActivityId activityId,
              @Nullable CancellationSignal cancellationSignal,
              @NonNull @CallbackExecutor Executor resultExecutor,
              @NonNull Consumer<List<DirectAction>> callback) {
          Preconditions.checkNotNull(activityId);
          Preconditions.checkNotNull(resultExecutor);
          Preconditions.checkNotNull(callback);
          if (mToken == null) {
              throw new IllegalStateException("Can't call before onCreate()");
          }
  
          if (cancellationSignal != null) {
              cancellationSignal.throwIfCanceled();
          }
  
          final RemoteCallback cancellationCallback = (cancellationSignal != null)
                  ? new RemoteCallback(b -> {
                      if (b != null) {
                          final IBinder cancellation = b.getBinder(
                                  VoiceInteractor.KEY_CANCELLATION_SIGNAL);
                          if (cancellation != null) {
                              cancellationSignal.setRemote(ICancellationSignal.Stub.asInterface(
                                      cancellation));
                          }
                      }
                  })
                  : null;
  
          try {
              mSystemService.requestDirectActions(mToken, activityId.getTaskId(),
                      activityId.getAssistToken(), cancellationCallback,
                      new RemoteCallback(createSafeResultListener((result) -> {
                  List<DirectAction> list;
                  if (result == null) {
                      list = Collections.emptyList();
                  } else {
                      final ParceledListSlice<DirectAction> pls = result.getParcelable(
                              DirectAction.KEY_ACTIONS_LIST);
                      if (pls != null) {
                          final List<DirectAction> receivedList = pls.getList();
                          list = (receivedList != null) ? receivedList : Collections.emptyList();
                      } else {
                          list = Collections.emptyList();
                      }
                  }
                  resultExecutor.execute(() -> callback.accept(list));
              })));
          } catch (RemoteException e) {
              e.rethrowFromSystemServer();
          }
      }
  
      /**
       * Called when the direct actions are invalidated.
       */
      public void onDirectActionsInvalidated(@NonNull ActivityId activityId) {
  
      }
  
      /**
       * Asks that an action be performed by the app. This will send a request to the app which
       * provided this action.
       *
       * <p> An action could take time to execute and the result is provided asynchronously
       * via a callback. If the action is taking longer and you want to cancel its execution
       * you can pass in a cancellation signal through which to notify the app to abort the
       * action.
       *
       * @param action The action to be performed.
       * @param extras Any optional extras sent to the app as part of the request
       * @param cancellationSignal A signal to cancel the operation in progress,
       *                          or {@code null} if none.
       * @param resultExecutor The handler to receive the callback.
       * @param resultListener The callback to receive the response.
       *
       * @see #requestDirectActions(ActivityId, CancellationSignal, Executor, Consumer)
       * @see Activity#onGetDirectActions(CancellationSignal, Consumer)
       */
      public final void performDirectAction(@NonNull DirectAction action, @Nullable Bundle extras,
              @Nullable CancellationSignal cancellationSignal,
              @NonNull @CallbackExecutor Executor resultExecutor,
              @NonNull Consumer<Bundle> resultListener) {
          if (mToken == null) {
              throw new IllegalStateException("Can't call before onCreate()");
          }
          Preconditions.checkNotNull(resultExecutor);
          Preconditions.checkNotNull(resultListener);
  
          if (cancellationSignal != null) {
              cancellationSignal.throwIfCanceled();
          }
  
          final RemoteCallback cancellationCallback = (cancellationSignal != null)
                  ? new RemoteCallback(createSafeResultListener(b -> {
                      if (b != null) {
                          final IBinder cancellation = b.getBinder(
                                  VoiceInteractor.KEY_CANCELLATION_SIGNAL);
                          if (cancellation != null) {
                              cancellationSignal.setRemote(ICancellationSignal.Stub.asInterface(
                                      cancellation));
                          }
                      }
                  }))
                  : null;
  
          final RemoteCallback resultCallback = new RemoteCallback(createSafeResultListener(b -> {
              if (b != null) {
                  resultExecutor.execute(() -> resultListener.accept(b));
              } else {
                  resultExecutor.execute(() -> resultListener.accept(Bundle.EMPTY));
              }
          }));
  
          try {
              mSystemService.performDirectAction(mToken, action.getId(), extras,
                      action.getTaskId(), action.getActivityId(), cancellationCallback,
                      resultCallback);
          } catch (RemoteException e) {
              e.rethrowFromSystemServer();
          }
      }
  
      /**
       * Set whether this session will keep the device awake while it is running a voice
       * activity.  By default, the system holds a wake lock for it while in this state,
       * so that it can work even if the screen is off.  Setting this to false removes that
       * wake lock, allowing the CPU to go to sleep.  This is typically used if the
       * session decides it has been waiting too long for a response from the user and
       * doesn't want to let this continue to drain the battery.
       *
       * <p>Passing false here will release the wake lock, and you can call later with
       * true to re-acquire it.  It will also be automatically re-acquired for you each
       * time you start a new voice activity task -- that is when you call
       * {@link #startVoiceActivity}.</p>
       */
      public void setKeepAwake(boolean keepAwake) {
          if (mToken == null) {
              throw new IllegalStateException("Can't call before onCreate()");
          }
          try {
              mSystemService.setKeepAwake(mToken, keepAwake);
          } catch (RemoteException e) {
          }
      }
  
      /**
       * Request that all system dialogs (and status bar shade etc) be closed, allowing
       * access to the session's UI.  This will <em>not</em> cause the lock screen to be
       * dismissed.
       */
      public void closeSystemDialogs() {
          if (mToken == null) {
              throw new IllegalStateException("Can't call before onCreate()");
          }
          try {
              mSystemService.closeSystemDialogs(mToken);
          } catch (RemoteException e) {
          }
      }
  
      /**
       * Convenience for inflating views.
       */
      public LayoutInflater getLayoutInflater() {
          ensureWindowCreated();
          return mInflater;
      }
  
      /**
       * Retrieve the window being used to show the session's UI.
       */
      public Dialog getWindow() {
          ensureWindowCreated();
          return mWindow;
      }
  
      /**
       * Finish the session.  This completely destroys the session -- the next time it is shown,
       * an entirely new one will be created.  You do not normally call this function; instead,
       * use {@link #hide} and allow the system to destroy your session if it needs its RAM.
       */
      public void finish() {
          if (mToken == null) {
              throw new IllegalStateException("Can't call before onCreate()");
          }
          try {
              mSystemService.finish(mToken);
          } catch (RemoteException e) {
          }
      }
  
      /**
       * Initiatize a new session.  At this point you don't know exactly what this
       * session will be used for; you will find that out in {@link #onShow}.
       */
      public void onCreate() {
          doOnCreate();
      }
  
      private void doOnCreate() {
          mTheme = mTheme != 0 ? mTheme
                  : com.android.internal.R.style.Theme_DeviceDefault_VoiceInteractionSession;
      }
  
      /**
       * Called prior to {@link #onShow} before any UI setup has occurred.  Not generally useful.
       *
       * @param args The arguments that were supplied to
       * {@link VoiceInteractionService#showSession VoiceInteractionService.showSession}.
       * @param showFlags The show flags originally provided to
       * {@link VoiceInteractionService#showSession VoiceInteractionService.showSession}.
       */
      public void onPrepareShow(Bundle args, int showFlags) {
      }
  
      /**
       * Called when the session UI is going to be shown.  This is called after
       * {@link #onCreateContentView} (if the session's content UI needed to be created) and
       * immediately prior to the window being shown.  This may be called while the window
       * is already shown, if a show request has come in while it is shown, to allow you to
       * update the UI to match the new show arguments.
       *
       * @param args The arguments that were supplied to
       * {@link VoiceInteractionService#showSession VoiceInteractionService.showSession}.
       * @param showFlags The show flags originally provided to
       * {@link VoiceInteractionService#showSession VoiceInteractionService.showSession}.
       */
      public void onShow(Bundle args, int showFlags) {
      }
  
      /**
       * Called immediately after stopping to show the session UI.
       */
      public void onHide() {
      }
  
      /**
       * Last callback to the session as it is being finished.
       */
      public void onDestroy() {
      }
  
      /**
       * Hook in which to create the session's UI.
       */
      public View onCreateContentView() {
          return null;
      }
  
      public void setContentView(View view) {
          ensureWindowCreated();
          mContentFrame.removeAllViews();
          mContentFrame.addView(view, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
          mContentFrame.requestApplyInsets();
      }
  
      void doOnHandleAssist(int taskId, IBinder assistToken, Bundle data, AssistStructure structure,
              Throwable failure, AssistContent content, int index, int count) {
          if (failure != null) {
              onAssistStructureFailure(failure);
          }
          AssistState assistState = new AssistState(new ActivityId(taskId, assistToken),
                  data, structure, content, index, count);
          onHandleAssist(assistState);
      }
  
      /**
       * Called when there has been a failure transferring the {@link AssistStructure} to
       * the assistant.  This may happen, for example, if the data is too large and results
       * in an out of memory exception, or the client has provided corrupt data.  This will
       * be called immediately before {@link #onHandleAssist} and the AssistStructure supplied
       * there afterwards will be null.
       *
       * @param failure The failure exception that was thrown when building the
       * {@link AssistStructure}.
       */
      public void onAssistStructureFailure(Throwable failure) {
      }
  
      /**
       * Called to receive data from the application that the user was currently viewing when
       * an assist session is started.  If the original show request did not specify
       * {@link #SHOW_WITH_ASSIST}, this method will not be called.
       *
       * @param data Arbitrary data supplied by the app through
       * {@link android.app.Activity#onProvideAssistData Activity.onProvideAssistData}.
       * May be null if assist data has been disabled by the user or device policy.
       * @param structure If available, the structure definition of all windows currently
       * displayed by the app.  May be null if assist data has been disabled by the user
       * or device policy; will be an empty stub if the application has disabled assist
       * by marking its window as secure.
       * @param content Additional content data supplied by the app through
       * {@link android.app.Activity#onProvideAssistContent Activity.onProvideAssistContent}.
       * May be null if assist data has been disabled by the user or device policy; will
       * not be automatically filled in with data from the app if the app has marked its
       * window as secure.
       *
       * @deprecated use {@link #onHandleAssist(AssistState)}
       */
      @Deprecated
      public void onHandleAssist(@Nullable Bundle data, @Nullable AssistStructure structure,
              @Nullable AssistContent content) {
      }
  
      /**
       * Called to receive data from the application that the user was currently viewing when
       * an assist session is started.  If the original show request did not specify
       * {@link #SHOW_WITH_ASSIST}, this method will not be called.
       *
       * <p>This method is called for all activities along with an index and count that indicates
       * which activity the data is for. {@code index} will be between 0 and {@code count}-1 and
       * this method is called once for each activity in no particular order. The {@code count}
       * indicates how many activities to expect assist data for, including the top focused one.
       * The focused activity can be determined by calling {@link AssistState#isFocused()}.
       *
       * <p>To be responsive to assist requests, process assist data as soon as it is received,
       * without waiting for all queued activities to return assist data.
       *
       * @param state The state object capturing the state of an activity.
       */
      public void onHandleAssist(@NonNull AssistState state) {
          if (state.getIndex() == 0) {
              onHandleAssist(state.getAssistData(), state.getAssistStructure(),
                      state.getAssistContent());
          } else {
              onHandleAssistSecondary(state.getAssistData(), state.getAssistStructure(),
                      state.getAssistContent(), state.getIndex(), state.getCount());
          }
      }
  
      /**
       * Called to receive data from other applications that the user was or is interacting with,
       * that are currently on the screen in a multi-window display environment, not including the
       * currently focused activity. This could be
       * a free-form window, a picture-in-picture window, or another window in a split-screen display.
       * <p>
       * This method is very similar to
       * {@link #onHandleAssist} except that it is called
       * for additional non-focused activities along with an index and count that indicates
       * which additional activity the data is for. {@code index} will be between 1 and
       * {@code count}-1 and this method is called once for each additional window, in no particular
       * order. The {@code count} indicates how many windows to expect assist data for, including the
       * top focused activity, which continues to be returned via {@link #onHandleAssist}.
       * <p>
       * To be responsive to assist requests, process assist data as soon as it is received,
       * without waiting for all queued activities to return assist data.
       *
       * @param data Arbitrary data supplied by the app through
       * {@link android.app.Activity#onProvideAssistData Activity.onProvideAssistData}.
       * May be null if assist data has been disabled by the user or device policy.
       * @param structure If available, the structure definition of all windows currently
       * displayed by the app.  May be null if assist data has been disabled by the user
       * or device policy; will be an empty stub if the application has disabled assist
       * by marking its window as secure.
       * @param content Additional content data supplied by the app through
       * {@link android.app.Activity#onProvideAssistContent Activity.onProvideAssistContent}.
       * May be null if assist data has been disabled by the user or device policy; will
       * not be automatically filled in with data from the app if the app has marked its
       * window as secure.
       * @param index the index of the additional activity that this data
       *        is for.
       * @param count the total number of additional activities for which the assist data is being
       *        returned, including the focused activity that is returned via
       *        {@link #onHandleAssist}.
       *
       * @deprecated use {@link #onHandleAssist(AssistState)}
       */
      @Deprecated
      public void onHandleAssistSecondary(@Nullable Bundle data, @Nullable AssistStructure structure,
              @Nullable AssistContent content, int index, int count) {
      }
  
      /**
       * Called to receive a screenshot of what the user was currently viewing when an assist
       * session is started.  May be null if screenshots are disabled by the user, policy,
       * or application.  If the original show request did not specify
       * {@link #SHOW_WITH_SCREENSHOT}, this method will not be called.
       */
      public void onHandleScreenshot(@Nullable Bitmap screenshot) {
      }
  
      public boolean onKeyDown(int keyCode, KeyEvent event) {
          return false;
      }
  
      public boolean onKeyLongPress(int keyCode, KeyEvent event) {
          return false;
      }
  
      public boolean onKeyUp(int keyCode, KeyEvent event) {
          return false;
      }
  
      public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) {
          return false;
      }
  
      /**
       * Called when the user presses the back button while focus is in the session UI.  Note
       * that this will only happen if the session UI has requested input focus in its window;
       * otherwise, the back key will go to whatever window has focus and do whatever behavior
       * it normally has there.  The default implementation simply calls {@link #hide}.
       */
      public void onBackPressed() {
          hide();
      }
  
      /**
       * Sessions automatically watch for requests that all system UI be closed (such as when
       * the user presses HOME), which will appear here.  The default implementation always
       * calls {@link #hide}.
       */
      public void onCloseSystemDialogs() {
          hide();
      }
  
      /**
       * Called when the lockscreen was shown.
       */
      public void onLockscreenShown() {
          hide();
      }
  
      @Override
      public void onConfigurationChanged(Configuration newConfig) {
      }
  
      @Override
      public void onLowMemory() {
      }
  
      @Override
      public void onTrimMemory(int level) {
      }
  
      /**
       * Compute the interesting insets into your UI.  The default implementation
       * sets {@link Insets#contentInsets outInsets.contentInsets.top} to the height
       * of the window, meaning it should not adjust content underneath.  The default touchable
       * insets are {@link Insets#TOUCHABLE_INSETS_FRAME}, meaning it consumes all touch
       * events within its window frame.
       *
       * @param outInsets Fill in with the current UI insets.
       */
      public void onComputeInsets(Insets outInsets) {
          outInsets.contentInsets.left = 0;
          outInsets.contentInsets.bottom = 0;
          outInsets.contentInsets.right = 0;
          View decor = getWindow().getWindow().getDecorView();
          outInsets.contentInsets.top = decor.getHeight();
          outInsets.touchableInsets = Insets.TOUCHABLE_INSETS_FRAME;
          outInsets.touchableRegion.setEmpty();
      }
  
      /**
       * Called when a task initiated by {@link #startVoiceActivity(android.content.Intent)}
       * has actually started.
       *
       * @param intent The original {@link Intent} supplied to
       * {@link #startVoiceActivity(android.content.Intent)}.
       * @param taskId Unique ID of the now running task.
       */
      public void onTaskStarted(Intent intent, int taskId) {
      }
  
      /**
       * Called when the last activity of a task initiated by
       * {@link #startVoiceActivity(android.content.Intent)} has finished.  The default
       * implementation calls {@link #finish()} on the assumption that this represents
       * the completion of a voice action.  You can override the implementation if you would
       * like a different behavior.
       *
       * @param intent The original {@link Intent} supplied to
       * {@link #startVoiceActivity(android.content.Intent)}.
       * @param taskId Unique ID of the finished task.
       */
      public void onTaskFinished(Intent intent, int taskId) {
          hide();
      }
  
      /**
       * Request to query for what extended commands the session supports.
       *
       * @param commands An array of commands that are being queried.
       * @return Return an array of booleans indicating which of each entry in the
       * command array is supported.  A true entry in the array indicates the command
       * is supported; false indicates it is not.  The default implementation returns
       * an array of all false entries.
       */
      public boolean[] onGetSupportedCommands(String[] commands) {
          return new boolean[commands.length];
      }
  
      /**
       * Request to confirm with the user before proceeding with an unrecoverable operation,
       * corresponding to a {@link android.app.VoiceInteractor.ConfirmationRequest
       * VoiceInteractor.ConfirmationRequest}.
       *
       * @param request The active request.
       */
      public void onRequestConfirmation(ConfirmationRequest request) {
      }
  
      /**
       * Request for the user to pick one of N options, corresponding to a
       * {@link android.app.VoiceInteractor.PickOptionRequest VoiceInteractor.PickOptionRequest}.
       *
       * @param request The active request.
       */
      public void onRequestPickOption(PickOptionRequest request) {
      }
  
      /**
       * Request to complete the voice interaction session because the voice activity successfully
       * completed its interaction using voice.  Corresponds to
       * {@link android.app.VoiceInteractor.CompleteVoiceRequest
       * VoiceInteractor.CompleteVoiceRequest}.  The default implementation just sends an empty
       * confirmation back to allow the activity to exit.
       *
       * @param request The active request.
       */
      public void onRequestCompleteVoice(CompleteVoiceRequest request) {
      }
  
      /**
       * Request to abort the voice interaction session because the voice activity can not
       * complete its interaction using voice.  Corresponds to
       * {@link android.app.VoiceInteractor.AbortVoiceRequest
       * VoiceInteractor.AbortVoiceRequest}.  The default implementation just sends an empty
       * confirmation back to allow the activity to exit.
       *
       * @param request The active request.
       */
      public void onRequestAbortVoice(AbortVoiceRequest request) {
      }
  
      /**
       * Process an arbitrary extended command from the caller,
       * corresponding to a {@link android.app.VoiceInteractor.CommandRequest
       * VoiceInteractor.CommandRequest}.
       *
       * @param request The active request.
       */
      public void onRequestCommand(CommandRequest request) {
      }
  
      /**
       * Called when the {@link android.app.VoiceInteractor} has asked to cancel a {@link Request}
       * that was previously delivered to {@link #onRequestConfirmation},
       * {@link #onRequestPickOption}, {@link #onRequestCompleteVoice}, {@link #onRequestAbortVoice},
       * or {@link #onRequestCommand}.
       *
       * @param request The request that is being canceled.
       */
      public void onCancelRequest(Request request) {
      }
  
      /**
       * Print the Service's state into the given stream.  This gets invoked by
       * {@link VoiceInteractionSessionService} when its Service
       * {@link android.app.Service#dump} method is called.
       *
       * @param prefix Text to print at the front of each line.
       * @param fd The raw file descriptor that the dump is being sent to.
       * @param writer The PrintWriter to which you should dump your state.  This will be
       * closed for you after you return.
       * @param args additional arguments to the dump request.
       */
      public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
          writer.print(prefix); writer.print("mToken="); writer.println(mToken);
          writer.print(prefix); writer.print("mTheme=#"); writer.println(Integer.toHexString(mTheme));
          writer.print(prefix); writer.print("mUiEnabled="); writer.println(mUiEnabled);
          writer.print(" mInitialized="); writer.println(mInitialized);
          writer.print(prefix); writer.print("mWindowAdded="); writer.print(mWindowAdded);
          writer.print(" mWindowVisible="); writer.println(mWindowVisible);
          writer.print(prefix); writer.print("mWindowWasVisible="); writer.print(mWindowWasVisible);
          writer.print(" mInShowWindow="); writer.println(mInShowWindow);
          if (mActiveRequests.size() > 0) {
              writer.print(prefix); writer.println("Active requests:");
              String innerPrefix = prefix + "    ";
              for (int i=0; i<mActiveRequests.size(); i++) {
                  Request req = mActiveRequests.valueAt(i);
                  writer.print(prefix); writer.print("  #"); writer.print(i);
                  writer.print(": ");
                  writer.println(req);
                  req.dump(innerPrefix, fd, writer, args);
  
              }
          }
      }
  
      private SafeResultListener createSafeResultListener(
              @NonNull Consumer<Bundle> consumer) {
          synchronized (this) {
              final SafeResultListener listener = new SafeResultListener(consumer, this);
              mRemoteCallbacks.put(listener, consumer);
              return listener;
          }
      }
  
      private Consumer<Bundle> removeSafeResultListener(@NonNull SafeResultListener listener) {
          synchronized (this) {
              return mRemoteCallbacks.remove(listener);
          }
      }
  
      /**
       * Represents assist state captured when this session was started.
       * It contains the various assist data objects and a reference to
       * the source activity.
       */
      @Immutable
      public static final class AssistState {
          private final @NonNull ActivityId mActivityId;
          private final int mIndex;
          private final int mCount;
          private final @Nullable Bundle mData;
          private final @Nullable AssistStructure mStructure;
          private final @Nullable AssistContent mContent;
  
          AssistState(@NonNull ActivityId activityId, @Nullable Bundle data,
                  @Nullable AssistStructure structure, @Nullable AssistContent content,
                  int index, int count) {
              mActivityId = activityId;
              mIndex = index;
              mCount = count;
              mData = data;
              mStructure = structure;
              mContent = content;
          }
  
          /**
           * @return whether the source activity is focused.
           */
          public boolean isFocused() {
              return mIndex == 0;
          }
  
          /**
           * @return the index of the activity that this state is for or -1
           *     if there was no assist data captured.
           */
          public @IntRange(from = -1) int getIndex() {
              return mIndex;
          }
  
          /**s
           * @return the total number of activities for which the assist data is
           * being returned.
           */
          public @IntRange(from = 0) int getCount() {
              return mCount;
          }
  
          /**
           * @return the id of the source activity
           */
          public @NonNull ActivityId getActivityId() {
              return mActivityId;
          }
  
          /**
           * @return Arbitrary data supplied by the app through
           * {@link android.app.Activity#onProvideAssistData Activity.onProvideAssistData}.
           * May be null if assist data has been disabled by the user or device policy.
           */
          public @Nullable Bundle getAssistData() {
              return mData;
          }
  
          /**
           * @return If available, the structure definition of all windows currently
           * displayed by the app. May be null if assist data has been disabled by the user
           * or device policy; will be an empty stub if the application has disabled assist
           * by marking its window as secure.
           */
          public @Nullable AssistStructure getAssistStructure() {
              return mStructure;
          }
  
          /**
           * @return Additional content data supplied by the app through
           * {@link android.app.Activity#onProvideAssistContent Activity.onProvideAssistContent}.
           * May be null if assist data has been disabled by the user or device policy; will
           * not be automatically filled in with data from the app if the app has marked its
           * window as secure.
           */
          public @Nullable AssistContent getAssistContent() {
              return mContent;
          }
      }
  
      /**
       * Represents the id of an assist source activity. You can use
       * {@link #equals(Object)} to compare instances of this class.
       */
      public static class ActivityId {
          private final int mTaskId;
          private final IBinder mAssistToken;
  
          ActivityId(int taskId, IBinder assistToken) {
              mTaskId = taskId;
              mAssistToken = assistToken;
          }
  
          int getTaskId() {
              return mTaskId;
          }
  
          IBinder getAssistToken() {
              return mAssistToken;
          }
  
          @Override
          public boolean equals(Object o) {
              if (this == o) {
                  return true;
              }
              if (o == null || getClass() != o.getClass()) {
                  return false;
              }
  
              ActivityId that = (ActivityId) o;
  
              if (mTaskId != that.mTaskId) {
                  return false;
              }
              return mAssistToken != null
                      ? mAssistToken.equals(that.mAssistToken)
                      : that.mAssistToken == null;
          }
  
          @Override
          public int hashCode() {
              int result = mTaskId;
              result = 31 * result + (mAssistToken != null ? mAssistToken.hashCode() : 0);
              return result;
          }
      }
  
      private static class SafeResultListener implements RemoteCallback.OnResultListener {
          private final @NonNull WeakReference<VoiceInteractionSession> mWeakSession;
  
          SafeResultListener(@NonNull Consumer<Bundle> action,
                  @NonNull VoiceInteractionSession session) {
              mWeakSession = new WeakReference<>(session);
          }
  
          @Override
          public void onResult(Bundle result) {
              final VoiceInteractionSession session = mWeakSession.get();
              if (session != null) {
                  final Consumer<Bundle> consumer = session.removeSafeResultListener(this);
                  if (consumer != null) {
                      consumer.accept(result);
                  }
              }
          }
      }
  }
  