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.settings.network.telephony;
18 
19 import android.app.settings.SettingsEnums;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.os.Bundle;
23 import android.os.Handler;
24 import android.os.Message;
25 import android.os.PersistableBundle;
26 import android.provider.Settings;
27 import android.telephony.CarrierConfigManager;
28 import android.telephony.CellIdentity;
29 import android.telephony.CellInfo;
30 import android.telephony.NetworkRegistrationInfo;
31 import android.telephony.SignalStrength;
32 import android.telephony.SubscriptionManager;
33 import android.telephony.TelephonyManager;
34 import android.telephony.satellite.SatelliteManager;
35 import android.util.Log;
36 import android.view.View;
37 
38 import androidx.annotation.Keep;
39 import androidx.annotation.NonNull;
40 import androidx.annotation.Nullable;
41 import androidx.annotation.VisibleForTesting;
42 import androidx.preference.Preference;
43 import androidx.preference.PreferenceCategory;
44 
45 import com.android.internal.annotations.Initializer;
46 import com.android.internal.telephony.OperatorInfo;
47 import com.android.internal.telephony.flags.Flags;
48 import com.android.settings.R;
49 import com.android.settings.dashboard.DashboardFragment;
50 import com.android.settings.network.telephony.scan.NetworkScanRepository;
51 import com.android.settings.overlay.FeatureFactory;
52 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
53 import com.android.settingslib.utils.ThreadUtils;
54 
55 import com.google.common.collect.ImmutableList;
56 
57 import kotlin.Unit;
58 
59 import kotlinx.coroutines.Job;
60 
61 import java.util.ArrayList;
62 import java.util.Arrays;
63 import java.util.List;
64 import java.util.concurrent.ExecutorService;
65 import java.util.concurrent.Executors;
66 import java.util.concurrent.atomic.AtomicBoolean;
67 import java.util.stream.Collectors;
68 
69 /**
70  * "Choose network" settings UI for the Settings app.
71  */
72 @Keep
73 public class NetworkSelectSettings extends DashboardFragment {
74 
75     private static final String TAG = "NetworkSelectSettings";
76 
77     private static final int EVENT_SET_NETWORK_SELECTION_MANUALLY_DONE = 1;
78 
79     private static final String PREF_KEY_NETWORK_OPERATORS = "network_operators_preference";
80 
81     private PreferenceCategory mPreferenceCategory;
82     @VisibleForTesting
83     NetworkOperatorPreference mSelectedPreference;
84     private View mProgressHeader;
85     private Preference mStatusMessagePreference;
86     @VisibleForTesting
87     @NonNull
88     List<CellInfo> mCellInfoList = ImmutableList.of();
89     private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
90     private TelephonyManager mTelephonyManager;
91     private SatelliteManager mSatelliteManager;
92     private CarrierConfigManager mCarrierConfigManager;
93     private List<String> mForbiddenPlmns;
94     private boolean mShow4GForLTE = false;
95     private final ExecutorService mNetworkScanExecutor = Executors.newFixedThreadPool(1);
96     private MetricsFeatureProvider mMetricsFeatureProvider;
97     private CarrierConfigManager.CarrierConfigChangeListener mCarrierConfigChangeListener;
98     private AtomicBoolean mShouldFilterOutSatellitePlmn = new AtomicBoolean();
99 
100     private NetworkScanRepository mNetworkScanRepository;
101     @Nullable
102     private Job mNetworkScanJob = null;
103 
104     private NetworkSelectRepository mNetworkSelectRepository;
105 
106     @Override
onCreate(Bundle icicle)107     public void onCreate(Bundle icicle) {
108         super.onCreate(icicle);
109         onCreateInitialization();
110     }
111 
112     @Keep
113     @VisibleForTesting
114     @Initializer
onCreateInitialization()115     protected void onCreateInitialization() {
116         Context context = getContext();
117         mSubId = getSubId();
118 
119         mPreferenceCategory = getPreferenceCategory(PREF_KEY_NETWORK_OPERATORS);
120         mStatusMessagePreference = new Preference(context);
121         mStatusMessagePreference.setSelectable(false);
122         mSelectedPreference = null;
123         mTelephonyManager = getTelephonyManager(context, mSubId);
124         mSatelliteManager = getSatelliteManager(context);
125         mCarrierConfigManager = getCarrierConfigManager(context);
126         PersistableBundle bundle = mCarrierConfigManager.getConfigForSubId(mSubId,
127                 CarrierConfigManager.KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL,
128                 CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL);
129         mShow4GForLTE = bundle.getBoolean(CarrierConfigManager.KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL,
130                 false);
131         mShouldFilterOutSatellitePlmn.set(bundle.getBoolean(
132                 CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL,
133                 true));
134 
135         mMetricsFeatureProvider = getMetricsFeatureProvider(context);
136 
137         mCarrierConfigChangeListener =
138                 (slotIndex, subId, carrierId, specificCarrierId) -> handleCarrierConfigChanged(
139                         subId);
140         mCarrierConfigManager.registerCarrierConfigChangeListener(mNetworkScanExecutor,
141                 mCarrierConfigChangeListener);
142         mNetworkScanRepository = new NetworkScanRepository(context, mSubId);
143         mNetworkSelectRepository = new NetworkSelectRepository(context, mSubId);
144     }
145 
146     @Keep
147     @VisibleForTesting
getPreferenceCategory(String preferenceKey)148     protected PreferenceCategory getPreferenceCategory(String preferenceKey) {
149         return findPreference(preferenceKey);
150     }
151 
152     @Keep
153     @VisibleForTesting
getTelephonyManager(Context context, int subscriptionId)154     protected TelephonyManager getTelephonyManager(Context context, int subscriptionId) {
155         return context.getSystemService(TelephonyManager.class)
156                 .createForSubscriptionId(subscriptionId);
157     }
158 
159     @Keep
160     @VisibleForTesting
getCarrierConfigManager(Context context)161     protected CarrierConfigManager getCarrierConfigManager(Context context) {
162         return context.getSystemService(CarrierConfigManager.class);
163     }
164 
165     @Keep
166     @VisibleForTesting
getMetricsFeatureProvider(Context context)167     protected MetricsFeatureProvider getMetricsFeatureProvider(Context context) {
168         return FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
169     }
170 
171     @Keep
172     @VisibleForTesting
173     @Nullable
getSatelliteManager(Context context)174     protected SatelliteManager getSatelliteManager(Context context) {
175         return context.getSystemService(SatelliteManager.class);
176     }
177 
178     @Keep
179     @VisibleForTesting
isPreferenceScreenEnabled()180     protected boolean isPreferenceScreenEnabled() {
181         return getPreferenceScreen().isEnabled();
182     }
183 
184     @Keep
185     @VisibleForTesting
enablePreferenceScreen(boolean enable)186     protected void enablePreferenceScreen(boolean enable) {
187         getPreferenceScreen().setEnabled(enable);
188     }
189 
190     @Keep
191     @VisibleForTesting
getSubId()192     protected int getSubId() {
193         int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
194         Intent intent = getActivity().getIntent();
195         if (intent != null) {
196             subId = intent.getIntExtra(Settings.EXTRA_SUB_ID,
197                     SubscriptionManager.INVALID_SUBSCRIPTION_ID);
198         }
199         return subId;
200     }
201 
202     @Override
onViewCreated(@onNull View view, @Nullable Bundle savedInstanceState)203     public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
204         super.onViewCreated(view, savedInstanceState);
205 
206         mProgressHeader = setPinnedHeaderView(
207                 com.android.settingslib.widget.progressbar.R.layout.progress_header
208         ).findViewById(com.android.settingslib.widget.progressbar.R.id.progress_bar_animation);
209         mNetworkSelectRepository.launchUpdateNetworkRegistrationInfo(
210                 getViewLifecycleOwner(),
211                 (info) -> {
212                     forceUpdateConnectedPreferenceCategory(info);
213                     return Unit.INSTANCE;
214                 });
215         launchNetworkScan();
216     }
217 
launchNetworkScan()218     private void launchNetworkScan() {
219         setProgressBarVisible(true);
220         mNetworkScanJob = mNetworkScanRepository.launchNetworkScan(getViewLifecycleOwner(),
221                 (networkScanResult) -> {
222                     if (isPreferenceScreenEnabled()) {
223                         scanResultHandler(networkScanResult);
224                     }
225 
226                     return Unit.INSTANCE;
227                 });
228     }
229 
230     /**
231      * Update forbidden PLMNs from the USIM App
232      */
233     @Keep
234     @VisibleForTesting
updateForbiddenPlmns()235     protected void updateForbiddenPlmns() {
236         final String[] forbiddenPlmns = mTelephonyManager.getForbiddenPlmns();
237         mForbiddenPlmns = forbiddenPlmns != null
238                 ? Arrays.asList(forbiddenPlmns)
239                 : new ArrayList<>();
240     }
241 
242     @Override
onPreferenceTreeClick(Preference preference)243     public boolean onPreferenceTreeClick(Preference preference) {
244         if (preference == mSelectedPreference) {
245             Log.d(TAG, "onPreferenceTreeClick: preference is mSelectedPreference. Do nothing.");
246             return true;
247         }
248         if (!(preference instanceof NetworkOperatorPreference)) {
249             Log.d(TAG, "onPreferenceTreeClick: preference is not the NetworkOperatorPreference.");
250             return false;
251         }
252 
253         // Need stop network scan before manual select network.
254         if (mNetworkScanJob != null) {
255             mNetworkScanJob.cancel(null);
256             mNetworkScanJob = null;
257         }
258 
259         // Refresh the last selected item in case users reselect network.
260         clearPreferenceSummary();
261         if (mSelectedPreference != null) {
262             // Set summary as "Disconnected" to the previously connected network
263             mSelectedPreference.setSummary(R.string.network_disconnected);
264         }
265 
266         mSelectedPreference = (NetworkOperatorPreference) preference;
267         mSelectedPreference.setSummary(R.string.network_connecting);
268 
269         mMetricsFeatureProvider.action(getContext(),
270                 SettingsEnums.ACTION_MOBILE_NETWORK_MANUAL_SELECT_NETWORK);
271 
272         setProgressBarVisible(true);
273         // Disable the screen until network is manually set
274         enablePreferenceScreen(false);
275 
276         final OperatorInfo operator = mSelectedPreference.getOperatorInfo();
277         ThreadUtils.postOnBackgroundThread(() -> {
278             final Message msg = mHandler.obtainMessage(
279                     EVENT_SET_NETWORK_SELECTION_MANUALLY_DONE);
280             msg.obj = mTelephonyManager.setNetworkSelectionModeManual(
281                     operator, true /* persistSelection */);
282             msg.sendToTarget();
283         });
284 
285         return true;
286     }
287 
288     @Override
getPreferenceScreenResId()289     protected int getPreferenceScreenResId() {
290         return R.xml.choose_network;
291     }
292 
293     @Override
getLogTag()294     protected String getLogTag() {
295         return TAG;
296     }
297 
298     @Override
getMetricsCategory()299     public int getMetricsCategory() {
300         return SettingsEnums.MOBILE_NETWORK_SELECT;
301     }
302 
303     private final Handler mHandler = new Handler() {
304         @Override
305         public void handleMessage(Message msg) {
306             switch (msg.what) {
307                 case EVENT_SET_NETWORK_SELECTION_MANUALLY_DONE:
308                     final boolean isSucceed = (boolean) msg.obj;
309                     setProgressBarVisible(false);
310                     enablePreferenceScreen(true);
311 
312                     if (mSelectedPreference != null) {
313                         mSelectedPreference.setSummary(isSucceed
314                                 ? R.string.network_connected
315                                 : R.string.network_could_not_connect);
316                     } else {
317                         Log.e(TAG, "No preference to update!");
318                     }
319                     break;
320             }
321         }
322     };
323 
324     /* We do not want to expose carrier satellite plmns to the user when manually scan the
325        cellular network. Therefore, it is needed to filter out satellite plmns from current cell
326        info list  */
327     @VisibleForTesting
filterOutSatellitePlmn(List<CellInfo> cellInfoList)328     List<CellInfo> filterOutSatellitePlmn(List<CellInfo> cellInfoList) {
329         List<String> aggregatedSatellitePlmn = getSatellitePlmnsForCarrierWrapper();
330         if (!mShouldFilterOutSatellitePlmn.get() || aggregatedSatellitePlmn.isEmpty()) {
331             return cellInfoList;
332         }
333         return cellInfoList.stream()
334                 .filter(cellInfo -> !aggregatedSatellitePlmn.contains(
335                         CellInfoUtil.getOperatorNumeric(cellInfo.getCellIdentity())))
336                 .collect(Collectors.toList());
337     }
338 
339     /**
340      * Serves as a wrapper method for {@link SatelliteManager#getSatellitePlmnsForCarrier(int)}.
341      * Since SatelliteManager is final, this wrapper enables mocking or spying of
342      * {@link SatelliteManager#getSatellitePlmnsForCarrier(int)} for unit testing purposes.
343      */
344     @VisibleForTesting
getSatellitePlmnsForCarrierWrapper()345     protected List<String> getSatellitePlmnsForCarrierWrapper() {
346         if (!Flags.carrierEnabledSatelliteFlag()) {
347             return new ArrayList<>();
348         }
349 
350         if (mSatelliteManager != null) {
351             return mSatelliteManager.getSatellitePlmnsForCarrier(mSubId);
352         } else {
353             Log.e(TAG, "mSatelliteManager is null, return empty list");
354             return new ArrayList<>();
355         }
356     }
357 
handleCarrierConfigChanged(int subId)358     private void handleCarrierConfigChanged(int subId) {
359         PersistableBundle config = mCarrierConfigManager.getConfigForSubId(subId,
360                 CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL);
361         boolean shouldFilterSatellitePlmn = config.getBoolean(
362                 CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL,
363                 true);
364         if (shouldFilterSatellitePlmn != mShouldFilterOutSatellitePlmn.get()) {
365             mShouldFilterOutSatellitePlmn.set(shouldFilterSatellitePlmn);
366         }
367     }
368 
369     @VisibleForTesting
scanResultHandler(NetworkScanRepository.NetworkScanResult results)370     protected void scanResultHandler(NetworkScanRepository.NetworkScanResult results) {
371         mCellInfoList = filterOutSatellitePlmn(results.getCellInfos());
372         Log.d(TAG, "CellInfoList: " + CellInfoUtil.cellInfoListToString(mCellInfoList));
373         updateAllPreferenceCategory();
374         NetworkScanRepository.NetworkScanState state = results.getState();
375         if (state == NetworkScanRepository.NetworkScanState.ERROR) {
376             addMessagePreference(R.string.network_query_error);
377         } else if (mCellInfoList.isEmpty()) {
378             addMessagePreference(R.string.empty_networks_list);
379         }
380         // keep showing progress bar, it will be stopped when error or completed
381         setProgressBarVisible(state == NetworkScanRepository.NetworkScanState.ACTIVE);
382     }
383 
384     @Keep
385     @VisibleForTesting
createNetworkOperatorPreference(CellInfo cellInfo)386     protected NetworkOperatorPreference createNetworkOperatorPreference(CellInfo cellInfo) {
387         if (mForbiddenPlmns == null) {
388             updateForbiddenPlmns();
389         }
390         NetworkOperatorPreference preference =
391                 new NetworkOperatorPreference(getPrefContext(), mForbiddenPlmns, mShow4GForLTE);
392         preference.updateCell(cellInfo);
393         return preference;
394     }
395 
396     /**
397      * Update the content of network operators list.
398      */
updateAllPreferenceCategory()399     private void updateAllPreferenceCategory() {
400         int numberOfPreferences = mPreferenceCategory.getPreferenceCount();
401 
402         // remove unused preferences
403         while (numberOfPreferences > mCellInfoList.size()) {
404             numberOfPreferences--;
405             mPreferenceCategory.removePreference(
406                     mPreferenceCategory.getPreference(numberOfPreferences));
407         }
408 
409         // update the content of preference
410         for (int index = 0; index < mCellInfoList.size(); index++) {
411             final CellInfo cellInfo = mCellInfoList.get(index);
412 
413             NetworkOperatorPreference pref = null;
414             if (index < numberOfPreferences) {
415                 final Preference rawPref = mPreferenceCategory.getPreference(index);
416                 if (rawPref instanceof NetworkOperatorPreference) {
417                     // replace existing preference
418                     pref = (NetworkOperatorPreference) rawPref;
419                     pref.updateCell(cellInfo);
420                 } else {
421                     mPreferenceCategory.removePreference(rawPref);
422                 }
423             }
424             if (pref == null) {
425                 // add new preference
426                 pref = createNetworkOperatorPreference(cellInfo);
427                 pref.setOrder(index);
428                 mPreferenceCategory.addPreference(pref);
429             }
430             pref.setKey(pref.getOperatorName());
431 
432             if (mCellInfoList.get(index).isRegistered()) {
433                 pref.setSummary(R.string.network_connected);
434             } else {
435                 pref.setSummary(null);
436             }
437         }
438     }
439 
440     /**
441      * Config the network operator list when the page was created. When user get
442      * into this page, the device might or might not have data connection.
443      * - If the device has data:
444      * 1. use {@code ServiceState#getNetworkRegistrationInfoList()} to get the currently
445      * registered cellIdentity, wrap it into a CellInfo;
446      * 2. set the signal strength level as strong;
447      * 3. get the title of the previously connected network operator, since the CellIdentity
448      * got from step 1 only has PLMN.
449      * - If the device has no data, we will remove the connected network operators list from the
450      * screen.
451      */
forceUpdateConnectedPreferenceCategory( NetworkSelectRepository.NetworkRegistrationAndForbiddenInfo info)452     private void forceUpdateConnectedPreferenceCategory(
453             NetworkSelectRepository.NetworkRegistrationAndForbiddenInfo info) {
454         mPreferenceCategory.removeAll();
455         for (NetworkRegistrationInfo regInfo : info.getNetworkList()) {
456             final CellIdentity cellIdentity = regInfo.getCellIdentity();
457             if (cellIdentity == null) {
458                 continue;
459             }
460             final NetworkOperatorPreference pref = new NetworkOperatorPreference(
461                     getPrefContext(), info.getForbiddenPlmns(), mShow4GForLTE);
462             pref.updateCell(null, cellIdentity);
463             if (pref.isForbiddenNetwork()) {
464                 continue;
465             }
466             pref.setSummary(R.string.network_connected);
467             // Update the signal strength icon, since the default signalStrength value
468             // would be zero
469             // (it would be quite confusing why the connected network has no signal)
470             pref.setIcon(SignalStrength.NUM_SIGNAL_STRENGTH_BINS - 1);
471             mPreferenceCategory.addPreference(pref);
472             break;
473         }
474     }
475 
476     /**
477      * Clear all of the preference summary
478      */
clearPreferenceSummary()479     private void clearPreferenceSummary() {
480         int idxPreference = mPreferenceCategory.getPreferenceCount();
481         while (idxPreference > 0) {
482             idxPreference--;
483             final Preference networkOperator = mPreferenceCategory.getPreference(idxPreference);
484             networkOperator.setSummary(null);
485         }
486     }
487 
setProgressBarVisible(boolean visible)488     protected void setProgressBarVisible(boolean visible) {
489         if (mProgressHeader != null) {
490             mProgressHeader.setVisibility(visible ? View.VISIBLE : View.GONE);
491         }
492     }
493 
addMessagePreference(int messageId)494     private void addMessagePreference(int messageId) {
495         mStatusMessagePreference.setTitle(messageId);
496         mPreferenceCategory.removeAll();
497         mPreferenceCategory.addPreference(mStatusMessagePreference);
498     }
499 
500     @Override
onDestroy()501     public void onDestroy() {
502         mNetworkScanExecutor.shutdown();
503         super.onDestroy();
504     }
505 }
506