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.car.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.telephony.SubscriptionInfo;
24 import android.telephony.SubscriptionManager;
25 
26 import androidx.annotation.NonNull;
27 import androidx.annotation.VisibleForTesting;
28 import androidx.lifecycle.DefaultLifecycleObserver;
29 import androidx.lifecycle.LifecycleOwner;
30 
31 import com.android.car.settings.common.Logger;
32 import com.android.internal.telephony.TelephonyIntents;
33 import com.android.internal.telephony.flags.Flags;
34 import com.android.internal.util.CollectionUtils;
35 
36 import java.util.ArrayList;
37 import java.util.List;
38 import java.util.Objects;
39 
40 /**
41  * Listens to potential changes in subscription id and updates registered {@link
42  * MobileNetworkUpdateManager.MobileNetworkUpdateListener} with the new subscription id.
43  */
44 public class MobileNetworkUpdateManager implements DefaultLifecycleObserver {
45 
46     /** Value to represent that the subscription id hasn't been computed yet. */
47     static final int SUB_ID_NULL = Integer.MIN_VALUE;
48     private static final Logger LOG = new Logger(MobileNetworkUpdateManager.class);
49 
50     private final List<MobileNetworkUpdateListener> mListeners = new ArrayList<>();
51     private final PhoneChangeReceiver mPhoneChangeReceiver;
52     private final SubscriptionManager mSubscriptionManager;
53     private List<SubscriptionInfo> mSubscriptionInfos;
54     private int mCurSubscriptionId;
55 
56     @VisibleForTesting
57     final SubscriptionManager.OnSubscriptionsChangedListener
58             mOnSubscriptionsChangeListener =
59             new SubscriptionManager.OnSubscriptionsChangedListener() {
60                 @Override
61                 public void onSubscriptionsChanged() {
62                     if (!Objects.equals(mSubscriptionInfos,
63                             mSubscriptionManager.getActiveSubscriptionInfoList(
64                                     /* userVisibleOnly= */ true))) {
65                         updateSubscriptions(/* forceRefresh= */ false);
66                     }
67                 }
68             };
69 
MobileNetworkUpdateManager(Context context, int subId)70     public MobileNetworkUpdateManager(Context context, int subId) {
71         mCurSubscriptionId = subId;
72         SubscriptionManager sm = context.getSystemService(SubscriptionManager.class);
73         if (Flags.workProfileApiSplit()) {
74             sm = sm.createForAllUserProfiles();
75         }
76         mSubscriptionManager = sm;
77         mSubscriptionInfos = mSubscriptionManager.getActiveSubscriptionInfoList();
78 
79         mPhoneChangeReceiver = new PhoneChangeReceiver(context, () -> {
80             if (mCurSubscriptionId != SUB_ID_NULL) {
81                 // When the radio changes (ex: CDMA->GSM), refresh the fragment.
82                 // This is very rare.
83                 LOG.d("Radio change (i.e. CDMA->GSM) received for valid subscription id: "
84                         + mCurSubscriptionId);
85                 updateReceived(mCurSubscriptionId);
86             }
87         });
88     }
89 
90     @VisibleForTesting
MobileNetworkUpdateManager(int subId, SubscriptionManager subscriptionManager, PhoneChangeReceiver phoneChangeReceiver)91     MobileNetworkUpdateManager(int subId, SubscriptionManager subscriptionManager,
92             PhoneChangeReceiver phoneChangeReceiver) {
93         mCurSubscriptionId = subId;
94         mSubscriptionManager = subscriptionManager;
95         mSubscriptionInfos = mSubscriptionManager.getActiveSubscriptionInfoList();
96 
97         mPhoneChangeReceiver = phoneChangeReceiver;
98     }
99 
100     /**
101      * Registers a listener that will receive necessary updates to changes in the mobile network.
102      */
registerListener(MobileNetworkUpdateListener listener)103     public void registerListener(MobileNetworkUpdateListener listener) {
104         mListeners.add(listener);
105     }
106 
107     /**
108      * Unregisters a listener that was previously added via
109      * {@link MobileNetworkUpdateManager#registerListener(MobileNetworkUpdateListener)}. The
110      * provided argument must refer to the same object that was registered in order to securely be
111      * unregistered.
112      */
unregisterListener(MobileNetworkUpdateListener listener)113     public void unregisterListener(MobileNetworkUpdateListener listener) {
114         mListeners.remove(listener);
115     }
116 
117     @Override
onCreate(@onNull LifecycleOwner owner)118     public void onCreate(@NonNull LifecycleOwner owner) {
119         updateSubscriptions(/* forceRefresh= */ true);
120     }
121 
122     @Override
onStart(@onNull LifecycleOwner owner)123     public final void onStart(@NonNull LifecycleOwner owner) {
124         mPhoneChangeReceiver.register();
125         mSubscriptionManager.addOnSubscriptionsChangedListener(mOnSubscriptionsChangeListener);
126     }
127 
128     @Override
onStop(@onNull LifecycleOwner owner)129     public final void onStop(@NonNull LifecycleOwner owner) {
130         mPhoneChangeReceiver.unregister();
131         mSubscriptionManager.removeOnSubscriptionsChangedListener(mOnSubscriptionsChangeListener);
132     }
133 
updateSubscriptions(boolean forceRefresh)134     private void updateSubscriptions(boolean forceRefresh) {
135         LOG.d("updateSubscriptions called");
136         mSubscriptionInfos = mSubscriptionManager.getActiveSubscriptionInfoList();
137         int subId = getSubscriptionId();
138         if (forceRefresh || mCurSubscriptionId != subId) {
139             LOG.d("updateSubscriptions updated subscription id! prev: " + mCurSubscriptionId
140                     + " new: " + subId);
141             mCurSubscriptionId = subId;
142             updateReceived(mCurSubscriptionId);
143         }
144     }
145 
updateReceived(int subId)146     private void updateReceived(int subId) {
147         for (MobileNetworkUpdateListener listener : mListeners) {
148             listener.onMobileNetworkUpdated(subId);
149         }
150     }
151 
getSubscriptionId()152     private int getSubscriptionId() {
153         SubscriptionInfo subscription = getSubscription();
154         return subscription != null ? subscription.getSubscriptionId()
155                 : SubscriptionManager.INVALID_SUBSCRIPTION_ID;
156     }
157 
158     /**
159      * First, find a subscription with the id provided at construction if it exists. If not, just
160      * return the first one in the mSubscriptionInfos list since it is already sorted by sim slot.
161      */
getSubscription()162     private SubscriptionInfo getSubscription() {
163         if (mCurSubscriptionId != SUB_ID_NULL) {
164             for (SubscriptionInfo subscriptionInfo :
165                     mSubscriptionManager.getSelectableSubscriptionInfoList()) {
166                 if (subscriptionInfo.getSubscriptionId() == mCurSubscriptionId) {
167                     return subscriptionInfo;
168                 }
169             }
170         }
171 
172         return CollectionUtils.isEmpty(mSubscriptionInfos) ? null : mSubscriptionInfos.get(0);
173     }
174 
175     /**
176      * Interface used by components listening to subscription id updates from {@link
177      * MobileNetworkUpdateManager}.
178      */
179     public interface MobileNetworkUpdateListener {
180         /** Called when there is a new subscription id that other components should be aware of. */
onMobileNetworkUpdated(int subId)181         void onMobileNetworkUpdated(int subId);
182     }
183 
184     /** Broadcast receiver which observes changes in radio technology (i.e. CDMA vs GSM). */
185     @VisibleForTesting
186     static class PhoneChangeReceiver extends BroadcastReceiver {
187         @VisibleForTesting
188         static final IntentFilter RADIO_TECHNOLOGY_CHANGED_FILTER = new IntentFilter(
189                 TelephonyIntents.ACTION_RADIO_TECHNOLOGY_CHANGED);
190 
191         private Context mContext;
192         private PhoneChangeReceiver.OnChangeAction mOnChangeAction;
193 
194         /** Action to take when receiver receives a non sticky broadcast intent. */
195         private interface OnChangeAction {
onReceive()196             void onReceive();
197         }
198 
PhoneChangeReceiver(Context context, PhoneChangeReceiver.OnChangeAction onChangeAction)199         PhoneChangeReceiver(Context context, PhoneChangeReceiver.OnChangeAction onChangeAction) {
200             mContext = context;
201             mOnChangeAction = onChangeAction;
202         }
203 
register()204         void register() {
205             mContext.registerReceiver(this, RADIO_TECHNOLOGY_CHANGED_FILTER);
206         }
207 
unregister()208         void unregister() {
209             mContext.unregisterReceiver(this);
210         }
211 
212         @Override
onReceive(Context context, Intent intent)213         public void onReceive(Context context, Intent intent) {
214             if (!isInitialStickyBroadcast()) {
215                 mOnChangeAction.onReceive();
216             }
217         }
218     }
219 }
220