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 com.android.internal.telephony.RILConstants.RADIO_NOT_AVAILABLE;
20 import static com.android.internal.telephony.RILConstants.REQUEST_NOT_SUPPORTED;
21 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_PHONE_CAPABILITY;
22 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_SLOT_STATUS;
23 import static com.android.internal.telephony.RILConstants
24         .RIL_REQUEST_SET_LOGICAL_TO_PHYSICAL_SLOT_MAPPING;
25 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_PREFERRED_DATA_MODEM;
26 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SWITCH_DUAL_SIM_CONFIG;
27 
28 import android.content.Context;
29 import android.hardware.radio.V1_0.RadioResponseInfo;
30 import android.hardware.radio.V1_0.RadioResponseType;
31 import android.hardware.radio.config.V1_0.IRadioConfig;
32 import android.hardware.radio.config.V1_1.ModemsConfig;
33 import android.net.ConnectivityManager;
34 import android.os.AsyncResult;
35 import android.os.Handler;
36 import android.os.HwBinder;
37 import android.os.Message;
38 import android.os.Registrant;
39 import android.os.RemoteException;
40 import android.os.WorkSource;
41 import android.util.SparseArray;
42 
43 import com.android.internal.telephony.uicc.IccSlotStatus;
44 import com.android.telephony.Rlog;
45 
46 import java.util.ArrayList;
47 import java.util.Arrays;
48 import java.util.NoSuchElementException;
49 import java.util.concurrent.atomic.AtomicLong;
50 
51 /**
52  * This class provides wrapper APIs for IRadioConfig interface.
53  */
54 public class RadioConfig extends Handler {
55     private static final String TAG = "RadioConfig";
56     private static final boolean DBG = true;
57     private static final boolean VDBG = false;   //STOPSHIP if true
58 
59     private static final int EVENT_SERVICE_DEAD = 1;
60 
61     private static final HalVersion RADIO_CONFIG_HAL_VERSION_UNKNOWN = new HalVersion(-1, -1);
62 
63     private static final HalVersion RADIO_CONFIG_HAL_VERSION_1_0 = new HalVersion(1, 0);
64 
65     private static final HalVersion RADIO_CONFIG_HAL_VERSION_1_1 = new HalVersion(1, 1);
66 
67     private final boolean mIsMobileNetworkSupported;
68     private volatile IRadioConfig mRadioConfigProxy = null;
69     // IRadioConfig version
70     private HalVersion mRadioConfigVersion = RADIO_CONFIG_HAL_VERSION_UNKNOWN;
71     private final ServiceDeathRecipient mServiceDeathRecipient;
72     private final AtomicLong mRadioConfigProxyCookie = new AtomicLong(0);
73     private final RadioConfigResponse mRadioConfigResponse;
74     private final RadioConfigIndication mRadioConfigIndication;
75     private final SparseArray<RILRequest> mRequestList = new SparseArray<RILRequest>();
76     /* default work source which will blame phone process */
77     private final WorkSource mDefaultWorkSource;
78     private static RadioConfig sRadioConfig;
79 
80     protected Registrant mSimSlotStatusRegistrant;
81 
82     final class ServiceDeathRecipient implements HwBinder.DeathRecipient {
83         @Override
serviceDied(long cookie)84         public void serviceDied(long cookie) {
85             // Deal with service going away
86             logd("serviceDied");
87             sendMessage(obtainMessage(EVENT_SERVICE_DEAD, cookie));
88         }
89     }
90 
RadioConfig(Context context)91     private RadioConfig(Context context) {
92         ConnectivityManager cm = (ConnectivityManager) context.getSystemService(
93                 Context.CONNECTIVITY_SERVICE);
94         mIsMobileNetworkSupported = cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
95 
96         mRadioConfigResponse = new RadioConfigResponse(this);
97         mRadioConfigIndication = new RadioConfigIndication(this);
98         mServiceDeathRecipient = new ServiceDeathRecipient();
99 
100         mDefaultWorkSource = new WorkSource(context.getApplicationInfo().uid,
101                 context.getPackageName());
102     }
103 
104     /**
105      * Returns the singleton static instance of RadioConfig
106      */
getInstance(Context context)107     public static RadioConfig getInstance(Context context) {
108         if (sRadioConfig == null) {
109             sRadioConfig = new RadioConfig(context);
110         }
111         return sRadioConfig;
112     }
113 
114     @Override
handleMessage(Message message)115     public void handleMessage(Message message) {
116         switch (message.what) {
117             case EVENT_SERVICE_DEAD:
118                 logd("handleMessage: EVENT_SERVICE_DEAD cookie = " + message.obj
119                         + " mRadioConfigProxyCookie = " + mRadioConfigProxyCookie.get());
120                 if ((long) message.obj == mRadioConfigProxyCookie.get()) {
121                     resetProxyAndRequestList("EVENT_SERVICE_DEAD", null);
122                 }
123                 break;
124         }
125     }
126 
127     /**
128      * Release each request in mRequestList then clear the list
129      * @param error is the RIL_Errno sent back
130      * @param loggable true means to print all requests in mRequestList
131      */
clearRequestList(int error, boolean loggable)132     private void clearRequestList(int error, boolean loggable) {
133         RILRequest rr;
134         synchronized (mRequestList) {
135             int count = mRequestList.size();
136             if (DBG && loggable) {
137                 logd("clearRequestList: mRequestList=" + count);
138             }
139 
140             for (int i = 0; i < count; i++) {
141                 rr = mRequestList.valueAt(i);
142                 if (DBG && loggable) {
143                     logd(i + ": [" + rr.mSerial + "] " + requestToString(rr.mRequest));
144                 }
145                 rr.onError(error, null);
146                 rr.release();
147             }
148             mRequestList.clear();
149         }
150     }
151 
resetProxyAndRequestList(String caller, Exception e)152     private void resetProxyAndRequestList(String caller, Exception e) {
153         loge(caller + ": " + e);
154         mRadioConfigProxy = null;
155 
156         // increment the cookie so that death notification can be ignored
157         mRadioConfigProxyCookie.incrementAndGet();
158 
159         RILRequest.resetSerial();
160         // Clear request list on close
161         clearRequestList(RADIO_NOT_AVAILABLE, false);
162 
163         getRadioConfigProxy(null);
164     }
165 
166     /** Returns a {@link IRadioConfig} instance or null if the service is not available. */
getRadioConfigProxy(Message result)167     public IRadioConfig getRadioConfigProxy(Message result) {
168         if (!mIsMobileNetworkSupported) {
169             if (VDBG) logd("getRadioConfigProxy: Not calling getService(): wifi-only");
170             if (result != null) {
171                 AsyncResult.forMessage(result, null,
172                         CommandException.fromRilErrno(RADIO_NOT_AVAILABLE));
173                 result.sendToTarget();
174             }
175             return null;
176         }
177 
178         if (mRadioConfigProxy != null) {
179             return mRadioConfigProxy;
180         }
181 
182         updateRadioConfigProxy();
183 
184         if (mRadioConfigProxy == null) {
185             if (result != null) {
186                 AsyncResult.forMessage(result, null,
187                         CommandException.fromRilErrno(RADIO_NOT_AVAILABLE));
188                 result.sendToTarget();
189             }
190         }
191 
192         return mRadioConfigProxy;
193     }
194 
updateRadioConfigProxy()195     private void updateRadioConfigProxy() {
196         try {
197             // Try to get service from different versions.
198             try {
199                 mRadioConfigProxy = android.hardware.radio.config.V1_1.IRadioConfig.getService(
200                         true);
201                 mRadioConfigVersion = RADIO_CONFIG_HAL_VERSION_1_1;
202             } catch (NoSuchElementException e) {
203             }
204 
205             if (mRadioConfigProxy == null) {
206                 try {
207                     mRadioConfigProxy = android.hardware.radio.config.V1_0
208                             .IRadioConfig.getService(true);
209                     mRadioConfigVersion = RADIO_CONFIG_HAL_VERSION_1_0;
210                 } catch (NoSuchElementException e) {
211                 }
212             }
213 
214             if (mRadioConfigProxy == null) {
215                 loge("getRadioConfigProxy: mRadioConfigProxy == null");
216                 return;
217             }
218 
219             // Link to death recipient and set response. If fails, set proxy to null and return.
220             mRadioConfigProxy.linkToDeath(mServiceDeathRecipient,
221                     mRadioConfigProxyCookie.incrementAndGet());
222             mRadioConfigProxy.setResponseFunctions(mRadioConfigResponse,
223                     mRadioConfigIndication);
224         } catch (RemoteException | RuntimeException e) {
225             mRadioConfigProxy = null;
226             loge("getRadioConfigProxy: RadioConfigProxy setResponseFunctions: " + e);
227             return;
228         }
229     }
230 
obtainRequest(int request, Message result, WorkSource workSource)231     private RILRequest obtainRequest(int request, Message result, WorkSource workSource) {
232         RILRequest rr = RILRequest.obtain(request, result, workSource);
233         synchronized (mRequestList) {
234             mRequestList.append(rr.mSerial, rr);
235         }
236         return rr;
237     }
238 
findAndRemoveRequestFromList(int serial)239     private RILRequest findAndRemoveRequestFromList(int serial) {
240         RILRequest rr;
241         synchronized (mRequestList) {
242             rr = mRequestList.get(serial);
243             if (rr != null) {
244                 mRequestList.remove(serial);
245             }
246         }
247 
248         return rr;
249     }
250 
251     /**
252      * This is a helper function to be called when a RadioConfigResponse callback is called.
253      * It finds and returns RILRequest corresponding to the response if one is found.
254      * @param responseInfo RadioResponseInfo received in response callback
255      * @return RILRequest corresponding to the response
256      */
processResponse(RadioResponseInfo responseInfo)257     public RILRequest processResponse(RadioResponseInfo responseInfo) {
258         int serial = responseInfo.serial;
259         int error = responseInfo.error;
260         int type = responseInfo.type;
261 
262         if (type != RadioResponseType.SOLICITED) {
263             loge("processResponse: Unexpected response type " + type);
264         }
265 
266         RILRequest rr = findAndRemoveRequestFromList(serial);
267         if (rr == null) {
268             loge("processResponse: Unexpected response! serial: " + serial + " error: " + error);
269             return null;
270         }
271 
272         return rr;
273     }
274 
275     /**
276      * Wrapper function for IRadioConfig.getSimSlotsStatus().
277      */
getSimSlotsStatus(Message result)278     public void getSimSlotsStatus(Message result) {
279         IRadioConfig radioConfigProxy = getRadioConfigProxy(result);
280         if (radioConfigProxy != null) {
281             RILRequest rr = obtainRequest(RIL_REQUEST_GET_SLOT_STATUS, result, mDefaultWorkSource);
282 
283             if (DBG) {
284                 logd(rr.serialString() + "> " + requestToString(rr.mRequest));
285             }
286 
287             try {
288                 radioConfigProxy.getSimSlotsStatus(rr.mSerial);
289             } catch (RemoteException | RuntimeException e) {
290                 resetProxyAndRequestList("getSimSlotsStatus", e);
291             }
292         }
293     }
294 
295     /**
296      * Wrapper function for IRadioConfig.setPreferredDataModem(int modemId).
297      */
setPreferredDataModem(int modemId, Message result)298     public void setPreferredDataModem(int modemId, Message result) {
299         if (!isSetPreferredDataCommandSupported()) {
300             if (result != null) {
301                 AsyncResult.forMessage(result, null,
302                         CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
303                 result.sendToTarget();
304             }
305             return;
306         }
307 
308         RILRequest rr = obtainRequest(RIL_REQUEST_SET_PREFERRED_DATA_MODEM,
309                 result, mDefaultWorkSource);
310 
311         if (DBG) {
312             logd(rr.serialString() + "> " + requestToString(rr.mRequest));
313         }
314 
315         try {
316             ((android.hardware.radio.config.V1_1.IRadioConfig) mRadioConfigProxy)
317                     .setPreferredDataModem(rr.mSerial, (byte) modemId);
318         } catch (RemoteException | RuntimeException e) {
319             resetProxyAndRequestList("setPreferredDataModem", e);
320         }
321     }
322 
323     /**
324      * Wrapper function for IRadioConfig.getPhoneCapability().
325      */
getPhoneCapability(Message result)326     public void getPhoneCapability(Message result) {
327         IRadioConfig radioConfigProxy = getRadioConfigProxy(null);
328         if (radioConfigProxy == null || mRadioConfigVersion.less(RADIO_CONFIG_HAL_VERSION_1_1)) {
329             if (result != null) {
330                 AsyncResult.forMessage(result, null,
331                         CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
332                 result.sendToTarget();
333             }
334             return;
335         }
336 
337         RILRequest rr = obtainRequest(RIL_REQUEST_GET_PHONE_CAPABILITY, result, mDefaultWorkSource);
338 
339         if (DBG) {
340             logd(rr.serialString() + "> " + requestToString(rr.mRequest));
341         }
342 
343         try {
344             ((android.hardware.radio.config.V1_1.IRadioConfig) mRadioConfigProxy)
345                     .getPhoneCapability(rr.mSerial);
346         } catch (RemoteException | RuntimeException e) {
347             resetProxyAndRequestList("getPhoneCapability", e);
348         }
349     }
350 
351     /**
352      * @return whether current radio config version supports SET_PREFERRED_DATA_MODEM command.
353      * If yes, we'll use RIL_REQUEST_SET_PREFERRED_DATA_MODEM to indicate which modem is preferred.
354      * If not, we shall use RIL_REQUEST_ALLOW_DATA for on-demand PS attach / detach.
355      * See PhoneSwitcher for more details.
356      */
isSetPreferredDataCommandSupported()357     public boolean isSetPreferredDataCommandSupported() {
358         IRadioConfig radioConfigProxy = getRadioConfigProxy(null);
359         return radioConfigProxy != null && mRadioConfigVersion
360                 .greaterOrEqual(RADIO_CONFIG_HAL_VERSION_1_1);
361     }
362 
363     /**
364      * Wrapper function for IRadioConfig.setSimSlotsMapping(int32_t serial, vec<uint32_t> slotMap).
365      */
setSimSlotsMapping(int[] physicalSlots, Message result)366     public void setSimSlotsMapping(int[] physicalSlots, Message result) {
367         IRadioConfig radioConfigProxy = getRadioConfigProxy(result);
368         if (radioConfigProxy != null) {
369             RILRequest rr = obtainRequest(RIL_REQUEST_SET_LOGICAL_TO_PHYSICAL_SLOT_MAPPING, result,
370                     mDefaultWorkSource);
371 
372             if (DBG) {
373                 logd(rr.serialString() + "> " + requestToString(rr.mRequest)
374                         + " " + Arrays.toString(physicalSlots));
375             }
376 
377             try {
378                 radioConfigProxy.setSimSlotsMapping(rr.mSerial,
379                         primitiveArrayToArrayList(physicalSlots));
380             } catch (RemoteException | RuntimeException e) {
381                 resetProxyAndRequestList("setSimSlotsMapping", e);
382             }
383         }
384     }
385 
primitiveArrayToArrayList(int[] arr)386     private static ArrayList<Integer> primitiveArrayToArrayList(int[] arr) {
387         ArrayList<Integer> arrayList = new ArrayList<>(arr.length);
388         for (int i : arr) {
389             arrayList.add(i);
390         }
391         return arrayList;
392     }
393 
requestToString(int request)394     static String requestToString(int request) {
395         switch (request) {
396             case RIL_REQUEST_GET_PHONE_CAPABILITY:
397                 return "GET_PHONE_CAPABILITY";
398             case RIL_REQUEST_GET_SLOT_STATUS:
399                 return "GET_SLOT_STATUS";
400             case RIL_REQUEST_SET_LOGICAL_TO_PHYSICAL_SLOT_MAPPING:
401                 return "SET_LOGICAL_TO_PHYSICAL_SLOT_MAPPING";
402             case RIL_REQUEST_SET_PREFERRED_DATA_MODEM:
403                 return "SET_PREFERRED_DATA_MODEM";
404             case RIL_REQUEST_SWITCH_DUAL_SIM_CONFIG:
405                 return "SWITCH_DUAL_SIM_CONFIG";
406             default:
407                 return "<unknown request " + request + ">";
408         }
409     }
410 
411     /**
412      * Wrapper function for using IRadioConfig.setModemsConfig(int32_t serial,
413      * ModemsConfig modemsConfig) to switch between single-sim and multi-sim.
414      */
setModemsConfig(int numOfLiveModems, Message result)415     public void setModemsConfig(int numOfLiveModems, Message result) {
416         IRadioConfig radioConfigProxy = getRadioConfigProxy(result);
417         if (radioConfigProxy != null
418                 && mRadioConfigVersion.greaterOrEqual(RADIO_CONFIG_HAL_VERSION_1_1)) {
419             android.hardware.radio.config.V1_1.IRadioConfig radioConfigProxy11 =
420                     (android.hardware.radio.config.V1_1.IRadioConfig) radioConfigProxy;
421             RILRequest rr = obtainRequest(RIL_REQUEST_SWITCH_DUAL_SIM_CONFIG,
422                     result, mDefaultWorkSource);
423 
424             if (DBG) {
425                 logd(rr.serialString() + "> " + requestToString(rr.mRequest)
426                         + ", numOfLiveModems = " + numOfLiveModems);
427             }
428 
429             try {
430                 ModemsConfig modemsConfig = new ModemsConfig();
431                 modemsConfig.numOfLiveModems = (byte) numOfLiveModems;
432                 radioConfigProxy11.setModemsConfig(rr.mSerial, modemsConfig);
433             } catch (RemoteException | RuntimeException e) {
434                 resetProxyAndRequestList("setModemsConfig", e);
435             }
436         }
437     }
438 
439     // TODO: not needed for now, but if we don't want to use System Properties any more,
440     // we need to implement a wrapper function for getModemsConfig as well
441 
442     /**
443      * Register a handler to get SIM slot status changed notifications.
444      */
registerForSimSlotStatusChanged(Handler h, int what, Object obj)445     public void registerForSimSlotStatusChanged(Handler h, int what, Object obj) {
446         mSimSlotStatusRegistrant = new Registrant(h, what, obj);
447     }
448 
449     /**
450      * Unregister corresponding to registerForSimSlotStatusChanged().
451      */
unregisterForSimSlotStatusChanged(Handler h)452     public void unregisterForSimSlotStatusChanged(Handler h) {
453         if (mSimSlotStatusRegistrant != null && mSimSlotStatusRegistrant.getHandler() == h) {
454             mSimSlotStatusRegistrant.clear();
455             mSimSlotStatusRegistrant = null;
456         }
457     }
458 
convertHalSlotStatus( ArrayList<android.hardware.radio.config.V1_0.SimSlotStatus> halSlotStatusList)459     static ArrayList<IccSlotStatus> convertHalSlotStatus(
460             ArrayList<android.hardware.radio.config.V1_0.SimSlotStatus> halSlotStatusList) {
461         ArrayList<IccSlotStatus> response = new ArrayList<IccSlotStatus>(halSlotStatusList.size());
462         for (android.hardware.radio.config.V1_0.SimSlotStatus slotStatus : halSlotStatusList) {
463             IccSlotStatus iccSlotStatus = new IccSlotStatus();
464             iccSlotStatus.setCardState(slotStatus.cardState);
465             iccSlotStatus.setSlotState(slotStatus.slotState);
466             iccSlotStatus.logicalSlotIndex = slotStatus.logicalSlotId;
467             iccSlotStatus.atr = slotStatus.atr;
468             iccSlotStatus.iccid = slotStatus.iccid;
469             response.add(iccSlotStatus);
470         }
471         return response;
472     }
473 
convertHalSlotStatus_1_2( ArrayList<android.hardware.radio.config.V1_2.SimSlotStatus> halSlotStatusList)474     static ArrayList<IccSlotStatus> convertHalSlotStatus_1_2(
475             ArrayList<android.hardware.radio.config.V1_2.SimSlotStatus> halSlotStatusList) {
476         ArrayList<IccSlotStatus> response = new ArrayList<IccSlotStatus>(halSlotStatusList.size());
477         for (android.hardware.radio.config.V1_2.SimSlotStatus slotStatus : halSlotStatusList) {
478             IccSlotStatus iccSlotStatus = new IccSlotStatus();
479             iccSlotStatus.setCardState(slotStatus.base.cardState);
480             iccSlotStatus.setSlotState(slotStatus.base.slotState);
481             iccSlotStatus.logicalSlotIndex = slotStatus.base.logicalSlotId;
482             iccSlotStatus.atr = slotStatus.base.atr;
483             iccSlotStatus.iccid = slotStatus.base.iccid;
484             iccSlotStatus.eid = slotStatus.eid;
485             response.add(iccSlotStatus);
486         }
487         return response;
488     }
489 
logd(String log)490     private static void logd(String log) {
491         Rlog.d(TAG, log);
492     }
493 
loge(String log)494     private static void loge(String log) {
495         Rlog.e(TAG, log);
496     }
497 }
498