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