1 /* 2 * Copyright (C) 2022 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.deviceinfo.simstatus; 18 19 import android.content.Context; 20 import android.telephony.SubscriptionInfo; 21 import android.telephony.SubscriptionManager; 22 import android.telephony.TelephonyManager; 23 import android.util.Log; 24 25 import androidx.lifecycle.DefaultLifecycleObserver; 26 import androidx.lifecycle.Lifecycle; 27 import androidx.lifecycle.LifecycleOwner; 28 import androidx.lifecycle.LiveData; 29 30 import com.android.settings.network.SubscriptionsChangeListener; 31 32 import java.util.List; 33 import java.util.concurrent.ConcurrentHashMap; 34 import java.util.concurrent.Executor; 35 import java.util.concurrent.Phaser; 36 import java.util.concurrent.atomic.AtomicInteger; 37 import java.util.concurrent.atomic.AtomicLong; 38 39 /** 40 * A class for showing a summary of status of sim slots. 41 */ 42 public class SlotSimStatus extends LiveData<Long> 43 implements DefaultLifecycleObserver, 44 SubscriptionsChangeListener.SubscriptionsChangeListenerClient { 45 46 private static final String TAG = "SlotSimStatus"; 47 48 private final AtomicInteger mNumberOfSlots = new AtomicInteger(0); 49 private final ConcurrentHashMap<Integer, SubscriptionInfo> mSubscriptionMap = 50 new ConcurrentHashMap<Integer, SubscriptionInfo>(); 51 private final Phaser mBlocker = new Phaser(1); 52 private final AtomicLong mDataVersion = new AtomicLong(0); 53 54 private Context mContext; 55 private int mBasePreferenceOrdering; 56 private SubscriptionsChangeListener mSubscriptionsChangeListener; 57 58 private static final String KEY_SIM_STATUS = "sim_status"; 59 60 /** 61 * Construct of class. 62 * @param context Context 63 */ SlotSimStatus(Context context)64 public SlotSimStatus(Context context) { 65 this(context, null, null); 66 } 67 68 /** 69 * Construct of class. 70 * @param context Context 71 * @param executor executor for offload to thread 72 * @param lifecycle Lifecycle 73 */ SlotSimStatus(Context context, Executor executor, Lifecycle lifecycle)74 public SlotSimStatus(Context context, Executor executor, Lifecycle lifecycle) { 75 mContext = context; 76 if (executor == null) { 77 queryRecords(context); 78 } else { 79 executor.execute(() -> asyncQueryRecords(context)); 80 } 81 if (lifecycle != null) { 82 lifecycle.addObserver(this); 83 mSubscriptionsChangeListener = new SubscriptionsChangeListener(context, this); 84 mSubscriptionsChangeListener.start(); 85 } 86 } 87 queryRecords(Context context)88 protected void queryRecords(Context context) { 89 queryDetails(context); 90 setValue(mDataVersion.incrementAndGet()); 91 mBlocker.arrive(); 92 } 93 asyncQueryRecords(Context context)94 protected void asyncQueryRecords(Context context) { 95 queryDetails(context); 96 postValue(mDataVersion.incrementAndGet()); 97 mBlocker.arrive(); 98 } 99 updateRecords()100 protected void updateRecords() { 101 queryDetails(mContext); 102 setValue(mDataVersion.incrementAndGet()); 103 } 104 queryDetails(Context context)105 protected void queryDetails(Context context) { 106 TelephonyManager telMgr = context.getSystemService(TelephonyManager.class); 107 if (telMgr != null) { 108 mNumberOfSlots.set(telMgr.getPhoneCount()); 109 } 110 111 SubscriptionManager subMgr = context.getSystemService(SubscriptionManager.class); 112 if (subMgr == null) { 113 mSubscriptionMap.clear(); 114 return; 115 } 116 117 List<SubscriptionInfo> subInfoList = subMgr.getActiveSubscriptionInfoList(); 118 if ((subInfoList == null) || (subInfoList.size() <= 0)) { 119 mSubscriptionMap.clear(); 120 Log.d(TAG, "No active SIM."); 121 return; 122 } 123 124 mSubscriptionMap.clear(); 125 subInfoList.forEach(subInfo -> { 126 int slotIndex = subInfo.getSimSlotIndex(); 127 mSubscriptionMap.put(slotIndex, subInfo); 128 }); 129 Log.d(TAG, "Number of active SIM: " + subInfoList.size()); 130 } 131 waitForResult()132 protected void waitForResult() { 133 mBlocker.awaitAdvance(0); 134 } 135 136 /** 137 * Set base ordering of Preference. 138 * @param baseOrdering the base ordering for SIM Status within "About Phone". 139 */ setBasePreferenceOrdering(int baseOrdering)140 public void setBasePreferenceOrdering(int baseOrdering) { 141 mBasePreferenceOrdering = baseOrdering; 142 } 143 144 /** 145 * Number of slots available. 146 * @return number of slots 147 */ size()148 public int size() { 149 waitForResult(); 150 return mNumberOfSlots.get(); 151 } 152 153 /** 154 * Get ordering of Preference based on index of slot. 155 * @param slotIndex index of slot 156 * @return Preference ordering. 157 */ getPreferenceOrdering(int slotIndex)158 public int getPreferenceOrdering(int slotIndex) { 159 return mBasePreferenceOrdering + 1 + slotIndex; 160 } 161 162 /** 163 * Get key of Preference and PreferenceController based on index of slot. 164 * @param slotIndex index of slot 165 * @return Preference key. 166 */ getPreferenceKey(int slotIndex)167 public String getPreferenceKey(int slotIndex) { 168 if (slotIndex == SubscriptionManager.INVALID_SIM_SLOT_INDEX) { 169 return KEY_SIM_STATUS; 170 } 171 return KEY_SIM_STATUS + (1 + slotIndex); 172 } 173 174 /** 175 * Get subscription based on slot index. 176 * @param slotIndex index of slot (starting from 0) 177 * @return SubscriptionInfo based on index of slot. 178 * {@code null} means no subscription on slot. 179 */ getSubscriptionInfo(int slotIndex)180 public SubscriptionInfo getSubscriptionInfo(int slotIndex) { 181 if (slotIndex >= size()) { 182 return null; 183 } 184 return mSubscriptionMap.get(slotIndex); 185 } 186 187 /** 188 * Get slot index based on Preference key 189 * @param prefKey is the preference key 190 * @return slot index. 191 */ findSlotIndexByKey(String prefKey)192 public int findSlotIndexByKey(String prefKey) { 193 int simSlotIndex = SubscriptionManager.INVALID_SIM_SLOT_INDEX + 1; 194 try { 195 simSlotIndex = Integer.parseInt(prefKey.substring(KEY_SIM_STATUS.length())); 196 } catch (Exception exception) { 197 Log.w(TAG, "Preference key invalid: " + prefKey + 198 ". Error Msg: " + exception.getMessage()); 199 } 200 return simSlotIndex - 1; 201 } 202 203 @Override onAirplaneModeChanged(boolean airplaneModeEnabled)204 public void onAirplaneModeChanged(boolean airplaneModeEnabled) { 205 if (airplaneModeEnabled) { 206 /** 207 * Only perform update when airplane mode ON. 208 * Relay on #onSubscriptionsChanged() when airplane mode OFF. 209 */ 210 updateRecords(); 211 } 212 } 213 214 @Override onSubscriptionsChanged()215 public void onSubscriptionsChanged() { 216 updateRecords(); 217 } 218 219 @Override onDestroy(LifecycleOwner lifecycleOwner)220 public void onDestroy(LifecycleOwner lifecycleOwner) { 221 if (mSubscriptionsChangeListener != null) { 222 mSubscriptionsChangeListener.stop(); 223 } 224 lifecycleOwner.getLifecycle().removeObserver(this); 225 } 226 } 227