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