/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.sim; import android.app.Activity; import android.app.settings.SettingsEnums; import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; import android.os.PersistableBundle; import android.telecom.PhoneAccountHandle; import android.telecom.TelecomManager; import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.telephony.ims.ImsException; import android.telephony.ims.ImsManager; import android.telephony.ims.ImsMmTelManager; import android.util.Log; import android.view.WindowManager; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentManager; import com.android.internal.annotations.VisibleForTesting; import com.android.settings.R; import com.android.settings.flags.Flags; import com.android.settings.network.CarrierConfigCache; import com.android.settings.network.SubscriptionUtil; import com.android.settings.network.ims.WifiCallingQueryImsState; import com.android.settings.network.telephony.MobileNetworkUtils; import com.android.settings.network.telephony.SubscriptionActionDialogActivity; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import java.util.List; /** * This activity provides singleton semantics per dialog type for showing various kinds of * dialogs asking the user to make choices about which SIM to use for various services * (calls, SMS, and data). */ public class SimDialogActivity extends FragmentActivity { private static String TAG = "SimDialogActivity"; public static String PREFERRED_SIM = "preferred_sim"; public static String DIALOG_TYPE_KEY = "dialog_type"; // sub ID returned from startActivityForResult public static String RESULT_SUB_ID = "result_sub_id"; public static final int INVALID_PICK = -1; public static final int DATA_PICK = 0; public static final int CALLS_PICK = 1; public static final int SMS_PICK = 2; public static final int PREFERRED_PICK = 3; // Show the "select SMS subscription" dialog, but don't save as default, just return a result public static final int SMS_PICK_FOR_MESSAGE = 4; // Dismiss the current dialog and finish the activity. public static final int PICK_DISMISS = 5; // Show auto data switch dialog(when user enables multi-SIM) public static final int ENABLE_AUTO_DATA_SWITCH = 6; private MetricsFeatureProvider mMetricsFeatureProvider; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (isUiRestricted()) { finish(); return; } if (!SubscriptionUtil.isSimHardwareVisible(this)) { Log.d(TAG, "Not support on device without SIM."); finish(); return; } SimDialogProhibitService.supportDismiss(this); mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider(); getWindow().addSystemFlags( WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); showOrUpdateDialog(); } @VisibleForTesting boolean isUiRestricted() { if (MobileNetworkUtils.isMobileNetworkUserRestricted(getApplicationContext())) { Log.e(TAG, "This setting isn't available due to user restriction."); return true; } return false; } @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); setIntent(intent); showOrUpdateDialog(); } private int getProgressState() { final SharedPreferences prefs = getSharedPreferences( SubscriptionActionDialogActivity.SIM_ACTION_DIALOG_PREFS, MODE_PRIVATE); return prefs.getInt(SubscriptionActionDialogActivity.KEY_PROGRESS_STATE, SubscriptionActionDialogActivity.PROGRESS_IS_NOT_SHOWING); } private void showOrUpdateDialog() { final int dialogType = getIntent().getIntExtra(DIALOG_TYPE_KEY, INVALID_PICK); if (dialogType == PICK_DISMISS) { finishAndRemoveTask(); return; } if (dialogType == PREFERRED_PICK && getProgressState() == SubscriptionActionDialogActivity.PROGRESS_IS_SHOWING) { Log.d(TAG, "Finish the sim dialog since the sim action dialog is showing the progress"); finish(); return; } if (Flags.isDualSimOnboardingEnabled() && (dialogType == DATA_PICK || dialogType == CALLS_PICK || dialogType == SMS_PICK)) { Log.d(TAG, "Finish the sim dialog since the sim onboarding is shown"); finish(); return; } final String tag = Integer.toString(dialogType); final FragmentManager fragmentManager = getSupportFragmentManager(); SimDialogFragment fragment = (SimDialogFragment) fragmentManager.findFragmentByTag(tag); if (fragment == null) { fragment = createFragment(dialogType); fragment.show(fragmentManager, tag); } else { fragment.updateDialog(); } } private SimDialogFragment createFragment(int dialogType) { switch (dialogType) { case DATA_PICK: return getDataPickDialogFragment(); case CALLS_PICK: return CallsSimListDialogFragment.newInstance(dialogType, R.string.select_sim_for_calls, true /* includeAskEveryTime */, false /* isCancelItemShowed */); case SMS_PICK: return SimListDialogFragment.newInstance(dialogType, R.string.select_sim_for_sms, true /* includeAskEveryTime */, false /* isCancelItemShowed */); case PREFERRED_PICK: if (!getIntent().hasExtra(PREFERRED_SIM)) { throw new IllegalArgumentException("Missing required extra " + PREFERRED_SIM); } return PreferredSimDialogFragment.newInstance(); case SMS_PICK_FOR_MESSAGE: return SimListDialogFragment.newInstance(dialogType, R.string.select_sim_for_sms, false /* includeAskEveryTime */, false /* isCancelItemShowed */); case ENABLE_AUTO_DATA_SWITCH: return EnableAutoDataSwitchDialogFragment.newInstance(); default: throw new IllegalArgumentException("Invalid dialog type " + dialogType + " sent."); } } private SimDialogFragment getDataPickDialogFragment() { if (SubscriptionManager.getDefaultDataSubscriptionId() == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { return SimListDialogFragment.newInstance(DATA_PICK, R.string.select_sim_for_data, false /* includeAskEveryTime */, true /* isCancelItemShowed */); } return SelectSpecificDataSimDialogFragment.newInstance(); } public void onSubscriptionSelected(int dialogType, int subId) { if (getSupportFragmentManager().findFragmentByTag(Integer.toString(dialogType)) == null) { Log.w(TAG, "onSubscriptionSelected ignored because stored fragment was null"); return; } switch (dialogType) { case DATA_PICK: setDefaultDataSubId(subId); break; case CALLS_PICK: setDefaultCallsSubId(subId); break; case SMS_PICK: setDefaultSmsSubId(subId); break; case PREFERRED_PICK: setPreferredSim(subId); break; case SMS_PICK_FOR_MESSAGE: // Don't set a default here. // The caller has created this dialog waiting for a result. Intent intent = new Intent(); intent.putExtra(RESULT_SUB_ID, subId); setResult(Activity.RESULT_OK, intent); break; case ENABLE_AUTO_DATA_SWITCH: onEnableAutoDataSwitch(subId); break; default: throw new IllegalArgumentException( "Invalid dialog type " + dialogType + " sent."); } } private PersistableBundle getCarrierConfigForSubId(int subId) { if (!SubscriptionManager.isValidSubscriptionId(subId)) { return null; } return CarrierConfigCache.getInstance(this).getConfigForSubId(subId); } private boolean isCrossSimCallingAllowedByPlatform(int subId) { if ((new WifiCallingQueryImsState(this, subId)).isWifiCallingSupported()) { PersistableBundle bundle = getCarrierConfigForSubId(subId); return (bundle != null) && bundle.getBoolean( CarrierConfigManager.KEY_CARRIER_CROSS_SIM_IMS_AVAILABLE_BOOL, false /*default*/); } return false; } private ImsMmTelManager getImsMmTelManager(int subId) { ImsManager imsMgr = getSystemService(ImsManager.class); return (imsMgr == null) ? null : imsMgr.getImsMmTelManager(subId); } private void trySetCrossSimCallingPerSub(int subId, boolean enabled) { try { getImsMmTelManager(subId).setCrossSimCallingEnabled(enabled); } catch (ImsException | IllegalArgumentException | NullPointerException exception) { Log.w(TAG, "failed to change cross SIM calling configuration to " + enabled + " for subID " + subId + "with exception: ", exception); } } private boolean autoDataSwitchEnabledOnNonDataSub(@NonNull int[] subIds, int defaultDataSub) { for (int subId : subIds) { if (subId != defaultDataSub) { final TelephonyManager telephonyManager = getSystemService( TelephonyManager.class).createForSubscriptionId(subId); if (telephonyManager.isMobileDataPolicyEnabled( TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH)) { return true; } } } return false; } private void trySetCrossSimCalling(int[] subIds, boolean enabled) { mMetricsFeatureProvider.action(this, SettingsEnums.ACTION_UPDATE_CROSS_SIM_CALLING_ON_2ND_SIM_ENABLE, enabled); for (int subId : subIds) { if (isCrossSimCallingAllowedByPlatform(subId)) { trySetCrossSimCallingPerSub(subId, enabled); } } } /** * Show dialog prompting the user to enable auto data switch */ public void showEnableAutoDataSwitchDialog() { final FragmentManager fragmentManager = getSupportFragmentManager(); SimDialogFragment fragment = createFragment(ENABLE_AUTO_DATA_SWITCH); if (fragmentManager.isStateSaved()) { Log.w(TAG, "Failed to show EnableAutoDataSwitchDialog. The fragmentManager " + "is StateSaved."); forceClose(); return; } try { fragment.show(fragmentManager, Integer.toString(ENABLE_AUTO_DATA_SWITCH)); } catch (Exception e) { Log.e(TAG, "Failed to show EnableAutoDataSwitchDialog.", e); forceClose(); return; } if (getResources().getBoolean( R.bool.config_auto_data_switch_enables_cross_sim_calling)) { // If auto data switch is already enabled on the non-DDS, the dialog for enabling it // is suppressed (no onEnableAutoDataSwitch()). so we ensure cross-SIM calling is // enabled. // OTOH, if auto data switch is disabled on the new non-DDS, the user may still not // enable it in the dialog. So we ensure cross-SIM calling is disabled before the // dialog. If the user does enable auto data switch, we will re-enable cross-SIM calling // through onEnableAutoDataSwitch()- a minor redundancy to ensure correctness. final SubscriptionManager subscriptionManager = getSystemService(SubscriptionManager.class); int[] subIds = subscriptionManager.getActiveSubscriptionIdList(); int defaultDataSub = subscriptionManager.getDefaultDataSubscriptionId(); if (subIds.length > 1) { trySetCrossSimCalling(subIds, autoDataSwitchEnabledOnNonDataSub(subIds, defaultDataSub)); } } } /** * @param subId The sub Id to enable auto data switch */ public void onEnableAutoDataSwitch(int subId) { Log.d(TAG, "onEnableAutoDataSwitch subId:" + subId); final TelephonyManager telephonyManager = getSystemService( TelephonyManager.class).createForSubscriptionId(subId); telephonyManager.setMobileDataPolicyEnabled( TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH, true); if (getResources().getBoolean( R.bool.config_auto_data_switch_enables_cross_sim_calling)) { final SubscriptionManager subscriptionManager = getSystemService(SubscriptionManager.class); trySetCrossSimCalling(subscriptionManager.getActiveSubscriptionIdList(), true /* enabled */); } } public void onFragmentDismissed(SimDialogFragment simDialogFragment) { final List fragments = getSupportFragmentManager().getFragments(); if (fragments.size() == 1 && fragments.get(0) == simDialogFragment || simDialogFragment.getDialogType() == ENABLE_AUTO_DATA_SWITCH) { Log.d(TAG, "onFragmentDismissed dialogType:" + simDialogFragment.getDialogType()); finishAndRemoveTask(); } } private void setDefaultDataSubId(final int subId) { final SubscriptionManager subscriptionManager = getSystemService(SubscriptionManager.class); final TelephonyManager telephonyManager = getSystemService( TelephonyManager.class).createForSubscriptionId(subId); subscriptionManager.setDefaultDataSubId(subId); if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { Log.d(TAG, "setDataEnabledForReason true"); telephonyManager.setDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_USER, true); Toast.makeText(this, R.string.data_switch_started, Toast.LENGTH_LONG).show(); } } private void setDefaultCallsSubId(final int subId) { final PhoneAccountHandle phoneAccount = subscriptionIdToPhoneAccountHandle(subId); final TelecomManager telecomManager = getSystemService(TelecomManager.class); telecomManager.setUserSelectedOutgoingPhoneAccount(phoneAccount); } private void setDefaultSmsSubId(final int subId) { final SubscriptionManager subscriptionManager = getSystemService(SubscriptionManager.class); subscriptionManager.setDefaultSmsSubId(subId); } private void setPreferredSim(final int subId) { setDefaultDataSubId(subId); setDefaultSmsSubId(subId); setDefaultCallsSubId(subId); } private PhoneAccountHandle subscriptionIdToPhoneAccountHandle(final int subId) { final TelecomManager telecomManager = getSystemService(TelecomManager.class); final TelephonyManager telephonyManager = getSystemService(TelephonyManager.class); for (PhoneAccountHandle handle : telecomManager.getCallCapablePhoneAccounts()) { if (subId == telephonyManager.getSubscriptionId(handle)) { return handle; } } return null; } /* * Force dismiss this Activity. */ protected void forceClose() { if (isFinishing() || isDestroyed()) { return; } Log.d(TAG, "Dismissed by Service"); finishAndRemoveTask(); } }