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, null); 163 } else { 164 Log.i(TAG, "Ready to switch to eSIM subscription with index: " + mSelectedItemIndex); 165 mSwitchToEuiccSubscriptionSidecar.run( 166 mEmbeddedSubscriptions.get(mSelectedItemIndex).getSubscriptionId(), 167 UiccSlotUtil.INVALID_PORT_ID, null); 168 } 169 } 170 171 @Override onStateChange(SidecarFragment fragment)172 public void onStateChange(SidecarFragment fragment) { 173 if (fragment == mSwitchToRemovableSlotSidecar) { 174 switch (mSwitchToRemovableSlotSidecar.getState()) { 175 case SidecarFragment.State.SUCCESS: 176 mSwitchToRemovableSlotSidecar.reset(); 177 Log.i(TAG, "Switch slot successfully."); 178 SubscriptionManager subMgr = getSystemService(SubscriptionManager.class); 179 if (subMgr.canDisablePhysicalSubscription()) { 180 SubscriptionInfo removableSub = 181 SubscriptionUtil.getFirstRemovableSubscription(this); 182 if (removableSub != null) { 183 subMgr.setUiccApplicationsEnabled( 184 removableSub.getSubscriptionId(), true); 185 } 186 } 187 finish(); 188 break; 189 case SidecarFragment.State.ERROR: 190 mSwitchToRemovableSlotSidecar.reset(); 191 Log.e(TAG, "Failed to switch slot in ChooseSubscriptionsActivity."); 192 handleEnableRemovableSimError(); 193 // We don't call finish() and just stay on this page. 194 break; 195 } 196 } else if (fragment == mSwitchToEuiccSubscriptionSidecar) { 197 switch (mSwitchToEuiccSubscriptionSidecar.getState()) { 198 case SidecarFragment.State.SUCCESS: 199 mSwitchToEuiccSubscriptionSidecar.reset(); 200 if (mNoPsimContinueToSettings) { 201 // Currently, there shouldn't be a case that mNoPsimContinueToSettings is 202 // true. If this can be true in the future, we should finish() this page 203 // and direct to Settings page here. 204 Log.e( 205 TAG, 206 "mNoPsimContinueToSettings is true which is not supported for" 207 + " now."); 208 } else { 209 Log.i(TAG, "User finished selecting eSIM profile."); 210 finish(); 211 } 212 break; 213 case SidecarFragment.State.ERROR: 214 mSwitchToEuiccSubscriptionSidecar.reset(); 215 Log.e(TAG, "Failed to switch subscription in ChooseSubscriptionsActivity."); 216 Item item = (Item) mItemGroup.getItemAt(mSelectedItemIndex); 217 item.setEnabled(false); 218 item.setSummary(getString(R.string.choose_sim_could_not_activate)); 219 mIsSwitching = false; 220 // We don't call finish() and just stay on this page. 221 break; 222 } 223 } 224 } 225 displaySubscriptions()226 private void displaySubscriptions() { 227 View rootView = findViewById(android.R.id.content); 228 GlifRecyclerLayout layout = rootView.findViewById(R.id.glif_layout); 229 RecyclerItemAdapter adapter = (RecyclerItemAdapter) layout.getAdapter(); 230 adapter.setOnItemSelectedListener(this); 231 mItemGroup = (ItemGroup) adapter.getRootItemHierarchy(); 232 233 // Display pSIM profile. 234 if (mHasPsim) { 235 Item item = new DisableableItem(); 236 // Title 237 CharSequence title = null; 238 if (mRemovableSubscription != null) { 239 title = 240 SubscriptionUtil.getUniqueSubscriptionDisplayName( 241 mRemovableSubscription.getSubscriptionId(), this); 242 } 243 item.setTitle(TextUtils.isEmpty(title) ? getString(R.string.sim_card_label) : title); 244 245 if (mIsSwitching && mSelectedItemIndex == INDEX_PSIM) { 246 item.setSummary(getString(R.string.choose_sim_activating)); 247 } else { 248 // Phone number 249 String phoneNumber = 250 SubscriptionUtil.getFormattedPhoneNumber(this, mRemovableSubscription); 251 item.setSummary(TextUtils.isEmpty(phoneNumber) ? "" : phoneNumber); 252 } 253 254 // pSIM profile has index -1. 255 item.setId(INDEX_PSIM); 256 mItemGroup.addChild(item); 257 } 258 259 // Display all eSIM profiles. 260 int index = 0; 261 for (SubscriptionInfo sub : mEmbeddedSubscriptions) { 262 Item item = new DisableableItem(); 263 CharSequence title = 264 SubscriptionUtil.getUniqueSubscriptionDisplayName( 265 sub.getSubscriptionId(), this); 266 item.setTitle(TextUtils.isEmpty(title) ? sub.getDisplayName() : title); 267 if (mIsSwitching && mSelectedItemIndex == index) { 268 item.setSummary(getString(R.string.choose_sim_activating)); 269 } else { 270 String phoneNumber = SubscriptionUtil.getFormattedPhoneNumber(this, sub); 271 item.setSummary(TextUtils.isEmpty(phoneNumber) ? "" : phoneNumber); 272 } 273 item.setId(index++); 274 mItemGroup.addChild(item); 275 } 276 } 277 updateSubscriptions()278 private void updateSubscriptions() { 279 List<SubscriptionInfo> subscriptions = 280 SubscriptionUtil.getSelectableSubscriptionInfoList(this); 281 if (subscriptions != null) { 282 for (SubscriptionInfo sub : subscriptions) { 283 if (sub == null) { 284 continue; 285 } 286 if (sub.isEmbedded()) { 287 mEmbeddedSubscriptions.add(sub); 288 } else { 289 mRemovableSubscription = sub; 290 } 291 } 292 } 293 } 294 handleEnableRemovableSimError()295 private void handleEnableRemovableSimError() { 296 // mSelectedItemIndex will be -1 if pSIM is selected. Since pSIM is always be 297 // listed at index 0, we change the itemIndex to 0 if pSIM is selected. 298 int itemIndex = mSelectedItemIndex == INDEX_PSIM ? 0 : mSelectedItemIndex; 299 Item item = (Item) mItemGroup.getItemAt(itemIndex); 300 item.setEnabled(false); 301 item.setSummary(getString(R.string.choose_sim_could_not_activate)); 302 mIsSwitching = false; 303 } 304 305 class DisableableItem extends Item implements Dividable { 306 @Override isDividerAllowedAbove()307 public boolean isDividerAllowedAbove() { 308 return true; 309 } 310 311 @Override isDividerAllowedBelow()312 public boolean isDividerAllowedBelow() { 313 return true; 314 } 315 316 @Override onBindView(View view)317 public void onBindView(View view) { 318 super.onBindView(view); 319 TextView title = view.findViewById(com.google.android.setupdesign.R.id.sud_items_title); 320 TextView summary = 321 view.findViewById(com.google.android.setupdesign.R.id.sud_items_summary); 322 title.setEnabled(isEnabled()); 323 summary.setEnabled(isEnabled()); 324 } 325 } 326 } 327