• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.shadows;
2 
3 import static android.os.Build.VERSION_CODES.M;
4 import static android.os.Build.VERSION_CODES.N_MR1;
5 import static android.os.Build.VERSION_CODES.P;
6 import static org.robolectric.shadow.api.Shadow.invokeConstructor;
7 import static org.robolectric.util.reflector.Reflector.reflector;
8 
9 import android.annotation.TargetApi;
10 import android.bluetooth.BluetoothDevice;
11 import android.os.Build.VERSION;
12 import android.os.Bundle;
13 import android.os.Handler;
14 import android.telecom.Call;
15 import android.telecom.CallAudioState;
16 import android.telecom.InCallAdapter;
17 import android.telecom.InCallService;
18 import android.telecom.ParcelableCall;
19 import android.telecom.Phone;
20 import com.android.internal.os.SomeArgs;
21 import com.android.internal.telecom.IInCallAdapter;
22 import org.robolectric.annotation.Implementation;
23 import org.robolectric.annotation.Implements;
24 import org.robolectric.annotation.RealObject;
25 import org.robolectric.shadow.api.Shadow;
26 import org.robolectric.util.ReflectionHelpers;
27 import org.robolectric.util.ReflectionHelpers.ClassParameter;
28 import org.robolectric.util.reflector.Accessor;
29 import org.robolectric.util.reflector.Direct;
30 import org.robolectric.util.reflector.ForType;
31 
32 /** Shadow for {@link android.telecom.InCallService}. */
33 @Implements(value = InCallService.class, minSdk = M)
34 public class ShadowInCallService extends ShadowService {
35   @RealObject private InCallService inCallService;
36   private static final int MSG_ADD_CALL = 2;
37   private static final int MSG_SET_POST_DIAL_WAIT = 4;
38   private static final int MSG_ON_CONNECTION_EVENT = 9;
39 
40   private ShadowPhone shadowPhone;
41   private boolean canAddCall;
42   private boolean muted;
43   private int audioRoute = CallAudioState.ROUTE_EARPIECE;
44   private BluetoothDevice bluetoothDevice;
45   private int supportedRouteMask;
46 
47   @Implementation
__constructor__()48   protected void __constructor__() {
49     InCallAdapter adapter = Shadow.newInstanceOf(InCallAdapter.class);
50     Phone phone;
51     if (VERSION.SDK_INT > N_MR1) {
52       phone =
53           ReflectionHelpers.callConstructor(
54               Phone.class,
55               ClassParameter.from(InCallAdapter.class, adapter),
56               ClassParameter.from(String.class, ""),
57               ClassParameter.from(int.class, 0));
58     } else {
59       phone =
60           ReflectionHelpers.callConstructor(
61               Phone.class, ClassParameter.from(InCallAdapter.class, adapter));
62     }
63     shadowPhone = Shadow.extract(phone);
64     ReflectionHelpers.setField(inCallService, "mPhone", phone);
65     invokeConstructor(InCallService.class, inCallService);
66   }
67 
addCall(Call call)68   public void addCall(Call call) {
69     shadowPhone.addCall(call);
70   }
71 
addCall(ParcelableCall parcelableCall)72   public void addCall(ParcelableCall parcelableCall) {
73     getHandler().obtainMessage(MSG_ADD_CALL, parcelableCall).sendToTarget();
74   }
75 
76   /**
77    * Exposes {@link IIInCallService.Stub#setPostDialWait}. This is normally invoked by Telecom but
78    * in Robolectric, Telecom doesn't exist, so tests can invoke this to simulate Telecom's actions.
79    */
setPostDialWait(String callId, String remaining)80   public void setPostDialWait(String callId, String remaining) {
81     SomeArgs args = SomeArgs.obtain();
82     args.arg1 = callId;
83     args.arg2 = remaining;
84     getHandler().obtainMessage(MSG_SET_POST_DIAL_WAIT, args).sendToTarget();
85   }
86 
87   /**
88    * Exposes {@link IIInCallService.Stub#onConnectionEvent}. This is normally invoked by Telecom but
89    * in Robolectric, Telecom doesn't exist, so tests can invoke this to simulate Telecom's actions.
90    */
onConnectionEvent(String callId, String event, Bundle extras)91   public void onConnectionEvent(String callId, String event, Bundle extras) {
92     SomeArgs args = SomeArgs.obtain();
93     args.arg1 = callId;
94     args.arg2 = event;
95     args.arg3 = extras;
96     getHandler().obtainMessage(MSG_ON_CONNECTION_EVENT, args).sendToTarget();
97   }
98 
removeCall(Call call)99   public void removeCall(Call call) {
100     shadowPhone.removeCall(call);
101   }
102 
103   @Implementation
canAddCall()104   protected boolean canAddCall() {
105     return canAddCall;
106   }
107 
108   /** Set the value that {@code canAddCall()} method should return. */
setCanAddCall(boolean canAddCall)109   public void setCanAddCall(boolean canAddCall) {
110     this.canAddCall = canAddCall;
111   }
112 
113   @Implementation
setMuted(boolean muted)114   protected void setMuted(boolean muted) {
115     this.muted = muted;
116     if (isInCallAdapterSet()) {
117       reflector(ReflectorInCallService.class, inCallService).setMuted(muted);
118     }
119   }
120 
121   @Implementation
setAudioRoute(int audioRoute)122   protected void setAudioRoute(int audioRoute) {
123     this.audioRoute = audioRoute;
124     if (isInCallAdapterSet()) {
125       reflector(ReflectorInCallService.class, inCallService).setAudioRoute(audioRoute);
126     }
127   }
128 
129   @Implementation
getCallAudioState()130   protected CallAudioState getCallAudioState() {
131     if (isInCallAdapterSet()) {
132       return reflector(ReflectorInCallService.class, inCallService).getCallAudioState();
133     }
134     return new CallAudioState(muted, audioRoute, supportedRouteMask);
135   }
136 
setSupportedRouteMask(int mask)137   public void setSupportedRouteMask(int mask) {
138     this.supportedRouteMask = mask;
139   }
140 
141   @Implementation(minSdk = P)
requestBluetoothAudio(BluetoothDevice bluetoothDevice)142   protected void requestBluetoothAudio(BluetoothDevice bluetoothDevice) {
143     this.bluetoothDevice = bluetoothDevice;
144     if (isInCallAdapterSet()) {
145       reflector(ReflectorInCallService.class, inCallService).requestBluetoothAudio(bluetoothDevice);
146     }
147   }
148 
149   /** @return the last value provided to {@code requestBluetoothAudio()}. */
150   @TargetApi(P)
getBluetoothAudio()151   public BluetoothDevice getBluetoothAudio() {
152     return bluetoothDevice;
153   }
154 
getHandler()155   private Handler getHandler() {
156     return reflector(ReflectorInCallService.class, inCallService).getHandler();
157   }
158 
159   /**
160    * Checks if the InCallService was bound using {@link
161    * com.android.internal.telecom.IInCallService#setInCallAdapter(IInCallAdapter)}.
162    *
163    * <p>If it was bound using this interface, the internal InCallAdapter will be set and it will
164    * forward invocations to FakeTelecomServer.
165    *
166    * <p>Otherwise, invoking these methods will yield NullPointerExceptions, so we will avoid
167    * forwarding the calls to the real objects.
168    */
isInCallAdapterSet()169   private boolean isInCallAdapterSet() {
170     Phone phone = reflector(ReflectorInCallService.class, inCallService).getPhone();
171     InCallAdapter inCallAdapter = reflector(ReflectorPhone.class, phone).getInCallAdapter();
172     Object internalAdapter =
173         reflector(ReflectorInCallAdapter.class, inCallAdapter).getInternalInCallAdapter();
174     return internalAdapter != null;
175   }
176 
177   @ForType(InCallService.class)
178   interface ReflectorInCallService {
179     @Accessor("mHandler")
getHandler()180     Handler getHandler();
181 
182     @Accessor("mPhone")
getPhone()183     Phone getPhone();
184 
185     @Direct
requestBluetoothAudio(BluetoothDevice bluetoothDevice)186     void requestBluetoothAudio(BluetoothDevice bluetoothDevice);
187 
188     @Direct
setAudioRoute(int audioRoute)189     void setAudioRoute(int audioRoute);
190 
191     @Direct
setMuted(boolean muted)192     void setMuted(boolean muted);
193 
194     @Direct
getCallAudioState()195     CallAudioState getCallAudioState();
196   }
197 
198   @ForType(Phone.class)
199   interface ReflectorPhone {
200     @Accessor("mInCallAdapter")
getInCallAdapter()201     InCallAdapter getInCallAdapter();
202   }
203 
204   @ForType(InCallAdapter.class)
205   interface ReflectorInCallAdapter {
206     @Accessor("mAdapter")
getInternalInCallAdapter()207     Object getInternalInCallAdapter();
208   }
209 }
210