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