1 /* 2 * Copyright (C) 2019 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.network; 18 19 import android.content.BroadcastReceiver; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.IntentFilter; 23 import android.os.Handler; 24 import android.os.Looper; 25 import android.telephony.CarrierConfigManager; 26 import android.telephony.SubscriptionInfo; 27 import android.telephony.SubscriptionManager; 28 import android.telephony.TelephonyManager; 29 import android.text.TextUtils; 30 import android.util.Log; 31 32 import androidx.annotation.NonNull; 33 import androidx.annotation.VisibleForTesting; 34 35 import com.android.internal.telephony.TelephonyIntents; 36 37 import java.util.List; 38 import java.util.concurrent.atomic.AtomicInteger; 39 40 /** 41 * A listener for active subscription change 42 */ 43 public abstract class ActiveSubscriptionsListener 44 extends SubscriptionManager.OnSubscriptionsChangedListener 45 implements AutoCloseable { 46 47 private static final String TAG = "ActiveSubsciptions"; 48 private static final boolean DEBUG = false; 49 50 private Looper mLooper; 51 private Context mContext; 52 53 private static final int STATE_NOT_LISTENING = 0; 54 private static final int STATE_STOPPING = 1; 55 private static final int STATE_PREPARING = 2; 56 private static final int STATE_LISTENING = 3; 57 private static final int STATE_DATA_CACHED = 4; 58 59 private AtomicInteger mCacheState; 60 private SubscriptionManager mSubscriptionManager; 61 62 private IntentFilter mSubscriptionChangeIntentFilter; 63 private BroadcastReceiver mSubscriptionChangeReceiver; 64 65 private static final int MAX_SUBSCRIPTION_UNKNOWN = -1; 66 private final int mTargetSubscriptionId; 67 68 private AtomicInteger mMaxActiveSubscriptionInfos; 69 private List<SubscriptionInfo> mCachedActiveSubscriptionInfo; 70 71 /** 72 * Constructor 73 * 74 * @param looper {@code Looper} of this listener 75 * @param context {@code Context} of this listener 76 */ ActiveSubscriptionsListener(Looper looper, Context context)77 public ActiveSubscriptionsListener(Looper looper, Context context) { 78 this(looper, context, SubscriptionManager.INVALID_SUBSCRIPTION_ID); 79 } 80 81 /** 82 * Constructor 83 * 84 * @param looper {@code Looper} of this listener 85 * @param context {@code Context} of this listener 86 * @param subscriptionId for subscription on this listener 87 */ ActiveSubscriptionsListener(Looper looper, Context context, int subscriptionId)88 public ActiveSubscriptionsListener(Looper looper, Context context, int subscriptionId) { 89 super(looper); 90 mLooper = looper; 91 mContext = context; 92 mTargetSubscriptionId = subscriptionId; 93 94 mCacheState = new AtomicInteger(STATE_NOT_LISTENING); 95 mMaxActiveSubscriptionInfos = new AtomicInteger(MAX_SUBSCRIPTION_UNKNOWN); 96 97 mSubscriptionChangeIntentFilter = new IntentFilter(); 98 mSubscriptionChangeIntentFilter.addAction( 99 CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED); 100 mSubscriptionChangeIntentFilter.addAction( 101 TelephonyIntents.ACTION_RADIO_TECHNOLOGY_CHANGED); 102 mSubscriptionChangeIntentFilter.addAction( 103 TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED); 104 } 105 106 @VisibleForTesting getSubscriptionChangeReceiver()107 BroadcastReceiver getSubscriptionChangeReceiver() { 108 return new BroadcastReceiver() { 109 @Override 110 public void onReceive(Context context, Intent intent) { 111 if (isInitialStickyBroadcast()) { 112 return; 113 } 114 final String action = intent.getAction(); 115 if (TextUtils.isEmpty(action)) { 116 return; 117 } 118 if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(action)) { 119 final int subId = intent.getIntExtra( 120 CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX, 121 SubscriptionManager.INVALID_SUBSCRIPTION_ID); 122 if (!clearCachedSubId(subId)) { 123 return; 124 } 125 if (SubscriptionManager.isValidSubscriptionId(mTargetSubscriptionId)) { 126 if (SubscriptionManager.isValidSubscriptionId(subId) 127 && (mTargetSubscriptionId != subId)) { 128 return; 129 } 130 } 131 } 132 onSubscriptionsChanged(); 133 } 134 }; 135 } 136 137 /** 138 * Active subscriptions got changed 139 */ 140 public abstract void onChanged(); 141 142 @Override 143 public void onSubscriptionsChanged() { 144 // clear value in cache 145 clearCache(); 146 listenerNotify(); 147 } 148 149 /** 150 * Start listening subscriptions change 151 */ 152 public void start() { 153 monitorSubscriptionsChange(true); 154 } 155 156 /** 157 * Stop listening subscriptions change 158 */ 159 public void stop() { 160 monitorSubscriptionsChange(false); 161 } 162 163 /** 164 * Implementation of {@code AutoCloseable} 165 */ 166 public void close() { 167 stop(); 168 } 169 170 /** 171 * Get SubscriptionManager 172 * 173 * @return a SubscriptionManager 174 */ 175 public SubscriptionManager getSubscriptionManager() { 176 if (mSubscriptionManager == null) { 177 mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class) 178 .createForAllUserProfiles(); 179 } 180 return mSubscriptionManager; 181 } 182 183 /** 184 * Get current max. number active subscription info(s) been setup within device 185 * 186 * @return max. number of active subscription info(s) 187 */ 188 public int getActiveSubscriptionInfoCountMax() { 189 int cacheState = mCacheState.get(); 190 if (cacheState < STATE_LISTENING) { 191 return getSubscriptionManager().getActiveSubscriptionInfoCountMax(); 192 } 193 194 mMaxActiveSubscriptionInfos.compareAndSet(MAX_SUBSCRIPTION_UNKNOWN, 195 getSubscriptionManager().getActiveSubscriptionInfoCountMax()); 196 return mMaxActiveSubscriptionInfos.get(); 197 } 198 199 /** 200 * Get a list of active subscription info 201 * 202 * @return A list of active subscription info 203 */ 204 public List<SubscriptionInfo> getActiveSubscriptionsInfo() { 205 if (mCacheState.get() >= STATE_DATA_CACHED) { 206 return mCachedActiveSubscriptionInfo; 207 } 208 mCachedActiveSubscriptionInfo = getSubscriptionManager().getActiveSubscriptionInfoList(); 209 mCacheState.compareAndSet(STATE_LISTENING, STATE_DATA_CACHED); 210 211 if (DEBUG) { 212 if ((mCachedActiveSubscriptionInfo == null) 213 || (mCachedActiveSubscriptionInfo.size() <= 0)) { 214 Log.d(TAG, "active subscriptions: " + mCachedActiveSubscriptionInfo); 215 } else { 216 final StringBuilder logString = new StringBuilder("active subscriptions:"); 217 for (SubscriptionInfo subInfo : mCachedActiveSubscriptionInfo) { 218 logString.append(" " + subInfo.getSubscriptionId()); 219 } 220 Log.d(TAG, logString.toString()); 221 } 222 } 223 224 return mCachedActiveSubscriptionInfo; 225 } 226 227 /** 228 * Get an active subscription info with given subscription ID 229 * 230 * @param subId target subscription ID 231 * @return A subscription info which is active list 232 */ 233 public SubscriptionInfo getActiveSubscriptionInfo(int subId) { 234 final List<SubscriptionInfo> subInfoList = getActiveSubscriptionsInfo(); 235 if (subInfoList == null) { 236 return null; 237 } 238 for (SubscriptionInfo subInfo : subInfoList) { 239 if (subInfo.getSubscriptionId() == subId) { 240 return subInfo; 241 } 242 } 243 return null; 244 } 245 246 /** 247 * Get a list of all subscription info which accessible by Settings app 248 * 249 * @return A list of accessible subscription info 250 */ 251 public List<SubscriptionInfo> getAccessibleSubscriptionsInfo() { 252 return getSubscriptionManager().getAvailableSubscriptionInfoList(); 253 } 254 255 /** 256 * Get an accessible subscription info with given subscription ID 257 * 258 * @param subId target subscription ID 259 * @return A subscription info which is accessible list 260 */ 261 public SubscriptionInfo getAccessibleSubscriptionInfo(int subId) { 262 // Always check if subId is part of activeSubscriptions 263 // since there's cache design within SubscriptionManager. 264 // That give us a chance to avoid from querying ContentProvider. 265 final SubscriptionInfo activeSubInfo = getActiveSubscriptionInfo(subId); 266 if (activeSubInfo != null) { 267 return activeSubInfo; 268 } 269 270 final List<SubscriptionInfo> subInfoList = getAccessibleSubscriptionsInfo(); 271 if (subInfoList == null) { 272 return null; 273 } 274 for (SubscriptionInfo subInfo : subInfoList) { 275 if (subInfo.getSubscriptionId() == subId) { 276 return subInfo; 277 } 278 } 279 return null; 280 } 281 282 /** 283 * Gets a list of active, visible subscription Id(s) of the currently active SIM(s). 284 * 285 * @return the list of subId's that are active and visible; the length may be 0. 286 */ 287 public @NonNull int[] getActiveSubscriptionIdList() { 288 return getSubscriptionManager().getActiveSubscriptionIdList(); 289 } 290 291 /** 292 * Clear data cached within listener 293 */ 294 public void clearCache() { 295 mMaxActiveSubscriptionInfos.set(MAX_SUBSCRIPTION_UNKNOWN); 296 mCacheState.compareAndSet(STATE_DATA_CACHED, STATE_LISTENING); 297 mCachedActiveSubscriptionInfo = null; 298 } 299 300 @VisibleForTesting 301 void registerForSubscriptionsChange() { 302 getSubscriptionManager().addOnSubscriptionsChangedListener( 303 mContext.getMainExecutor(), this); 304 } 305 306 private void monitorSubscriptionsChange(boolean on) { 307 if (on) { 308 if (!mCacheState.compareAndSet(STATE_NOT_LISTENING, STATE_PREPARING)) { 309 return; 310 } 311 312 if (mSubscriptionChangeReceiver == null) { 313 mSubscriptionChangeReceiver = getSubscriptionChangeReceiver(); 314 } 315 mContext.registerReceiver(mSubscriptionChangeReceiver, 316 mSubscriptionChangeIntentFilter, null, new Handler(mLooper), 317 Context.RECEIVER_EXPORTED_UNAUDITED); 318 registerForSubscriptionsChange(); 319 mCacheState.compareAndSet(STATE_PREPARING, STATE_LISTENING); 320 return; 321 } 322 323 final int currentState = mCacheState.getAndSet(STATE_STOPPING); 324 if (currentState <= STATE_STOPPING) { 325 mCacheState.compareAndSet(STATE_STOPPING, currentState); 326 return; 327 } 328 if (mSubscriptionChangeReceiver != null) { 329 mContext.unregisterReceiver(mSubscriptionChangeReceiver); 330 } 331 getSubscriptionManager().removeOnSubscriptionsChangedListener(this); 332 clearCache(); 333 mCacheState.compareAndSet(STATE_STOPPING, STATE_NOT_LISTENING); 334 } 335 336 private void listenerNotify() { 337 if (mCacheState.get() < STATE_LISTENING) { 338 return; 339 } 340 onChanged(); 341 } 342 343 private boolean clearCachedSubId(int subId) { 344 if (mCacheState.get() < STATE_DATA_CACHED) { 345 return false; 346 } 347 if (mCachedActiveSubscriptionInfo == null) { 348 return false; 349 } 350 for (SubscriptionInfo subInfo : mCachedActiveSubscriptionInfo) { 351 if (subInfo.getSubscriptionId() == subId) { 352 clearCache(); 353 return true; 354 } 355 } 356 return false; 357 } 358 } 359