1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.sim; 18 19 import android.app.Activity; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.icu.text.MessageFormat; 23 import android.os.Bundle; 24 import android.telephony.SubscriptionInfo; 25 import android.telephony.SubscriptionManager; 26 import android.text.TextUtils; 27 import android.util.Log; 28 import android.view.View; 29 import android.widget.TextView; 30 31 import com.android.settings.R; 32 import com.android.settings.SidecarFragment; 33 import com.android.settings.network.SubscriptionUtil; 34 import com.android.settings.network.SwitchToEuiccSubscriptionSidecar; 35 import com.android.settings.network.SwitchToRemovableSlotSidecar; 36 import com.android.settings.network.UiccSlotUtil; 37 38 import com.google.android.setupdesign.GlifLayout; 39 import com.google.android.setupdesign.GlifRecyclerLayout; 40 import com.google.android.setupdesign.items.Dividable; 41 import com.google.android.setupdesign.items.IItem; 42 import com.google.android.setupdesign.items.Item; 43 import com.google.android.setupdesign.items.ItemGroup; 44 import com.google.android.setupdesign.items.RecyclerItemAdapter; 45 46 import java.util.ArrayList; 47 import java.util.HashMap; 48 import java.util.List; 49 import java.util.Locale; 50 import java.util.Map; 51 52 /** Activity to show a list of profiles for user to choose. */ 53 public class ChooseSimActivity extends Activity 54 implements RecyclerItemAdapter.OnItemSelectedListener, SidecarFragment.Listener { 55 // Whether there is a pSIM profile in the selection list. 56 public static final String KEY_HAS_PSIM = "has_psim"; 57 // After the user selects eSIM profile, whether continue to show Mobile Network Settings screen 58 // to select other preferences. 59 // Note: KEY_NO_PSIM_CONTINUE_TO_SETTINGS and mNoPsimContinueToSettings are not used for now 60 // for UI changes. We may use them in the future. 61 public static final String KEY_NO_PSIM_CONTINUE_TO_SETTINGS = "no_psim_continue_to_settings"; 62 63 private static final String TAG = "ChooseSimActivity"; 64 private static final int INDEX_PSIM = -1; 65 private static final String STATE_SELECTED_INDEX = "selected_index"; 66 private static final String STATE_IS_SWITCHING = "is_switching"; 67 68 private boolean mHasPsim; 69 private boolean mNoPsimContinueToSettings; 70 private ArrayList<SubscriptionInfo> mEmbeddedSubscriptions = new ArrayList<>(); 71 private SubscriptionInfo mRemovableSubscription = null; 72 73 private ItemGroup mItemGroup; 74 private SwitchToEuiccSubscriptionSidecar mSwitchToEuiccSubscriptionSidecar; 75 private SwitchToRemovableSlotSidecar mSwitchToRemovableSlotSidecar; 76 77 // Variables have states. 78 private int mSelectedItemIndex; 79 private boolean mIsSwitching; 80 81 /** Returns an intent of {@code ChooseSimActivity} */ getIntent(Context context)82 public static Intent getIntent(Context context) { 83 return new Intent(context, ChooseSimActivity.class); 84 } 85 86 @Override onCreate(Bundle savedInstanceState)87 protected void onCreate(Bundle savedInstanceState) { 88 super.onCreate(savedInstanceState); 89 90 setContentView(R.layout.choose_sim_activity); 91 92 Intent intent = getIntent(); 93 mHasPsim = intent.getBooleanExtra(KEY_HAS_PSIM, false); 94 mNoPsimContinueToSettings = intent.getBooleanExtra(KEY_NO_PSIM_CONTINUE_TO_SETTINGS, false); 95 96 updateSubscriptions(); 97 98 if (mEmbeddedSubscriptions.size() == 0) { 99 Log.e(TAG, "Unable to find available eSIM subscriptions."); 100 finish(); 101 return; 102 } 103 104 if (savedInstanceState != null) { 105 mSelectedItemIndex = savedInstanceState.getInt(STATE_SELECTED_INDEX); 106 mIsSwitching = savedInstanceState.getBoolean(STATE_IS_SWITCHING); 107 } 108 109 GlifLayout layout = findViewById(R.id.glif_layout); 110 int subscriptionCount = mEmbeddedSubscriptions.size(); 111 if (mHasPsim) { // Choose a number to use 112 subscriptionCount++; 113 } 114 layout.setHeaderText(getString(R.string.choose_sim_title)); 115 MessageFormat msgFormat = new MessageFormat( 116 getString(R.string.choose_sim_text), 117 Locale.getDefault()); 118 Map<String, Object> arguments = new HashMap<>(); 119 arguments.put("count", subscriptionCount); 120 layout.setDescriptionText(msgFormat.format(arguments)); 121 122 displaySubscriptions(); 123 124 mSwitchToRemovableSlotSidecar = SwitchToRemovableSlotSidecar.get(getFragmentManager()); 125 mSwitchToEuiccSubscriptionSidecar = 126 SwitchToEuiccSubscriptionSidecar.get(getFragmentManager()); 127 } 128 129 @Override onResume()130 public void onResume() { 131 super.onResume(); 132 mSwitchToRemovableSlotSidecar.addListener(this); 133 mSwitchToEuiccSubscriptionSidecar.addListener(this); 134 } 135 136 @Override onPause()137 public void onPause() { 138 mSwitchToEuiccSubscriptionSidecar.removeListener(this); 139 mSwitchToRemovableSlotSidecar.removeListener(this); 140 super.onPause(); 141 } 142 143 @Override onSaveInstanceState(Bundle outState)144 protected void onSaveInstanceState(Bundle outState) { 145 outState.putInt(STATE_SELECTED_INDEX, mSelectedItemIndex); 146 outState.putBoolean(STATE_IS_SWITCHING, mIsSwitching); 147 super.onSaveInstanceState(outState); 148 } 149 150 @Override onItemSelected(IItem item)151 public void onItemSelected(IItem item) { 152 if (mIsSwitching) { 153 // If we already selected an item, do not try to switch to another one. 154 return; 155 } 156 mIsSwitching = true; 157 Item subItem = (Item) item; 158 subItem.setSummary(getString(R.string.choose_sim_activating)); 159 mSelectedItemIndex = subItem.getId(); 160 if (mSelectedItemIndex == INDEX_PSIM) { 161 Log.i(TAG, "Ready to switch to pSIM slot."); 162 mSwitchToRemovableSlotSidecar.run(UiccSlotUtil.INVALID_PHYSICAL_SLOT_ID); 163 } else { 164 Log.i(TAG, "Ready to switch to eSIM subscription with index: " + mSelectedItemIndex); 165 mSwitchToEuiccSubscriptionSidecar.run( 166 mEmbeddedSubscriptions.get(mSelectedItemIndex).getSubscriptionId()); 167 } 168 } 169 170 @Override onStateChange(SidecarFragment fragment)171 public void onStateChange(SidecarFragment fragment) { 172 if (fragment == mSwitchToRemovableSlotSidecar) { 173 switch (mSwitchToRemovableSlotSidecar.getState()) { 174 case SidecarFragment.State.SUCCESS: 175 mSwitchToRemovableSlotSidecar.reset(); 176 Log.i(TAG, "Switch slot successfully."); 177 SubscriptionManager subMgr = getSystemService(SubscriptionManager.class); 178 if (subMgr.canDisablePhysicalSubscription()) { 179 SubscriptionInfo removableSub = 180 SubscriptionUtil.getFirstRemovableSubscription(this); 181 if (removableSub != null) { 182 subMgr.setUiccApplicationsEnabled( 183 removableSub.getSubscriptionId(), true); 184 } 185 } 186 finish(); 187 break; 188 case SidecarFragment.State.ERROR: 189 mSwitchToRemovableSlotSidecar.reset(); 190 Log.e(TAG, "Failed to switch slot in ChooseSubscriptionsActivity."); 191 handleEnableRemovableSimError(); 192 // We don't call finish() and just stay on this page. 193 break; 194 } 195 } else if (fragment == mSwitchToEuiccSubscriptionSidecar) { 196 switch (mSwitchToEuiccSubscriptionSidecar.getState()) { 197 case SidecarFragment.State.SUCCESS: 198 mSwitchToEuiccSubscriptionSidecar.reset(); 199 if (mNoPsimContinueToSettings) { 200 // Currently, there shouldn't be a case that mNoPsimContinueToSettings is 201 // true. If this can be true in the future, we should finish() this page 202 // and direct to Settings page here. 203 Log.e( 204 TAG, 205 "mNoPsimContinueToSettings is true which is not supported for" 206 + " now."); 207 } else { 208 Log.i(TAG, "User finished selecting eSIM profile."); 209 finish(); 210 } 211 break; 212 case SidecarFragment.State.ERROR: 213 mSwitchToEuiccSubscriptionSidecar.reset(); 214 Log.e(TAG, "Failed to switch subscription in ChooseSubscriptionsActivity."); 215 Item item = (Item) mItemGroup.getItemAt(mSelectedItemIndex); 216 item.setEnabled(false); 217 item.setSummary(getString(R.string.choose_sim_could_not_activate)); 218 mIsSwitching = false; 219 // We don't call finish() and just stay on this page. 220 break; 221 } 222 } 223 } 224 displaySubscriptions()225 private void displaySubscriptions() { 226 View rootView = findViewById(android.R.id.content); 227 GlifRecyclerLayout layout = rootView.findViewById(R.id.glif_layout); 228 RecyclerItemAdapter adapter = (RecyclerItemAdapter) layout.getAdapter(); 229 adapter.setOnItemSelectedListener(this); 230 mItemGroup = (ItemGroup) adapter.getRootItemHierarchy(); 231 232 // Display pSIM profile. 233 if (mHasPsim) { 234 Item item = new DisableableItem(); 235 // Title 236 CharSequence title = null; 237 if (mRemovableSubscription != null) { 238 title = 239 SubscriptionUtil.getUniqueSubscriptionDisplayName( 240 mRemovableSubscription.getSubscriptionId(), this); 241 } 242 item.setTitle(TextUtils.isEmpty(title) ? getString(R.string.sim_card_label) : title); 243 244 if (mIsSwitching && mSelectedItemIndex == INDEX_PSIM) { 245 item.setSummary(getString(R.string.choose_sim_activating)); 246 } else { 247 // Phone number 248 String phoneNumber = 249 SubscriptionUtil.getFormattedPhoneNumber(this, mRemovableSubscription); 250 item.setSummary(TextUtils.isEmpty(phoneNumber) ? "" : phoneNumber); 251 } 252 253 // pSIM profile has index -1. 254 item.setId(INDEX_PSIM); 255 mItemGroup.addChild(item); 256 } 257 258 // Display all eSIM profiles. 259 int index = 0; 260 for (SubscriptionInfo sub : mEmbeddedSubscriptions) { 261 Item item = new DisableableItem(); 262 CharSequence title = 263 SubscriptionUtil.getUniqueSubscriptionDisplayName( 264 sub.getSubscriptionId(), this); 265 item.setTitle(TextUtils.isEmpty(title) ? sub.getDisplayName() : title); 266 if (mIsSwitching && mSelectedItemIndex == index) { 267 item.setSummary(getString(R.string.choose_sim_activating)); 268 } else { 269 String phoneNumber = SubscriptionUtil.getFormattedPhoneNumber(this, sub); 270 item.setSummary(TextUtils.isEmpty(phoneNumber) ? "" : phoneNumber); 271 } 272 item.setId(index++); 273 mItemGroup.addChild(item); 274 } 275 } 276 updateSubscriptions()277 private void updateSubscriptions() { 278 List<SubscriptionInfo> subscriptions = 279 SubscriptionUtil.getSelectableSubscriptionInfoList(this); 280 if (subscriptions != null) { 281 for (SubscriptionInfo sub : subscriptions) { 282 if (sub == null) { 283 continue; 284 } 285 if (sub.isEmbedded()) { 286 mEmbeddedSubscriptions.add(sub); 287 } else { 288 mRemovableSubscription = sub; 289 } 290 } 291 } 292 } 293 handleEnableRemovableSimError()294 private void handleEnableRemovableSimError() { 295 // mSelectedItemIndex will be -1 if pSIM is selected. Since pSIM is always be 296 // listed at index 0, we change the itemIndex to 0 if pSIM is selected. 297 int itemIndex = mSelectedItemIndex == INDEX_PSIM ? 0 : mSelectedItemIndex; 298 Item item = (Item) mItemGroup.getItemAt(itemIndex); 299 item.setEnabled(false); 300 item.setSummary(getString(R.string.choose_sim_could_not_activate)); 301 mIsSwitching = false; 302 } 303 304 class DisableableItem extends Item implements Dividable { 305 @Override isDividerAllowedAbove()306 public boolean isDividerAllowedAbove() { 307 return true; 308 } 309 310 @Override isDividerAllowedBelow()311 public boolean isDividerAllowedBelow() { 312 return true; 313 } 314 315 @Override onBindView(View view)316 public void onBindView(View view) { 317 super.onBindView(view); 318 TextView title = view.findViewById(R.id.sud_items_title); 319 TextView summary = view.findViewById(R.id.sud_items_summary); 320 title.setEnabled(isEnabled()); 321 summary.setEnabled(isEnabled()); 322 } 323 } 324 } 325