Merge changes Iee650897,Icb00b3fc,I5c6ad4b3,Ief4299e7,I85800227, ...

* changes:
  [MEP] disable enabled esim profile before set simSlotMapping
  [MEP] renew the sim confirm dialog UI
  [MEP] renew the default data selection UI
  switch SIM refactor to support MEP
  switch SIM refactor to support MEP
  Support RTL mode in Sim switch dialog
This commit is contained in:
SongFerng Wang
2022-03-27 14:44:48 +00:00
committed by Gerrit Code Review
29 changed files with 1248 additions and 210 deletions

View File

@@ -690,12 +690,12 @@
<activity android:name=".network.telephony.ToggleSubscriptionDialogActivity"
android:exported="false"
android:permission="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS"
android:theme="@*android:style/Theme.DeviceDefault.Dialog.Alert.DayNight" />
android:theme="@style/Theme.AlertDialog.SimConfirmDialog"/>
<activity android:name=".network.telephony.DeleteEuiccSubscriptionDialogActivity"
android:exported="false"
android:permission="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS"
android:theme="@*android:style/Theme.DeviceDefault.Dialog.Alert.DayNight" />
android:theme="@style/Theme.AlertDialog.SimConfirmDialog"/>
<activity
android:name="Settings$TetherSettingsActivity"
@@ -3370,7 +3370,7 @@
<activity
android:name=".sim.SimDialogActivity"
android:theme="@style/Theme.AlertDialog"
android:theme="@style/Theme.AlertDialog.SimConfirmDialog"
android:label="@string/sim_settings_title"
android:launchMode="singleTop"
android:exported="true"
@@ -4202,14 +4202,14 @@
android:exported="false"
android:permission="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS"
android:launchMode="singleInstance"
android:theme="@*android:style/Theme.DeviceDefault.Dialog.Alert.DayNight" />
android:theme="@style/Theme.AlertDialog.SimConfirmDialog"/>
<activity
android:name=".sim.DsdsDialogActivity"
android:exported="false"
android:permission="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS"
android:launchMode="singleInstance"
android:theme="@*android:style/Theme.DeviceDefault.Dialog.Alert.DayNight" />
android:theme="@style/Theme.AlertDialog.SimConfirmDialog"/>
<service android:name=".sim.SimNotificationService"
android:permission="android.permission.BIND_JOB_SERVICE" />

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2021 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.
-->
<inset xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:insetTop="16dp"
android:insetBottom="24dp">
<ripple android:color="?android:attr/colorControlHighlight">
<item android:id="@android:id/mask">
<shape android:shape="rectangle">
<solid android:color="@android:color/white"/>
<corners android:radius="24dp"/>
</shape>
</item>
<item>
<shape android:shape="rectangle">
<corners android:radius="24dp"/>
<solid android:color="@android:color/transparent"/>
<stroke android:color="?androidprv:attr/colorAccentPrimaryVariant"
android:width="1dp" />
<padding android:left="16dp"
android:top="8dp"
android:right="16dp"
android:bottom="8dp"/>
</shape>
</item>
</ripple>
</inset>

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2021 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.
-->
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:insetLeft="24dp"
android:insetRight="24dp">
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/settingslib_state_on_color"/>
<corners
android:bottomLeftRadius="8dp"
android:topLeftRadius="8dp"
android:bottomRightRadius="8dp"
android:topRightRadius="8dp"
/>
</shape>
</inset>

View File

@@ -15,43 +15,27 @@
-->
<!-- Layout of a single item for displaying sim cards. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground" >
<ImageView android:id="@+id/icon"
android:layout_width="48dp"
android:layout_height="48dp"
android:scaleType="center" />
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_marginStart="15dip"
android:layout_marginEnd="6dip"
android:layout_marginTop="6dip"
android:layout_marginBottom="6dip"
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_weight="1" >
<TextView android:id="@+id/title"
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="start|center_vertical"
android:layout_marginLeft="8dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?attr/listPreferredItemHeightSmall"
android:background="@drawable/sim_confirm_dialog_rounded_bg"
android:gravity="center">
<TextView android:id="@+id/title"
android:textAppearance="@style/TextAppearance.SimConfirmDialogList"
android:gravity="start|center_vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="marquee"
android:fadingEdge="horizontal" />
<TextView android:id="@+id/summary"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textAppearance="@style/TextAppearance.SimConfirmDialogList.Summary"
android:gravity="start|center_vertical"
android:layout_marginLeft="8dp"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/title"
android:textColor="?android:attr/textColorSecondary"
android:layout_alignStart="@id/title" />
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2021 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.
-->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?attr/listPreferredItemHeightSmall"
style="?attr/materialAlertDialogBodyTextStyle"
android:gravity="center"
android:paddingTop="?attr/listPreferredItemPaddingStart"
android:paddingBottom="?attr/listPreferredItemPaddingEnd"
android:paddingLeft="?attr/listPreferredItemPaddingLeft"
android:paddingRight="?attr/listPreferredItemPaddingRight"
android:background="@drawable/sim_confirm_dialog_rounded_bg"
android:textAppearance="@style/TextAppearance.SimConfirmDialogList"
/>

View File

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2021 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.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/msg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingEnd="24dp"
android:paddingTop="16dp"
android:paddingStart="24dp"
android:gravity="center"
android:textAppearance="@style/TextAppearance.DialogMessage"/>
<ListView
android:id="@+id/carrier_list"
android:layout_gravity="center"
android:paddingTop="16dp"
android:dividerHeight="1dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<LinearLayout
android:id="@+id/info_outline_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="start|top"
android:orientation="horizontal"
android:paddingEnd="24dp"
android:paddingTop="16dp"
android:paddingStart="24dp"
android:layout_marginBottom="16dp"
android:baselineAligned="true">
<ImageView
android:src="@drawable/ic_info_outline_24dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tint="?android:attr/textColorTertiary"/>
<TextView
android:id="@+id/info_outline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="16dp"
android:text="@string/sim_action_switch_sub_dialog_info_outline_for_turning_off"
android:textColor="?android:attr/textColorSecondary"
android:textAppearance="@style/TextAppearance.DialogMessage"/>
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2021 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.
-->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingEnd="24dp"
android:paddingTop="16dp"
android:paddingLeft="24dp"
android:gravity="center"
style="?android:attr/textAppearanceLarge"/>

View File

@@ -16,9 +16,18 @@
-->
<resources>
<style name="Widget.ActionBar.Base" parent="@android:style/Widget.DeviceDefault.ActionBar.Solid">
<item name="android:background">?android:attr/colorPrimaryDark</item>
</style>
<style name="TextAppearance.SimConfirmDialogList" parent="@style/TextAppearance.DialogMessage">
<item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
<item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
</style>
<style name="TextAppearance.SimConfirmDialogList.Summary">
<item name="android:textAppearance">?android:attr/textAppearanceSmall</item>
<item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
<item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
</style>
</resources>

View File

@@ -34,6 +34,12 @@
<item name="android:colorBackground">@*android:color/surface_dark</item>
</style>
<style name="Theme.AlertDialog.Base.Material3" parent="Theme.MaterialComponents.DayNight.Dialog.Alert">
<item name="colorPrimary">@*android:color/primary_device_default_settings</item>
<item name="colorAccent">@*android:color/accent_device_default_dark</item>
<item name="android:colorBackground">@*android:color/surface_dark</item>
</style>
<!-- Material theme for the pages containing TabLayout and ViewPager -->
<style name="Theme.TabTheme" parent="@style/Theme.MaterialComponents.DayNight">
<item name="colorPrimary">@*android:color/edge_effect_device_default_dark</item>

View File

