1 /*
2  * Copyright (C) 2022 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.deviceinfo.simstatus;
18 
19 import android.content.Context;
20 import android.telephony.SubscriptionInfo;
21 import android.telephony.SubscriptionManager;
22 import android.telephony.TelephonyManager;
23 import android.util.Log;
24 
25 import androidx.lifecycle.DefaultLifecycleObserver;
26 import androidx.lifecycle.Lifecycle;
27 import androidx.lifecycle.LifecycleOwner;
28 import androidx.lifecycle.LiveData;
29 
30 import com.android.settings.network.SubscriptionsChangeListener;
31 
32 import java.util.List;
33 import java.util.concurrent.ConcurrentHashMap;
34 import java.util.concurrent.Executor;
35 import java.util.concurrent.Phaser;
36 import java.util.concurrent.atomic.AtomicInteger;
37 import java.util.concurrent.atomic.AtomicLong;
38 
39 /**
40  * A class for showing a summary of status of sim slots.
41  */
42 public class SlotSimStatus extends LiveData<Long>
43         implements DefaultLifecycleObserver,
44         SubscriptionsChangeListener.SubscriptionsChangeListenerClient {
45 
46     private static final String TAG = "SlotSimStatus";
47 
48     private final AtomicInteger mNumberOfSlots = new AtomicInteger(0);
49     private final ConcurrentHashMap<Integer, SubscriptionInfo> mSubscriptionMap =
50             new ConcurrentHashMap<Integer, SubscriptionInfo>();
51     private final Phaser mBlocker = new Phaser(1);
52     private final AtomicLong mDataVersion = new AtomicLong(0);
53 
54     private Context mContext;
55     private int mBasePreferenceOrdering;
56     private SubscriptionsChangeListener mSubscriptionsChangeListener;
57 
58     private static final String KEY_SIM_STATUS = "sim_status";
59 
60     /**
61      * Construct of class.
62      * @param context Context
63      */
SlotSimStatus(Context context)64     public SlotSimStatus(Context context) {
65         this(context, null, null);
66     }
67 
68     /**
69      * Construct of class.
70      * @param context Context
71      * @param executor executor for offload to thread
72      * @param lifecycle Lifecycle
73      */
SlotSimStatus(Context context, Executor executor, Lifecycle lifecycle)74     public SlotSimStatus(Context context, Executor executor, Lifecycle lifecycle) {
75         mContext = context;
76         if (executor == null) {
77             queryRecords(context);
78         } else {
79             executor.execute(() -> asyncQueryRecords(context));
80         }
81         if (lifecycle != null) {
82             lifecycle.addObserver(this);
83             mSubscriptionsChangeListener = new SubscriptionsChangeListener(context, this);
84             mSubscriptionsChangeListener.start();
85         }
86     }
87 
queryRecords(Context context)88     protected void queryRecords(Context context) {
89         queryDetails(context);
90         setValue(mDataVersion.incrementAndGet());
91         mBlocker.arrive();
92     }
93 
asyncQueryRecords(Context context)94     protected void asyncQueryRecords(Context context) {
95         queryDetails(context);
96         postValue(mDataVersion.incrementAndGet());
97         mBlocker.arrive();
98     }
99 
updateRecords()100     protected void updateRecords() {
101         queryDetails(mContext);
102         setValue(mDataVersion.incrementAndGet());
103     }
104 
queryDetails(Context context)105     protected void queryDetails(Context context) {
106         TelephonyManager telMgr = context.getSystemService(TelephonyManager.class);
107         if (telMgr != null) {
108             mNumberOfSlots.set(telMgr.getPhoneCount());
109         }
110 
111         SubscriptionManager subMgr = context.getSystemService(SubscriptionManager.class);
112         if (subMgr == null) {
113             mSubscriptionMap.clear();
114             return;
115         }
116 
117         List<SubscriptionInfo> subInfoList = subMgr.getActiveSubscriptionInfoList();
118         if ((subInfoList == null) || (subInfoList.size() <= 0)) {
119             mSubscriptionMap.clear();
120             Log.d(TAG, "No active SIM.");
121             return;
122         }
123 
124         mSubscriptionMap.clear();
125         subInfoList.forEach(subInfo -> {
126             int slotIndex = subInfo.getSimSlotIndex();
127             mSubscriptionMap.put(slotIndex, subInfo);
128         });
129         Log.d(TAG, "Number of active SIM: " + subInfoList.size());
130     }
131 
waitForResult()132     protected void waitForResult() {
133         mBlocker.awaitAdvance(0);
134     }
135 
136     /**
137      * Set base ordering of Preference.
138      * @param baseOrdering the base ordering for SIM Status within "About Phone".
139      */
setBasePreferenceOrdering(int baseOrdering)140     public void setBasePreferenceOrdering(int baseOrdering) {
141         mBasePreferenceOrdering = baseOrdering;
142     }
143 
144     /**
145      * Number of slots available.
146      * @return number of slots
147      */
size()148     public int size() {
149         waitForResult();
150         return mNumberOfSlots.get();
151     }
152 
153     /**
154      * Get ordering of Preference based on index of slot.
155      * @param slotIndex index of slot
156      * @return Preference ordering.
157      */
getPreferenceOrdering(int slotIndex)158     public int getPreferenceOrdering(int slotIndex) {
159         return mBasePreferenceOrdering + 1 + slotIndex;
160     }
161 
162     /**
163      * Get key of Preference and PreferenceController based on index of slot.
164      * @param slotIndex index of slot
165      * @return Preference key.
166      */
getPreferenceKey(int slotIndex)167     public String getPreferenceKey(int slotIndex) {
168         if (slotIndex == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
169             return KEY_SIM_STATUS;
170         }
171         return KEY_SIM_STATUS + (1 + slotIndex);
172     }
173 
174     /**
175      * Get subscription based on slot index.
176      * @param slotIndex index of slot (starting from 0)
177      * @return SubscriptionInfo based on index of slot.
178      *         {@code null} means no subscription on slot.
179      */
getSubscriptionInfo(int slotIndex)180     public SubscriptionInfo getSubscriptionInfo(int slotIndex) {
181         if (slotIndex >= size()) {
182             return null;
183         }
184         return mSubscriptionMap.get(slotIndex);
185     }
186 
187     /**
188      * Get slot index based on Preference key
189      * @param prefKey is the preference key
190      * @return slot index.
191      */
findSlotIndexByKey(String prefKey)192     public int findSlotIndexByKey(String prefKey) {
193         int simSlotIndex = SubscriptionManager.INVALID_SIM_SLOT_INDEX + 1;
194         try {
195             simSlotIndex = Integer.parseInt(prefKey.substring(KEY_SIM_STATUS.length()));
196         } catch (Exception exception) {
197             Log.w(TAG, "Preference key invalid: " + prefKey +
198                        ". Error Msg: " + exception.getMessage());
199         }
200         return simSlotIndex - 1;
201     }
202 
203     @Override
onAirplaneModeChanged(boolean airplaneModeEnabled)204     public void onAirplaneModeChanged(boolean airplaneModeEnabled) {
205         if (airplaneModeEnabled) {
206             /**
207              * Only perform update when airplane mode ON.
208              * Relay on #onSubscriptionsChanged() when airplane mode OFF.
209              */
210             updateRecords();
211         }
212     }
213 
214     @Override
onSubscriptionsChanged()215     public void onSubscriptionsChanged() {
216         updateRecords();
217     }
218 
219     @Override
onDestroy(LifecycleOwner lifecycleOwner)220     public void onDestroy(LifecycleOwner lifecycleOwner) {
221         if (mSubscriptionsChangeListener != null) {
222             mSubscriptionsChangeListener.stop();
223         }
224         lifecycleOwner.getLifecycle().removeObserver(this);
225     }
226 }
227