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 android.content.Context;
20 import android.os.AsyncResult;
21 import android.os.Handler;
22 import android.os.Message;
23 import android.os.PowerManager;
24 import android.os.SystemProperties;
25 import android.os.storage.StorageManager;
26 import android.telephony.PhoneCapability;
27 import android.telephony.Rlog;
28 import android.telephony.TelephonyManager;
29 import android.util.Log;
30 
31 import java.util.HashMap;
32 import java.util.Map;
33 import java.util.NoSuchElementException;
34 
35 /**
36  * This class manages phone's configuration which defines the potential capability (static) of the
37  * phone and its current activated capability (current).
38  * It gets and monitors static and current phone capability from the modem; send broadcast
39  * if they change, and and sends commands to modem to enable or disable phones.
40  */
41 public class PhoneConfigurationManager {
42     public static final String DSDA = "dsda";
43     public static final String DSDS = "dsds";
44     public static final String TSTS = "tsts";
45     public static final String SSSS = "";
46     private static final String LOG_TAG = "PhoneCfgMgr";
47     private static final int EVENT_SWITCH_DSDS_CONFIG_DONE = 100;
48     private static final int EVENT_GET_MODEM_STATUS = 101;
49     private static final int EVENT_GET_MODEM_STATUS_DONE = 102;
50     private static final int EVENT_GET_PHONE_CAPABILITY_DONE = 103;
51 
52     private static PhoneConfigurationManager sInstance = null;
53     private final Context mContext;
54     private PhoneCapability mStaticCapability;
55     private final RadioConfig mRadioConfig;
56     private final MainThreadHandler mHandler;
57     private final Phone[] mPhones;
58     private final Map<Integer, Boolean> mPhoneStatusMap;
59 
60     /**
61      * Init method to instantiate the object
62      * Should only be called once.
63      */
init(Context context)64     public static PhoneConfigurationManager init(Context context) {
65         synchronized (PhoneConfigurationManager.class) {
66             if (sInstance == null) {
67                 sInstance = new PhoneConfigurationManager(context);
68             } else {
69                 Log.wtf(LOG_TAG, "init() called multiple times!  sInstance = " + sInstance);
70             }
71             return sInstance;
72         }
73     }
74 
75     /**
76      * Constructor.
77      * @param context context needed to send broadcast.
78      */
PhoneConfigurationManager(Context context)79     private PhoneConfigurationManager(Context context) {
80         mContext = context;
81         // TODO: send commands to modem once interface is ready.
82         TelephonyManager telephonyManager = new TelephonyManager(context);
83         //initialize with default, it'll get updated when RADIO is ON/AVAILABLE
84         mStaticCapability = getDefaultCapability();
85         mRadioConfig = RadioConfig.getInstance(mContext);
86         mHandler = new MainThreadHandler();
87         mPhoneStatusMap = new HashMap<>();
88 
89         notifyCapabilityChanged();
90 
91         mPhones = PhoneFactory.getPhones();
92         if (!StorageManager.inCryptKeeperBounce()) {
93             for (Phone phone : mPhones) {
94                 phone.mCi.registerForAvailable(mHandler, Phone.EVENT_RADIO_AVAILABLE, phone);
95             }
96         } else {
97             for (Phone phone : mPhones) {
98                 phone.mCi.registerForOn(mHandler, Phone.EVENT_RADIO_ON, phone);
99             }
100         }
101     }
102 
getDefaultCapability()103     private PhoneCapability getDefaultCapability() {
104         if (getPhoneCount() > 1) {
105             return PhoneCapability.DEFAULT_DSDS_CAPABILITY;
106         } else {
107             return PhoneCapability.DEFAULT_SSSS_CAPABILITY;
108         }
109     }
110 
111     /**
112      * Static method to get instance.
113      */
getInstance()114     public static PhoneConfigurationManager getInstance() {
115         if (sInstance == null) {
116             Log.wtf(LOG_TAG, "getInstance null");
117         }
118 
119         return sInstance;
120     }
121 
122     /**
123      * Handler class to handle callbacks
124      */
125     private final class MainThreadHandler extends Handler {
126         @Override
handleMessage(Message msg)127         public void handleMessage(Message msg) {
128             AsyncResult ar;
129             Phone phone = null;
130             switch (msg.what) {
131                 case Phone.EVENT_RADIO_AVAILABLE:
132                 case Phone.EVENT_RADIO_ON:
133                     log("Received EVENT_RADIO_AVAILABLE/EVENT_RADIO_ON");
134                     ar = (AsyncResult) msg.obj;
135                     if (ar.userObj != null && ar.userObj instanceof Phone) {
136                         phone = (Phone) ar.userObj;
137                         updatePhoneStatus(phone);
138                     } else {
139                         // phone is null
140                         log("Unable to add phoneStatus to cache. "
141                                 + "No phone object provided for event " + msg.what);
142                     }
143                     getStaticPhoneCapability();
144                     break;
145                 case EVENT_SWITCH_DSDS_CONFIG_DONE:
146                     ar = (AsyncResult) msg.obj;
147                     if (ar != null && ar.exception == null) {
148                         int numOfLiveModems = msg.arg1;
149                         setMultiSimProperties(numOfLiveModems);
150                     } else {
151                         log(msg.what + " failure. Not switching multi-sim config." + ar.exception);
152                     }
153                     break;
154                 case EVENT_GET_MODEM_STATUS_DONE:
155                     ar = (AsyncResult) msg.obj;
156                     if (ar != null && ar.exception == null) {
157                         int phoneId = msg.arg1;
158                         boolean enabled = (boolean) ar.result;
159                         //update the cache each time getModemStatus is requested
160                         addToPhoneStatusCache(phoneId, enabled);
161                     } else {
162                         log(msg.what + " failure. Not updating modem status." + ar.exception);
163                     }
164                     break;
165                 case EVENT_GET_PHONE_CAPABILITY_DONE:
166                     ar = (AsyncResult) msg.obj;
167                     if (ar != null && ar.exception == null) {
168                         mStaticCapability = (PhoneCapability) ar.result;
169                         notifyCapabilityChanged();
170                     } else {
171                         log(msg.what + " failure. Not getting phone capability." + ar.exception);
172                     }
173             }
174         }
175     }
176 
177     /**
178      * Enable or disable phone
179      *
180      * @param phone which phone to operate on
181      * @param enable true or false
182      * @param result the message to sent back when it's done.
183      */
enablePhone(Phone phone, boolean enable, Message result)184     public void enablePhone(Phone phone, boolean enable, Message result) {
185         if (phone == null) {
186             log("enablePhone failed phone is null");
187             return;
188         }
189         phone.mCi.enableModem(enable, result);
190     }
191 
192     /**
193      * Get phone status (enabled/disabled)
194      * first query cache, if the status is not in cache,
195      * add it to cache and return a default value true (non-blocking).
196      *
197      * @param phone which phone to operate on
198      */
getPhoneStatus(Phone phone)199     public boolean getPhoneStatus(Phone phone) {
200         if (phone == null) {
201             log("getPhoneStatus failed phone is null");
202             return false;
203         }
204 
205         int phoneId = phone.getPhoneId();
206 
207         //use cache if the status has already been updated/queried
208         try {
209             return getPhoneStatusFromCache(phoneId);
210         } catch (NoSuchElementException ex) {
211             updatePhoneStatus(phone);
212             // Return true if modem status cannot be retrieved. For most cases, modem status
213             // is on. And for older version modems, GET_MODEM_STATUS and disable modem are not
214             // supported. Modem is always on.
215             //TODO: this should be fixed in R to support a third status UNKNOWN b/131631629
216             return true;
217         }
218     }
219 
220     /**
221      * Get phone status (enabled/disabled) directly from modem, and use a result Message object
222      * Note: the caller of this method is reponsible to call this in a blocking fashion as well
223      * as read the results and handle the error case.
224      * (In order to be consistent, in error case, we should return default value of true; refer
225      *  to #getPhoneStatus method)
226      *
227      * @param phone which phone to operate on
228      * @param result message that will be updated with result
229      */
getPhoneStatusFromModem(Phone phone, Message result)230     public void getPhoneStatusFromModem(Phone phone, Message result) {
231         if (phone == null) {
232             log("getPhoneStatus failed phone is null");
233         }
234         phone.mCi.getModemStatus(result);
235     }
236 
237     /**
238      * return modem status from cache, NoSuchElementException if phoneId not in cache
239      * @param phoneId
240      */
getPhoneStatusFromCache(int phoneId)241     public boolean getPhoneStatusFromCache(int phoneId) throws NoSuchElementException {
242         if (mPhoneStatusMap.containsKey(phoneId)) {
243             return mPhoneStatusMap.get(phoneId);
244         } else {
245             throw new NoSuchElementException("phoneId not found: " + phoneId);
246         }
247     }
248 
249     /**
250      * method to call RIL getModemStatus
251      */
updatePhoneStatus(Phone phone)252     private void updatePhoneStatus(Phone phone) {
253         Message result = Message.obtain(
254                 mHandler, EVENT_GET_MODEM_STATUS_DONE, phone.getPhoneId(), 0 /**dummy arg*/);
255         phone.mCi.getModemStatus(result);
256     }
257 
258     /**
259      * Add status of the phone to the status HashMap
260      * @param phoneId
261      * @param status
262      */
addToPhoneStatusCache(int phoneId, boolean status)263     public void addToPhoneStatusCache(int phoneId, boolean status) {
264         mPhoneStatusMap.put(phoneId, status);
265     }
266 
267     /**
268      * Returns how many phone objects the device supports.
269      */
getPhoneCount()270     public int getPhoneCount() {
271         TelephonyManager tm = new TelephonyManager(mContext);
272         return tm.getPhoneCount();
273     }
274 
275     /**
276      * get static overall phone capabilities for all phones.
277      */
getStaticPhoneCapability()278     public synchronized PhoneCapability getStaticPhoneCapability() {
279         if (getDefaultCapability().equals(mStaticCapability)) {
280             log("getStaticPhoneCapability: sending the request for getting PhoneCapability");
281             Message callback = Message.obtain(
282                     mHandler, EVENT_GET_PHONE_CAPABILITY_DONE);
283             mRadioConfig.getPhoneCapability(callback);
284         }
285         return mStaticCapability;
286     }
287 
288     /**
289      * get configuration related status of each phone.
290      */
getCurrentPhoneCapability()291     public PhoneCapability getCurrentPhoneCapability() {
292         return getStaticPhoneCapability();
293     }
294 
getNumberOfModemsWithSimultaneousDataConnections()295     public int getNumberOfModemsWithSimultaneousDataConnections() {
296         return mStaticCapability.maxActiveData;
297     }
298 
notifyCapabilityChanged()299     private void notifyCapabilityChanged() {
300         PhoneNotifier notifier = new DefaultPhoneNotifier();
301 
302         notifier.notifyPhoneCapabilityChanged(mStaticCapability);
303     }
304 
305     /**
306      * Switch configs to enable multi-sim or switch back to single-sim
307      * @param numOfSims number of active sims we want to switch to
308      */
switchMultiSimConfig(int numOfSims)309     public void switchMultiSimConfig(int numOfSims) {
310         log("switchMultiSimConfig: with numOfSims = " + numOfSims);
311         if (getStaticPhoneCapability().logicalModemList.size() < numOfSims) {
312             log("switchMultiSimConfig: Phone is not capable of enabling "
313                     + numOfSims + " sims, exiting!");
314             return;
315         }
316         if (getPhoneCount() != numOfSims) {
317             log("switchMultiSimConfig: sending the request for switching");
318             Message callback = Message.obtain(
319                     mHandler, EVENT_SWITCH_DSDS_CONFIG_DONE, numOfSims, 0 /**dummy arg*/);
320             mRadioConfig.setModemsConfig(numOfSims, callback);
321         } else {
322             log("switchMultiSimConfig: No need to switch. getNumOfActiveSims is already "
323                     + numOfSims);
324         }
325     }
326 
327     /**
328      * Get whether reboot is required or not after making changes to modem configurations.
329      * Return value defaults to true
330      */
isRebootRequiredForModemConfigChange()331     public boolean isRebootRequiredForModemConfigChange() {
332         String rebootRequired = SystemProperties.get(
333                 TelephonyProperties.PROPERTY_REBOOT_REQUIRED_ON_MODEM_CHANGE);
334         log("isRebootRequiredForModemConfigChange: isRebootRequired = " + rebootRequired);
335         return !rebootRequired.equals("false");
336     }
337 
338     /**
339      * Helper method to set system properties for setting multi sim configs,
340      * as well as doing the phone reboot
341      * NOTE: In order to support more than 3 sims, we need to change this method.
342      * @param numOfSims number of active sims
343      */
setMultiSimProperties(int numOfSims)344     private void setMultiSimProperties(int numOfSims) {
345         String finalMultiSimConfig;
346         switch(numOfSims) {
347             case 3:
348                 finalMultiSimConfig = TSTS;
349                 break;
350             case 2:
351                 finalMultiSimConfig = DSDS;
352                 break;
353             default:
354                 finalMultiSimConfig = SSSS;
355         }
356 
357         SystemProperties.set(TelephonyProperties.PROPERTY_MULTI_SIM_CONFIG, finalMultiSimConfig);
358         if (isRebootRequiredForModemConfigChange()) {
359             log("setMultiSimProperties: Rebooting due to switching multi-sim config to "
360                     + finalMultiSimConfig);
361             PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
362             pm.reboot("Switching to " + finalMultiSimConfig);
363         } else {
364             log("setMultiSimProperties: Rebooting is not required to switch multi-sim config to "
365                     + finalMultiSimConfig);
366         }
367     }
368 
log(String s)369     private static void log(String s) {
370         Rlog.d(LOG_TAG, s);
371     }
372 }
373