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.Activity;
20 import android.app.settings.SettingsEnums;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.os.Bundle;
24 import android.os.Handler;
25 import android.os.Message;
26 import android.os.PersistableBundle;
27 import android.provider.Settings;
28 import android.telephony.AccessNetworkConstants;
29 import android.telephony.CarrierConfigManager;
30 import android.telephony.CellIdentity;
31 import android.telephony.CellInfo;
32 import android.telephony.NetworkRegistrationInfo;
33 import android.telephony.ServiceState;
34 import android.telephony.SignalStrength;
35 import android.telephony.SubscriptionManager;
36 import android.telephony.TelephonyManager;
37 import android.util.Log;
38 import android.view.View;
39 
40 import androidx.annotation.VisibleForTesting;
41 import androidx.preference.Preference;
42 import androidx.preference.PreferenceCategory;
43 
44 import com.android.internal.telephony.OperatorInfo;
45 import com.android.settings.R;
46 import com.android.settings.dashboard.DashboardFragment;
47 import com.android.settings.overlay.FeatureFactory;
48 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
49 import com.android.settingslib.utils.ThreadUtils;
50 
51 import java.util.ArrayList;
52 import java.util.Arrays;
53 import java.util.List;
54 import java.util.Optional;
55 import java.util.concurrent.ExecutorService;
56 import java.util.concurrent.Executors;
57 
58 /**
59  * "Choose network" settings UI for the Settings app.
60  */
61 public class NetworkSelectSettings extends DashboardFragment {
62 
63     private static final String TAG = "NetworkSelectSettings";
64 
65     private static final int EVENT_SET_NETWORK_SELECTION_MANUALLY_DONE = 1;
66     private static final int EVENT_NETWORK_SCAN_RESULTS = 2;
67     private static final int EVENT_NETWORK_SCAN_ERROR = 3;
68     private static final int EVENT_NETWORK_SCAN_COMPLETED = 4;
69 
70     private static final String PREF_KEY_NETWORK_OPERATORS = "network_operators_preference";
71     private static final int MIN_NUMBER_OF_SCAN_REQUIRED = 2;
72 
73     @VisibleForTesting
74     PreferenceCategory mPreferenceCategory;
75     @VisibleForTesting
76     NetworkOperatorPreference mSelectedPreference;
77     private View mProgressHeader;
78     private Preference mStatusMessagePreference;
79     @VisibleForTesting
80     List<CellInfo> mCellInfoList;
81     private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
82     @VisibleForTesting
83     TelephonyManager mTelephonyManager;
84     private List<String> mForbiddenPlmns;
85     private boolean mShow4GForLTE = false;
86     private NetworkScanHelper mNetworkScanHelper;
87     private final ExecutorService mNetworkScanExecutor = Executors.newFixedThreadPool(1);
88     private MetricsFeatureProvider mMetricsFeatureProvider;
89     private boolean mUseNewApi;
90     private long mRequestIdManualNetworkSelect;
91     private long mRequestIdManualNetworkScan;
92     private long mWaitingForNumberOfScanResults;
93     @VisibleForTesting
94     boolean mIsAggregationEnabled = false;
95 
96     @Override
onCreate(Bundle icicle)97     public void onCreate(Bundle icicle) {
98         super.onCreate(icicle);
99 
100         mUseNewApi = getContext().getResources().getBoolean(
101                 com.android.internal.R.bool.config_enableNewAutoSelectNetworkUI);
102         Intent intent = getActivity().getIntent();
103         if (intent != null) {
104             mSubId = intent.getIntExtra(Settings.EXTRA_SUB_ID,
105                     SubscriptionManager.INVALID_SUBSCRIPTION_ID);
106         }
107 
108         mPreferenceCategory = findPreference(PREF_KEY_NETWORK_OPERATORS);
109         mStatusMessagePreference = new Preference(getContext());
110         mStatusMessagePreference.setSelectable(false);
111         mSelectedPreference = null;
112         mTelephonyManager = getContext().getSystemService(TelephonyManager.class)
113                 .createForSubscriptionId(mSubId);
114         mNetworkScanHelper = new NetworkScanHelper(
115                 mTelephonyManager, mCallback, mNetworkScanExecutor);
116         PersistableBundle bundle = ((CarrierConfigManager) getContext().getSystemService(
117                 Context.CARRIER_CONFIG_SERVICE)).getConfigForSubId(mSubId);
118         if (bundle != null) {
119             mShow4GForLTE = bundle.getBoolean(
120                     CarrierConfigManager.KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL);
121         }
122 
123         mMetricsFeatureProvider = FeatureFactory
124                 .getFactory(getContext()).getMetricsFeatureProvider();
125 
126         mIsAggregationEnabled = getContext().getResources().getBoolean(
127                 R.bool.config_network_selection_list_aggregation_enabled);
128         Log.d(TAG, "init: mUseNewApi:" + mUseNewApi
129                 + " ,mIsAggregationEnabled:" + mIsAggregationEnabled + " ,mSubId:" + mSubId);
130     }
131 
132     @Override
onViewCreated(View view, Bundle savedInstanceState)133     public void onViewCreated(View view, Bundle savedInstanceState) {
134         super.onViewCreated(view, savedInstanceState);
135         final Activity activity = getActivity();
136         if (activity != null) {
137             mProgressHeader = setPinnedHeaderView(R.layout.progress_header)
138                     .findViewById(R.id.progress_bar_animation);
139             setProgressBarVisible(false);
140         }
141         forceUpdateConnectedPreferenceCategory();
142     }
143 
144     @Override
onStart()145     public void onStart() {
146         super.onStart();
147 
148         updateForbiddenPlmns();
149         if (isProgressBarVisible()) {
150             return;
151         }
152         if (mWaitingForNumberOfScanResults <= 0) {
153             startNetworkQuery();
154         }
155     }
156 
157     /**
158      * Update forbidden PLMNs from the USIM App
159      */
160     @VisibleForTesting
updateForbiddenPlmns()161     void updateForbiddenPlmns() {
162         final String[] forbiddenPlmns = mTelephonyManager.getForbiddenPlmns();
163         mForbiddenPlmns = forbiddenPlmns != null
164                 ? Arrays.asList(forbiddenPlmns)
165                 : new ArrayList<>();
166     }
167 
168     @Override
onStop()169     public void onStop() {
170         super.onStop();
171         if (mWaitingForNumberOfScanResults <= 0) {
172             stopNetworkQuery();
173         }
174     }
175 
176     @Override
onPreferenceTreeClick(Preference preference)177     public boolean onPreferenceTreeClick(Preference preference) {
178         if (preference != mSelectedPreference) {
179             stopNetworkQuery();
180 
181             // Refresh the last selected item in case users reselect network.
182             clearPreferenceSummary();
183             if (mSelectedPreference != null) {
184                 // Set summary as "Disconnected" to the previously connected network
185                 mSelectedPreference.setSummary(R.string.network_disconnected);
186             }
187 
188             mSelectedPreference = (NetworkOperatorPreference) preference;
189             mSelectedPreference.setSummary(R.string.network_connecting);
190 
191             mMetricsFeatureProvider.action(getContext(),
192                     SettingsEnums.ACTION_MOBILE_NETWORK_MANUAL_SELECT_NETWORK);
193 
194             setProgressBarVisible(true);
195             // Disable the screen until network is manually set
196             getPreferenceScreen().setEnabled(false);
197 
198             mRequestIdManualNetworkSelect = getNewRequestId();
199             mWaitingForNumberOfScanResults = MIN_NUMBER_OF_SCAN_REQUIRED;
200             final OperatorInfo operator = mSelectedPreference.getOperatorInfo();
201             ThreadUtils.postOnBackgroundThread(() -> {
202                 final Message msg = mHandler.obtainMessage(
203                         EVENT_SET_NETWORK_SELECTION_MANUALLY_DONE);
204                 msg.obj = mTelephonyManager.setNetworkSelectionModeManual(
205                         operator, true /* persistSelection */);
206                 msg.sendToTarget();
207             });
208         }
209 
210         return true;
211     }
212 
213     @Override
getPreferenceScreenResId()214     protected int getPreferenceScreenResId() {
215         return R.xml.choose_network;
216     }
217 
218     @Override
getLogTag()219     protected String getLogTag() {
220         return TAG;
221     }
222 
223     @Override
getMetricsCategory()224     public int getMetricsCategory() {
225         return SettingsEnums.MOBILE_NETWORK_SELECT;
226     }
227 
228     private final Handler mHandler = new Handler() {
229         @Override
230         public void handleMessage(Message msg) {
231             switch (msg.what) {
232                 case EVENT_SET_NETWORK_SELECTION_MANUALLY_DONE:
233                     final boolean isSucceed = (boolean) msg.obj;
234                     stopNetworkQuery();
235                     setProgressBarVisible(false);
236                     getPreferenceScreen().setEnabled(true);
237 
238                     if (mSelectedPreference != null) {
239                         mSelectedPreference.setSummary(isSucceed
240                                 ? R.string.network_connected
241                                 : R.string.network_could_not_connect);
242                     } else {
243                         Log.e(TAG, "No preference to update!");
244                     }
245                     break;
246                 case EVENT_NETWORK_SCAN_RESULTS:
247                     final List<CellInfo> results = (List<CellInfo>) msg.obj;
248                     if (mRequestIdManualNetworkScan < mRequestIdManualNetworkSelect) {
249                         Log.d(TAG, "CellInfoList (drop): "
250                                 + CellInfoUtil.cellInfoListToString(new ArrayList<>(results)));
251                         break;
252                     }
253                     mWaitingForNumberOfScanResults--;
254                     if ((mWaitingForNumberOfScanResults <= 0) && (!isResumed())) {
255                         stopNetworkQuery();
256                     }
257 
258                     mCellInfoList = doAggregation(results);
259                     Log.d(TAG, "CellInfoList: " + CellInfoUtil.cellInfoListToString(mCellInfoList));
260                     if (mCellInfoList != null && mCellInfoList.size() != 0) {
261                         final NetworkOperatorPreference connectedPref =
262                                 updateAllPreferenceCategory();
263                         if (connectedPref != null) {
264                             // update selected preference instance into connected preference
265                             if (mSelectedPreference != null) {
266                                 mSelectedPreference = connectedPref;
267                             }
268                         } else if (!getPreferenceScreen().isEnabled()) {
269                             if (connectedPref == null) {
270                                 mSelectedPreference.setSummary(R.string.network_connecting);
271                             }
272                         }
273                         getPreferenceScreen().setEnabled(true);
274                     } else if (getPreferenceScreen().isEnabled()) {
275                         addMessagePreference(R.string.empty_networks_list);
276                         // keep showing progress bar, it will be stopped when error or completed
277                         setProgressBarVisible(true);
278                     }
279                     break;
280 
281                 case EVENT_NETWORK_SCAN_ERROR:
282                     stopNetworkQuery();
283                     Log.i(TAG, "Network scan failure " + msg.arg1 + ":"
284                             + " scan request 0x" + Long.toHexString(mRequestIdManualNetworkScan)
285                             + ", waiting for scan results = " + mWaitingForNumberOfScanResults
286                             + ", select request 0x"
287                             + Long.toHexString(mRequestIdManualNetworkSelect));
288                     if (mRequestIdManualNetworkScan < mRequestIdManualNetworkSelect) {
289                         break;
290                     }
291                     if (!getPreferenceScreen().isEnabled()) {
292                         clearPreferenceSummary();
293                         getPreferenceScreen().setEnabled(true);
294                     } else {
295                         addMessagePreference(R.string.network_query_error);
296                     }
297                     break;
298 
299                 case EVENT_NETWORK_SCAN_COMPLETED:
300                     stopNetworkQuery();
301                     Log.d(TAG, "Network scan complete:"
302                             + " scan request 0x" + Long.toHexString(mRequestIdManualNetworkScan)
303                             + ", waiting for scan results = " + mWaitingForNumberOfScanResults
304                             + ", select request 0x"
305                             + Long.toHexString(mRequestIdManualNetworkSelect));
306                     if (mRequestIdManualNetworkScan < mRequestIdManualNetworkSelect) {
307                         break;
308                     }
309                     if (!getPreferenceScreen().isEnabled()) {
310                         clearPreferenceSummary();
311                         getPreferenceScreen().setEnabled(true);
312                     } else if (mCellInfoList == null) {
313                         // In case the scan timeout before getting any results
314                         addMessagePreference(R.string.empty_networks_list);
315                     }
316                     break;
317             }
318             return;
319         }
320     };
321 
322     @VisibleForTesting
doAggregation(List<CellInfo> cellInfoListInput)323     List<CellInfo> doAggregation(List<CellInfo> cellInfoListInput) {
324         if (!mIsAggregationEnabled) {
325             Log.d(TAG, "no aggregation");
326             return new ArrayList<>(cellInfoListInput);
327         }
328         ArrayList<CellInfo> aggregatedList = new ArrayList<>();
329         for (CellInfo cellInfo : cellInfoListInput) {
330             String plmn = CellInfoUtil.getNetworkTitle(cellInfo.getCellIdentity(),
331                     CellInfoUtil.getCellIdentityMccMnc(cellInfo.getCellIdentity()));
332             Class className = cellInfo.getClass();
333 
334             Optional<CellInfo> itemInTheList = aggregatedList.stream().filter(
335                     item -> {
336                         String itemPlmn = CellInfoUtil.getNetworkTitle(item.getCellIdentity(),
337                                 CellInfoUtil.getCellIdentityMccMnc(item.getCellIdentity()));
338                         return itemPlmn.equals(plmn) && item.getClass().equals(className);
339                     })
340                     .findFirst();
341             if (itemInTheList.isPresent()) {
342                 if (cellInfo.isRegistered() && !itemInTheList.get().isRegistered()) {
343                     // Adding the registered cellinfo item into list. If there are two registered
344                     // cellinfo items, then select first one from source list.
345                     aggregatedList.set(aggregatedList.indexOf(itemInTheList.get()), cellInfo);
346                 }
347                 continue;
348             }
349             aggregatedList.add(cellInfo);
350         }
351         return aggregatedList;
352     }
353 
354     private final NetworkScanHelper.NetworkScanCallback mCallback =
355             new NetworkScanHelper.NetworkScanCallback() {
356                 public void onResults(List<CellInfo> results) {
357                     final Message msg = mHandler.obtainMessage(EVENT_NETWORK_SCAN_RESULTS, results);
358                     msg.sendToTarget();
359                 }
360 
361                 public void onComplete() {
362                     final Message msg = mHandler.obtainMessage(EVENT_NETWORK_SCAN_COMPLETED);
363                     msg.sendToTarget();
364                 }
365 
366                 public void onError(int error) {
367                     final Message msg = mHandler.obtainMessage(EVENT_NETWORK_SCAN_ERROR, error,
368                             0 /* arg2 */);
369                     msg.sendToTarget();
370                 }
371             };
372 
373     /**
374      * Update the content of network operators list.
375      *
376      * @return preference which shows connected
377      */
378     @VisibleForTesting
updateAllPreferenceCategory()379     NetworkOperatorPreference updateAllPreferenceCategory() {
380         int numberOfPreferences = mPreferenceCategory.getPreferenceCount();
381 
382         // remove unused preferences
383         while (numberOfPreferences > mCellInfoList.size()) {
384             numberOfPreferences--;
385             mPreferenceCategory.removePreference(
386                     mPreferenceCategory.getPreference(numberOfPreferences));
387         }
388 
389         // update the content of preference
390         NetworkOperatorPreference connectedPref = null;
391         for (int index = 0; index < mCellInfoList.size(); index++) {
392             final CellInfo cellInfo = mCellInfoList.get(index);
393 
394             NetworkOperatorPreference pref = null;
395             if (index < numberOfPreferences) {
396                 final Preference rawPref = mPreferenceCategory.getPreference(index);
397                 if (rawPref instanceof NetworkOperatorPreference) {
398                     // replace existing preference
399                     pref = (NetworkOperatorPreference) rawPref;
400                     pref.updateCell(cellInfo);
401                 } else {
402                     mPreferenceCategory.removePreference(rawPref);
403                 }
404             }
405             if (pref == null) {
406                 // add new preference
407                 pref = new NetworkOperatorPreference(getPrefContext(),
408                         cellInfo, mForbiddenPlmns, mShow4GForLTE);
409                 pref.setOrder(index);
410                 mPreferenceCategory.addPreference(pref);
411             }
412             pref.setKey(pref.getOperatorName());
413 
414             if (mCellInfoList.get(index).isRegistered()) {
415                 pref.setSummary(R.string.network_connected);
416                 connectedPref = pref;
417             } else {
418                 pref.setSummary(null);
419             }
420         }
421 
422         // update selected preference instance by index
423         for (int index = 0; index < mCellInfoList.size(); index++) {
424             final CellInfo cellInfo = mCellInfoList.get(index);
425 
426             if ((mSelectedPreference != null) && mSelectedPreference.isSameCell(cellInfo)) {
427                 mSelectedPreference = (NetworkOperatorPreference)
428                         (mPreferenceCategory.getPreference(index));
429             }
430         }
431 
432         return connectedPref;
433     }
434 
435     /**
436      * Config the network operator list when the page was created. When user get
437      * into this page, the device might or might not have data connection.
438      * - If the device has data:
439      * 1. use {@code ServiceState#getNetworkRegistrationInfoList()} to get the currently
440      * registered cellIdentity, wrap it into a CellInfo;
441      * 2. set the signal strength level as strong;
442      * 3. get the title of the previously connected network operator, since the CellIdentity
443      * got from step 1 only has PLMN.
444      * - If the device has no data, we will remove the connected network operators list from the
445      * screen.
446      */
forceUpdateConnectedPreferenceCategory()447     private void forceUpdateConnectedPreferenceCategory() {
448         if (mTelephonyManager.getDataState() == mTelephonyManager.DATA_CONNECTED) {
449             // Try to get the network registration states
450             final ServiceState ss = mTelephonyManager.getServiceState();
451             if (ss == null) {
452                 return;
453             }
454             final List<NetworkRegistrationInfo> networkList =
455                     ss.getNetworkRegistrationInfoListForTransportType(
456                             AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
457             if (networkList == null || networkList.size() == 0) {
458                 return;
459             }
460             // Due to the aggregation of cell between carriers, it's possible to get CellIdentity
461             // containing forbidden PLMN.
462             // Getting current network from ServiceState is no longer a good idea.
463             // Add an additional rule to avoid from showing forbidden PLMN to the user.
464             if (mForbiddenPlmns == null) {
465                 updateForbiddenPlmns();
466             }
467             for (NetworkRegistrationInfo regInfo : networkList) {
468                 final CellIdentity cellIdentity = regInfo.getCellIdentity();
469                 if (cellIdentity == null) {
470                     continue;
471                 }
472                 final NetworkOperatorPreference pref = new NetworkOperatorPreference(
473                         getPrefContext(), cellIdentity, mForbiddenPlmns, mShow4GForLTE);
474                 if (pref.isForbiddenNetwork()) {
475                     continue;
476                 }
477                 pref.setSummary(R.string.network_connected);
478                 // Update the signal strength icon, since the default signalStrength value
479                 // would be zero
480                 // (it would be quite confusing why the connected network has no signal)
481                 pref.setIcon(SignalStrength.NUM_SIGNAL_STRENGTH_BINS - 1);
482                 mPreferenceCategory.addPreference(pref);
483                 break;
484             }
485         }
486     }
487 
488     /**
489      * Clear all of the preference summary
490      */
clearPreferenceSummary()491     private void clearPreferenceSummary() {
492         int idxPreference = mPreferenceCategory.getPreferenceCount();
493         while (idxPreference > 0) {
494             idxPreference--;
495             final NetworkOperatorPreference networkOperator = (NetworkOperatorPreference)
496                     (mPreferenceCategory.getPreference(idxPreference));
497             networkOperator.setSummary(null);
498         }
499     }
500 
getNewRequestId()501     private long getNewRequestId() {
502         return Math.max(mRequestIdManualNetworkSelect,
503                 mRequestIdManualNetworkScan) + 1;
504     }
505 
isProgressBarVisible()506     private boolean isProgressBarVisible() {
507         if (mProgressHeader == null) {
508             return false;
509         }
510         return (mProgressHeader.getVisibility() == View.VISIBLE);
511     }
512 
setProgressBarVisible(boolean visible)513     protected void setProgressBarVisible(boolean visible) {
514         if (mProgressHeader != null) {
515             mProgressHeader.setVisibility(visible ? View.VISIBLE : View.GONE);
516         }
517     }
518 
addMessagePreference(int messageId)519     private void addMessagePreference(int messageId) {
520         setProgressBarVisible(false);
521         mStatusMessagePreference.setTitle(messageId);
522         mPreferenceCategory.removeAll();
523         mPreferenceCategory.addPreference(mStatusMessagePreference);
524     }
525 
startNetworkQuery()526     private void startNetworkQuery() {
527         setProgressBarVisible(true);
528         if (mNetworkScanHelper != null) {
529             mRequestIdManualNetworkScan = getNewRequestId();
530             mWaitingForNumberOfScanResults = MIN_NUMBER_OF_SCAN_REQUIRED;
531             mNetworkScanHelper.startNetworkScan(
532                     mUseNewApi
533                             ? NetworkScanHelper.NETWORK_SCAN_TYPE_INCREMENTAL_RESULTS
534                             : NetworkScanHelper.NETWORK_SCAN_TYPE_WAIT_FOR_ALL_RESULTS);
535         }
536     }
537 
stopNetworkQuery()538     private void stopNetworkQuery() {
539         setProgressBarVisible(false);
540         if (mNetworkScanHelper != null) {
541             mWaitingForNumberOfScanResults = 0;
542             mNetworkScanHelper.stopNetworkQuery();
543         }
544     }
545 
546     @Override
onDestroy()547     public void onDestroy() {
548         stopNetworkQuery();
549         mNetworkScanExecutor.shutdown();
550         super.onDestroy();
551     }
552 }
553