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