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