• 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 static androidx.lifecycle.Lifecycle.Event.ON_DESTROY;
20 import static androidx.lifecycle.Lifecycle.Event.ON_START;
21 import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
22 
23 import android.content.Context;
24 import android.os.Looper;
25 import android.provider.Settings;
26 import android.telephony.SubscriptionInfo;
27 import android.telephony.SubscriptionManager;
28 import android.util.Log;
29 
30 import androidx.annotation.Keep;
31 import androidx.annotation.VisibleForTesting;
32 import androidx.lifecycle.Lifecycle;
33 import androidx.lifecycle.LifecycleObserver;
34 import androidx.lifecycle.OnLifecycleEvent;
35 
36 import java.util.ArrayList;
37 import java.util.List;
38 import java.util.Map;
39 import java.util.stream.Collectors;
40 
41 /**
42  * A proxy to the subscription manager
43  */
44 public class ProxySubscriptionManager implements LifecycleObserver {
45 
46     private static final String LOG_TAG = "ProxySubscriptionManager";
47 
48     private static final int LISTENER_END_OF_LIFE = -1;
49     private static final int LISTENER_IS_INACTIVE = 0;
50     private static final int LISTENER_IS_ACTIVE = 1;
51 
52     /**
53      * Interface for monitor active subscriptions list changing
54      */
55     public interface OnActiveSubscriptionChangedListener {
56         /**
57          * When active subscriptions list get changed
58          */
onChanged()59         void onChanged();
60         /**
61          * get Lifecycle of listener
62          *
63          * @return Returns Lifecycle.
64          */
getLifecycle()65         default Lifecycle getLifecycle() {
66             return null;
67         }
68     }
69 
70     /**
71      * Get proxy instance to subscription manager
72      *
73      * @return proxy to subscription manager
74      */
getInstance(Context context)75     public static ProxySubscriptionManager getInstance(Context context) {
76         if (sSingleton != null) {
77             return sSingleton;
78         }
79         sSingleton = new ProxySubscriptionManager(context.getApplicationContext());
80         return sSingleton;
81     }
82 
83     private static ProxySubscriptionManager sSingleton;
84 
ProxySubscriptionManager(Context context)85     private ProxySubscriptionManager(Context context) {
86         final Looper looper = context.getMainLooper();
87 
88         ActiveSubscriptionsListener subscriptionMonitor = new ActiveSubscriptionsListener(
89                 looper, context) {
90             public void onChanged() {
91                 notifySubscriptionInfoMightChanged();
92             }
93         };
94         GlobalSettingsChangeListener airplaneModeMonitor = new GlobalSettingsChangeListener(
95                 looper, context, Settings.Global.AIRPLANE_MODE_ON) {
96             public void onChanged(String field) {
97                 subscriptionMonitor.clearCache();
98                 notifySubscriptionInfoMightChanged();
99             }
100         };
101 
102         init(context, subscriptionMonitor, airplaneModeMonitor);
103     }
104 
105     @Keep
106     @VisibleForTesting
init(Context context, ActiveSubscriptionsListener activeSubscriptionsListener, GlobalSettingsChangeListener airplaneModeOnSettingsChangeListener)107     protected void init(Context context, ActiveSubscriptionsListener activeSubscriptionsListener,
108             GlobalSettingsChangeListener airplaneModeOnSettingsChangeListener) {
109 
110         mActiveSubscriptionsListeners =
111                 new ArrayList<OnActiveSubscriptionChangedListener>();
112         mPendingNotifyListeners =
113                 new ArrayList<OnActiveSubscriptionChangedListener>();
114 
115         mSubscriptionMonitor = activeSubscriptionsListener;
116         mAirplaneModeMonitor = airplaneModeOnSettingsChangeListener;
117 
118         mSubscriptionMonitor.start();
119     }
120 
121     private Lifecycle mLifecycle;
122     private ActiveSubscriptionsListener mSubscriptionMonitor;
123     private GlobalSettingsChangeListener mAirplaneModeMonitor;
124 
125     private List<OnActiveSubscriptionChangedListener> mActiveSubscriptionsListeners;
126     private List<OnActiveSubscriptionChangedListener> mPendingNotifyListeners;
127 
128     @Keep
129     @VisibleForTesting
notifySubscriptionInfoMightChanged()130     protected void notifySubscriptionInfoMightChanged() {
131         // create a merged list for processing all listeners
132         List<OnActiveSubscriptionChangedListener> listeners =
133                 new ArrayList<OnActiveSubscriptionChangedListener>(mPendingNotifyListeners);
134         listeners.addAll(mActiveSubscriptionsListeners);
135 
136         mActiveSubscriptionsListeners.clear();
137         mPendingNotifyListeners.clear();
138         processStatusChangeOnListeners(listeners);
139     }
140 
141     /**
142      * Lifecycle for data within proxy
143      *
144      * @param lifecycle life cycle to reference
145      */
setLifecycle(Lifecycle lifecycle)146     public void setLifecycle(Lifecycle lifecycle) {
147         if (mLifecycle == lifecycle) {
148             return;
149         }
150         if (mLifecycle != null) {
151             mLifecycle.removeObserver(this);
152         }
153         if (lifecycle != null) {
154             lifecycle.addObserver(this);
155         }
156         mLifecycle = lifecycle;
157         mAirplaneModeMonitor.notifyChangeBasedOn(lifecycle);
158     }
159 
160     @OnLifecycleEvent(ON_START)
onStart()161     void onStart() {
162         mSubscriptionMonitor.start();
163 
164         // callback notify those listener(s) which back to active state
165         List<OnActiveSubscriptionChangedListener> listeners = mPendingNotifyListeners;
166         mPendingNotifyListeners = new ArrayList<OnActiveSubscriptionChangedListener>();
167         processStatusChangeOnListeners(listeners);
168     }
169 
170     @OnLifecycleEvent(ON_STOP)
onStop()171     void onStop() {
172         mSubscriptionMonitor.stop();
173     }
174 
175     @OnLifecycleEvent(ON_DESTROY)
onDestroy()176     void onDestroy() {
177         mSubscriptionMonitor.close();
178         mAirplaneModeMonitor.close();
179 
180         if (mLifecycle != null) {
181             mLifecycle.removeObserver(this);
182             mLifecycle = null;
183 
184             sSingleton = null;
185         }
186     }
187 
188     /**
189      * Get SubscriptionManager
190      *
191      * @return a SubscriptionManager
192      */
get()193     public SubscriptionManager get() {
194         return mSubscriptionMonitor.getSubscriptionManager();
195     }
196 
197     /**
198      * Get current max. number active subscription info(s) been setup within device
199      *
200      * @return max. number of active subscription info(s)
201      */
getActiveSubscriptionInfoCountMax()202     public int getActiveSubscriptionInfoCountMax() {
203         return mSubscriptionMonitor.getActiveSubscriptionInfoCountMax();
204     }
205 
206     /**
207      * Get a list of active subscription info
208      *
209      * @return A list of active subscription info
210      */
getActiveSubscriptionsInfo()211     public List<SubscriptionInfo> getActiveSubscriptionsInfo() {
212         return mSubscriptionMonitor.getActiveSubscriptionsInfo();
213     }
214 
215     /**
216      * Get an active subscription info with given subscription ID
217      *
218      * @param subId target subscription ID
219      * @return A subscription info which is active list
220      */
getActiveSubscriptionInfo(int subId)221     public SubscriptionInfo getActiveSubscriptionInfo(int subId) {
222         return mSubscriptionMonitor.getActiveSubscriptionInfo(subId);
223     }
224 
225     /**
226      * Get a list of accessible subscription info
227      *
228      * @return A list of accessible subscription info
229      */
getAccessibleSubscriptionsInfo()230     public List<SubscriptionInfo> getAccessibleSubscriptionsInfo() {
231         return mSubscriptionMonitor.getAccessibleSubscriptionsInfo();
232     }
233 
234     /**
235      * Get an accessible subscription info with given subscription ID
236      *
237      * @param subId target subscription ID
238      * @return A subscription info which is accessible list
239      */
getAccessibleSubscriptionInfo(int subId)240     public SubscriptionInfo getAccessibleSubscriptionInfo(int subId) {
241         return mSubscriptionMonitor.getAccessibleSubscriptionInfo(subId);
242     }
243 
244     /**
245      * Clear data cached within proxy
246      */
clearCache()247     public void clearCache() {
248         mSubscriptionMonitor.clearCache();
249     }
250 
251     /**
252      * Add listener to active subscriptions monitor list.
253      * Note: listener only take place when change happens.
254      *       No immediate callback performed after the invoke of this method.
255      *
256      * @param listener listener to active subscriptions change
257      */
258     @Keep
addActiveSubscriptionsListener(OnActiveSubscriptionChangedListener listener)259     public void addActiveSubscriptionsListener(OnActiveSubscriptionChangedListener listener) {
260         removeSpecificListenerAndCleanList(listener, mPendingNotifyListeners);
261         removeSpecificListenerAndCleanList(listener, mActiveSubscriptionsListeners);
262         if ((listener == null) || (getListenerState(listener) == LISTENER_END_OF_LIFE)) {
263             return;
264         }
265         mActiveSubscriptionsListeners.add(listener);
266     }
267 
268     /**
269      * Remove listener from active subscriptions monitor list
270      *
271      * @param listener listener to active subscriptions change
272      */
273     @Keep
removeActiveSubscriptionsListener(OnActiveSubscriptionChangedListener listener)274     public void removeActiveSubscriptionsListener(OnActiveSubscriptionChangedListener listener) {
275         removeSpecificListenerAndCleanList(listener, mPendingNotifyListeners);
276         removeSpecificListenerAndCleanList(listener, mActiveSubscriptionsListeners);
277     }
278 
getListenerState(OnActiveSubscriptionChangedListener listener)279     private int getListenerState(OnActiveSubscriptionChangedListener listener) {
280         Lifecycle lifecycle = listener.getLifecycle();
281         if (lifecycle == null) {
282             return LISTENER_IS_ACTIVE;
283         }
284         Lifecycle.State lifecycleState = lifecycle.getCurrentState();
285         if (lifecycleState == Lifecycle.State.DESTROYED) {
286             Log.d(LOG_TAG, "Listener dead detected - " + listener);
287             return LISTENER_END_OF_LIFE;
288         }
289         return lifecycleState.isAtLeast(Lifecycle.State.STARTED) ?
290                 LISTENER_IS_ACTIVE : LISTENER_IS_INACTIVE;
291     }
292 
removeSpecificListenerAndCleanList(OnActiveSubscriptionChangedListener listener, List<OnActiveSubscriptionChangedListener> list)293     private void removeSpecificListenerAndCleanList(OnActiveSubscriptionChangedListener listener,
294             List<OnActiveSubscriptionChangedListener> list) {
295         // also drop listener(s) which is end of life
296         list.removeIf(it -> (it == listener) || (getListenerState(it) == LISTENER_END_OF_LIFE));
297     }
298 
processStatusChangeOnListeners( List<OnActiveSubscriptionChangedListener> listeners)299     private void processStatusChangeOnListeners(
300             List<OnActiveSubscriptionChangedListener> listeners) {
301         // categorize listener(s), and end of life listener(s) been ignored
302         Map<Integer, List<OnActiveSubscriptionChangedListener>> categorizedListeners =
303                 listeners.stream()
304                 .collect(Collectors.groupingBy(it -> getListenerState(it)));
305 
306         // have inactive listener(s) in pending list
307         categorizedListeners.computeIfPresent(LISTENER_IS_INACTIVE, (category, list) -> {
308             mPendingNotifyListeners.addAll(list);
309             return list;
310         });
311 
312         // get active listener(s)
313         categorizedListeners.computeIfPresent(LISTENER_IS_ACTIVE, (category, list) -> {
314             mActiveSubscriptionsListeners.addAll(list);
315             // notify each one of them
316             list.stream().forEach(it -> it.onChanged());
317             return list;
318         });
319     }
320 }
321