@@ -7954,11 +7954,17 @@
<!-- Checkbox to always use for calls. [CHAR LIMIT=40] -->
<string name="sim_calls_always_use">Always use this for calls</string>
<!-- Message for selecting sim for data in settings. [CHAR LIMIT=40] -->
<string name="select_sim_for_data">Select a SIM for data</string>
<string name="select_sim_for_data">Choose SIM for mobile data</string>
<!-- Message for selecting sim for SMS in settings. [CHAR LIMIT=40] -->
<string name="select_sim_for_sms">Select a SIM for SMS</string>
<!-- Message for switching data SIM; switching takes a while -->
<string name="data_switch_started">Switching data SIM, this may take up to a minute\u2026</string>
<!-- Title for selecting specific sim for data in settings. [CHAR LIMIT=40] -->
<string name="select_specific_sim_for_data_title">Use <xliff:g id="new_sim" example="carrierA">%1$s</xliff:g> for mobile data?</string>
<!-- Message for selecting specific sim for data in settings. [CHAR LIMIT=NONE] -->
<string name="select_specific_sim_for_data_msg">If you switch to <xliff:g id="new_sim" example="carrierA">%1$s</xliff:g>, <xliff:g id="old_sim" example="carrierB">%2$s</xliff:g> will no longer be used for mobile data.</string>
<!-- Button on a selecting specific sim dialog to confirm data in settings. [CHAR LIMIT=40] -->
<string name="select_specific_sim_for_data_button">Use <xliff:g id="new_sim" example="carrierA">%1$s</xliff:g></string>
<!-- Message for selecting sim for call in settings. [CHAR LIMIT=40] -->
<string name="select_sim_for_calls">Call with</string>
<!-- Title for selecting a SIM card. [CHAR LIMIT=40] -->
@@ -12737,18 +12743,26 @@
<string name="sim_action_switch_sub_dialog_title">Switch to <xliff:g id="carrier_name" example="Google Fi">%1$s</xliff:g>?</string>
<!-- Title of confirmation dialog asking the user if they want to switch to the SIM card. [CHAR_LIMIT=NONE] -->
<string name="sim_action_switch_psim_dialog_title">Switch to using SIM card?</string>
<!-- Title of confirmation dialog asking the user if they want to switch subscription. [CHAR_LIMIT=NONE] -->
<string name="sim_action_switch_sub_dialog_mep_title">Use <xliff:g id="carrier_name" example="Google Fi">%1$s</xliff:g>?</string>
<!-- Body text of confirmation dialog for switching subscription that involves switching SIM slots. Indicates that only one SIM can be active at a time. Also that switching will not cancel the user's mobile service plan. [CHAR_LIMIT=NONE] -->
<string name="sim_action_switch_sub_dialog_text">Only one SIM can be active at a time.\n\nSwitching to <xliff:g id="to_carrier_name" example="Google Fi">%1$s</xliff:g> won\u2019t cancel your <xliff:g id="from_carrier_name" example="Sprint">%2$s</xliff:g> service.</string>
<!-- Body text of confirmation dialog for switching subscription between two eSIM profiles. Indicates that only one downloaded SIM can be active at a time. Also that switching will not cancel the user's mobile service plan. [CHAR_LIMIT=NONE] -->
<string name="sim_action_switch_sub_dialog_text_downloaded">Only one downloaded SIM can be active at a time.\n\nSwitching to <xliff:g id="to_carrier_name" example="Google Fi">%1$s</xliff:g> won\u2019t cancel your <xliff:g id="from_carrier_name" example="Sprint">%2$s</xliff:g> service.</string>
<!-- Body text of confirmation dialog for switching subscription between two eSIM profiles. Indicates that only one SIM can be active at a time. Also that switching will not cancel the user's mobile service plan. [CHAR_LIMIT=NONE] -->
<string name="sim_action_switch_sub_dialog_text_single_sim">Only one SIM can be active at a time.\n\nSwitching won\u2019t cancel your <xliff:g id="to_carrier_name" example="Google Fi">%1$s</xliff:g> service.</string>
<!-- Body text of confirmation dialog for switching subscription between two eSIM profiles. Indicates that only one downloaded SIM can be active at a time. Also that switching will not cancel the user's mobile service plan. [CHAR_LIMIT=NONE] -->
<string name="sim_action_switch_sub_dialog_mep_text">You can use 2 SIMs at a time. To use <xliff:g id="carrier_name" example="Google Fi">%1$s</xliff:g>, turn off another SIM.</string>
<!-- Text of confirm button in the confirmation dialog asking the user if they want to switch subscription. [CHAR_LIMIT=NONE] -->
<string name="sim_action_switch_sub_dialog_confirm">Switch to <xliff:g id="carrier_name" example="Google Fi">%1$s</xliff:g></string>
<!-- Text of carrier list item in the mep confirmation dialog asking the user if they want to turn off the carrier. [CHAR_LIMIT=NONE] -->
<string name="sim_action_switch_sub_dialog_carrier_list_item_for_turning_off">Turn off <xliff:g id="carrier_name" example="Google Fi">%1$s</xliff:g></string>
<!-- Text of carrier list item in the mep confirmation dialog asking the user if they want to turn off the carrier. [CHAR_LIMIT=NONE] -->
<string name="sim_action_switch_sub_dialog_info_outline_for_turning_off">Turning off a SIM won\u2019t cancel your service</string>
<!-- Status message indicating the device is in the process of disconnecting from one mobile network and immediately connecting to another. [CHAR_LIMIT=NONE] -->
<string name="sim_action_enabling_sim_without_carrier_name">Connecting to network&#8230;</string>
<!-- Text of progress dialog indicating the subscription switch is in progress. [CHAR_LIMIT=NONE] -->
<string name="sim_action_switch_sub_dialog_progress">Switching to <xliff:g id="carrier_name" example="Google Fi">%1$s</xliff:g></string>
<string name="sim_action_switch_sub_dialog_progress">Switching to <xliff:g id="carrier_name" example="Google Fi">%1$s</xliff:g> for calls and messages&#8230;</string>
<!-- Title of error message indicating that the device could not disconnect from one mobile network and immediately connect to another. [CHAR_LIMIT=NONE] -->
<string name="sim_action_enable_sim_fail_title">Can\u2019t switch carrier</string>
<!-- Body text of error message indicating the device could not disconnect from one mobile network and immediately connect to another, due to an unspecified issue. [CHAR_LIMIT=NONE] -->

View File

@@ -914,4 +914,25 @@
parent="@*android:style/TextAppearance.DeviceDefault">
<item name="android:textColor">?android:attr/textColorSecondary</item>
</style>
<style name="TextAppearance.SimConfirmDialogList" parent="@style/TextAppearance.DialogMessage"/>
<style name="TextAppearance.SimConfirmDialogList.Summary">
<item name="android:textAppearance">?android:attr/textAppearanceSmall</item>
</style>
<style name="SimConfirmDialog.OutlineButton" parent="@android:style/Widget.Material.Button">
<item name="android:layout_marginStart">8dp</item>
<item name="android:layout_height">36dp</item>
<item name="android:minWidth">0dp</item>
<item name="android:textSize">14sp</item>
<item name="android:lineHeight">20sp</item>
<item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
<item name="android:background">@drawable/sim_confirm_dialog_btn_outline</item>
</style>
<style name="SimConfirmDialog.ButtonBarStyle" parent="@android:style/Widget.Material.ButtonBar">
<item name="android:paddingEnd">8dp</item>
</style>
</resources>

View File

@@ -142,6 +142,13 @@
<item name="buttonBarButtonStyle">@*android:style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
</style>
<style name="Theme.AlertDialog.SimConfirmDialog">
<item name="buttonBarStyle">@style/SimConfirmDialog.ButtonBarStyle</item>
<item name="buttonBarButtonStyle">@style/SimConfirmDialog.OutlineButton</item>
<item name="android:textAllCaps">false</item>
</style>
<style name="Theme.ConfirmDeviceCredentials" parent="Theme.SubSettings">
<item name="confirmDeviceCredentialsSideMargin">@dimen/confirm_credentials_side_margin</item>
<item name="confirmDeviceCredentialsTopMargin">@dimen/confirm_credentials_top_margin</item>

View File

