All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.robolectric.shadows.ShadowInCallService Maven / Gradle / Ivy

package org.robolectric.shadows;

import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.N_MR1;
import static android.os.Build.VERSION_CODES.P;
import static org.robolectric.shadow.api.Shadow.invokeConstructor;
import static org.robolectric.util.reflector.Reflector.reflector;

import android.annotation.TargetApi;
import android.bluetooth.BluetoothDevice;
import android.os.Build.VERSION;
import android.os.Bundle;
import android.os.Handler;
import android.telecom.Call;
import android.telecom.CallAudioState;
import android.telecom.InCallAdapter;
import android.telecom.InCallService;
import android.telecom.ParcelableCall;
import android.telecom.Phone;
import com.android.internal.os.SomeArgs;
import com.android.internal.telecom.IInCallAdapter;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.ReflectionHelpers;
import org.robolectric.util.ReflectionHelpers.ClassParameter;
import org.robolectric.util.reflector.Accessor;
import org.robolectric.util.reflector.Direct;
import org.robolectric.util.reflector.ForType;

/** Shadow for {@link android.telecom.InCallService}. */
@Implements(value = InCallService.class, minSdk = M)
public class ShadowInCallService extends ShadowService {
  @RealObject private InCallService inCallService;
  private static final int MSG_ADD_CALL = 2;
  private static final int MSG_SET_POST_DIAL_WAIT = 4;
  private static final int MSG_ON_CONNECTION_EVENT = 9;

  private ShadowPhone shadowPhone;
  private boolean canAddCall;
  private boolean muted;
  private int audioRoute = CallAudioState.ROUTE_EARPIECE;
  private BluetoothDevice bluetoothDevice;
  private int supportedRouteMask;

  @Implementation
  protected void __constructor__() {
    InCallAdapter adapter = Shadow.newInstanceOf(InCallAdapter.class);
    Phone phone;
    if (VERSION.SDK_INT > N_MR1) {
      phone =
          ReflectionHelpers.callConstructor(
              Phone.class,
              ClassParameter.from(InCallAdapter.class, adapter),
              ClassParameter.from(String.class, ""),
              ClassParameter.from(int.class, 0));
    } else {
      phone =
          ReflectionHelpers.callConstructor(
              Phone.class, ClassParameter.from(InCallAdapter.class, adapter));
    }
    shadowPhone = Shadow.extract(phone);
    ReflectionHelpers.setField(inCallService, "mPhone", phone);
    invokeConstructor(InCallService.class, inCallService);
  }

  public void addCall(Call call) {
    shadowPhone.addCall(call);
  }

  public void addCall(ParcelableCall parcelableCall) {
    getHandler().obtainMessage(MSG_ADD_CALL, parcelableCall).sendToTarget();
  }

  /**
   * Exposes {@link IIInCallService.Stub#setPostDialWait}. This is normally invoked by Telecom but
   * in Robolectric, Telecom doesn't exist, so tests can invoke this to simulate Telecom's actions.
   */
  public void setPostDialWait(String callId, String remaining) {
    SomeArgs args = SomeArgs.obtain();
    args.arg1 = callId;
    args.arg2 = remaining;
    getHandler().obtainMessage(MSG_SET_POST_DIAL_WAIT, args).sendToTarget();
  }

  /**
   * Exposes {@link IIInCallService.Stub#onConnectionEvent}. This is normally invoked by Telecom but
   * in Robolectric, Telecom doesn't exist, so tests can invoke this to simulate Telecom's actions.
   */
  public void onConnectionEvent(String callId, String event, Bundle extras) {
    SomeArgs args = SomeArgs.obtain();
    args.arg1 = callId;
    args.arg2 = event;
    args.arg3 = extras;
    getHandler().obtainMessage(MSG_ON_CONNECTION_EVENT, args).sendToTarget();
  }

  public void removeCall(Call call) {
    shadowPhone.removeCall(call);
  }

  @Implementation
  protected boolean canAddCall() {
    return canAddCall;
  }

  /** Set the value that {@code canAddCall()} method should return. */
  public void setCanAddCall(boolean canAddCall) {
    this.canAddCall = canAddCall;
  }

  @Implementation
  protected void setMuted(boolean muted) {
    this.muted = muted;
    if (isInCallAdapterSet()) {
      reflector(ReflectorInCallService.class, inCallService).setMuted(muted);
    }
  }

  @Implementation
  protected void setAudioRoute(int audioRoute) {
    this.audioRoute = audioRoute;
    if (isInCallAdapterSet()) {
      reflector(ReflectorInCallService.class, inCallService).setAudioRoute(audioRoute);
    }
  }

  @Implementation
  protected CallAudioState getCallAudioState() {
    if (isInCallAdapterSet()) {
      return reflector(ReflectorInCallService.class, inCallService).getCallAudioState();
    }
    return new CallAudioState(muted, audioRoute, supportedRouteMask);
  }

  public void setSupportedRouteMask(int mask) {
    this.supportedRouteMask = mask;
  }

  @Implementation(minSdk = P)
  protected void requestBluetoothAudio(BluetoothDevice bluetoothDevice) {
    this.bluetoothDevice = bluetoothDevice;
    if (isInCallAdapterSet()) {
      reflector(ReflectorInCallService.class, inCallService).requestBluetoothAudio(bluetoothDevice);
    }
  }

  /** @return the last value provided to {@code requestBluetoothAudio()}. */
  @TargetApi(P)
  public BluetoothDevice getBluetoothAudio() {
    return bluetoothDevice;
  }

  private Handler getHandler() {
    return reflector(ReflectorInCallService.class, inCallService).getHandler();
  }

  /**
   * Checks if the InCallService was bound using {@link
   * com.android.internal.telecom.IInCallService#setInCallAdapter(IInCallAdapter)}.
   *
   * 

If it was bound using this interface, the internal InCallAdapter will be set and it will * forward invocations to FakeTelecomServer. * *

Otherwise, invoking these methods will yield NullPointerExceptions, so we will avoid * forwarding the calls to the real objects. */ private boolean isInCallAdapterSet() { Phone phone = reflector(ReflectorInCallService.class, inCallService).getPhone(); InCallAdapter inCallAdapter = reflector(ReflectorPhone.class, phone).getInCallAdapter(); Object internalAdapter = reflector(ReflectorInCallAdapter.class, inCallAdapter).getInternalInCallAdapter(); return internalAdapter != null; } @ForType(InCallService.class) interface ReflectorInCallService { @Accessor("mHandler") Handler getHandler(); @Accessor("mPhone") Phone getPhone(); @Direct void requestBluetoothAudio(BluetoothDevice bluetoothDevice); @Direct void setAudioRoute(int audioRoute); @Direct void setMuted(boolean muted); @Direct CallAudioState getCallAudioState(); } @ForType(Phone.class) interface ReflectorPhone { @Accessor("mInCallAdapter") InCallAdapter getInCallAdapter(); } @ForType(InCallAdapter.class) interface ReflectorInCallAdapter { @Accessor("mAdapter") Object getInternalInCallAdapter(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy