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