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