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