1 /*
2  * Copyright (C) 2019 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.car.developeroptions.sim;
18 
19 import android.app.settings.SettingsEnums;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.res.Resources;
23 import android.graphics.drawable.BitmapDrawable;
24 import android.os.Bundle;
25 import android.provider.SearchIndexableResource;
26 import android.sysprop.TelephonyProperties;
27 import android.telecom.PhoneAccountHandle;
28 import android.telecom.TelecomManager;
29 import android.telephony.PhoneNumberUtils;
30 import android.telephony.PhoneStateListener;
31 import android.telephony.SubscriptionInfo;
32 import android.telephony.SubscriptionManager;
33 import android.telephony.TelephonyManager;
34 import android.text.TextUtils;
35 import android.util.Log;
36 
37 import androidx.preference.Preference;
38 import androidx.preference.PreferenceScreen;
39 
40 import com.android.car.developeroptions.R;
41 import com.android.car.developeroptions.RestrictedSettingsFragment;
42 import com.android.car.developeroptions.Utils;
43 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
44 import com.android.settingslib.search.Indexable;
45 import com.android.settingslib.search.SearchIndexable;
46 
47 import java.util.ArrayList;
48 import java.util.List;
49 
50 @SearchIndexable
51 public class SimSettings extends RestrictedSettingsFragment implements Indexable {
52     private static final String TAG = "SimSettings";
53     private static final boolean DBG = false;
54 
55     private static final String DISALLOW_CONFIG_SIM = "no_config_sim";
56     private static final String SIM_CARD_CATEGORY = "sim_cards";
57     private static final String KEY_CELLULAR_DATA = "sim_cellular_data";
58     private static final String KEY_CALLS = "sim_calls";
59     private static final String KEY_SMS = "sim_sms";
60     public static final String EXTRA_SLOT_ID = "slot_id";
61 
62     /**
63      * By UX design we use only one Subscription Information(SubInfo) record per SIM slot.
64      * mAvalableSubInfos is the list of SubInfos we present to the user.
65      * mSubInfoList is the list of all SubInfos.
66      * mSelectableSubInfos is the list of SubInfos that a user can select for data, calls, and SMS.
67      */
68     private List<SubscriptionInfo> mAvailableSubInfos = null;
69     private List<SubscriptionInfo> mSubInfoList = null;
70     private List<SubscriptionInfo> mSelectableSubInfos = null;
71     private PreferenceScreen mSimCards = null;
72     private SubscriptionManager mSubscriptionManager;
73     private int mNumSlots;
74     private Context mContext;
75 
76     private int mPhoneCount = TelephonyManager.getDefault().getPhoneCount();
77     private int[] mCallState = new int[mPhoneCount];
78     private PhoneStateListener[] mPhoneStateListener = new PhoneStateListener[mPhoneCount];
79 
SimSettings()80     public SimSettings() {
81         super(DISALLOW_CONFIG_SIM);
82     }
83 
84     @Override
getMetricsCategory()85     public int getMetricsCategory() {
86         return SettingsEnums.SIM;
87     }
88 
89     @Override
onCreate(final Bundle bundle)90     public void onCreate(final Bundle bundle) {
91         super.onCreate(bundle);
92         mContext = getActivity();
93 
94         mSubscriptionManager = SubscriptionManager.from(getActivity());
95         final TelephonyManager tm =
96                 (TelephonyManager) getActivity().getSystemService(Context.TELEPHONY_SERVICE);
97         addPreferencesFromResource(R.xml.sim_settings);
98 
99         mNumSlots = tm.getSimCount();
100         mSimCards = (PreferenceScreen)findPreference(SIM_CARD_CATEGORY);
101         mAvailableSubInfos = new ArrayList<SubscriptionInfo>(mNumSlots);
102         mSelectableSubInfos = new ArrayList<SubscriptionInfo>();
103         SimSelectNotification.cancelNotification(getActivity());
104     }
105 
106     private final SubscriptionManager.OnSubscriptionsChangedListener mOnSubscriptionsChangeListener
107             = new SubscriptionManager.OnSubscriptionsChangedListener() {
108         @Override
109         public void onSubscriptionsChanged() {
110             if (DBG) log("onSubscriptionsChanged:");
111             updateSubscriptions();
112         }
113     };
114 
updateSubscriptions()115     private void updateSubscriptions() {
116         mSubInfoList = mSubscriptionManager.getActiveSubscriptionInfoList(true);
117         for (int i = 0; i < mNumSlots; ++i) {
118             Preference pref = mSimCards.findPreference("sim" + i);
119             if (pref instanceof SimPreference) {
120                 mSimCards.removePreference(pref);
121             }
122         }
123         mAvailableSubInfos.clear();
124         mSelectableSubInfos.clear();
125 
126         for (int i = 0; i < mNumSlots; ++i) {
127             final SubscriptionInfo sir = mSubscriptionManager
128                     .getActiveSubscriptionInfoForSimSlotIndex(i);
129             SimPreference simPreference = new SimPreference(getPrefContext(), sir, i);
130             simPreference.setOrder(i-mNumSlots);
131             mSimCards.addPreference(simPreference);
132             mAvailableSubInfos.add(sir);
133             if (sir != null) {
134                 mSelectableSubInfos.add(sir);
135             }
136         }
137         updateAllOptions();
138     }
139 
updateAllOptions()140     private void updateAllOptions() {
141         updateSimSlotValues();
142         updateActivitesCategory();
143     }
144 
updateSimSlotValues()145     private void updateSimSlotValues() {
146         final int prefSize = mSimCards.getPreferenceCount();
147         for (int i = 0; i < prefSize; ++i) {
148             Preference pref = mSimCards.getPreference(i);
149             if (pref instanceof SimPreference) {
150                 ((SimPreference)pref).update();
151             }
152         }
153     }
154 
updateActivitesCategory()155     private void updateActivitesCategory() {
156         updateCellularDataValues();
157         updateCallValues();
158         updateSmsValues();
159     }
160 
updateSmsValues()161     private void updateSmsValues() {
162         final Preference simPref = findPreference(KEY_SMS);
163         final SubscriptionInfo sir = mSubscriptionManager.getDefaultSmsSubscriptionInfo();
164         simPref.setTitle(R.string.sms_messages_title);
165         if (DBG) log("[updateSmsValues] mSubInfoList=" + mSubInfoList);
166 
167         if (sir != null) {
168             simPref.setSummary(sir.getDisplayName());
169             simPref.setEnabled(mSelectableSubInfos.size() > 1);
170         } else if (sir == null) {
171             simPref.setSummary(R.string.sim_selection_required_pref);
172             simPref.setEnabled(mSelectableSubInfos.size() >= 1);
173         }
174     }
175 
updateCellularDataValues()176     private void updateCellularDataValues() {
177         final Preference simPref = findPreference(KEY_CELLULAR_DATA);
178         final SubscriptionInfo sir = mSubscriptionManager.getDefaultDataSubscriptionInfo();
179         simPref.setTitle(R.string.cellular_data_title);
180         if (DBG) log("[updateCellularDataValues] mSubInfoList=" + mSubInfoList);
181 
182         boolean callStateIdle = isCallStateIdle();
183         final boolean ecbMode = TelephonyProperties.in_ecm_mode().orElse(false);
184         if (sir != null) {
185             simPref.setSummary(sir.getDisplayName());
186             // Enable data preference in msim mode and call state idle
187             simPref.setEnabled((mSelectableSubInfos.size() > 1) && callStateIdle && !ecbMode);
188         } else if (sir == null) {
189             simPref.setSummary(R.string.sim_selection_required_pref);
190             // Enable data preference in msim mode and call state idle
191             simPref.setEnabled((mSelectableSubInfos.size() >= 1) && callStateIdle && !ecbMode);
192         }
193     }
194 
updateCallValues()195     private void updateCallValues() {
196         final Preference simPref = findPreference(KEY_CALLS);
197         final TelecomManager telecomManager = TelecomManager.from(mContext);
198         final PhoneAccountHandle phoneAccount =
199             telecomManager.getUserSelectedOutgoingPhoneAccount();
200         final List<PhoneAccountHandle> allPhoneAccounts =
201             telecomManager.getCallCapablePhoneAccounts();
202 
203         simPref.setTitle(R.string.calls_title);
204         simPref.setSummary(phoneAccount == null
205                 ? mContext.getResources().getString(R.string.sim_calls_ask_first_prefs_title)
206                 : (String)telecomManager.getPhoneAccount(phoneAccount).getLabel());
207         simPref.setEnabled(allPhoneAccounts.size() > 1);
208     }
209 
210     @Override
onResume()211     public void onResume() {
212         super.onResume();
213         mSubscriptionManager.addOnSubscriptionsChangedListener(mOnSubscriptionsChangeListener);
214         updateSubscriptions();
215         final TelephonyManager tm =
216                 (TelephonyManager) getActivity().getSystemService(Context.TELEPHONY_SERVICE);
217         if (mSelectableSubInfos.size() > 1) {
218             Log.d(TAG, "Register for call state change");
219             for (int i = 0; i < mPhoneCount; i++) {
220                 int subId = mSelectableSubInfos.get(i).getSubscriptionId();
221                 tm.createForSubscriptionId(subId).listen(getPhoneStateListener(i),
222                         PhoneStateListener.LISTEN_CALL_STATE);
223             }
224         }
225     }
226 
227     @Override
onPause()228     public void onPause() {
229         super.onPause();
230         mSubscriptionManager.removeOnSubscriptionsChangedListener(mOnSubscriptionsChangeListener);
231         final TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
232         for (int i = 0; i < mPhoneCount; i++) {
233             if (mPhoneStateListener[i] != null) {
234                 tm.listen(mPhoneStateListener[i], PhoneStateListener.LISTEN_NONE);
235                 mPhoneStateListener[i] = null;
236             }
237         }
238     }
239 
getPhoneStateListener(int phoneId)240     private PhoneStateListener getPhoneStateListener(int phoneId) {
241         // Disable Sim selection for Data when voice call is going on as changing the default data
242         // sim causes a modem reset currently and call gets disconnected
243         // ToDo : Add subtext on disabled preference to let user know that default data sim cannot
244         // be changed while call is going on
245         final int i = phoneId;
246         mPhoneStateListener[phoneId]  = new PhoneStateListener() {
247             @Override
248             public void onCallStateChanged(int state, String incomingNumber) {
249                 if (DBG) log("PhoneStateListener.onCallStateChanged: state=" + state);
250                 mCallState[i] = state;
251                 updateCellularDataValues();
252             }
253         };
254         return mPhoneStateListener[phoneId];
255     }
256 
257     @Override
onPreferenceTreeClick(final Preference preference)258     public boolean onPreferenceTreeClick(final Preference preference) {
259         final Context context = mContext;
260         Intent intent = new Intent(context, SimDialogActivity.class);
261         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
262 
263         if (preference instanceof SimPreference) {
264             Intent newIntent = new Intent(context, SimPreferenceDialog.class);
265             newIntent.putExtra(EXTRA_SLOT_ID, ((SimPreference)preference).getSlotId());
266             startActivity(newIntent);
267         } else if (findPreference(KEY_CELLULAR_DATA) == preference) {
268             intent.putExtra(SimDialogActivity.DIALOG_TYPE_KEY, SimDialogActivity.DATA_PICK);
269             context.startActivity(intent);
270         } else if (findPreference(KEY_CALLS) == preference) {
271             intent.putExtra(SimDialogActivity.DIALOG_TYPE_KEY, SimDialogActivity.CALLS_PICK);
272             context.startActivity(intent);
273         } else if (findPreference(KEY_SMS) == preference) {
274             intent.putExtra(SimDialogActivity.DIALOG_TYPE_KEY, SimDialogActivity.SMS_PICK);
275             context.startActivity(intent);
276         }
277 
278         return true;
279     }
280 
281     private class SimPreference extends Preference {
282         private SubscriptionInfo mSubInfoRecord;
283         private int mSlotId;
284         Context mContext;
285 
SimPreference(Context context, SubscriptionInfo subInfoRecord, int slotId)286         public SimPreference(Context context, SubscriptionInfo subInfoRecord, int slotId) {
287             super(context);
288 
289             mContext = context;
290             mSubInfoRecord = subInfoRecord;
291             mSlotId = slotId;
292             setKey("sim" + mSlotId);
293             update();
294         }
295 
update()296         public void update() {
297             final Resources res = mContext.getResources();
298 
299             setTitle(String.format(mContext.getResources()
300                     .getString(R.string.sim_editor_title), (mSlotId + 1)));
301             if (mSubInfoRecord != null) {
302                 if (TextUtils.isEmpty(getPhoneNumber(mSubInfoRecord))) {
303                     setSummary(mSubInfoRecord.getDisplayName());
304                 } else {
305                     setSummary(mSubInfoRecord.getDisplayName() + " - " +
306                             PhoneNumberUtils.createTtsSpannable(getPhoneNumber(mSubInfoRecord)));
307                     setEnabled(true);
308                 }
309                 setIcon(new BitmapDrawable(res, (mSubInfoRecord.createIconBitmap(mContext))));
310             } else {
311                 setSummary(R.string.sim_slot_empty);
312                 setFragment(null);
313                 setEnabled(false);
314             }
315         }
316 
getSlotId()317         private int getSlotId() {
318             return mSlotId;
319         }
320     }
321 
322     // Returns the line1Number. Line1number should always be read from TelephonyManager since it can
323     // be overridden for display purposes.
getPhoneNumber(SubscriptionInfo info)324     private String getPhoneNumber(SubscriptionInfo info) {
325         final TelephonyManager tm =
326             (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
327         return tm.getLine1Number(info.getSubscriptionId());
328     }
329 
log(String s)330     private void log(String s) {
331         Log.d(TAG, s);
332     }
333 
334     /**
335      * For search
336      */
337     public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
338             new BaseSearchIndexProvider() {
339                 @Override
340                 public List<SearchIndexableResource> getXmlResourcesToIndex(Context context,
341                         boolean enabled) {
342                     ArrayList<SearchIndexableResource> result =
343                             new ArrayList<SearchIndexableResource>();
344 
345                     if (Utils.showSimCardTile(context)) {
346                         SearchIndexableResource sir = new SearchIndexableResource(context);
347                         sir.xmlResId = R.xml.sim_settings;
348                         result.add(sir);
349                     }
350 
351                     return result;
352                 }
353             };
354 
isCallStateIdle()355     private boolean isCallStateIdle() {
356         boolean callStateIdle = true;
357         for (int i = 0; i < mCallState.length; i++) {
358             if (TelephonyManager.CALL_STATE_IDLE != mCallState[i]) {
359                 callStateIdle = false;
360             }
361         }
362         Log.d(TAG, "isCallStateIdle " + callStateIdle);
363         return callStateIdle;
364     }
365 }
366