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.ActionBar; 20 import android.content.BroadcastReceiver; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.IntentFilter; 24 import android.os.Bundle; 25 import android.provider.Settings; 26 import android.telephony.CarrierConfigManager; 27 import android.telephony.SubscriptionInfo; 28 import android.telephony.SubscriptionManager; 29 import android.telephony.ims.ImsRcsManager; 30 import android.text.TextUtils; 31 import android.view.Menu; 32 import android.view.View; 33 34 import androidx.annotation.NonNull; 35 import androidx.annotation.VisibleForTesting; 36 import androidx.fragment.app.Fragment; 37 import androidx.fragment.app.FragmentManager; 38 import androidx.fragment.app.FragmentTransaction; 39 40 import com.android.internal.telephony.TelephonyIntents; 41 import com.android.internal.util.CollectionUtils; 42 import com.android.settings.R; 43 import com.android.settings.core.FeatureFlags; 44 import com.android.settings.core.SettingsBaseActivity; 45 import com.android.settings.development.featureflags.FeatureFlagPersistent; 46 import com.android.settings.network.SubscriptionUtil; 47 48 import com.google.android.material.bottomnavigation.BottomNavigationView; 49 50 import java.util.ArrayList; 51 import java.util.List; 52 import java.util.Objects; 53 54 public class MobileNetworkActivity extends SettingsBaseActivity { 55 56 private static final String TAG = "MobileNetworkActivity"; 57 @VisibleForTesting 58 static final String MOBILE_SETTINGS_TAG = "mobile_settings:"; 59 @VisibleForTesting 60 static final int SUB_ID_NULL = Integer.MIN_VALUE; 61 62 @VisibleForTesting 63 SubscriptionManager mSubscriptionManager; 64 @VisibleForTesting 65 int mCurSubscriptionId; 66 @VisibleForTesting 67 List<SubscriptionInfo> mSubscriptionInfos = new ArrayList<>(); 68 private PhoneChangeReceiver mPhoneChangeReceiver; 69 70 private final SubscriptionManager.OnSubscriptionsChangedListener 71 mOnSubscriptionsChangeListener 72 = new SubscriptionManager.OnSubscriptionsChangedListener() { 73 @Override 74 public void onSubscriptionsChanged() { 75 if (!Objects.equals(mSubscriptionInfos, 76 mSubscriptionManager.getActiveSubscriptionInfoList(true))) { 77 int oldSubIndex = mCurSubscriptionId; 78 updateSubscriptions(null); 79 // Remove the dialog if the subscription associated with this activity changes. 80 if (mCurSubscriptionId != oldSubIndex) { 81 removeContactDiscoveryDialog(oldSubIndex); 82 } 83 } 84 } 85 }; 86 87 @Override onNewIntent(Intent intent)88 protected void onNewIntent(Intent intent) { 89 super.onNewIntent(intent); 90 validate(intent); 91 setIntent(intent); 92 93 int updateSubscriptionIndex = SUB_ID_NULL; 94 if (intent != null) { 95 updateSubscriptionIndex = intent.getIntExtra(Settings.EXTRA_SUB_ID, SUB_ID_NULL); 96 } 97 int oldSubId = mCurSubscriptionId; 98 updateSubscriptions(null); 99 100 // If the subscription has changed or the new intent doesnt contain the opt in action, 101 // remove the old discovery dialog. If the activity is being recreated, we will see 102 // onCreate -> onNewIntent, so the dialog will first be recreated for the old subscription 103 // and then removed. 104 if (updateSubscriptionIndex != oldSubId || !doesIntentContainOptInAction(intent)) { 105 removeContactDiscoveryDialog(oldSubId); 106 } 107 // evaluate showing the new discovery dialog if this intent contains an action to show the 108 // opt-in. 109 if (doesIntentContainOptInAction(intent)) { 110 maybeShowContactDiscoveryDialog(updateSubscriptionIndex); 111 } 112 } 113 114 @Override onCreate(Bundle savedInstanceState)115 protected void onCreate(Bundle savedInstanceState) { 116 super.onCreate(savedInstanceState); 117 if (FeatureFlagPersistent.isEnabled(this, FeatureFlags.NETWORK_INTERNET_V2)) { 118 setContentView(R.layout.mobile_network_settings_container_v2); 119 } else { 120 setContentView(R.layout.mobile_network_settings_container); 121 } 122 setActionBar(findViewById(R.id.mobile_action_bar)); 123 mPhoneChangeReceiver = new PhoneChangeReceiver(this, new PhoneChangeReceiver.Client() { 124 @Override 125 public void onPhoneChange() { 126 // When the radio or carrier config changes (ex: CDMA->GSM), refresh the fragment. 127 switchFragment(new MobileNetworkSettings(), mCurSubscriptionId, 128 true /* forceUpdate */); 129 } 130 131 @Override 132 public int getSubscriptionId() { 133 return mCurSubscriptionId; 134 } 135 }); 136 mSubscriptionManager = getSystemService(SubscriptionManager.class); 137 mSubscriptionInfos = mSubscriptionManager.getActiveSubscriptionInfoList(true); 138 final Intent startIntent = getIntent(); 139 validate(startIntent); 140 mCurSubscriptionId = savedInstanceState != null 141 ? savedInstanceState.getInt(Settings.EXTRA_SUB_ID, SUB_ID_NULL) 142 : SUB_ID_NULL; 143 144 final ActionBar actionBar = getActionBar(); 145 if (actionBar != null) { 146 actionBar.setDisplayHomeAsUpEnabled(true); 147 } 148 updateSubscriptions(savedInstanceState); 149 maybeShowContactDiscoveryDialog(mCurSubscriptionId); 150 } 151 152 @Override onStart()153 protected void onStart() { 154 super.onStart(); 155 mPhoneChangeReceiver.register(); 156 mSubscriptionManager.addOnSubscriptionsChangedListener(mOnSubscriptionsChangeListener); 157 } 158 159 @Override onStop()160 protected void onStop() { 161 super.onStop(); 162 mPhoneChangeReceiver.unregister(); 163 mSubscriptionManager.removeOnSubscriptionsChangedListener(mOnSubscriptionsChangeListener); 164 } 165 166 @Override onSaveInstanceState(@onNull Bundle outState)167 protected void onSaveInstanceState(@NonNull Bundle outState) { 168 super.onSaveInstanceState(outState); 169 saveInstanceState(outState); 170 } 171 172 @VisibleForTesting saveInstanceState(@onNull Bundle outState)173 void saveInstanceState(@NonNull Bundle outState) { 174 outState.putInt(Settings.EXTRA_SUB_ID, mCurSubscriptionId); 175 } 176 177 @VisibleForTesting updateSubscriptions(Bundle savedInstanceState)178 void updateSubscriptions(Bundle savedInstanceState) { 179 // Set the title to the name of the subscription. If we don't have subscription info, the 180 // title will just default to the label for this activity that's already specified in 181 // AndroidManifest.xml. 182 final SubscriptionInfo subscription = getSubscription(); 183 if (subscription != null) { 184 setTitle(subscription.getDisplayName()); 185 } 186 187 mSubscriptionInfos = mSubscriptionManager.getActiveSubscriptionInfoList(true); 188 189 if (!FeatureFlagPersistent.isEnabled(this, FeatureFlags.NETWORK_INTERNET_V2)) { 190 updateBottomNavigationView(); 191 } 192 193 if (savedInstanceState == null) { 194 switchFragment(new MobileNetworkSettings(), getSubscriptionId()); 195 } 196 } 197 198 /** 199 * Get the current subscription to display. First check whether intent has {@link 200 * Settings#EXTRA_SUB_ID} and if so find the subscription with that id. If not, just return the 201 * first one in the mSubscriptionInfos list since it is already sorted by sim slot. 202 */ 203 @VisibleForTesting getSubscription()204 SubscriptionInfo getSubscription() { 205 final Intent intent = getIntent(); 206 if (intent != null) { 207 final int subId = intent.getIntExtra(Settings.EXTRA_SUB_ID, SUB_ID_NULL); 208 SubscriptionInfo info = getSubscriptionInfo(subId); 209 if (info != null) return info; 210 } 211 if (CollectionUtils.isEmpty(mSubscriptionInfos)) { 212 return null; 213 } 214 return mSubscriptionInfos.get(0); 215 } 216 217 /** 218 * @return the subscription associated with a given subscription ID or null if none can be 219 * found. 220 */ getSubscriptionInfo(int subId)221 SubscriptionInfo getSubscriptionInfo(int subId) { 222 if (subId == SUB_ID_NULL) { 223 return null; 224 } 225 226 for (SubscriptionInfo subscription : SubscriptionUtil.getAvailableSubscriptions(this)) { 227 if (subscription.getSubscriptionId() == subId) { 228 return subscription; 229 } 230 } 231 return null; 232 } 233 234 /** 235 * Get the current subId to display. 236 */ 237 @VisibleForTesting getSubscriptionId()238 int getSubscriptionId() { 239 final SubscriptionInfo subscription = getSubscription(); 240 if (subscription != null) { 241 return subscription.getSubscriptionId(); 242 } 243 return SubscriptionManager.INVALID_SUBSCRIPTION_ID; 244 } 245 246 @VisibleForTesting updateBottomNavigationView()247 void updateBottomNavigationView() { 248 final BottomNavigationView navigation = findViewById(R.id.bottom_nav); 249 250 if (CollectionUtils.size(mSubscriptionInfos) <= 1) { 251 navigation.setVisibility(View.GONE); 252 } else { 253 final Menu menu = navigation.getMenu(); 254 menu.clear(); 255 for (int i = 0, size = mSubscriptionInfos.size(); i < size; i++) { 256 final SubscriptionInfo subscriptionInfo = mSubscriptionInfos.get(i); 257 menu.add(0, subscriptionInfo.getSubscriptionId(), i, 258 subscriptionInfo.getDisplayName()) 259 .setIcon(R.drawable.ic_settings_sim); 260 } 261 navigation.setOnNavigationItemSelectedListener(item -> { 262 switchFragment(new MobileNetworkSettings(), item.getItemId()); 263 return true; 264 }); 265 } 266 } 267 268 @VisibleForTesting switchFragment(Fragment fragment, int subscriptionId)269 void switchFragment(Fragment fragment, int subscriptionId) { 270 switchFragment(fragment, subscriptionId, false /* forceUpdate */); 271 } 272 273 @VisibleForTesting switchFragment(Fragment fragment, int subscriptionId, boolean forceUpdate)274 void switchFragment(Fragment fragment, int subscriptionId, boolean forceUpdate) { 275 if (mCurSubscriptionId != SUB_ID_NULL && subscriptionId == mCurSubscriptionId 276 && !forceUpdate) { 277 return; 278 } 279 final FragmentManager fragmentManager = getSupportFragmentManager(); 280 final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); 281 final Bundle bundle = new Bundle(); 282 bundle.putInt(Settings.EXTRA_SUB_ID, subscriptionId); 283 284 fragment.setArguments(bundle); 285 fragmentTransaction.replace(R.id.main_content, fragment, 286 buildFragmentTag(subscriptionId)); 287 fragmentTransaction.commit(); 288 mCurSubscriptionId = subscriptionId; 289 } 290 291 @VisibleForTesting buildFragmentTag(int subscriptionId)292 private String buildFragmentTag(int subscriptionId) { 293 return MOBILE_SETTINGS_TAG + subscriptionId; 294 } 295 296 @VisibleForTesting 297 static class PhoneChangeReceiver extends BroadcastReceiver { 298 private Context mContext; 299 private Client mClient; 300 301 interface Client { onPhoneChange()302 void onPhoneChange(); getSubscriptionId()303 int getSubscriptionId(); 304 } 305 PhoneChangeReceiver(Context context, Client client)306 public PhoneChangeReceiver(Context context, Client client) { 307 mContext = context; 308 mClient = client; 309 } 310 register()311 public void register() { 312 final IntentFilter intentFilter = new IntentFilter(); 313 intentFilter.addAction(TelephonyIntents.ACTION_RADIO_TECHNOLOGY_CHANGED); 314 intentFilter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED); 315 mContext.registerReceiver(this, intentFilter); 316 } 317 unregister()318 public void unregister() { 319 mContext.unregisterReceiver(this); 320 } 321 322 @Override onReceive(Context context, Intent intent)323 public void onReceive(Context context, Intent intent) { 324 if (isInitialStickyBroadcast()) { 325 return; 326 } 327 if (intent.getAction().equals(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) { 328 if (!intent.hasExtra(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX) || 329 intent.getIntExtra(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX, -1) 330 != mClient.getSubscriptionId()) { 331 return; 332 } 333 } 334 mClient.onPhoneChange(); 335 } 336 } 337 removeContactDiscoveryDialog(int subId)338 private void removeContactDiscoveryDialog(int subId) { 339 ContactDiscoveryDialogFragment fragment = getContactDiscoveryFragment(subId); 340 if (fragment != null) { 341 fragment.dismiss(); 342 } 343 } 344 getContactDiscoveryFragment(int subId)345 private ContactDiscoveryDialogFragment getContactDiscoveryFragment(int subId) { 346 // In the case that we are rebuilding this activity after it has been destroyed and 347 // recreated, look up the dialog in the fragment manager. 348 return (ContactDiscoveryDialogFragment) getSupportFragmentManager() 349 .findFragmentByTag(ContactDiscoveryDialogFragment.getFragmentTag(subId)); 350 } 351 maybeShowContactDiscoveryDialog(int subId)352 private void maybeShowContactDiscoveryDialog(int subId) { 353 SubscriptionInfo info = getSubscriptionInfo(subId); 354 CharSequence carrierName = ""; 355 if (info != null) { 356 carrierName = info.getDisplayName(); 357 } 358 // If this activity was launched using ACTION_SHOW_CAPABILITY_DISCOVERY_OPT_IN, show the 359 // associated dialog only if the opt-in has not been granted yet. 360 boolean showOptInDialog = doesIntentContainOptInAction(getIntent()) 361 // has the carrier config enabled capability discovery? 362 && MobileNetworkUtils.isContactDiscoveryVisible(this, subId) 363 // has the user already enabled this configuration? 364 && !MobileNetworkUtils.isContactDiscoveryEnabled(this, subId); 365 ContactDiscoveryDialogFragment fragment = getContactDiscoveryFragment(subId); 366 if (showOptInDialog) { 367 if (fragment == null) { 368 fragment = ContactDiscoveryDialogFragment.newInstance(subId, carrierName); 369 } 370 // Only try to show the dialog if it has not already been added, otherwise we may 371 // accidentally add it multiple times, causing multiple dialogs. 372 if (!fragment.isAdded()) { 373 fragment.show(getSupportFragmentManager(), 374 ContactDiscoveryDialogFragment.getFragmentTag(subId)); 375 } 376 } 377 } 378 doesIntentContainOptInAction(Intent intent)379 private boolean doesIntentContainOptInAction(Intent intent) { 380 String intentAction = (intent != null ? intent.getAction() : null); 381 return TextUtils.equals(intentAction, 382 ImsRcsManager.ACTION_SHOW_CAPABILITY_DISCOVERY_OPT_IN); 383 } 384 validate(Intent intent)385 private void validate(Intent intent) { 386 // Do not allow ACTION_SHOW_CAPABILITY_DISCOVERY_OPT_IN without a subscription id specified, 387 // since we do not want the user to accidentally turn on capability polling for the wrong 388 // subscription. 389 if (doesIntentContainOptInAction(intent)) { 390 if (SubscriptionManager.INVALID_SUBSCRIPTION_ID == intent.getIntExtra( 391 Settings.EXTRA_SUB_ID, SubscriptionManager.INVALID_SUBSCRIPTION_ID)) { 392 throw new IllegalArgumentException("Intent with action " 393 + "SHOW_CAPABILITY_DISCOVERY_OPT_IN must also include the extra " 394 + "Settings#EXTRA_SUB_ID"); 395 } 396 } 397 } 398 } 399