• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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