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