1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.internal.telephony;
18 
19 import static android.telephony.PhoneCapability.DEVICE_NR_CAPABILITY_NSA;
20 import static android.telephony.PhoneCapability.DEVICE_NR_CAPABILITY_SA;
21 
22 import static com.android.internal.telephony.RILConstants.RADIO_NOT_AVAILABLE;
23 import static com.android.internal.telephony.RILConstants.REQUEST_NOT_SUPPORTED;
24 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_HAL_DEVICE_CAPABILITIES;
25 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_PHONE_CAPABILITY;
26 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_SIMULTANEOUS_CALLING_SUPPORT;
27 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_SLOT_STATUS;
28 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_LOGICAL_TO_PHYSICAL_SLOT_MAPPING;
29 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_PREFERRED_DATA_MODEM;
30 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SWITCH_DUAL_SIM_CONFIG;
31 
32 import android.content.Context;
33 import android.os.AsyncResult;
34 import android.os.Handler;
35 import android.os.IBinder;
36 import android.os.Message;
37 import android.os.Registrant;
38 import android.os.RemoteException;
39 import android.os.ServiceManager;
40 import android.os.Trace;
41 import android.os.WorkSource;
42 import android.telephony.TelephonyManager;
43 import android.telephony.UiccSlotMapping;
44 import android.util.SparseArray;
45 
46 import com.android.telephony.Rlog;
47 
48 import java.util.ArrayList;
49 import java.util.List;
50 import java.util.NoSuchElementException;
51 import java.util.concurrent.atomic.AtomicLong;
52 
53 /**
54  * This class provides wrapper APIs for IRadioConfig interface.
55  */
56 public class RadioConfig extends Handler {
57     private static final String TAG = "RadioConfig";
58     private static final boolean DBG = true;
59     private static final boolean VDBG = false; //STOPSHIP if true
60     private static final Object sLock = new Object();
61 
62     static final int EVENT_HIDL_SERVICE_DEAD = 1;
63     static final int EVENT_AIDL_SERVICE_DEAD = 2;
64 
65     private final boolean mIsMobileNetworkSupported;
66     private final SparseArray<RILRequest> mRequestList = new SparseArray<>();
67     /* default work source which will blame phone process */
68     private final WorkSource mDefaultWorkSource;
69     private final int[] mDeviceNrCapabilities;
70     private final AtomicLong mRadioConfigProxyCookie = new AtomicLong(0);
71     private final RadioConfigProxy mRadioConfigProxy;
72     private MockModem mMockModem;
73     private static Context sContext;
74 
75     private static RadioConfig sRadioConfig;
76 
77     protected Registrant mSimSlotStatusRegistrant;
78 
79     protected Registrant mSimultaneousCallingSupportStatusRegistrant;
80 
isMobileDataCapable(Context context)81     private boolean isMobileDataCapable(Context context) {
82         final TelephonyManager tm = context.getSystemService(TelephonyManager.class);
83         return tm != null && tm.isDataCapable();
84     }
85 
RadioConfig(Context context, HalVersion radioHalVersion)86     private RadioConfig(Context context, HalVersion radioHalVersion) {
87         mIsMobileNetworkSupported = isMobileDataCapable(context);
88         mRadioConfigProxy = new RadioConfigProxy(this, radioHalVersion);
89         mDefaultWorkSource = new WorkSource(context.getApplicationInfo().uid,
90                 context.getPackageName());
91 
92         boolean is5gStandalone = context.getResources().getBoolean(
93                 com.android.internal.R.bool.config_telephony5gStandalone);
94         boolean is5gNonStandalone = context.getResources().getBoolean(
95                 com.android.internal.R.bool.config_telephony5gNonStandalone);
96 
97         if (!is5gStandalone && !is5gNonStandalone) {
98             mDeviceNrCapabilities = new int[0];
99         } else {
100             List<Integer> list = new ArrayList<>();
101             if (is5gNonStandalone) {
102                 list.add(DEVICE_NR_CAPABILITY_NSA);
103             }
104             if (is5gStandalone) {
105                 list.add(DEVICE_NR_CAPABILITY_SA);
106             }
107             mDeviceNrCapabilities = list.stream().mapToInt(Integer::valueOf).toArray();
108         }
109     }
110 
111     /**
112      * Returns the singleton static instance of RadioConfig
113      */
getInstance()114     public static RadioConfig getInstance() {
115         synchronized (sLock) {
116             if (sRadioConfig == null) {
117                 throw new RuntimeException(
118                         "RadioConfig.getInstance can't be called before make()");
119             }
120             return sRadioConfig;
121         }
122     }
123 
124     /**
125      * Makes the radio config based on the context and the radio hal version passed in
126      */
make(Context c, HalVersion radioHalVersion)127     public static RadioConfig make(Context c, HalVersion radioHalVersion) {
128         synchronized (sLock) {
129             if (sRadioConfig != null) {
130                 throw new RuntimeException("RadioConfig.make() should only be called once");
131             }
132             sContext = c;
133             sRadioConfig = new RadioConfig(c, radioHalVersion);
134             return sRadioConfig;
135         }
136     }
137 
138     @Override
handleMessage(Message message)139     public void handleMessage(Message message) {
140         if (message.what == EVENT_HIDL_SERVICE_DEAD) {
141             logd("handleMessage: EVENT_HIDL_SERVICE_DEAD cookie = " + message.obj
142                     + " mRadioConfigProxyCookie = " + mRadioConfigProxyCookie.get());
143             if ((long) message.obj == mRadioConfigProxyCookie.get()) {
144                 resetProxyAndRequestList("EVENT_HIDL_SERVICE_DEAD", null);
145             }
146         } else if (message.what == EVENT_AIDL_SERVICE_DEAD) {
147             logd("handleMessage: EVENT_AIDL_SERVICE_DEAD mRadioConfigProxyCookie = "
148                     + mRadioConfigProxyCookie.get());
149             resetProxyAndRequestList("EVENT_AIDL_SERVICE_DEAD", null);
150         }
151     }
152 
153     /**
154      * Release each request in mRequestList then clear the list
155      * @param error is the RIL_Errno sent back
156      * @param loggable true means to print all requests in mRequestList
157      */
clearRequestList(int error, boolean loggable)158     private void clearRequestList(int error, boolean loggable) {
159         RILRequest rr;
160         synchronized (mRequestList) {
161             int count = mRequestList.size();
162             if (DBG && loggable) {
163                 logd("clearRequestList: mRequestList=" + count);
164             }
165 
166             for (int i = 0; i < count; i++) {
167                 rr = mRequestList.valueAt(i);
168                 if (DBG && loggable) {
169                     logd(i + ": [" + rr.mSerial + "] " + RILUtils.requestToString(rr.mRequest));
170                 }
171                 rr.onError(error, null);
172                 rr.release();
173             }
174             mRequestList.clear();
175         }
176     }
177 
resetProxyAndRequestList(String caller, Exception e)178     private void resetProxyAndRequestList(String caller, Exception e) {
179         loge(caller + ": " + e);
180         mRadioConfigProxy.clear();
181 
182         // increment the cookie so that death notification can be ignored
183         mRadioConfigProxyCookie.incrementAndGet();
184 
185         RILRequest.resetSerial();
186         // Clear request list on close
187         clearRequestList(RADIO_NOT_AVAILABLE, false);
188 
189         getRadioConfigProxy(null);
190     }
191 
192     /**
193      * Returns a holder that has either:
194      * - getV1() -> {@link android.hardware.radio.config.V1_0.IRadioConfig}
195      * - getV2() -> {@link android.hardware.radio.config.IRadioConfig}
196      * that returns corresponding hal implementation
197      */
getRadioConfigProxy(Message result)198     public RadioConfigProxy getRadioConfigProxy(Message result) {
199         if (!mIsMobileNetworkSupported) {
200             if (VDBG) logd("getRadioConfigProxy: Not calling getService(): wifi-only");
201             if (result != null) {
202                 AsyncResult.forMessage(result, null,
203                         CommandException.fromRilErrno(RADIO_NOT_AVAILABLE));
204                 result.sendToTarget();
205             }
206             mRadioConfigProxy.clear();
207             return mRadioConfigProxy;
208         }
209 
210         if (!mRadioConfigProxy.isEmpty()) {
211             return mRadioConfigProxy;
212         }
213 
214         updateRadioConfigProxy();
215 
216         if (mRadioConfigProxy.isEmpty() && result != null) {
217             AsyncResult.forMessage(
218                     result, null, CommandException.fromRilErrno(RADIO_NOT_AVAILABLE));
219             result.sendToTarget();
220         }
221 
222         return mRadioConfigProxy;
223     }
224 
225     /**
226      * Request to enable/disable the mock modem service.
227      * This is invoked from shell commands during CTS testing only.
228      *
229      * @param serviceName the service name we want to bind to
230      */
setModemService(String serviceName)231     public boolean setModemService(String serviceName) {
232         boolean serviceBound = true;
233 
234         if (serviceName != null) {
235             logd("Overriding connected service to MockModemService");
236             mMockModem = null;
237 
238             mMockModem = new MockModem(sContext, serviceName);
239             if (mMockModem == null) {
240                 loge("MockModem creation failed.");
241                 return false;
242             }
243 
244             mMockModem.bindToMockModemService(MockModem.RADIOCONFIG_SERVICE);
245 
246             int retryCount = 0;
247             IBinder binder;
248             do {
249                 binder = mMockModem.getServiceBinder(MockModem.RADIOCONFIG_SERVICE);
250 
251                 retryCount++;
252                 if (binder == null) {
253                     logd("Retry(" + retryCount + ") Mock RadioConfig");
254                     try {
255                         Thread.sleep(MockModem.BINDER_RETRY_MILLIS);
256                     } catch (InterruptedException e) {
257                     }
258                 }
259             } while ((binder == null) && (retryCount < MockModem.BINDER_MAX_RETRY));
260 
261             if (binder == null) {
262                 loge("Mock RadioConfig bind fail");
263                 serviceBound = false;
264             }
265 
266             if (serviceBound) resetProxyAndRequestList("EVENT_HIDL_SERVICE_DEAD", null);
267         }
268 
269         if ((serviceName == null) || (!serviceBound)) {
270             if (serviceBound) logd("Unbinding to mock RadioConfig service");
271 
272             if (mMockModem != null) {
273                 mMockModem = null;
274                 resetProxyAndRequestList("EVENT_AIDL_SERVICE_DEAD", null);
275             }
276         }
277 
278         return serviceBound;
279     }
280 
updateRadioConfigProxy()281     private void updateRadioConfigProxy() {
282         IBinder service;
283         if (mMockModem == null) {
284             service = ServiceManager.waitForDeclaredService(
285                 android.hardware.radio.config.IRadioConfig.DESCRIPTOR + "/default");
286         } else {
287             // Binds to Mock RadioConfig Service
288             service = mMockModem.getServiceBinder(MockModem.RADIOCONFIG_SERVICE);
289         }
290 
291         if (service != null) {
292             mRadioConfigProxy.setAidl(
293                     android.hardware.radio.config.IRadioConfig.Stub.asInterface(service));
294         }
295 
296         if (mRadioConfigProxy.isEmpty()) {
297             try {
298                 mRadioConfigProxy.setHidl(RIL.RADIO_HAL_VERSION_1_3,
299                         android.hardware.radio.config.V1_3.IRadioConfig.getService(true));
300             } catch (RemoteException | NoSuchElementException e) {
301                 mRadioConfigProxy.clear();
302                 loge("getHidlRadioConfigProxy1_3: RadioConfigProxy getService: " + e);
303             }
304         }
305 
306         if (mRadioConfigProxy.isEmpty()) {
307             try {
308                 mRadioConfigProxy.setHidl(RIL.RADIO_HAL_VERSION_1_1,
309                         android.hardware.radio.config.V1_1.IRadioConfig.getService(true));
310             } catch (RemoteException | NoSuchElementException e) {
311                 mRadioConfigProxy.clear();
312                 loge("getHidlRadioConfigProxy1_1: RadioConfigProxy getService | linkToDeath: " + e);
313             }
314         }
315 
316         if (mRadioConfigProxy.isEmpty()) {
317             loge("IRadioConfig <1.1 is no longer supported.");
318         }
319 
320         if (!mRadioConfigProxy.isEmpty()) {
321             try {
322                 mRadioConfigProxy.linkToDeath(mRadioConfigProxyCookie.incrementAndGet());
323                 mRadioConfigProxy.setResponseFunctions(this);
324                 return;
325             } catch (RemoteException e) {
326                 mRadioConfigProxy.clear();
327                 loge("RadioConfigProxy: failed to linkToDeath() or setResponseFunction()");
328             }
329         }
330 
331         loge("getRadioConfigProxy: mRadioConfigProxy == null");
332     }
333 
obtainRequest(int request, Message result, WorkSource workSource)334     private RILRequest obtainRequest(int request, Message result, WorkSource workSource) {
335         RILRequest rr = RILRequest.obtain(request, result, workSource);
336         Trace.asyncTraceForTrackBegin(
337                 Trace.TRACE_TAG_NETWORK, "RIL", RILUtils.requestToString(rr.mRequest), rr.mSerial);
338 
339         synchronized (mRequestList) {
340             mRequestList.append(rr.mSerial, rr);
341         }
342         return rr;
343     }
344 
findAndRemoveRequestFromList(int serial)345     private RILRequest findAndRemoveRequestFromList(int serial) {
346         RILRequest rr;
347         synchronized (mRequestList) {
348             rr = mRequestList.get(serial);
349 
350             if (rr != null) {
351                 Trace.asyncTraceForTrackEnd(
352                         Trace.TRACE_TAG_NETWORK, "RIL", rr.mSerial);
353                 mRequestList.remove(serial);
354             }
355         }
356 
357         return rr;
358     }
359 
360     /**
361      * This is a helper function to be called when a RadioConfigResponse callback is called.
362      * It finds and returns RILRequest corresponding to the response if one is found.
363      * @param responseInfo RadioResponseInfo received in response callback
364      * @return RILRequest corresponding to the response
365      */
processResponse(android.hardware.radio.RadioResponseInfo responseInfo)366     public RILRequest processResponse(android.hardware.radio.RadioResponseInfo responseInfo) {
367         int serial = responseInfo.serial;
368         int error = responseInfo.error;
369         int type = responseInfo.type;
370 
371         if (type != android.hardware.radio.RadioResponseType.SOLICITED) {
372             loge("processResponse: Unexpected response type " + type);
373         }
374 
375         RILRequest rr = findAndRemoveRequestFromList(serial);
376         if (rr == null) {
377             loge("processResponse: Unexpected response! serial: " + serial + " error: " + error);
378             return null;
379         }
380 
381         return rr;
382     }
383 
384     /**
385      * This is a helper function to be called when a RadioConfigResponse callback is called.
386      * It finds and returns RILRequest corresponding to the response if one is found.
387      * @param responseInfo RadioResponseInfo received in response callback
388      * @return RILRequest corresponding to the response
389      */
processResponse(android.hardware.radio.V1_0.RadioResponseInfo responseInfo)390     public RILRequest processResponse(android.hardware.radio.V1_0.RadioResponseInfo responseInfo) {
391         int serial = responseInfo.serial;
392         int error = responseInfo.error;
393         int type = responseInfo.type;
394 
395         if (type != android.hardware.radio.RadioResponseType.SOLICITED) {
396             loge("processResponse: Unexpected response type " + type);
397         }
398 
399         RILRequest rr = findAndRemoveRequestFromList(serial);
400         if (rr == null) {
401             loge("processResponse: Unexpected response! serial: " + serial + " error: " + error);
402             return null;
403         }
404 
405         return rr;
406     }
407 
408     /**
409      * This is a helper function to be called when a RadioConfigResponse callback is called.
410      * It finds and returns RILRequest corresponding to the response if one is found.
411      * @param responseInfo RadioResponseInfo received in response callback
412      * @return RILRequest corresponding to the response
413      */
processResponse_1_6( android.hardware.radio.V1_6.RadioResponseInfo responseInfo)414     public RILRequest processResponse_1_6(
415             android.hardware.radio.V1_6.RadioResponseInfo responseInfo) {
416         int serial = responseInfo.serial;
417         int error = responseInfo.error;
418         int type = responseInfo.type;
419         if (type != android.hardware.radio.RadioResponseType.SOLICITED) {
420             loge("processResponse: Unexpected response type " + type);
421         }
422 
423         RILRequest rr = findAndRemoveRequestFromList(serial);
424         if (rr == null) {
425             loge("processResponse: Unexpected response! serial: " + serial + " error: " + error);
426             return null;
427         }
428 
429         return rr;
430     }
431 
432     /**
433      * Wrapper function for IRadioConfig.getSimSlotsStatus().
434      */
getSimSlotsStatus(Message result)435     public void getSimSlotsStatus(Message result) {
436         RadioConfigProxy proxy = getRadioConfigProxy(result);
437         if (proxy.isEmpty()) return;
438 
439         RILRequest rr = obtainRequest(RIL_REQUEST_GET_SLOT_STATUS, result, mDefaultWorkSource);
440         if (DBG) {
441             logd(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
442         }
443         try {
444             proxy.getSimSlotStatus(rr.mSerial);
445         } catch (RemoteException | RuntimeException e) {
446             resetProxyAndRequestList("getSimSlotsStatus", e);
447         }
448     }
449 
450     /**
451      * Wrapper function for IRadioConfig.setPreferredDataModem(int modemId).
452      */
setPreferredDataModem(int modemId, Message result)453     public void setPreferredDataModem(int modemId, Message result) {
454         RadioConfigProxy proxy = getRadioConfigProxy(null);
455         if (proxy.isEmpty()) return;
456 
457         if (!isSetPreferredDataCommandSupported()) {
458             if (result != null) {
459                 AsyncResult.forMessage(result, null,
460                         CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
461                 result.sendToTarget();
462             }
463             return;
464         }
465 
466         RILRequest rr = obtainRequest(RIL_REQUEST_SET_PREFERRED_DATA_MODEM,
467                 result, mDefaultWorkSource);
468         if (DBG) {
469             logd(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
470         }
471         try {
472             proxy.setPreferredDataModem(rr.mSerial, modemId);
473         } catch (RemoteException | RuntimeException e) {
474             resetProxyAndRequestList("setPreferredDataModem", e);
475         }
476     }
477 
478     /**
479      * Wrapper function for IRadioConfig.getSimultaneousCallingSupport().
480      */
updateSimultaneousCallingSupport(Message result)481     public void updateSimultaneousCallingSupport(Message result) {
482         RadioConfigProxy proxy = getRadioConfigProxy(null);
483         if (proxy.isEmpty()) return;
484 
485         if (proxy.getVersion().less(RIL.RADIO_HAL_VERSION_2_2)) {
486             if (result != null) {
487                 AsyncResult.forMessage(result, null,
488                         CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
489                 result.sendToTarget();
490             }
491             return;
492         }
493 
494         RILRequest rr = obtainRequest(RIL_REQUEST_GET_SIMULTANEOUS_CALLING_SUPPORT, result,
495                 mDefaultWorkSource);
496         if (DBG) {
497             logd(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
498         }
499         try {
500             proxy.updateSimultaneousCallingSupport(rr.mSerial);
501         } catch (RemoteException | RuntimeException e) {
502             resetProxyAndRequestList("updateSimultaneousCallingSupport", e);
503         }
504     }
505 
506     /**
507      * Wrapper function for IRadioConfig.getPhoneCapability().
508      */
getPhoneCapability(Message result)509     public void getPhoneCapability(Message result) {
510         RadioConfigProxy proxy = getRadioConfigProxy(null);
511         if (proxy.isEmpty()) return;
512 
513         if (proxy.getVersion().less(RIL.RADIO_HAL_VERSION_1_1)) {
514             if (result != null) {
515                 AsyncResult.forMessage(result, null,
516                         CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
517                 result.sendToTarget();
518             }
519             return;
520         }
521 
522         RILRequest rr = obtainRequest(RIL_REQUEST_GET_PHONE_CAPABILITY, result, mDefaultWorkSource);
523         if (DBG) {
524             logd(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
525         }
526         try {
527             proxy.getPhoneCapability(rr.mSerial);
528         } catch (RemoteException | RuntimeException e) {
529             resetProxyAndRequestList("getPhoneCapability", e);
530         }
531     }
532 
533     /**
534      * @return whether current radio config version supports SET_PREFERRED_DATA_MODEM command.
535      * If yes, we'll use RIL_REQUEST_SET_PREFERRED_DATA_MODEM to indicate which modem is preferred.
536      * If not, we shall use RIL_REQUEST_ALLOW_DATA for on-demand PS attach / detach.
537      * See PhoneSwitcher for more details.
538      */
isSetPreferredDataCommandSupported()539     public boolean isSetPreferredDataCommandSupported() {
540         RadioConfigProxy proxy = getRadioConfigProxy(null);
541         return !proxy.isEmpty() && proxy.getVersion().greaterOrEqual(RIL.RADIO_HAL_VERSION_1_1);
542     }
543 
544     /**
545      * Wrapper function for IRadioConfig.setSimSlotsMapping(int32_t serial, vec<uint32_t> slotMap).
546      */
setSimSlotsMapping(List<UiccSlotMapping> slotMapping, Message result)547     public void setSimSlotsMapping(List<UiccSlotMapping> slotMapping, Message result) {
548         RadioConfigProxy proxy = getRadioConfigProxy(result);
549         if (proxy.isEmpty()) return;
550 
551         RILRequest rr = obtainRequest(RIL_REQUEST_SET_LOGICAL_TO_PHYSICAL_SLOT_MAPPING, result,
552                 mDefaultWorkSource);
553         if (DBG) {
554             logd(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest) + " "
555                     + slotMapping);
556         }
557         try {
558             proxy.setSimSlotsMapping(rr.mSerial, slotMapping);
559         } catch (RemoteException | RuntimeException e) {
560             resetProxyAndRequestList("setSimSlotsMapping", e);
561         }
562     }
563 
564     /**
565      * Wrapper function for using IRadioConfig.setNumOfLiveModems(int32_t serial,
566      * byte numOfLiveModems) to switch between single-sim and multi-sim.
567      */
setNumOfLiveModems(int numOfLiveModems, Message result)568     public void setNumOfLiveModems(int numOfLiveModems, Message result) {
569         RadioConfigProxy proxy = getRadioConfigProxy(result);
570         if (proxy.isEmpty()) return;
571 
572         if (proxy.getVersion().less(RIL.RADIO_HAL_VERSION_1_1)) {
573             if (result != null) {
574                 AsyncResult.forMessage(
575                         result, null, CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
576                 result.sendToTarget();
577             }
578             return;
579         }
580 
581         RILRequest rr = obtainRequest(RIL_REQUEST_SWITCH_DUAL_SIM_CONFIG,
582                 result, mDefaultWorkSource);
583         if (DBG) {
584             logd(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
585                     + ", numOfLiveModems = " + numOfLiveModems);
586         }
587         try {
588             proxy.setNumOfLiveModems(rr.mSerial, numOfLiveModems);
589         } catch (RemoteException | RuntimeException e) {
590             resetProxyAndRequestList("setNumOfLiveModems", e);
591         }
592     }
593 
594     /**
595      * Register a handler to get SIM slots that support simultaneous calling changed notifications.
596      */
registerForSimultaneousCallingSupportStatusChanged(Handler h, int what, Object obj)597     public void registerForSimultaneousCallingSupportStatusChanged(Handler h, int what,
598             Object obj) {
599         mSimultaneousCallingSupportStatusRegistrant = new Registrant(h, what, obj);
600     }
601 
602     /**
603      * Register a handler to get SIM slot status changed notifications.
604      */
registerForSimSlotStatusChanged(Handler h, int what, Object obj)605     public void registerForSimSlotStatusChanged(Handler h, int what, Object obj) {
606         mSimSlotStatusRegistrant = new Registrant(h, what, obj);
607     }
608 
609     /**
610      * Unregister corresponding to registerForSimSlotStatusChanged().
611      */
unregisterForSimSlotStatusChanged(Handler h)612     public void unregisterForSimSlotStatusChanged(Handler h) {
613         if (mSimSlotStatusRegistrant != null && mSimSlotStatusRegistrant.getHandler() == h) {
614             mSimSlotStatusRegistrant.clear();
615             mSimSlotStatusRegistrant = null;
616         }
617     }
618 
619     /**
620      * Gets the hal capabilities from the device.
621      */
getHalDeviceCapabilities(Message result)622     public void getHalDeviceCapabilities(Message result) {
623         RadioConfigProxy proxy = getRadioConfigProxy(Message.obtain(result));
624         if (proxy.isEmpty()) return;
625 
626         if (proxy.getVersion().less(RIL.RADIO_HAL_VERSION_1_3)) {
627             if (result != null) {
628                 if (DBG) {
629                     logd("RIL_REQUEST_GET_HAL_DEVICE_CAPABILITIES > REQUEST_NOT_SUPPORTED");
630                 }
631                 AsyncResult.forMessage(result,
632                         /* Send response such that all capabilities are supported (depending on
633                            the hal version of course.) */
634                         proxy.getFullCapabilitySet(),
635                         CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
636                 result.sendToTarget();
637             } else {
638                 if (DBG) {
639                     logd("RIL_REQUEST_GET_HAL_DEVICE_CAPABILITIES > REQUEST_NOT_SUPPORTED "
640                             + "on complete message not set.");
641                 }
642             }
643             return;
644         }
645 
646         RILRequest rr = obtainRequest(RIL_REQUEST_GET_HAL_DEVICE_CAPABILITIES,
647                 result, mDefaultWorkSource);
648         if (DBG) {
649             logd(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
650         }
651         try {
652             proxy.getHalDeviceCapabilities(rr.mSerial);
653         } catch (RemoteException | RuntimeException e) {
654             resetProxyAndRequestList("getHalDeviceCapabilities", e);
655         }
656     }
657 
658     /**
659      * Returns the device's nr capability.
660      */
getDeviceNrCapabilities()661     public int[] getDeviceNrCapabilities() {
662         return mDeviceNrCapabilities;
663     }
664 
logd(String log)665     private static void logd(String log) {
666         Rlog.d(TAG, log);
667     }
668 
loge(String log)669     private static void loge(String log) {
670         Rlog.e(TAG, log);
671     }
672 
673     @Override
toString()674     public String toString() {
675         return "RadioConfig[" + "mRadioConfigProxy=" + mRadioConfigProxy + ']';
676     }
677 }
678