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