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