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;
18 
19 import static androidx.lifecycle.Lifecycle.Event.ON_PAUSE;
20 import static androidx.lifecycle.Lifecycle.Event.ON_RESUME;
21 
22 import static com.android.settings.network.telephony.MobileNetworkUtils.NO_CELL_DATA_TYPE_ICON;
23 
24 import android.content.Context;
25 import android.content.Intent;
26 import android.graphics.drawable.Drawable;
27 import android.net.ConnectivityManager;
28 import android.net.Network;
29 import android.net.NetworkCapabilities;
30 import android.provider.Settings;
31 import android.telephony.SignalStrength;
32 import android.telephony.SubscriptionInfo;
33 import android.telephony.SubscriptionManager;
34 import android.telephony.TelephonyManager;
35 import android.util.ArraySet;
36 
37 import androidx.annotation.VisibleForTesting;
38 import androidx.collection.ArrayMap;
39 import androidx.lifecycle.Lifecycle;
40 import androidx.lifecycle.LifecycleObserver;
41 import androidx.lifecycle.OnLifecycleEvent;
42 import androidx.preference.Preference;
43 import androidx.preference.PreferenceGroup;
44 import androidx.preference.PreferenceScreen;
45 
46 import com.android.settings.R;
47 import com.android.settings.network.telephony.DataConnectivityListener;
48 import com.android.settings.network.telephony.MobileNetworkActivity;
49 import com.android.settings.network.telephony.MobileNetworkUtils;
50 import com.android.settings.network.telephony.SignalStrengthListener;
51 import com.android.settingslib.core.AbstractPreferenceController;
52 import com.android.settingslib.net.SignalStrengthUtil;
53 
54 import java.util.Collections;
55 import java.util.Map;
56 import java.util.Set;
57 
58 /**
59  * This manages a set of Preferences it places into a PreferenceGroup owned by some parent
60  * controller class - one for each available subscription. This controller is only considered
61  * available if there are 2 or more subscriptions.
62  */
63 public class SubscriptionsPreferenceController extends AbstractPreferenceController implements
64         LifecycleObserver, SubscriptionsChangeListener.SubscriptionsChangeListenerClient,
65         MobileDataEnabledListener.Client, DataConnectivityListener.Client,
66         SignalStrengthListener.Callback {
67     private static final String TAG = "SubscriptionsPrefCntrlr";
68 
69     private UpdateListener mUpdateListener;
70     private String mPreferenceGroupKey;
71     private PreferenceGroup mPreferenceGroup;
72     private SubscriptionManager mManager;
73     private ConnectivityManager mConnectivityManager;
74     private SubscriptionsChangeListener mSubscriptionsListener;
75     private MobileDataEnabledListener mDataEnabledListener;
76     private DataConnectivityListener mConnectivityListener;
77     private SignalStrengthListener mSignalStrengthListener;
78 
79 
80     // Map of subscription id to Preference
81     private Map<Integer, Preference> mSubscriptionPreferences;
82     private int mStartOrder;
83 
84     /**
85      * This interface lets a parent of this class know that some change happened - this could
86      * either be because overall availability changed, or because we've added/removed/updated some
87      * preferences.
88      */
89     public interface UpdateListener {
onChildrenUpdated()90         void onChildrenUpdated();
91     }
92 
93     /**
94      * @param context            the context for the UI where we're placing these preferences
95      * @param lifecycle          for listening to lifecycle events for the UI
96      * @param updateListener     called to let our parent controller know that our availability has
97      *                           changed, or that one or more of the preferences we've placed in the
98      *                           PreferenceGroup has changed
99      * @param preferenceGroupKey the key used to lookup the PreferenceGroup where Preferences will
100      *                           be placed
101      * @param startOrder         the order that should be given to the first Preference placed into
102      *                           the PreferenceGroup; the second will use startOrder+1, third will
103      *                           use startOrder+2, etc. - this is useful for when the parent wants
104      *                           to have other preferences in the same PreferenceGroup and wants
105      *                           a specific ordering relative to this controller's prefs.
106      */
SubscriptionsPreferenceController(Context context, Lifecycle lifecycle, UpdateListener updateListener, String preferenceGroupKey, int startOrder)107     public SubscriptionsPreferenceController(Context context, Lifecycle lifecycle,
108             UpdateListener updateListener, String preferenceGroupKey, int startOrder) {
109         super(context);
110         mUpdateListener = updateListener;
111         mPreferenceGroupKey = preferenceGroupKey;
112         mStartOrder = startOrder;
113         mManager = context.getSystemService(SubscriptionManager.class);
114         mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
115         mSubscriptionPreferences = new ArrayMap<>();
116         mSubscriptionsListener = new SubscriptionsChangeListener(context, this);
117         mDataEnabledListener = new MobileDataEnabledListener(context, this);
118         mConnectivityListener = new DataConnectivityListener(context, this);
119         mSignalStrengthListener = new SignalStrengthListener(context, this);
120         lifecycle.addObserver(this);
121     }
122 
123     @OnLifecycleEvent(ON_RESUME)
onResume()124     public void onResume() {
125         mSubscriptionsListener.start();
126         mDataEnabledListener.start(SubscriptionManager.getDefaultDataSubscriptionId());
127         mConnectivityListener.start();
128         mSignalStrengthListener.resume();
129         update();
130     }
131 
132     @OnLifecycleEvent(ON_PAUSE)
onPause()133     public void onPause() {
134         mSubscriptionsListener.stop();
135         mDataEnabledListener.stop();
136         mConnectivityListener.stop();
137         mSignalStrengthListener.pause();
138     }
139 
140     @Override
displayPreference(PreferenceScreen screen)141     public void displayPreference(PreferenceScreen screen) {
142         mPreferenceGroup = screen.findPreference(mPreferenceGroupKey);
143         update();
144     }
145 
update()146     private void update() {
147         if (mPreferenceGroup == null) {
148             return;
149         }
150 
151         if (!isAvailable()) {
152             for (Preference pref : mSubscriptionPreferences.values()) {
153                 mPreferenceGroup.removePreference(pref);
154             }
155             mSubscriptionPreferences.clear();
156             mSignalStrengthListener.updateSubscriptionIds(Collections.emptySet());
157             mUpdateListener.onChildrenUpdated();
158             return;
159         }
160 
161         final Map<Integer, Preference> existingPrefs = mSubscriptionPreferences;
162         mSubscriptionPreferences = new ArrayMap<>();
163 
164         int order = mStartOrder;
165         final Set<Integer> activeSubIds = new ArraySet<>();
166         final int dataDefaultSubId = SubscriptionManager.getDefaultDataSubscriptionId();
167         for (SubscriptionInfo info : SubscriptionUtil.getActiveSubscriptions(mManager)) {
168             final int subId = info.getSubscriptionId();
169             activeSubIds.add(subId);
170             Preference pref = existingPrefs.remove(subId);
171             if (pref == null) {
172                 pref = new Preference(mPreferenceGroup.getContext());
173                 mPreferenceGroup.addPreference(pref);
174             }
175             pref.setTitle(info.getDisplayName());
176             final boolean isDefaultForData = (subId == dataDefaultSubId);
177             pref.setSummary(getSummary(subId, isDefaultForData));
178             setIcon(pref, subId, isDefaultForData);
179             pref.setOrder(order++);
180 
181             pref.setOnPreferenceClickListener(clickedPref -> {
182                 final Intent intent = new Intent(mContext, MobileNetworkActivity.class);
183                 intent.putExtra(Settings.EXTRA_SUB_ID, subId);
184                 mContext.startActivity(intent);
185                 return true;
186             });
187 
188             mSubscriptionPreferences.put(subId, pref);
189         }
190         mSignalStrengthListener.updateSubscriptionIds(activeSubIds);
191 
192         // Remove any old preferences that no longer map to a subscription.
193         for (Preference pref : existingPrefs.values()) {
194             mPreferenceGroup.removePreference(pref);
195         }
196         mUpdateListener.onChildrenUpdated();
197     }
198 
199     @VisibleForTesting
shouldInflateSignalStrength(int subId)200     boolean shouldInflateSignalStrength(int subId) {
201         return SignalStrengthUtil.shouldInflateSignalStrength(mContext, subId);
202     }
203 
204     @VisibleForTesting
setIcon(Preference pref, int subId, boolean isDefaultForData)205     void setIcon(Preference pref, int subId, boolean isDefaultForData) {
206         final TelephonyManager mgr = mContext.getSystemService(
207                 TelephonyManager.class).createForSubscriptionId(subId);
208         final SignalStrength strength = mgr.getSignalStrength();
209         int level = (strength == null) ? 0 : strength.getLevel();
210         int numLevels = SignalStrength.NUM_SIGNAL_STRENGTH_BINS;
211         if (shouldInflateSignalStrength(subId)) {
212             level += 1;
213             numLevels += 1;
214         }
215         final boolean showCutOut = !isDefaultForData || !mgr.isDataEnabled();
216         pref.setIcon(getIcon(level, numLevels, showCutOut));
217     }
218 
219     @VisibleForTesting
getIcon(int level, int numLevels, boolean cutOut)220     Drawable getIcon(int level, int numLevels, boolean cutOut) {
221         return MobileNetworkUtils.getSignalStrengthIcon(mContext, level, numLevels,
222                 NO_CELL_DATA_TYPE_ICON, cutOut);
223     }
224 
activeNetworkIsCellular()225     private boolean activeNetworkIsCellular() {
226         final Network activeNetwork = mConnectivityManager.getActiveNetwork();
227         if (activeNetwork == null) {
228             return false;
229         }
230         final NetworkCapabilities networkCapabilities = mConnectivityManager.getNetworkCapabilities(
231                 activeNetwork);
232         if (networkCapabilities == null) {
233             return false;
234         }
235         return networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR);
236     }
237 
238     /**
239      * The summary can have either 1 or 2 lines depending on which services (calls, SMS, data) this
240      * subscription is the default for.
241      *
242      * If this subscription is the default for calls and/or SMS, we add a line to show that.
243      *
244      * If this subscription is the default for data, we add a line with detail about
245      * whether the data connection is active.
246      *
247      * If a subscription isn't the default for anything, we just say it is available.
248      */
getSummary(int subId, boolean isDefaultForData)249     protected String getSummary(int subId, boolean isDefaultForData) {
250         final int callsDefaultSubId = SubscriptionManager.getDefaultVoiceSubscriptionId();
251         final int smsDefaultSubId = SubscriptionManager.getDefaultSmsSubscriptionId();
252 
253         String line1 = null;
254         if (subId == callsDefaultSubId && subId == smsDefaultSubId) {
255             line1 = mContext.getString(R.string.default_for_calls_and_sms);
256         } else if (subId == callsDefaultSubId) {
257             line1 = mContext.getString(R.string.default_for_calls);
258         } else if (subId == smsDefaultSubId) {
259             line1 = mContext.getString(R.string.default_for_sms);
260         }
261 
262         String line2 = null;
263         if (isDefaultForData) {
264             final TelephonyManager telMgrForSub = mContext.getSystemService(
265                     TelephonyManager.class).createForSubscriptionId(subId);
266             final boolean dataEnabled = telMgrForSub.isDataEnabled();
267             if (dataEnabled && activeNetworkIsCellular()) {
268                 line2 = mContext.getString(R.string.mobile_data_active);
269             } else if (!dataEnabled) {
270                 line2 = mContext.getString(R.string.mobile_data_off);
271             } else {
272                 line2 = mContext.getString(R.string.default_for_mobile_data);
273             }
274         }
275 
276         if (line1 != null && line2 != null) {
277             return String.join(System.lineSeparator(), line1, line2);
278         } else if (line1 != null) {
279             return line1;
280         } else if (line2 != null) {
281             return line2;
282         } else {
283             return mContext.getString(R.string.subscription_available);
284         }
285     }
286 
287     /**
288      * @return true if there are at least 2 available subscriptions.
289      */
290     @Override
isAvailable()291     public boolean isAvailable() {
292         if (mSubscriptionsListener.isAirplaneModeOn()) {
293             return false;
294         }
295         return SubscriptionUtil.getActiveSubscriptions(mManager).size() >= 2;
296     }
297 
298     @Override
getPreferenceKey()299     public String getPreferenceKey() {
300         return null;
301     }
302 
303     @Override
onAirplaneModeChanged(boolean airplaneModeEnabled)304     public void onAirplaneModeChanged(boolean airplaneModeEnabled) {
305         update();
306     }
307 
308     @Override
onSubscriptionsChanged()309     public void onSubscriptionsChanged() {
310         // See if we need to change which sub id we're using to listen for enabled/disabled changes.
311         int defaultDataSubId = SubscriptionManager.getDefaultDataSubscriptionId();
312         if (defaultDataSubId != mDataEnabledListener.getSubId()) {
313             mDataEnabledListener.stop();
314             mDataEnabledListener.start(defaultDataSubId);
315         }
316         update();
317     }
318 
319     @Override
onMobileDataEnabledChange()320     public void onMobileDataEnabledChange() {
321         update();
322     }
323 
324     @Override
onDataConnectivityChange()325     public void onDataConnectivityChange() {
326         update();
327     }
328 
329     @Override
onSignalStrengthChanged()330     public void onSignalStrengthChanged() {
331         update();
332     }
333 }
334