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