Merge "Initial setup for slot change receiver migration"
This commit is contained in:
229
src/com/android/settings/sim/receivers/SimSlotChangeHandler.java
Normal file
229
src/com/android/settings/sim/receivers/SimSlotChangeHandler.java
Normal file
@@ -0,0 +1,229 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.receivers;
|
||||
|
||||
import static android.content.Context.MODE_PRIVATE;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Looper;
|
||||
import android.provider.Settings;
|
||||
import android.telephony.SubscriptionInfo;
|
||||
import android.telephony.SubscriptionManager;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.telephony.UiccSlotInfo;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.settings.network.SubscriptionUtil;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/** Perform actions after a slot change event is triggered. */
|
||||
public class SimSlotChangeHandler {
|
||||
private static final String TAG = "SimSlotChangeHandler";
|
||||
|
||||
private static final String EUICC_PREFS = "euicc_prefs";
|
||||
private static final String KEY_REMOVABLE_SLOT_STATE = "removable_slot_state";
|
||||
|
||||
private static volatile SimSlotChangeHandler sSlotChangeHandler;
|
||||
|
||||
/** Returns a SIM slot change handler singleton. */
|
||||
public static SimSlotChangeHandler get() {
|
||||
if (sSlotChangeHandler == null) {
|
||||
synchronized (SimSlotChangeHandler.class) {
|
||||
if (sSlotChangeHandler == null) {
|
||||
sSlotChangeHandler = new SimSlotChangeHandler();
|
||||
}
|
||||
}
|
||||
}
|
||||
return sSlotChangeHandler;
|
||||
}
|
||||
|
||||
private SubscriptionManager mSubMgr;
|
||||
private TelephonyManager mTelMgr;
|
||||
private Context mContext;
|
||||
|
||||
void onSlotsStatusChange(Context context) {
|
||||
init(context);
|
||||
|
||||
if (Looper.myLooper() == Looper.getMainLooper()) {
|
||||
throw new IllegalStateException("Cannot be called from main thread.");
|
||||
}
|
||||
|
||||
if (mTelMgr.getActiveModemCount() > 1) {
|
||||
Log.i(TAG, "The device is already in DSDS mode. Do nothing.");
|
||||
return;
|
||||
}
|
||||
|
||||
UiccSlotInfo removableSlotInfo = getRemovableUiccSlotInfo();
|
||||
if (removableSlotInfo == null) {
|
||||
Log.e(TAG, "Unable to find the removable slot. Do nothing.");
|
||||
return;
|
||||
}
|
||||
|
||||
int lastRemovableSlotState = getLastRemovableSimSlotState(mContext);
|
||||
int currentRemovableSlotState = removableSlotInfo.getCardStateInfo();
|
||||
|
||||
// Sets the current removable slot state.
|
||||
setRemovableSimSlotState(mContext, currentRemovableSlotState);
|
||||
|
||||
if (lastRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_ABSENT
|
||||
&& currentRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_PRESENT) {
|
||||
handleSimInsert(removableSlotInfo);
|
||||
return;
|
||||
}
|
||||
if (lastRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_PRESENT
|
||||
&& currentRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_ABSENT) {
|
||||
handleSimRemove(removableSlotInfo);
|
||||
return;
|
||||
}
|
||||
Log.i(TAG, "Do nothing on slot status changes.");
|
||||
}
|
||||
|
||||
private void init(Context context) {
|
||||
mSubMgr =
|
||||
(SubscriptionManager)
|
||||
context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
|
||||
mTelMgr = context.getSystemService(TelephonyManager.class);
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
private void handleSimInsert(UiccSlotInfo removableSlotInfo) {
|
||||
Log.i(TAG, "Detect SIM inserted.");
|
||||
|
||||
if (!isSuwFinished(mContext)) {
|
||||
// TODO(b/170508680): Store the action and handle it after SUW is finished.
|
||||
Log.i(TAG, "Still in SUW. Handle SIM insertion after SUW is finished");
|
||||
return;
|
||||
}
|
||||
|
||||
if (removableSlotInfo.getIsActive()) {
|
||||
Log.i(TAG, "The removable slot is already active. Do nothing.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hasActiveEsimSubscription()) {
|
||||
if (mTelMgr.isMultiSimEnabled()) {
|
||||
Log.i(TAG, "Enabled profile exists. DSDS condition satisfied.");
|
||||
// TODO(b/170508680): Display DSDS dialog to ask users whether to enable DSDS.
|
||||
} else {
|
||||
Log.i(TAG, "Enabled profile exists. DSDS condition not satisfied.");
|
||||
// TODO(b/170508680): Display Choose a number to use screen for subscription
|
||||
// selection.
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Log.i(
|
||||
TAG,
|
||||
"No enabled eSIM profile. Ready to switch to removable slot and show"
|
||||
+ " notification.");
|
||||
// TODO(b/170508680): Switch the slot to the removebale slot and show the notification.
|
||||
}
|
||||
|
||||
private void handleSimRemove(UiccSlotInfo removableSlotInfo) {
|
||||
Log.i(TAG, "Detect SIM removed.");
|
||||
|
||||
if (!isSuwFinished(mContext)) {
|
||||
// TODO(b/170508680): Store the action and handle it after SUW is finished.
|
||||
Log.i(TAG, "Still in SUW. Handle SIM removal after SUW is finished");
|
||||
return;
|
||||
}
|
||||
|
||||
List<SubscriptionInfo> groupedEmbeddedSubscriptions = getGroupedEmbeddedSubscriptions();
|
||||
|
||||
if (groupedEmbeddedSubscriptions.size() == 0 || !removableSlotInfo.getIsActive()) {
|
||||
Log.i(TAG, "eSIM slot is active or no subscriptions exist. Do nothing.");
|
||||
return;
|
||||
}
|
||||
|
||||
// If there is only 1 eSIM profile exists, we ask the user if they want to switch to that
|
||||
// profile.
|
||||
if (groupedEmbeddedSubscriptions.size() == 1) {
|
||||
Log.i(TAG, "Only 1 eSIM profile found. Ask user's consent to switch.");
|
||||
// TODO(b/170508680): Display a dialog to ask users to switch.
|
||||
return;
|
||||
}
|
||||
|
||||
// If there are more than 1 eSIM profiles installed, we show a screen to let users to choose
|
||||
// the number they want to use.
|
||||
Log.i(TAG, "Multiple eSIM profiles found. Ask user which subscription to use.");
|
||||
// TODO(b/170508680): Display a dialog to ask user which SIM to switch.
|
||||
}
|
||||
|
||||
private int getLastRemovableSimSlotState(Context context) {
|
||||
final SharedPreferences prefs = context.getSharedPreferences(EUICC_PREFS, MODE_PRIVATE);
|
||||
return prefs.getInt(KEY_REMOVABLE_SLOT_STATE, UiccSlotInfo.CARD_STATE_INFO_ABSENT);
|
||||
}
|
||||
|
||||
private void setRemovableSimSlotState(Context context, int state) {
|
||||
final SharedPreferences prefs = context.getSharedPreferences(EUICC_PREFS, MODE_PRIVATE);
|
||||
prefs.edit().putInt(KEY_REMOVABLE_SLOT_STATE, state).apply();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private UiccSlotInfo getRemovableUiccSlotInfo() {
|
||||
UiccSlotInfo[] slotInfos = mTelMgr.getUiccSlotsInfo();
|
||||
if (slotInfos == null) {
|
||||
Log.e(TAG, "slotInfos is null. Unable to get slot infos.");
|
||||
return null;
|
||||
}
|
||||
for (UiccSlotInfo slotInfo : slotInfos) {
|
||||
if (slotInfo != null && slotInfo.isRemovable()) {
|
||||
|
||||
return slotInfo;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean isSuwFinished(Context context) {
|
||||
try {
|
||||
// DEVICE_PROVISIONED is 0 if still in setup wizard. 1 if setup completed.
|
||||
return Settings.Global.getInt(
|
||||
context.getContentResolver(), Settings.Global.DEVICE_PROVISIONED)
|
||||
== 1;
|
||||
} catch (Settings.SettingNotFoundException e) {
|
||||
Log.e(TAG, "Cannot get DEVICE_PROVISIONED from the device.", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasActiveEsimSubscription() {
|
||||
List<SubscriptionInfo> activeSubs = SubscriptionUtil.getActiveSubscriptions(mSubMgr);
|
||||
return activeSubs.stream().anyMatch(SubscriptionInfo::isEmbedded);
|
||||
}
|
||||
|
||||
private List<SubscriptionInfo> getGroupedEmbeddedSubscriptions() {
|
||||
List<SubscriptionInfo> groupedSubscriptions =
|
||||
SubscriptionUtil.getSelectableSubscriptionInfoList(mContext);
|
||||
if (groupedSubscriptions == null) {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
return ImmutableList.copyOf(
|
||||
groupedSubscriptions.stream()
|
||||
.filter(sub -> sub.isEmbedded())
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
private SimSlotChangeHandler() {}
|
||||
}
|
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.receivers;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.telephony.UiccCardInfo;
|
||||
import android.telephony.UiccSlotInfo;
|
||||
import android.telephony.euicc.EuiccManager;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.settingslib.utils.ThreadUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** The receiver when the slot status changes. */
|
||||
public class SimSlotChangeReceiver extends BroadcastReceiver {
|
||||
private static final String TAG = "SlotChangeReceiver";
|
||||
|
||||
private final SimSlotChangeHandler mSlotChangeHandler = SimSlotChangeHandler.get();
|
||||
private final Object mLock = new Object();
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
|
||||
String action = intent.getAction();
|
||||
if (!TelephonyManager.ACTION_SIM_SLOT_STATUS_CHANGED.equals(action)) {
|
||||
Log.e(TAG, "Ignore slot changes due to unexpected action: " + action);
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadUtils.postOnBackgroundThread(
|
||||
() -> {
|
||||
synchronized (mLock) {
|
||||
if (!shouldHandleSlotChange(context)) {
|
||||
return;
|
||||
}
|
||||
mSlotChangeHandler.onSlotsStatusChange(context);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Checks whether the slot event should be handled.
|
||||
private boolean shouldHandleSlotChange(Context context) {
|
||||
final EuiccManager euiccManager = context.getSystemService(EuiccManager.class);
|
||||
if (euiccManager == null || !euiccManager.isEnabled()) {
|
||||
Log.i(TAG, "Ignore slot changes because EuiccManager is disabled.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (euiccManager.getOtaStatus() == EuiccManager.EUICC_OTA_IN_PROGRESS) {
|
||||
Log.i(TAG, "Ignore slot changes because eSIM OTA is in progress.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isSimSlotStateValid(context)) {
|
||||
Log.i(TAG, "Ignore slot changes because SIM states are not valid.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Checks whether the SIM slot state is valid for slot change event.
|
||||
private boolean isSimSlotStateValid(Context context) {
|
||||
final TelephonyManager telMgr = context.getSystemService(TelephonyManager.class);
|
||||
UiccSlotInfo[] slotInfos = telMgr.getUiccSlotsInfo();
|
||||
if (slotInfos == null) {
|
||||
Log.e(TAG, "slotInfos is null. Unable to get slot infos.");
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean isAllCardStringsEmpty = true;
|
||||
for (int i = 0; i < slotInfos.length; i++) {
|
||||
UiccSlotInfo slotInfo = slotInfos[i];
|
||||
|
||||
if (slotInfo == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// After pSIM is inserted, there might be a short period that the status of both slots
|
||||
// are not accurate. We drop the event if any of sim presence state is ERROR or
|
||||
// RESTRICTED.
|
||||
if (slotInfo.getCardStateInfo() == UiccSlotInfo.CARD_STATE_INFO_ERROR
|
||||
|| slotInfo.getCardStateInfo() == UiccSlotInfo.CARD_STATE_INFO_RESTRICTED) {
|
||||
Log.i(TAG, "The SIM state is in an error. Drop the event. SIM info: " + slotInfo);
|
||||
return false;
|
||||
}
|
||||
|
||||
UiccCardInfo cardInfo = findUiccCardInfoBySlot(telMgr, i);
|
||||
if (cardInfo == null) {
|
||||
continue;
|
||||
}
|
||||
if (!TextUtils.isEmpty(slotInfo.getCardId())
|
||||
|| !TextUtils.isEmpty(cardInfo.getIccId())) {
|
||||
isAllCardStringsEmpty = false;
|
||||
}
|
||||
}
|
||||
|
||||
// We also drop the event if both the card strings are empty, which usually means it's
|
||||
// between SIM slots switch the slot status is not stable at this moment.
|
||||
if (isAllCardStringsEmpty) {
|
||||
Log.i(TAG, "All UICC card strings are empty. Drop this event.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private UiccCardInfo findUiccCardInfoBySlot(TelephonyManager telMgr, int physicalSlotIndex) {
|
||||
List<UiccCardInfo> cardInfos = telMgr.getUiccCardsInfo();
|
||||
if (cardInfos == null) {
|
||||
return null;
|
||||
}
|
||||
return cardInfos.stream()
|
||||
.filter(info -> info.getSlotIndex() == physicalSlotIndex)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user