@@ -19,6 +19,7 @@ package com.android.settings.network;
import android.annotation.IntDef;
import android.app.FragmentManager;
import android.os.Bundle;
import android.telephony.SubscriptionInfo;
import android.util.Log;
import com.android.settings.AsyncTaskSidecar;
@@ -41,11 +42,14 @@ public class SwitchSlotSidecar
})
private @interface Command {
int SWITCH_TO_REMOVABLE_SIM = 0;
int SWITCH_TO_EUICC_SIM = 1;
}
static class Param {
@Command int command;
int slotId;
int port;
SubscriptionInfo removedSubInfo;
}
static class Result {
@@ -65,13 +69,24 @@ public class SwitchSlotSidecar
}
/** Starts switching to the removable slot. */
public void runSwitchToRemovableSlot(int id) {
public void runSwitchToRemovableSlot(int id, SubscriptionInfo removedSubInfo) {
Param param = new Param();
param.command = Command.SWITCH_TO_REMOVABLE_SIM;
param.slotId = id;
param.removedSubInfo = removedSubInfo;
param.port = 0;
super.run(param);
}
/** Starts switching to the removable slot. */
public void runSwitchToEuiccSlot(int id, int port, SubscriptionInfo removedSubInfo) {
Param param = new Param();
param.command = Command.SWITCH_TO_EUICC_SIM;
param.slotId = id;
param.removedSubInfo = removedSubInfo;
param.port = port;
super.run(param);
}
/**
* Returns the exception thrown during the execution of a command. Will be null in any state
* other than {@link State#SUCCESS}, and may be null in that state if there was not an error.
@@ -91,7 +106,14 @@ public class SwitchSlotSidecar
try {
switch (param.command) {
case Command.SWITCH_TO_REMOVABLE_SIM:
UiccSlotUtil.switchToRemovableSlot(param.slotId, getContext());
Log.i(TAG, "Start to switch to removable slot.");
UiccSlotUtil.switchToRemovableSlot(getContext(), param.slotId,
param.removedSubInfo);
break;
case Command.SWITCH_TO_EUICC_SIM:
Log.i(TAG, "Start to switch to euicc slot.");
UiccSlotUtil.switchToEuiccSlot(getContext(), param.slotId, param.port,
param.removedSubInfo);
break;
default:
Log.e(TAG, "Wrong command.");

View File

@@ -18,17 +18,33 @@ package com.android.settings.network;
import android.app.FragmentManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.UiccCardInfo;
import android.telephony.UiccSlotMapping;
import android.telephony.euicc.EuiccManager;
import android.util.Log;
import com.android.settings.SidecarFragment;
import com.android.settings.network.telephony.EuiccOperationSidecar;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
/** A headless fragment encapsulating long-running eSIM enabling/disabling operations. */
public class SwitchToEuiccSubscriptionSidecar extends EuiccOperationSidecar {
private static final String TAG = "SwitchToEuiccSubscriptionSidecar";
private static final String TAG = "SwitchToEuiccSidecar";
private static final String ACTION_SWITCH_TO_SUBSCRIPTION =
"com.android.settings.network.SWITCH_TO_SUBSCRIPTION";
private static final int ESIM_SLOT_ID = 1;
private PendingIntent mCallbackIntent;
private int mSubId;
private int mPort;
private SubscriptionInfo mRemovedSubInfo;
private boolean mIsDuringSimSlotMapping;
/** Returns a SwitchToEuiccSubscriptionSidecar sidecar instance. */
public static SwitchToEuiccSubscriptionSidecar get(FragmentManager fm) {
@@ -46,10 +62,155 @@ public class SwitchToEuiccSubscriptionSidecar extends EuiccOperationSidecar {
return mCallbackIntent;
}
@Override
public void onStateChange(SidecarFragment fragment) {
if (fragment == mSwitchSlotSidecar) {
onSwitchSlotSidecarStateChange();
} else {
Log.wtf(TAG, "Received state change from a sidecar not expected.");
}
}
/** Starts calling EuiccManager#switchToSubscription to enable/disable the eSIM profile. */
// ToDo: delete this api and refactor the related code.
public void run(int subscriptionId) {
setState(State.RUNNING, Substate.UNUSED);
mCallbackIntent = createCallbackIntent();
mEuiccManager.switchToSubscription(subscriptionId, mCallbackIntent);
}
/**
* Starts calling EuiccManager#switchToSubscription to enable/disable the eSIM profile.
*
* @param subscriptionId the esim's subscriptionId.
* @param port the esim's portId. If user wants to inactivate esim, then user must to assign the
* the port. If user wants to activate esim, then the port can be -1.
* @param removedSubInfo if the all of slots have sims, it should remove the one of active sim.
* If the removedSubInfo is null, then use the default value.
* The default value is the esim slot and portId 0.
*/
public void run(int subscriptionId, int port, SubscriptionInfo removedSubInfo) {
setState(State.RUNNING, Substate.UNUSED);
mCallbackIntent = createCallbackIntent();
mSubId = subscriptionId;
// To check whether the esim slot's port is active. If yes, skip setSlotMapping. If no,
// set this slot+port into setSimSlotMapping.
mPort = (port < 0) ? getTargetPortId(removedSubInfo) : port;
mRemovedSubInfo = removedSubInfo;
Log.i(TAG, "The SubId is " + mSubId + ". The port is " + mPort);
if (mTelephonyManager.isMultiSimEnabled() && removedSubInfo != null
&& removedSubInfo.isEmbedded()) {
// In DSDS mode+MEP, if the replaced esim is active, then it should be disabled esim
// profile before changing SimSlotMapping process.
// Use INVALID_SUBSCRIPTION_ID to disable the esim profile.
// The SimSlotMapping is ready, then to execute activate/inactivate esim.
mIsDuringSimSlotMapping = true;
EuiccManager.ResultListener callback = new EuiccManager.ResultListener() {
@Override
public void onComplete(int resultCode, Intent resultIntent) {
Log.i(TAG, String.format("Result code : %d;", resultCode));
if (resultCode == EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK) {
mSwitchSlotSidecar.runSwitchToEuiccSlot(getTargetSlot(), mPort,
removedSubInfo);
} else {
setState(State.ERROR, resultCode);
}
}
};
mEuiccManager.switchToSubscription(SubscriptionManager.INVALID_SUBSCRIPTION_ID, mPort,
getContext().getMainExecutor(),
callback);
} else {
mSwitchSlotSidecar.runSwitchToEuiccSlot(getTargetSlot(), mPort, removedSubInfo);
}
}
private int getTargetPortId(SubscriptionInfo removedSubInfo) {
if (!mTelephonyManager.isMultiSimEnabled() || !isMultipleEnabledProfilesSupported()) {
// In the 'SS mode' or 'DSDS+no MEP', the port is 0.
return 0;
}
// In the 'DSDS+MEP', if the removedSubInfo is esim, then the port is
// removedSubInfo's port.
if (removedSubInfo != null && removedSubInfo.isEmbedded()) {
return removedSubInfo.getPortIndex();
}
// In DSDS+MEP mode, the removedSubInfo is psim or is null, it means the this esim need
// another port in the esim slot.
// To find another esim's port and value is from 0;
int port = 0;
Collection<UiccSlotMapping> uiccSlotMappings = mTelephonyManager.getSimSlotMapping();
for (UiccSlotMapping uiccSlotMapping :
uiccSlotMappings.stream()
.filter(
uiccSlotMapping -> uiccSlotMapping.getPhysicalSlotIndex()
== getTargetSlot())
.collect(Collectors.toList())) {
if (uiccSlotMapping.getPortIndex() == port) {
port++;
}
}
return port;
}
private int getTargetSlot() {
return ESIM_SLOT_ID;
}
private void onSwitchSlotSidecarStateChange() {
switch (mSwitchSlotSidecar.getState()) {
case State.SUCCESS:
mSwitchSlotSidecar.reset();
Log.i(TAG, "Successfully SimSlotMapping. Start to enable/disable esim");
switchToSubscription();
break;
case State.ERROR:
mSwitchSlotSidecar.reset();
Log.i(TAG, "Failed to set SimSlotMapping");
setState(State.ERROR, Substate.UNUSED);
break;
}
}
private boolean isMultipleEnabledProfilesSupported() {
List<UiccCardInfo> cardInfos = mTelephonyManager.getUiccCardsInfo();
if (cardInfos == null) {
Log.w(TAG, "UICC cards info list is empty.");
return false;
}
return cardInfos.stream().anyMatch(
cardInfo -> cardInfo.isMultipleEnabledProfilesSupported());
}
private void switchToSubscription() {
// The SimSlotMapping is ready, then to execute activate/inactivate esim.
EuiccManager.ResultListener callback = new EuiccManager.ResultListener() {
@Override
public void onComplete(int resultCode, Intent resultIntent) {
Log.i(TAG, String.format("Result code : %d;", resultCode));
if (resultCode == EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK) {
setState(State.SUCCESS, Substate.UNUSED);
} else {
setState(State.ERROR, resultCode);
}
}
};
mEuiccManager.switchToSubscription(mSubId, mPort, getContext().getMainExecutor(),
callback);
}
@Override
protected void onActionReceived() {
if (getResultCode() == EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK
&& mIsDuringSimSlotMapping) {
// Continue to switch the SimSlotMapping, after the esim is disabled.
mIsDuringSimSlotMapping = false;
mSwitchSlotSidecar.runSwitchToEuiccSlot(getTargetSlot(), mPort, mRemovedSubInfo);
} else {
super.onActionReceived();
}
}
}

View File

@@ -31,15 +31,14 @@ import com.android.settings.network.telephony.EuiccOperationSidecar;
*/
public class SwitchToRemovableSlotSidecar extends EuiccOperationSidecar
implements SidecarFragment.Listener {
private static final String TAG = "DisableSubscriptionAndSwitchSlotSidecar";
private static final String TAG = "SwitchRemovableSidecar";
private static final String ACTION_DISABLE_SUBSCRIPTION_AND_SWITCH_SLOT =
"disable_subscription_and_switch_slot_sidecar";
// Stateless members.
private SwitchToEuiccSubscriptionSidecar mSwitchToSubscriptionSidecar;
private SwitchSlotSidecar mSwitchSlotSidecar;
private int mPhysicalSlotId;
private SubscriptionInfo mRemovedSubInfo;
/** Returns a SwitchToRemovableSlotSidecar sidecar instance. */
public static SwitchToRemovableSlotSidecar get(FragmentManager fm) {
@@ -51,20 +50,17 @@ public class SwitchToRemovableSlotSidecar extends EuiccOperationSidecar
super.onCreate(savedInstanceState);
mSwitchToSubscriptionSidecar =
SwitchToEuiccSubscriptionSidecar.get(getChildFragmentManager());
mSwitchSlotSidecar = SwitchSlotSidecar.get(getChildFragmentManager());
}
@Override
public void onResume() {
super.onResume();
mSwitchToSubscriptionSidecar.addListener(this);
mSwitchSlotSidecar.addListener(this);
}
@Override
public void onPause() {
mSwitchToSubscriptionSidecar.removeListener(this);
mSwitchSlotSidecar.removeListener(this);
super.onPause();
}
@@ -90,18 +86,52 @@ public class SwitchToRemovableSlotSidecar extends EuiccOperationSidecar
*
* @param physicalSlotId removable physical SIM slot ID.
*/
// ToDo: delete this api and refactor the related code.
public void run(int physicalSlotId) {
mPhysicalSlotId = physicalSlotId;
SubscriptionManager subscriptionManager =
getContext().getSystemService(SubscriptionManager.class);
if (SubscriptionUtil.getActiveSubscriptions(subscriptionManager).stream()
.anyMatch(SubscriptionInfo::isEmbedded)) {
// In SS mode, the esim is active, then inactivate the esim.
Log.i(TAG, "There is an active eSIM profile. Disable the profile first.");
// Use INVALID_SUBSCRIPTION_ID to disable the only active profile.
mSwitchToSubscriptionSidecar.run(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
mSwitchToSubscriptionSidecar.run(SubscriptionManager.INVALID_SUBSCRIPTION_ID, 0, null);
} else {
Log.i(TAG, "There is no active eSIM profiles. Start to switch to removable slot.");
mSwitchSlotSidecar.runSwitchToRemovableSlot(mPhysicalSlotId);
mSwitchSlotSidecar.runSwitchToRemovableSlot(mPhysicalSlotId, null);
}
}
/**
* Starts switching to the removable slot.
*
* @param physicalSlotId removable physical SIM slot ID.
* @param removedSubInfo if the all of slots have sims, it should remove the one of active sim.
* If the removedSubInfo is null, then use the default value.
* The default value is the removable physical SIM slot and portId 0.
*/
public void run(int physicalSlotId, SubscriptionInfo removedSubInfo) {
mPhysicalSlotId = physicalSlotId;
mRemovedSubInfo = removedSubInfo;
SubscriptionManager subscriptionManager =
getContext().getSystemService(SubscriptionManager.class);
if (!mTelephonyManager.isMultiSimEnabled()
&& SubscriptionUtil.getActiveSubscriptions(subscriptionManager).stream().anyMatch(
SubscriptionInfo::isEmbedded)) {
// In SS mode, the esim is active, then inactivate the esim.
Log.i(TAG, "There is an active eSIM profile. Disable the profile first.");
// Use INVALID_SUBSCRIPTION_ID to disable the only active profile.
mSwitchToSubscriptionSidecar.run(SubscriptionManager.INVALID_SUBSCRIPTION_ID, 0, null);
} else if (mTelephonyManager.isMultiSimEnabled() && mRemovedSubInfo != null) {
// In DSDS mode+MEP, if the replaced esim is active, then it should be disabled esim
// profile before changing SimSlotMapping process.
// Use INVALID_SUBSCRIPTION_ID to disable the esim profile.
mSwitchToSubscriptionSidecar.run(SubscriptionManager.INVALID_SUBSCRIPTION_ID,
mRemovedSubInfo.getPortIndex(), null);
} else {
Log.i(TAG, "Start to switch to removable slot.");
mSwitchSlotSidecar.runSwitchToRemovableSlot(mPhysicalSlotId, mRemovedSubInfo);
}
}
@@ -109,10 +139,9 @@ public class SwitchToRemovableSlotSidecar extends EuiccOperationSidecar
switch (mSwitchToSubscriptionSidecar.getState()) {
case State.SUCCESS:
mSwitchToSubscriptionSidecar.reset();
Log.i(
TAG,
Log.i(TAG,
"Successfully disabled eSIM profile. Start to switch to Removable slot.");
mSwitchSlotSidecar.runSwitchToRemovableSlot(mPhysicalSlotId);
mSwitchSlotSidecar.runSwitchToRemovableSlot(mPhysicalSlotId, mRemovedSubInfo);
break;
case State.ERROR:
mSwitchToSubscriptionSidecar.reset();

View File

@@ -19,8 +19,10 @@ package com.android.settings.network;
import android.annotation.IntDef;
import android.content.Context;
import android.provider.Settings;
import android.telephony.SubscriptionInfo;
import android.telephony.TelephonyManager;
import android.telephony.UiccSlotInfo;
import android.telephony.UiccSlotMapping;
import android.util.Log;
import com.android.settingslib.utils.ThreadUtils;
@@ -29,17 +31,21 @@ import com.google.common.collect.ImmutableList;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
// ToDo: to do the refactor for renaming
public class UiccSlotUtil {
private static final String TAG = "UiccSlotUtil";
private static final long DEFAULT_WAIT_AFTER_SWITCH_TIMEOUT_MILLIS = 25 * 1000L;
;
public static final int INVALID_PHYSICAL_SLOT_ID = -1;
public static final int INVALID_PORT_ID = -1;
/**
* Mode for switching to eSIM slot which decides whether there is cleanup process, e.g.
@@ -80,40 +86,104 @@ public class UiccSlotUtil {
* @param context the application context.
* @throws UiccSlotsException if there is an error.
*/
//ToDo: delete this api and refactor the related code.
public static synchronized void switchToRemovableSlot(int slotId, Context context)
throws UiccSlotsException {
switchToRemovableSlot(context, slotId, null);
}
/**
* Switches to the removable slot. It waits for SIM_STATE_LOADED after switch. If slotId is
* INVALID_PHYSICAL_SLOT_ID, the method will use the first detected inactive removable slot.
*
* @param slotId the physical removable slot id.
* @param context the application context.
* @param removedSubInfo In the DSDS+MEP mode, if the all of slots have sims, it should
* remove the one of active sim.
* If the removedSubInfo is null, then use the default value.
* The default value is the esim slot and portId 0.
* @throws UiccSlotsException if there is an error.
*/
public static synchronized void switchToRemovableSlot(Context context, int slotId,
SubscriptionInfo removedSubInfo) throws UiccSlotsException {
if (ThreadUtils.isMainThread()) {
throw new IllegalThreadStateException(
"Do not call switchToRemovableSlot on the main thread.");
}
TelephonyManager telMgr = context.getSystemService(TelephonyManager.class);
if (telMgr.isMultiSimEnabled()) {
// If this device supports multiple active slots, don't mess with TelephonyManager.
Log.i(TAG, "Multiple active slots supported. Not calling switchSlots.");
return;
}
UiccSlotInfo[] slots = telMgr.getUiccSlotsInfo();
if (slotId == INVALID_PHYSICAL_SLOT_ID) {
for (int i = 0; i < slots.length; i++) {
if (slots[i].isRemovable()
&& !slots[i].getPorts().stream().findFirst().get().isActive()
&& slots[i].getCardStateInfo() != UiccSlotInfo.CARD_STATE_INFO_ERROR
&& slots[i].getCardStateInfo() != UiccSlotInfo.CARD_STATE_INFO_RESTRICTED) {
performSwitchToRemovableSlot(i, context);
return;
}
}
} else {
if (slotId >= slots.length || !slots[slotId].isRemovable()) {
throw new UiccSlotsException("The given slotId is not a removable slot: " + slotId);
}
if (!slots[slotId].getPorts().stream().findFirst().get().isActive()) {
performSwitchToRemovableSlot(slotId, context);
}
}
int inactiveRemovableSlot = getInactiveRemovableSlot(telMgr.getUiccSlotsInfo(), slotId);
performSwitchToSlot(telMgr,
prepareUiccSlotMappingsForRemovableSlot(telMgr.getSimSlotMapping(),
inactiveRemovableSlot, removedSubInfo, telMgr.isMultiSimEnabled()),
context);
}
private static void performSwitchToRemovableSlot(int slotId, Context context)
/**
* Switches to the Euicc slot. It waits for SIM_STATE_LOADED after switch.
*
* @param context the application context.
* @param slotId the Euicc slot id.
* @param port the Euicc slot port id.
* @param removedSubInfo In the DSDS+MEP mode, if the all of slots have sims, it should
* remove the one of active sim.
* If the removedSubInfo is null, then it uses the default value.
* The default value is the esim slot and portId 0.
* @throws UiccSlotsException if there is an error.
*/
public static synchronized void switchToEuiccSlot(Context context, int slotId, int port,
SubscriptionInfo removedSubInfo) throws UiccSlotsException {
if (ThreadUtils.isMainThread()) {
throw new IllegalThreadStateException(
"Do not call switchToRemovableSlot on the main thread.");
}
TelephonyManager telMgr = context.getSystemService(TelephonyManager.class);
Collection<UiccSlotMapping> uiccSlotMappings = telMgr.getSimSlotMapping();
Log.i(TAG, "The SimSlotMapping: " + uiccSlotMappings);
if (isTargetSlotActive(uiccSlotMappings, slotId, port)) {
Log.i(TAG, "The slot is active, then the sim can enable directly.");
return;
}
Collection<UiccSlotMapping> newUiccSlotMappings = new ArrayList<>();
if (!telMgr.isMultiSimEnabled()) {
// In the 'SS mode', the port is 0.
newUiccSlotMappings.add(new UiccSlotMapping(port, slotId, 0));
} else {
// DSDS+MEP
// The target slot+port is not active, but the all of logical slots are full. It
// needs to replace one of logical slots.
int removedSlot =
(removedSubInfo != null) ? removedSubInfo.getSimSlotIndex() : slotId;
int removedPort = (removedSubInfo != null) ? removedSubInfo.getPortIndex() : 0;
Log.i(TAG,
String.format("Start to set SimSlotMapping from slot%d-port%d to slot%d-port%d",
slotId, port, removedSlot, removedPort));
newUiccSlotMappings =
uiccSlotMappings.stream().map(uiccSlotMapping -> {
if (uiccSlotMapping.getPhysicalSlotIndex() == removedSlot
&& uiccSlotMapping.getPortIndex() == removedPort) {
return new UiccSlotMapping(port, slotId,
uiccSlotMapping.getLogicalSlotIndex());
}
return uiccSlotMapping;
}).collect(Collectors.toList());
}
Log.i(TAG, "The SimSlotMapping: " + newUiccSlotMappings);
performSwitchToSlot(telMgr, newUiccSlotMappings, context);
}
private static boolean isTargetSlotActive(Collection<UiccSlotMapping> uiccSlotMappings,
int slotId, int port) {
return uiccSlotMappings.stream()
.anyMatch(
uiccSlotMapping -> uiccSlotMapping.getPhysicalSlotIndex() == slotId
&& uiccSlotMapping.getPortIndex() == port);
}
private static void performSwitchToSlot(TelephonyManager telMgr,
Collection<UiccSlotMapping> uiccSlotMappings, Context context)
throws UiccSlotsException {
CarrierConfigChangedReceiver receiver = null;
long waitingTimeMillis =
@@ -125,7 +195,7 @@ public class UiccSlotUtil {
CountDownLatch latch = new CountDownLatch(1);
receiver = new CarrierConfigChangedReceiver(latch);
receiver.registerOn(context);
switchSlots(context, slotId);
telMgr.setSimSlotMapping(uiccSlotMappings);
latch.await(waitingTimeMillis, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
@@ -138,22 +208,80 @@ public class UiccSlotUtil {
}
/**
* Changes the logical slot to physical slot mapping. OEM should override this to provide
* device-specific implementation if the device supports switching slots.
*
* @param context the application context.
* @param physicalSlots List of physical slot ids in the order of logical slots.
* @param slots The UiccSlotInfo list.
* @param slotId The physical removable slot id.
* @return The inactive physical removable slot id. If the physical removable slot id is
* active, then return -1.
* @throws UiccSlotsException if there is an error.
*/
private static void switchSlots(Context context, int... physicalSlots)
private static int getInactiveRemovableSlot(UiccSlotInfo[] slots, int slotId)
throws UiccSlotsException {
TelephonyManager telMgr = context.getSystemService(TelephonyManager.class);
if (telMgr.isMultiSimEnabled()) {
// If this device supports multiple active slots, don't mess with TelephonyManager.
Log.i(TAG, "Multiple active slots supported. Not calling switchSlots.");
return;
if (slots == null) {
throw new UiccSlotsException("UiccSlotInfo is null");
}
if (!telMgr.switchSlots(physicalSlots)) {
throw new UiccSlotsException("Failed to switch slots");
if (slotId == INVALID_PHYSICAL_SLOT_ID) {
for (int i = 0; i < slots.length; i++) {
if (slots[i].isRemovable()
&& !slots[i].getPorts().stream().findFirst().get().isActive()
&& slots[i].getCardStateInfo() != UiccSlotInfo.CARD_STATE_INFO_ERROR
&& slots[i].getCardStateInfo() != UiccSlotInfo.CARD_STATE_INFO_RESTRICTED) {
return i;
}
}
} else {
if (slotId >= slots.length || !slots[slotId].isRemovable()) {
throw new UiccSlotsException("The given slotId is not a removable slot: " + slotId);
}
if (!slots[slotId].getPorts().stream().findFirst().get().isActive()) {
return slotId;
}
}
return INVALID_PHYSICAL_SLOT_ID;
}
private static Collection<UiccSlotMapping> prepareUiccSlotMappingsForRemovableSlot(
Collection<UiccSlotMapping> uiccSlotMappings, int slotId,
SubscriptionInfo removedSubInfo, boolean isMultiSimEnabled) {
if (slotId == INVALID_PHYSICAL_SLOT_ID
|| uiccSlotMappings.stream().anyMatch(uiccSlotMapping ->
uiccSlotMapping.getPhysicalSlotIndex() == slotId
&& uiccSlotMapping.getPortIndex() == 0)) {
// The slot is invalid slot id, then to skip this.
// The slot is active, then the sim can enable directly.
return uiccSlotMappings;
}
Collection<UiccSlotMapping> newUiccSlotMappings = new ArrayList<>();
if (!isMultiSimEnabled) {
// In the 'SS mode', the port is 0.
newUiccSlotMappings.add(new UiccSlotMapping(0, slotId, 0));
} else if (removedSubInfo != null) {
// DSDS+MEP
// The target slot+port is not active, but the all of logical slots are full. It
// needs to replace one of logical slots.
Log.i(TAG,
String.format("Start to set SimSlotMapping from slot%d-port%d to slot%d-port%d",
slotId, 0, removedSubInfo.getSimSlotIndex(),
removedSubInfo.getPortIndex()));
newUiccSlotMappings =
uiccSlotMappings.stream().map(uiccSlotMapping -> {
if (uiccSlotMapping.getPhysicalSlotIndex()
== removedSubInfo.getSimSlotIndex()
&& uiccSlotMapping.getPortIndex()
== removedSubInfo.getPortIndex()) {
return new UiccSlotMapping(0, slotId,
uiccSlotMapping.getLogicalSlotIndex());
}
return uiccSlotMapping;
}).collect(Collectors.toList());
} else {
// DSDS+no MEP
// The removable slot should be in UiccSlotMapping.
newUiccSlotMappings = uiccSlotMappings;
Log.i(TAG, "The removedSubInfo is null");
}
Log.i(TAG, "The SimSlotMapping: " + newUiccSlotMappings);
return newUiccSlotMappings;
}
}

View File

@@ -16,13 +16,14 @@
package com.android.settings.network.telephony;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.text.TextUtils;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.FragmentActivity;
/** Fragment to show an alert dialog which only has the positive button. */
public class AlertDialogFragment extends BaseDialogFragment
implements DialogInterface.OnClickListener {
@@ -37,13 +38,13 @@ public class AlertDialogFragment extends BaseDialogFragment
* @param title
* @param msg
*/
public static void show(Activity activity, String title, String msg) {
public static void show(FragmentActivity activity, String title, String msg) {
AlertDialogFragment fragment = new AlertDialogFragment();
Bundle arguments = new Bundle();
arguments.putString(ARG_TITLE, title);
arguments.putString(ARG_MSG, msg);
fragment.setArguments(arguments);
fragment.show(activity.getFragmentManager(), TAG);
fragment.show(activity.getSupportFragmentManager(), TAG);
}
@Override
@@ -55,7 +56,7 @@ public class AlertDialogFragment extends BaseDialogFragment
if (!TextUtils.isEmpty(getArguments().getString(ARG_MSG))) {
builder.setMessage(getArguments().getString(ARG_MSG));
}
return builder.show();
return builder.create();
}
@Override

View File

@@ -17,11 +17,11 @@
package com.android.settings.network.telephony;
import android.app.Activity;
import android.app.DialogFragment;
import android.app.Fragment;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
/**
* Base dialog fragment class with the functionality to make a fragment or an activity as a listener

View File

@@ -16,13 +16,24 @@
package com.android.settings.network.telephony;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.FragmentActivity;
import com.android.settings.R;
import java.util.ArrayList;
/** Fragment to show a confirm dialog. The caller should implement onConfirmListener. */
public class ConfirmDialogFragment extends BaseDialogFragment
@@ -32,6 +43,7 @@ public class ConfirmDialogFragment extends BaseDialogFragment
private static final String ARG_MSG = "msg";
private static final String ARG_POS_BUTTON_STRING = "pos_button_string";
private static final String ARG_NEG_BUTTON_STRING = "neg_button_string";
private static final String ARG_LIST = "list";
/**
* Interface defining the method that will be invoked when the user has done with the dialog.
@@ -39,15 +51,19 @@ public class ConfirmDialogFragment extends BaseDialogFragment
public interface OnConfirmListener {
/**
* @param tag The tag in the caller.
* @param confirmed True if the user has clicked the positive button. False if the user has
* @param confirmed True if the user has clicked the positive button. False if the
* user has
* clicked the negative button or cancel the dialog.
* @param itemPosition It is the position of item, if user selects one of the list item.
* If the user select "cancel" or the dialog does not have list, then
* the value is -1.
*/
void onConfirm(int tag, boolean confirmed);
void onConfirm(int tag, boolean confirmed, int itemPosition);
}
/** Displays a confirmation dialog which has confirm and cancel buttons. */
public static <T> void show(
Activity activity,
FragmentActivity activity,
Class<T> callbackInterfaceClass,
int tagInCaller,
String title,
@@ -62,7 +78,29 @@ public class ConfirmDialogFragment extends BaseDialogFragment
arguments.putString(ARG_NEG_BUTTON_STRING, negButtonString);
setListener(activity, null, callbackInterfaceClass, tagInCaller, arguments);
fragment.setArguments(arguments);
fragment.show(activity.getFragmentManager(), TAG);
fragment.show(activity.getSupportFragmentManager(), TAG);
}
/** Displays a confirmation dialog which has confirm and cancel buttons and carrier list.*/
public static <T> void show(
FragmentActivity activity,
Class<T> callbackInterfaceClass,
int tagInCaller,
String title,
String msg,
String posButtonString,
String negButtonString,
ArrayList<String> list) {
ConfirmDialogFragment fragment = new ConfirmDialogFragment();
Bundle arguments = new Bundle();
arguments.putString(ARG_TITLE, title);
arguments.putCharSequence(ARG_MSG, msg);
arguments.putString(ARG_POS_BUTTON_STRING, posButtonString);
arguments.putString(ARG_NEG_BUTTON_STRING, negButtonString);
arguments.putStringArrayList(ARG_LIST, list);
setListener(activity, null, callbackInterfaceClass, tagInCaller, arguments);
fragment.setArguments(arguments);
fragment.show(activity.getSupportFragmentManager(), TAG);
}
@Override
@@ -71,37 +109,87 @@ public class ConfirmDialogFragment extends BaseDialogFragment
String message = getArguments().getString(ARG_MSG);
String posBtnString = getArguments().getString(ARG_POS_BUTTON_STRING);
String negBtnString = getArguments().getString(ARG_NEG_BUTTON_STRING);
ArrayList<String> list = getArguments().getStringArrayList(ARG_LIST);
Log.i("Showing dialog with title = %s", title);
AlertDialog.Builder builder =
new AlertDialog.Builder(getContext())
.setTitle(title)
Log.i(TAG, "Showing dialog with title =" + title);
AlertDialog.Builder builder = new AlertDialog.Builder(getContext())
.setPositiveButton(posBtnString, this)
.setNegativeButton(negBtnString, this);
if (list != null && !list.isEmpty()) {
Log.i(TAG, "list =" + list.toString());
View content = LayoutInflater.from(getContext()).inflate(
R.layout.sim_confirm_dialog_multiple_enabled_profiles_supported, null);
if (!TextUtils.isEmpty(title)) {
View titleView = LayoutInflater.from(getContext()).inflate(
R.layout.sim_confirm_dialog_title_multiple_enabled_profiles_supported,
null);
TextView titleTextView = titleView.findViewById(R.id.title);
titleTextView.setText(title);
builder.setCustomTitle(titleTextView);
}
TextView dialogMessage = content.findViewById(R.id.msg);
if (!TextUtils.isEmpty(message) && dialogMessage != null) {
dialogMessage.setText(message);
}
final ArrayAdapter<String> arrayAdapterItems = new ArrayAdapter<String>(
getContext(),
R.layout.sim_confirm_dialog_item_multiple_enabled_profiles_supported, list);
final ListView lvItems = content.findViewById(R.id.carrier_list);
if (lvItems != null) {
lvItems.setAdapter(arrayAdapterItems);
lvItems.setChoiceMode(ListView.CHOICE_MODE_NONE);
lvItems.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
Log.i(TAG, "list onClick =" + position);
Log.i(TAG, "list item =" + list.get(position));
if (position == list.size() - 1) {
// user select the "cancel" item;
informCaller(false, -1);
} else {
informCaller(true, position);
}
}
});
}
builder.setView(content);
} else {
if (!TextUtils.isEmpty(title)) {
builder.setTitle(title);
}
if (!TextUtils.isEmpty(message)) {
builder.setMessage(message);
}
AlertDialog dialog = builder.show();
}
AlertDialog dialog = builder.create();
dialog.setCanceledOnTouchOutside(false);
return dialog;
}
@Override
public void onClick(DialogInterface dialog, int which) {
informCaller(which == DialogInterface.BUTTON_POSITIVE);
Log.i(TAG, "dialog onClick =" + which);
informCaller(which == DialogInterface.BUTTON_POSITIVE, -1);
}
@Override
public void onCancel(DialogInterface dialog) {
informCaller(false);
informCaller(false, -1);
}
private void informCaller(boolean confirmed) {
private void informCaller(boolean confirmed, int itemPosition) {
OnConfirmListener listener = getListener(OnConfirmListener.class);
if (listener == null) {
return;
}
listener.onConfirm(getTagInCaller(), confirmed);
listener.onConfirm(getTagInCaller(), confirmed, itemPosition);
}
}

View File

@@ -96,7 +96,7 @@ public class DeleteEuiccSubscriptionDialogActivity extends SubscriptionActionDia
}
@Override
public void onConfirm(int tag, boolean confirmed) {
public void onConfirm(int tag, boolean confirmed, int itemPosition) {
if (!confirmed) {
finish();
return;

View File

@@ -24,10 +24,12 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.SystemClock;
import android.telephony.TelephonyManager;
import android.telephony.euicc.EuiccManager;
import android.util.Log;
import com.android.settings.SidecarFragment;
import com.android.settings.network.SwitchSlotSidecar;
import java.util.concurrent.atomic.AtomicInteger;
@@ -37,7 +39,8 @@ import java.util.concurrent.atomic.AtomicInteger;
* should implement its own get() function to return an instance of that class, and implement the
* functional class like run() to actually trigger the function in EuiccManager.
*/
public abstract class EuiccOperationSidecar extends SidecarFragment {
public abstract class EuiccOperationSidecar extends SidecarFragment
implements SidecarFragment.Listener{
private static final String TAG = "EuiccOperationSidecar";
private static final int REQUEST_CODE = 0;
private static final String EXTRA_OP_ID = "op_id";
@@ -45,6 +48,9 @@ public abstract class EuiccOperationSidecar extends SidecarFragment {
new AtomicInteger((int) SystemClock.elapsedRealtime());
protected EuiccManager mEuiccManager;
protected TelephonyManager mTelephonyManager;
protected SwitchSlotSidecar mSwitchSlotSidecar;
private int mResultCode;
private int mDetailedCode;
@@ -107,6 +113,8 @@ public abstract class EuiccOperationSidecar extends SidecarFragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mEuiccManager = getContext().getSystemService(EuiccManager.class);
mTelephonyManager = getContext().getSystemService(TelephonyManager.class);
mSwitchSlotSidecar = SwitchSlotSidecar.get(getChildFragmentManager());
getContext()
.getApplicationContext()
@@ -117,12 +125,42 @@ public abstract class EuiccOperationSidecar extends SidecarFragment {
null);
}
@Override
public void onResume() {
super.onResume();
mSwitchSlotSidecar.addListener(this);
}
@Override
public void onPause() {
mSwitchSlotSidecar.removeListener(this);
super.onPause();
}
@Override
public void onDestroy() {
getContext().getApplicationContext().unregisterReceiver(mReceiver);
super.onDestroy();
}
@Override
public void onStateChange(SidecarFragment fragment) {
if (fragment == mSwitchSlotSidecar) {
switch (mSwitchSlotSidecar.getState()) {
case State.SUCCESS:
mSwitchSlotSidecar.reset();
Log.i(TAG, "mSwitchSlotSidecar SUCCESS");
break;
case State.ERROR:
mSwitchSlotSidecar.reset();
Log.i(TAG, "mSwitchSlotSidecar ERROR");
break;
}
} else {
Log.wtf(TAG, "Received state change from a sidecar not expected.");
}
}
public int getResultCode() {
return mResultCode;
}

View File

@@ -16,12 +16,13 @@
package com.android.settings.network.telephony;
import android.app.Activity;
import android.os.Bundle;
import android.telephony.SubscriptionManager;
import androidx.fragment.app.FragmentActivity;
/** The base class for subscription action dialogs */
public class SubscriptionActionDialogActivity extends Activity {
public class SubscriptionActionDialogActivity extends FragmentActivity {
private static final String TAG = "SubscriptionActionDialogActivity";
// Arguments

View File

@@ -23,9 +23,11 @@ import android.os.UserManager;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.UiccCardInfo;
import android.telephony.UiccSlotInfo;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R;
@@ -39,7 +41,9 @@ import com.android.settings.sim.SimActivationNotifier;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/** This dialog activity handles both eSIM and pSIM subscriptions enabling and disabling. */
public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogActivity
@@ -54,8 +58,15 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc
private static final int DIALOG_TAG_ENABLE_SIM_CONFIRMATION = 2;
private static final int DIALOG_TAG_ENABLE_DSDS_CONFIRMATION = 3;
private static final int DIALOG_TAG_ENABLE_DSDS_REBOOT_CONFIRMATION = 4;
private static final int DIALOG_TAG_ENABLE_SIM_CONFIRMATION_MEP = 5;
// Number of SIMs for DSDS
private static final int NUM_OF_SIMS_FOR_DSDS = 2;
// Support RTL mode
private static final String LINE_BREAK = "\n";
private static final int LINE_BREAK_OFFSET_ONE = 1;
private static final int LINE_BREAK_OFFSET_TWO = 2;
private static final String RTL_MARK = "\u200F";
/**
* Returns an intent of ToggleSubscriptionDialogActivity.
@@ -78,11 +89,12 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc
private boolean mEnable;
private boolean mIsEsimOperation;
private TelephonyManager mTelMgr;
private boolean isRtlMode;
private List<SubscriptionInfo> mActiveSubInfos;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
int subId = intent.getIntExtra(ARG_SUB_ID, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
mTelMgr = getSystemService(TelephonyManager.class);
@@ -100,6 +112,7 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc
return;
}
mActiveSubInfos = SubscriptionUtil.getActiveSubscriptions(mSubscriptionManager);
mSubInfo = SubscriptionUtil.getSubById(mSubscriptionManager, subId);
mIsEsimOperation = mSubInfo != null && mSubInfo.isEmbedded();
mSwitchToEuiccSubscriptionSidecar =
@@ -107,6 +120,9 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc
mSwitchToRemovableSlotSidecar = SwitchToRemovableSlotSidecar.get(getFragmentManager());
mEnableMultiSimSidecar = EnableMultiSimSidecar.get(getFragmentManager());
mEnable = intent.getBooleanExtra(ARG_enable, true);
isRtlMode = getResources().getConfiguration().getLayoutDirection()
== View.LAYOUT_DIRECTION_RTL;
Log.i(TAG, "isMultipleEnabledProfilesSupported():" + isMultipleEnabledProfilesSupported());
if (savedInstanceState == null) {
if (mEnable) {
@@ -145,7 +161,7 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc
}
@Override
public void onConfirm(int tag, boolean confirmed) {
public void onConfirm(int tag, boolean confirmed, int itemPosition) {
if (!confirmed
&& tag != DIALOG_TAG_ENABLE_DSDS_CONFIRMATION
&& tag != DIALOG_TAG_ENABLE_DSDS_REBOOT_CONFIRMATION) {
@@ -153,14 +169,16 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc
return;
}
SubscriptionInfo removedSubInfo = null;
switch (tag) {
case DIALOG_TAG_DISABLE_SIM_CONFIRMATION:
if (mIsEsimOperation) {
Log.i(TAG, "Disabling the eSIM profile.");
showProgressDialog(
getString(R.string.privileged_action_disable_sub_dialog_progress));
int port = mSubInfo != null ? mSubInfo.getPortIndex() : 0;
mSwitchToEuiccSubscriptionSidecar.run(
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
SubscriptionManager.INVALID_SUBSCRIPTION_ID, port, null);
return;
}
Log.i(TAG, "Disabling the pSIM profile.");
@@ -192,6 +210,11 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc
SimActivationNotifier.setShowSimSettingsNotification(this, true);
mTelMgr.switchMultiSimConfig(NUM_OF_SIMS_FOR_DSDS);
break;
case DIALOG_TAG_ENABLE_SIM_CONFIRMATION_MEP:
if (itemPosition != -1) {
removedSubInfo = (mActiveSubInfos != null) ? mActiveSubInfos.get(itemPosition)
: null;
}
case DIALOG_TAG_ENABLE_SIM_CONFIRMATION:
Log.i(TAG, "User confirmed to enable the subscription.");
if (mIsEsimOperation) {
@@ -200,12 +223,15 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc
R.string.sim_action_switch_sub_dialog_progress,
SubscriptionUtil.getUniqueSubscriptionDisplayName(
mSubInfo, this)));
mSwitchToEuiccSubscriptionSidecar.run(mSubInfo.getSubscriptionId());
mSwitchToEuiccSubscriptionSidecar.run(mSubInfo.getSubscriptionId(),
UiccSlotUtil.INVALID_PORT_ID,
removedSubInfo);
return;
}
showProgressDialog(
getString(R.string.sim_action_enabling_sim_without_carrier_name));
mSwitchToRemovableSlotSidecar.run(UiccSlotUtil.INVALID_PHYSICAL_SLOT_ID);
mSwitchToRemovableSlotSidecar.run(UiccSlotUtil.INVALID_PHYSICAL_SLOT_ID,
removedSubInfo);
break;
default:
Log.e(TAG, "Unrecognized confirmation dialog tag: " + tag);
@@ -216,8 +242,7 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc
private void handleSwitchToEuiccSubscriptionSidecarStateChange() {
switch (mSwitchToEuiccSubscriptionSidecar.getState()) {
case SidecarFragment.State.SUCCESS:
Log.i(
TAG,
Log.i(TAG,
String.format(
"Successfully %s the eSIM profile.",
mEnable ? "enable" : "disable"));
@@ -226,8 +251,7 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc
finish();
break;
case SidecarFragment.State.ERROR:
Log.i(
TAG,
Log.i(TAG,
String.format(
"Failed to %s the eSIM profile.", mEnable ? "enable" : "disable"));
mSwitchToEuiccSubscriptionSidecar.reset();
@@ -281,7 +305,8 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc
if (mIsEsimOperation) {
Log.i(TAG, "DSDS enabled, start to enable profile: " + mSubInfo.getSubscriptionId());
// For eSIM operations, we simply switch to the selected eSIM profile.
mSwitchToEuiccSubscriptionSidecar.run(mSubInfo.getSubscriptionId());
mSwitchToEuiccSubscriptionSidecar.run(mSubInfo.getSubscriptionId(),
UiccSlotUtil.INVALID_PORT_ID, null);
return;
}
@@ -296,9 +321,7 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc
mSubscriptionManager.setUiccApplicationsEnabled(mSubInfo.getSubscriptionId(), mEnable);
finish();
} else {
Log.i(
TAG,
"The device does not support toggling pSIM. It is enough to just "
Log.i(TAG, "The device does not support toggling pSIM. It is enough to just "
+ "enable the removable slot.");
}
}
@@ -310,7 +333,10 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc
showEnableDsdsConfirmDialog();
return;
}
if (!mIsEsimOperation && mTelMgr.isMultiSimEnabled()) {
if (!mIsEsimOperation && mTelMgr.isMultiSimEnabled()
&& isRemovableSimEnabled()) {
// This case is for switching on psim when device is not multiple enable profile
// supported.
Log.i(TAG, "Toggle on pSIM, no dialog displayed.");
handleTogglePsimAction();
finish();
@@ -363,26 +389,55 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc
}
private void showEnableSimConfirmDialog() {
List<SubscriptionInfo> activeSubs =
SubscriptionUtil.getActiveSubscriptions(mSubscriptionManager);
SubscriptionInfo activeSub = activeSubs.isEmpty() ? null : activeSubs.get(0);
if (activeSub == null) {
if (mActiveSubInfos == null || mActiveSubInfos.isEmpty()) {
Log.i(TAG, "No active subscriptions available.");
showNonSwitchSimConfirmDialog();
return;
}
Log.i(TAG, "Found active subscription.");
boolean isBetweenEsim = mIsEsimOperation && activeSub.isEmbedded();
if (mTelMgr.isMultiSimEnabled() && !isBetweenEsim) {
Log.i(TAG, "mActiveSubInfos:" + mActiveSubInfos);
boolean isSwitchingBetweenEsims = mIsEsimOperation
&& mActiveSubInfos.stream().anyMatch(activeSubInfo -> activeSubInfo.isEmbedded());
boolean isMultiSimEnabled = mTelMgr.isMultiSimEnabled();
if (isMultiSimEnabled
&& !isMultipleEnabledProfilesSupported()
&& !isSwitchingBetweenEsims) {
// Showing the "no switch dialog" for below cases.
// DSDS mode + no MEP +
// (there is the active psim -> esim switch on => active (psim + esim))
showNonSwitchSimConfirmDialog();
return;
}
if (isMultiSimEnabled && isMultipleEnabledProfilesSupported()) {
if (mActiveSubInfos.size() < NUM_OF_SIMS_FOR_DSDS) {
// The sim can add into device directly, so showing the "no switch dialog".
// DSDS + MEP + (active sim < NUM_OF_SIMS_FOR_DSDS)
showNonSwitchSimConfirmDialog();
} else {
// The all of slots have sim, it needs to show the "MEP switch dialog".
// DSDS + MEP + two active sims
showMepSwitchSimConfirmDialog();
}
return;
}
// Showing the "switch dialog" for below cases.
// case1: SS mode + psim switch on from esim.
// case2: SS mode + esim switch from psim.
// case3: DSDS mode + No MEP + esim switch on from another esim.
SubscriptionInfo activeSub =
(isMultiSimEnabled && isSwitchingBetweenEsims)
? mActiveSubInfos.stream()
.filter(activeSubInfo -> activeSubInfo.isEmbedded())
.findFirst().get()
: mActiveSubInfos.get(0);
ConfirmDialogFragment.show(
this,
ConfirmDialogFragment.OnConfirmListener.class,
DIALOG_TAG_ENABLE_SIM_CONFIRMATION,
getSwitchSubscriptionTitle(),
getSwitchDialogBodyMsg(activeSub, isBetweenEsim),
getSwitchDialogBodyMsg(activeSub, isSwitchingBetweenEsims),
getSwitchDialogPosBtnText(),
getString(R.string.sim_action_cancel));
}
@@ -398,6 +453,35 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc
getString(R.string.sim_action_cancel));
}
private void showMepSwitchSimConfirmDialog() {
Log.i(TAG, "showMepSwitchSimConfirmDialog");
final CharSequence displayName = SubscriptionUtil.getUniqueSubscriptionDisplayName(
mSubInfo, this);
String title = getString(R.string.sim_action_switch_sub_dialog_mep_title, displayName);
final StringBuilder switchDialogMsg = new StringBuilder();
switchDialogMsg.append(
getString(R.string.sim_action_switch_sub_dialog_mep_text, displayName));
if (isRtlMode) {
/* There are two lines of message in the dialog, and the RTL symbols must be added
* before and after each sentence, so use the line break symbol to find the position.
* (Each message are all with two line break symbols)
*/
switchDialogMsg.insert(0, RTL_MARK)
.insert(switchDialogMsg.indexOf(LINE_BREAK) - LINE_BREAK_OFFSET_ONE, RTL_MARK)
.insert(switchDialogMsg.indexOf(LINE_BREAK) + LINE_BREAK_OFFSET_TWO, RTL_MARK)
.insert(switchDialogMsg.length(), RTL_MARK);
}
ConfirmDialogFragment.show(
this,
ConfirmDialogFragment.OnConfirmListener.class,
DIALOG_TAG_ENABLE_SIM_CONFIRMATION_MEP,
title,
switchDialogMsg.toString(),
null,
null,
getSwitchDialogBodyList());
}
private String getSwitchDialogPosBtnText() {
return mIsEsimOperation
? getString(
@@ -429,21 +513,47 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc
mSubInfo, this);
final CharSequence activeSubName = SubscriptionUtil.getUniqueSubscriptionDisplayName(
activeSub, this);
final StringBuilder switchDialogMsg = new StringBuilder();
if (betweenEsim && mIsEsimOperation) {
return getString(
switchDialogMsg.append(getString(
R.string.sim_action_switch_sub_dialog_text_downloaded,
subInfoName,
activeSubName);
activeSubName));
} else if (mIsEsimOperation) {
return getString(
switchDialogMsg.append(getString(
R.string.sim_action_switch_sub_dialog_text,
subInfoName,
activeSubName);
activeSubName));
} else {
return getString(
switchDialogMsg.append(getString(
R.string.sim_action_switch_sub_dialog_text_single_sim,
activeSubName);
activeSubName));
}
if (isRtlMode) {
/* There are two lines of message in the dialog, and the RTL symbols must be added
* before and after each sentence, so use the line break symbol to find the position.
* (Each message are all with two line break symbols)
*/
switchDialogMsg.insert(0, RTL_MARK)
.insert(switchDialogMsg.indexOf(LINE_BREAK) - LINE_BREAK_OFFSET_ONE, RTL_MARK)
.insert(switchDialogMsg.indexOf(LINE_BREAK) + LINE_BREAK_OFFSET_TWO, RTL_MARK)
.insert(switchDialogMsg.length(), RTL_MARK);
}
return switchDialogMsg.toString();
}
private ArrayList<String> getSwitchDialogBodyList() {
ArrayList<String> list = new ArrayList<String>(mActiveSubInfos.stream()
.map(subInfo -> {
CharSequence subInfoName = SubscriptionUtil.getUniqueSubscriptionDisplayName(
subInfo, this);
return getString(
R.string.sim_action_switch_sub_dialog_carrier_list_item_for_turning_off,
subInfoName);
})
.collect(Collectors.toList()));
list.add(getString(R.string.sim_action_cancel));
return list;
}
private boolean isDsdsConditionSatisfied() {
@@ -455,17 +565,7 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc
Log.i(TAG, "Hardware does not support DSDS.");
return false;
}
ImmutableList<UiccSlotInfo> slotInfos = UiccSlotUtil.getSlotInfos(mTelMgr);
boolean isRemovableSimEnabled =
slotInfos.stream()
.anyMatch(
slot ->
slot != null
&& slot.isRemovable()
&& slot.getPorts().stream().anyMatch(
port -> port.isActive())
&& slot.getCardStateInfo()
== UiccSlotInfo.CARD_STATE_INFO_PRESENT);
boolean isRemovableSimEnabled = isRemovableSimEnabled();
if (mIsEsimOperation && isRemovableSimEnabled) {
Log.i(TAG, "eSIM operation and removable SIM is enabled. DSDS condition satisfied.");
return true;
@@ -474,13 +574,36 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc
SubscriptionUtil.getActiveSubscriptions(mSubscriptionManager).stream()
.anyMatch(SubscriptionInfo::isEmbedded);
if (!mIsEsimOperation && isEsimProfileEnabled) {
Log.i(
TAG,
"Removable SIM operation and eSIM profile is enabled. DSDS condition"
Log.i(TAG, "Removable SIM operation and eSIM profile is enabled. DSDS condition"
+ " satisfied.");
return true;
}
Log.i(TAG, "DSDS condition not satisfied.");
return false;
}
private boolean isRemovableSimEnabled() {
ImmutableList<UiccSlotInfo> slotInfos = UiccSlotUtil.getSlotInfos(mTelMgr);
boolean isRemovableSimEnabled =
slotInfos.stream()
.anyMatch(
slot -> slot != null
&& slot.isRemovable()
&& slot.getPorts().stream().anyMatch(
port -> port.isActive())
&& slot.getCardStateInfo()
== UiccSlotInfo.CARD_STATE_INFO_PRESENT);
Log.i(TAG, "isRemovableSimEnabled: " + isRemovableSimEnabled);
return isRemovableSimEnabled;
}
private boolean isMultipleEnabledProfilesSupported() {
List<UiccCardInfo> cardInfos = mTelMgr.getUiccCardsInfo();
if (cardInfos == null) {
Log.w(TAG, "UICC cards info list is empty.");
return false;
}
return cardInfos.stream().anyMatch(
cardInfo -> cardInfo.isMultipleEnabledProfilesSupported());
}
}

View File

@@ -85,7 +85,7 @@ public class DsdsDialogActivity extends SubscriptionActionDialogActivity
}
@Override
public void onConfirm(int tag, boolean confirmed) {
public void onConfirm(int tag, boolean confirmed, int itemPosition) {
if (!confirmed) {
Log.i(TAG, "User cancel the dialog to enable DSDS.");
startChooseSimActivity();

View File

@@ -0,0 +1,172 @@
/*
* Copyright (C) 2021 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.Dialog;
import android.app.settings.SettingsEnums;
import android.content.DialogInterface;
import android.os.Bundle;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AlertDialog;
import com.android.settings.R;
import com.android.settings.network.SubscriptionUtil;
import java.util.List;
/**
* Presents a dialog asking the user if they want to switch the data to another sim
*/
public class SelectSpecificDataSimDialogFragment extends SimDialogFragment implements
DialogInterface.OnClickListener {
private static final String TAG = "PreferredSimDialogFrag";
private SubscriptionInfo mSubscriptionInfo;
/**
* @return the dialog fragment.
*/
public static SelectSpecificDataSimDialogFragment newInstance() {
final SelectSpecificDataSimDialogFragment
fragment = new SelectSpecificDataSimDialogFragment();
final Bundle args = initArguments(SimDialogActivity.DATA_PICK,
R.string.select_specific_sim_for_data_title);
fragment.setArguments(args);
return fragment;
}
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
final AlertDialog dialog = new AlertDialog.Builder(getContext())
.setNegativeButton(R.string.sim_action_no_thanks, null)
.create();
updateDialog(dialog);
return dialog;
}
@Override
public void onClick(DialogInterface dialog, int buttonClicked) {
if (buttonClicked != DialogInterface.BUTTON_POSITIVE) {
return;
}
final SimDialogActivity activity = (SimDialogActivity) getActivity();
final SubscriptionInfo info = getTargetSubscriptionInfo();
if (info != null) {
activity.onSubscriptionSelected(getDialogType(), info.getSubscriptionId());
}
}
private SubscriptionInfo getNonDefaultDataSubscriptionInfo(SubscriptionInfo dds) {
List<SubscriptionInfo> subInfos = getSubscriptionManager().getActiveSubscriptionInfoList();
if (subInfos == null || dds == null) {
return null;
}
return subInfos.stream().filter(subinfo -> subinfo != dds).findFirst().orElse(null);
}
private SubscriptionInfo getDefaultDataSubId() {
return getSubscriptionManager().getDefaultDataSubscriptionInfo();
}
private void updateDialog(AlertDialog dialog) {
Log.d(TAG, "Dialog updated, dismiss status: " + mWasDismissed);
if (mWasDismissed) {
return;
}
SubscriptionInfo activeSubInfo = getDefaultDataSubId();
SubscriptionInfo newSubInfo = getNonDefaultDataSubscriptionInfo(activeSubInfo);
if (newSubInfo == null || activeSubInfo == null) {
dismiss();
return;
}
setTargetSubscriptionInfo(newSubInfo);
CharSequence newDataCarrierName = SubscriptionUtil.getUniqueSubscriptionDisplayName(
newSubInfo, getContext());
CharSequence currentDataCarrierName = SubscriptionUtil.getUniqueSubscriptionDisplayName(
activeSubInfo, getContext());
String positive = getContext().getString(
R.string.select_specific_sim_for_data_button, newDataCarrierName);
String message = getContext().getString(R.string.select_specific_sim_for_data_msg,
newDataCarrierName, currentDataCarrierName);
View content = LayoutInflater.from(getContext()).inflate(
R.layout.sim_confirm_dialog_multiple_enabled_profiles_supported, null);
TextView dialogMessage = content.findViewById(R.id.msg);
if (!TextUtils.isEmpty(message) && dialogMessage != null) {
dialogMessage.setText(message);
}
final ListView lvItems = content.findViewById(R.id.carrier_list);
if (lvItems != null) {
lvItems.setVisibility(View.GONE);
}
final LinearLayout infoOutline = content.findViewById(R.id.info_outline_layout);
if (infoOutline != null) {
infoOutline.setVisibility(View.GONE);
}
dialog.setView(content);
View titleView = LayoutInflater.from(getContext()).inflate(
R.layout.sim_confirm_dialog_title_multiple_enabled_profiles_supported, null);
TextView titleTextView = titleView.findViewById(R.id.title);
titleTextView.setText(getContext().getString(getTitleResId(), newDataCarrierName));
dialog.setCustomTitle(titleTextView);
dialog.setButton(AlertDialog.BUTTON_POSITIVE, positive, this);
}
private void setTargetSubscriptionInfo(SubscriptionInfo subInfo) {
mSubscriptionInfo = subInfo;
}
private SubscriptionInfo getTargetSubscriptionInfo() {
return mSubscriptionInfo;
}
@Override
public void updateDialog() {
updateDialog((AlertDialog) getDialog());
}
@VisibleForTesting
protected SubscriptionManager getSubscriptionManager() {
return getContext().getSystemService(SubscriptionManager.class);
}
@Override
public int getMetricsCategory() {
return SettingsEnums.DIALOG_PREFERRED_SIM_PICKER;
}
}

View File

@@ -95,15 +95,16 @@ public class SimDialogActivity extends FragmentActivity {
private SimDialogFragment createFragment(int dialogType) {
switch (dialogType) {
case DATA_PICK:
return SimListDialogFragment.newInstance(dialogType, R.string.select_sim_for_data,
false /* includeAskEveryTime */);
return getDataPickDialogFramgent();
case CALLS_PICK:
return CallsSimListDialogFragment.newInstance(dialogType,
R.string.select_sim_for_calls,
true /* includeAskEveryTime */);
true /* includeAskEveryTime */,
false /* isCancelItemShowed */);
case SMS_PICK:
return SimListDialogFragment.newInstance(dialogType, R.string.select_sim_for_sms,
true /* includeAskEveryTime */);
true /* includeAskEveryTime */,
false /* isCancelItemShowed */);
case PREFERRED_PICK:
if (!getIntent().hasExtra(PREFERRED_SIM)) {
throw new IllegalArgumentException("Missing required extra " + PREFERRED_SIM);
@@ -111,12 +112,23 @@ public class SimDialogActivity extends FragmentActivity {
return PreferredSimDialogFragment.newInstance();
case SMS_PICK_FOR_MESSAGE:
return SimListDialogFragment.newInstance(dialogType, R.string.select_sim_for_sms,
false /* includeAskEveryTime */);
false /* includeAskEveryTime */,
false /* isCancelItemShowed */);
default:
throw new IllegalArgumentException("Invalid dialog type " + dialogType + " sent.");
}
}
private SimDialogFragment getDataPickDialogFramgent() {
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");
@@ -160,9 +172,11 @@ public class SimDialogActivity extends FragmentActivity {
final TelephonyManager telephonyManager = getSystemService(
TelephonyManager.class).createForSubscriptionId(subId);
subscriptionManager.setDefaultDataSubId(subId);
if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
telephonyManager.setDataEnabled(true);
Toast.makeText(this, R.string.data_switch_started, Toast.LENGTH_LONG).show();
}
}
private void setDefaultCallsSubId(final int subId) {
final PhoneAccountHandle phoneAccount = subscriptionIdToPhoneAccountHandle(subId);

View File

@@ -29,7 +29,7 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import androidx.annotation.NonNull;
@@ -38,7 +38,6 @@ import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AlertDialog;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.network.SubscriptionUtil;
import java.util.ArrayList;
@@ -52,16 +51,19 @@ public class SimListDialogFragment extends SimDialogFragment implements
DialogInterface.OnClickListener {
private static final String TAG = "SimListDialogFragment";
protected static final String KEY_INCLUDE_ASK_EVERY_TIME = "include_ask_every_time";
protected static final String KEY_SHOW_CANCEL_ITEM = "show_cancel_item";
private static final int LIST_VIEW_DIVIDER_LINE_WEIGHT = 2;
protected SelectSubscriptionAdapter mAdapter;
@VisibleForTesting
List<SubscriptionInfo> mSubscriptions;
public static SimListDialogFragment newInstance(int dialogType, int titleResId,
boolean includeAskEveryTime) {
boolean includeAskEveryTime, boolean isCancelItemShowed) {
final SimListDialogFragment fragment = new SimListDialogFragment();
final Bundle args = initArguments(dialogType, titleResId);
args.putBoolean(KEY_INCLUDE_ASK_EVERY_TIME, includeAskEveryTime);
args.putBoolean(KEY_SHOW_CANCEL_ITEM, isCancelItemShowed);
fragment.setArguments(args);
return fragment;
}
@@ -72,12 +74,20 @@ public class SimListDialogFragment extends SimDialogFragment implements
mSubscriptions = new ArrayList<>();
final AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
builder.setTitle(getTitleResId());
View titleView = LayoutInflater.from(getContext()).inflate(
R.layout.sim_confirm_dialog_title_multiple_enabled_profiles_supported, null);
TextView titleTextView = titleView.findViewById(R.id.title);
titleTextView.setText(getContext().getString(getTitleResId()));
builder.setCustomTitle(titleTextView);
mAdapter = new SelectSubscriptionAdapter(builder.getContext(), mSubscriptions);
setAdapter(builder);
final Dialog dialog = builder.create();
final AlertDialog dialog = builder.create();
ListView listView = dialog.getListView();
if (listView != null) {
listView.setDividerHeight(LIST_VIEW_DIVIDER_LINE_WEIGHT);
}
updateDialog();
return dialog;
}
@@ -112,10 +122,22 @@ public class SimListDialogFragment extends SimDialogFragment implements
}
return;
}
if (getArguments().getBoolean(KEY_INCLUDE_ASK_EVERY_TIME)) {
final List<SubscriptionInfo> tmp = new ArrayList<>(currentSubscriptions.size() + 1);
boolean includeAskEveryTime = getArguments().getBoolean(KEY_INCLUDE_ASK_EVERY_TIME);
boolean isCancelItemShowed = getArguments().getBoolean(KEY_SHOW_CANCEL_ITEM);
if (includeAskEveryTime || isCancelItemShowed) {
int arraySize = currentSubscriptions.size()
+ (includeAskEveryTime ? 1 : 0)
+ (isCancelItemShowed ? 1 : 0);
final List<SubscriptionInfo> tmp = new ArrayList<>(arraySize);
if (includeAskEveryTime) {
// add the value of 'AskEveryTime' item
tmp.add(null);
}
tmp.addAll(currentSubscriptions);
if (isCancelItemShowed) {
// add the value of 'Cancel' item
tmp.add(null);
}
currentSubscriptions = tmp;
}
if (currentSubscriptions.equals(mSubscriptions)) {
@@ -177,19 +199,23 @@ public class SimListDialogFragment extends SimDialogFragment implements
final TextView title = convertView.findViewById(R.id.title);
final TextView summary = convertView.findViewById(R.id.summary);
final ImageView icon = convertView.findViewById(R.id.icon);
if (sub == null) {
if (position == 0) {
title.setText(R.string.sim_calls_ask_first_prefs_title);
summary.setText("");
icon.setImageDrawable(mContext.getDrawable(R.drawable.ic_feedback_24dp));
icon.setImageTintList(
Utils.getColorAttr(mContext, android.R.attr.textColorSecondary));
} else {
title.setText(R.string.sim_action_cancel);
}
summary.setVisibility(View.GONE);
} else {
title.setText(SubscriptionUtil.getUniqueSubscriptionDisplayName(sub, mContext));
summary.setText(isMdnProvisioned(sub.getNumber()) ? sub.getNumber() : "");
icon.setImageBitmap(sub.createIconBitmap(mContext));
String phoneNumber = isMdnProvisioned(sub.getNumber()) ? sub.getNumber() : "";
if (!TextUtils.isEmpty(phoneNumber)) {
summary.setVisibility(View.VISIBLE);
summary.setText(phoneNumber);
} else {
summary.setVisibility(View.GONE);
}
}
return convertView;
}

View File

@@ -101,7 +101,7 @@ public class SwitchToEsimConfirmDialogActivity extends SubscriptionActionDialogA
}
@Override
public void onConfirm(int tag, boolean confirmed) {
public void onConfirm(int tag, boolean confirmed, int itemPosition) {
if (!confirmed) {
AlertDialogFragment.show(
this,

View File

@@ -53,7 +53,8 @@ public class SimListDialogFragmentTest extends SimDialogFragmentTestBase<SimList
final int dialogType = DATA_PICK;
setDialogType(dialogType);
mFragment = spy(SimListDialogFragment.newInstance(dialogType, R.string.select_sim_for_data,
false /* includeAskEveryTime */));
false /* includeAskEveryTime */,
false /* isCancelItemShowed */));
doReturn(null).when(mFragment).getCurrentSubscriptions();
startDialog();
verify(mFragment).dismiss();
@@ -64,7 +65,8 @@ public class SimListDialogFragmentTest extends SimDialogFragmentTestBase<SimList
final int dialogType = DATA_PICK;
setDialogType(dialogType);
mFragment = spy(SimListDialogFragment.newInstance(dialogType, R.string.select_sim_for_data,
false /* includeAskEveryTime */));
false /* includeAskEveryTime */,
false /* isCancelItemShowed */));
doReturn(Arrays.asList(mSim1, mSim2)).when(mFragment).getCurrentSubscriptions();
// Avoid problems robolectric has with our real adapter.
doNothing().when(mFragment).setAdapter(any());
@@ -84,7 +86,8 @@ public class SimListDialogFragmentTest extends SimDialogFragmentTestBase<SimList
final int dialogType = DATA_PICK;
setDialogType(dialogType);
mFragment = spy(SimListDialogFragment.newInstance(dialogType, R.string.select_sim_for_data,
false /* includeAskEveryTime */));
false /* includeAskEveryTime */,
false /* isCancelItemShowed */));
doReturn(Arrays.asList(mSim1, mSim2)).when(mFragment).getCurrentSubscriptions();
// Avoid problems robolectric has with our real adapter.
doNothing().when(mFragment).setAdapter(any());
@@ -101,7 +104,8 @@ public class SimListDialogFragmentTest extends SimDialogFragmentTestBase<SimList
final int dialogType = SMS_PICK;
setDialogType(dialogType);
mFragment = spy(SimListDialogFragment.newInstance(dialogType, R.string.select_sim_for_sms,
true /* includeAskEveryTime */));
true /* includeAskEveryTime */,
false /* isCancelItemShowed */));
doReturn(Arrays.asList(mSim1, mSim2)).when(mFragment).getCurrentSubscriptions();
// Avoid problems robolectric has with our real adapter.
doNothing().when(mFragment).setAdapter(any());