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.telephony.qns;
18 
19 import android.annotation.NonNull;
20 import android.content.Context;
21 import android.os.Handler;
22 import android.os.HandlerThread;
23 import android.os.Looper;
24 import android.os.Message;
25 import android.telephony.SubscriptionManager;
26 import android.telephony.ims.ProvisioningManager;
27 import android.text.TextUtils;
28 import android.util.Log;
29 
30 import com.android.internal.annotations.VisibleForTesting;
31 
32 import java.util.concurrent.ConcurrentHashMap;
33 
34 class QnsProvisioningListener {
35 
36     private static final long REG_CALLBACK_DELAY = 2000L; // 3sec
37     private static final int REG_CALLBACK_RETRY = 10; // 10 times
38     private static final int EVENT_BASE = 11000;
39     private static final int EVENT_REGISTER_PROVISIONING_CALLBACK = EVENT_BASE + 1;
40     private static final int EVENT_CALLBACK_REGISTERED = EVENT_BASE + 3;
41     private static final int EVENT_NOTIFY_PROVISION_INFO_CHANGED = EVENT_BASE + 4;
42     private static final int EVENT_IMS_STATE_CHANGED = EVENT_BASE + 5;
43     private final String mLogTag;
44     private final Context mContext;
45     private final int mSlotIndex;
46     private final QnsProvisioningInfo mProvisioningInfo;
47     private final QnsImsManager mQnsImsManager;
48     @VisibleForTesting QnsProvisioningHandler mQnsProvisioningHandler;
49 
50     private final QnsProvisioningCallback mQnsProvisioningCallback;
51     private final QnsRegistrantList mRegistrantList;
52     private ProvisioningManager mProvisioningManager;
53     private boolean mIsProvisioningCallbackRegistered;
54 
QnsProvisioningListener(Context context, QnsImsManager imsManager, int slotIndex)55     QnsProvisioningListener(Context context, QnsImsManager imsManager, int slotIndex) {
56         mSlotIndex = slotIndex;
57         mLogTag = QnsProvisioningListener.class.getSimpleName() + "_" + mSlotIndex;
58         mContext = context;
59         mQnsImsManager = imsManager;
60         mProvisioningInfo = new QnsProvisioningInfo();
61         mQnsProvisioningCallback = new QnsProvisioningCallback();
62         mIsProvisioningCallbackRegistered = false;
63         mRegistrantList = new QnsRegistrantList();
64 
65         HandlerThread handlerThread = new HandlerThread(mLogTag);
66         handlerThread.start();
67         mQnsProvisioningHandler = new QnsProvisioningHandler(handlerThread.getLooper());
68 
69         registerProvisioningCallback();
70         mQnsImsManager.registerImsStateChanged(mQnsProvisioningHandler, EVENT_IMS_STATE_CHANGED);
71     }
72 
close()73     void close() {
74         mQnsImsManager.unregisterImsStateChanged(mQnsProvisioningHandler);
75         mRegistrantList.removeAll();
76         mProvisioningInfo.clear();
77         unregisterProvisioningCallback();
78     }
79 
registerProvisioningCallback()80     private void registerProvisioningCallback() {
81         // checks if the callback is already registered
82         if (mIsProvisioningCallbackRegistered) {
83             log("registerProvisioningCallback: already registered.");
84             return;
85         }
86 
87         // checks for validation subscription id.
88         int subId = QnsUtils.getSubId(mContext, mSlotIndex);
89         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
90             log("registerProvisioningCallback failed Invalid Subscription Id");
91             return;
92         }
93 
94         log("on registering provisioning callback");
95 
96         try {
97             // checks ImsException for ims not supported or unavailable.
98             if (mQnsImsManager.getImsServiceState() != 2) { // STATE_READY
99                 throw new Exception();
100             }
101 
102             // create provisioning manager.
103             if (mProvisioningManager == null) {
104                 mProvisioningManager = ProvisioningManager.createForSubscriptionId(subId);
105             }
106 
107             // Register provisioning changed callback
108             mProvisioningManager.registerProvisioningChangedCallback(
109                     mContext.getMainExecutor(), mQnsProvisioningCallback);
110 
111             // Set the provisioning callback is registered.
112             mIsProvisioningCallbackRegistered = true;
113 
114             log("registered provisioning callback");
115 
116             mQnsProvisioningHandler.sendProvisioningCallbackRegistered();
117         } catch (Exception e) {
118             loge("registerProvisioningCallback error: " + e);
119 
120             // Unregister the callback
121             unregisterProvisioningCallback();
122 
123             // Retry registering provisioning callback.
124             if (!mIsProvisioningCallbackRegistered) {
125                 mQnsProvisioningHandler.sendRegisterProvisioningCallback();
126             }
127         }
128     }
129 
unregisterProvisioningCallback()130     private void unregisterProvisioningCallback() {
131         log("unregisterProvisioningCallback");
132 
133         if (mProvisioningManager != null) {
134             try {
135                 mProvisioningManager.unregisterProvisioningChangedCallback(
136                         mQnsProvisioningCallback);
137             } catch (Exception e) {
138                 loge("unregisterProvisioningCallback error:" + e);
139             }
140         }
141         if (mIsProvisioningCallbackRegistered) {
142             mIsProvisioningCallbackRegistered = false;
143         }
144         if (mProvisioningManager != null) {
145             mProvisioningManager = null;
146         }
147     }
148 
149     /**
150      * Register an event for Provisioning value changed.
151      *
152      * @param h the Handler to get event.
153      * @param what the event.
154      * @param userObj user object.
155      * @param notifyImmediately set true if you want to notify immediately.
156      */
registerProvisioningItemInfoChanged( Handler h, int what, Object userObj, boolean notifyImmediately)157     void registerProvisioningItemInfoChanged(
158             Handler h, int what, Object userObj, boolean notifyImmediately) {
159         if (h != null) {
160             QnsRegistrant r = new QnsRegistrant(h, what, userObj);
161             mRegistrantList.add(r);
162             if (notifyImmediately) {
163                 r.notifyRegistrant(
164                         new QnsAsyncResult(null, new QnsProvisioningInfo(mProvisioningInfo), null));
165             }
166         }
167     }
168 
169     /**
170      * Unregister an event for Provisioning value changed.
171      *
172      * @param h the handler to get event.
173      */
unregisterProvisioningItemInfoChanged(Handler h)174     void unregisterProvisioningItemInfoChanged(Handler h) {
175         if (h != null) {
176             mRegistrantList.remove(h);
177         }
178     }
179 
getLastProvisioningWfcRoamingEnabledInfo()180     boolean getLastProvisioningWfcRoamingEnabledInfo() {
181         try {
182             return mProvisioningInfo.getIntegerItem(
183                             ProvisioningManager.KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE)
184                     != 0;
185         } catch (Exception e) {
186             return false;
187         }
188     }
189 
notifyProvisioningItemInfoChanged(@onNull QnsProvisioningInfo info)190     private void notifyProvisioningItemInfoChanged(@NonNull QnsProvisioningInfo info) {
191         log("notify ProvisioningItemInfo:" + info);
192         mRegistrantList.notifyRegistrants(new QnsAsyncResult(null, info, null));
193     }
194 
loadDefaultItems()195     private void loadDefaultItems() {
196         synchronized (mProvisioningInfo) {
197             loadIntegerItem(ProvisioningManager.KEY_VOLTE_PROVISIONING_STATUS);
198             loadIntegerItem(ProvisioningManager.KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE);
199             loadIntegerItem(ProvisioningManager.KEY_VOICE_OVER_WIFI_MODE_OVERRIDE);
200             loadIntegerItem(ProvisioningManager.KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE);
201             loadIntegerItem(ProvisioningManager.KEY_LTE_THRESHOLD_1);
202             loadIntegerItem(ProvisioningManager.KEY_LTE_THRESHOLD_2);
203             loadIntegerItem(ProvisioningManager.KEY_LTE_THRESHOLD_3);
204             loadIntegerItem(ProvisioningManager.KEY_1X_THRESHOLD);
205             loadIntegerItem(ProvisioningManager.KEY_WIFI_THRESHOLD_A);
206             loadIntegerItem(ProvisioningManager.KEY_WIFI_THRESHOLD_B);
207             loadIntegerItem(ProvisioningManager.KEY_LTE_EPDG_TIMER_SEC);
208             loadIntegerItem(ProvisioningManager.KEY_WIFI_EPDG_TIMER_SEC);
209             loadIntegerItem(ProvisioningManager.KEY_1X_EPDG_TIMER_SEC);
210             loadStringItem(ProvisioningManager.KEY_VOICE_OVER_WIFI_ENTITLEMENT_ID);
211             if (mProvisioningInfo.isUpdated()) {
212                 mQnsProvisioningHandler.sendNotifyProvisioningInfoChanged();
213             }
214         }
215     }
216 
loadIntegerItem(int item)217     private void loadIntegerItem(int item) {
218         try {
219             int value = mProvisioningManager.getProvisioningIntValue(item);
220             log("loadIntegerItem item:" + item + " value:" + value);
221             mProvisioningInfo.setIntegerItem(item, value);
222         } catch (Exception e) {
223             loge("got exception e:" + e);
224         }
225     }
226 
loadStringItem(int item)227     private void loadStringItem(int item) {
228         try {
229             String value = mProvisioningManager.getProvisioningStringValue(item);
230             log("loadStringItem item:" + item + " value:" + value);
231             mProvisioningInfo.setStringItem(item, value);
232         } catch (Exception e) {
233             loge("got exception e:" + e);
234         }
235     }
236 
log(String s)237     protected void log(String s) {
238         Log.d(mLogTag, s);
239     }
240 
loge(String s)241     protected void loge(String s) {
242         Log.e(mLogTag, s);
243     }
244 
245     static class QnsProvisioningInfo {
246 
247         private final ConcurrentHashMap<Integer, Integer> mIntegerItems;
248         private final ConcurrentHashMap<Integer, String> mStringItems;
249         private boolean mUpdated;
250 
QnsProvisioningInfo()251         QnsProvisioningInfo() {
252             mIntegerItems = new ConcurrentHashMap<>();
253             mStringItems = new ConcurrentHashMap<>();
254             mUpdated = false;
255         }
256 
QnsProvisioningInfo(QnsProvisioningInfo info)257         QnsProvisioningInfo(QnsProvisioningInfo info) {
258             mIntegerItems = new ConcurrentHashMap<>();
259             mStringItems = new ConcurrentHashMap<>();
260             mIntegerItems.putAll(info.mIntegerItems);
261             mStringItems.putAll(info.mStringItems);
262             mUpdated = info.mUpdated;
263         }
264 
265         @Override
toString()266         public String toString() {
267             return "QnsProvisioningInfo{"
268                     + "mIntegerItems="
269                     + mIntegerItems
270                     + ", mStringItems="
271                     + mStringItems
272                     + ", mUpdated="
273                     + mUpdated
274                     + '}';
275         }
276 
hasItem(int item)277         boolean hasItem(int item) {
278             return mIntegerItems.get(item) != null || mStringItems.get(item) != null;
279         }
280 
setIntegerItem(int item, int value)281         private void setIntegerItem(int item, int value) {
282             if (value == ProvisioningManager.PROVISIONING_RESULT_UNKNOWN
283                     || (!isValueZeroValidItem(item)
284                             && value == ProvisioningManager.PROVISIONING_VALUE_DISABLED)) {
285                 if (mIntegerItems.remove(item) != null) {
286                     markUpdated(true);
287                 }
288                 return;
289             }
290             if (getIntegerItem(item) != null && getIntegerItem(item) == value) {
291                 return;
292             }
293             mIntegerItems.put(item, value);
294             markUpdated(true);
295         }
296 
getIntegerItem(int item)297         Integer getIntegerItem(int item) {
298             return mIntegerItems.get(item);
299         }
300 
isValueZeroValidItem(int key)301         private boolean isValueZeroValidItem(int key) {
302             switch (key) {
303                 case ProvisioningManager.KEY_VOLTE_PROVISIONING_STATUS:
304                 case ProvisioningManager.KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE:
305                 case ProvisioningManager.KEY_VOICE_OVER_WIFI_MODE_OVERRIDE:
306                 case ProvisioningManager.KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE:
307                 case ProvisioningManager.KEY_VOICE_OVER_WIFI_ENTITLEMENT_ID:
308                     return true;
309                 case ProvisioningManager.KEY_LTE_THRESHOLD_1:
310                 case ProvisioningManager.KEY_LTE_THRESHOLD_2:
311                 case ProvisioningManager.KEY_LTE_THRESHOLD_3:
312                 case ProvisioningManager.KEY_1X_THRESHOLD:
313                 case ProvisioningManager.KEY_WIFI_THRESHOLD_A:
314                 case ProvisioningManager.KEY_WIFI_THRESHOLD_B:
315                 case ProvisioningManager.KEY_LTE_EPDG_TIMER_SEC:
316                 case ProvisioningManager.KEY_WIFI_EPDG_TIMER_SEC:
317                 case ProvisioningManager.KEY_1X_EPDG_TIMER_SEC:
318                     return false;
319             }
320             return true;
321         }
322 
setStringItem(int item, String value)323         private void setStringItem(int item, String value) {
324             if (ProvisioningManager.STRING_QUERY_RESULT_ERROR_GENERIC.equals(value)
325                     || ProvisioningManager.STRING_QUERY_RESULT_ERROR_NOT_READY.equals(value)) {
326                 if (mStringItems.remove(item) != null) {
327                     markUpdated(true);
328                 }
329                 return;
330             }
331             if (TextUtils.equals(value, getStringItem(item))) {
332                 return;
333             }
334             mStringItems.put(item, value);
335             markUpdated(true);
336         }
337 
getStringItem(int item)338         String getStringItem(int item) {
339             return mStringItems.get(item);
340         }
341 
clear()342         void clear() {
343             mIntegerItems.clear();
344             mStringItems.clear();
345             markUpdated(false);
346         }
347 
markUpdated(boolean bUpdated)348         void markUpdated(boolean bUpdated) {
349             mUpdated = bUpdated;
350         }
351 
isUpdated()352         boolean isUpdated() {
353             return mUpdated;
354         }
355 
equalsIntegerItem(QnsProvisioningInfo info, int key)356         boolean equalsIntegerItem(QnsProvisioningInfo info, int key) {
357             Integer my = getIntegerItem(key);
358             Integer other = info.getIntegerItem(key);
359             if (my == null && other == null) {
360                 return true;
361             } else if (my != null && other != null) {
362                 int myvalue = my;
363                 int othervalue = other;
364                 return myvalue == othervalue;
365             }
366             return false;
367         }
368     }
369 
370     private class QnsProvisioningCallback extends ProvisioningManager.Callback {
371         /** Constructor */
QnsProvisioningCallback()372         QnsProvisioningCallback() {}
373 
374         /**
375          * Called when a provisioning item has changed.
376          *
377          * @param item the IMS provisioning key constant, as defined by the OEM.
378          * @param value the new integer value of the IMS provisioning key.
379          */
380         @Override
onProvisioningIntChanged(int item, int value)381         public void onProvisioningIntChanged(int item, int value) {
382             synchronized (mProvisioningInfo) {
383                 mProvisioningInfo.setIntegerItem(item, value);
384                 if (mProvisioningInfo.isUpdated()) {
385                     mQnsProvisioningHandler.sendNotifyProvisioningInfoChanged();
386                 }
387             }
388         }
389 
390         /**
391          * Called when a provisioning item has changed.
392          *
393          * @param item the IMS provisioning key constant, as defined by the OEM.
394          * @param value the new String value of the IMS configuration constant.
395          */
396         @Override
onProvisioningStringChanged(int item, String value)397         public void onProvisioningStringChanged(int item, String value) {
398             synchronized (mProvisioningInfo) {
399                 mProvisioningInfo.setStringItem(item, value);
400                 if (mProvisioningInfo.isUpdated()) {
401                     mQnsProvisioningHandler.sendNotifyProvisioningInfoChanged();
402                 }
403             }
404         }
405     }
406 
407     @VisibleForTesting
408     class QnsProvisioningHandler extends Handler {
409         private int mRetryRegisterProvisioningCallbackCount;
410 
QnsProvisioningHandler(Looper looper)411         QnsProvisioningHandler(Looper looper) {
412             super(looper);
413             mRetryRegisterProvisioningCallbackCount = REG_CALLBACK_RETRY;
414         }
415 
resetRetryRegisterProvisioningCallbackCount()416         void resetRetryRegisterProvisioningCallbackCount() {
417             mRetryRegisterProvisioningCallbackCount = REG_CALLBACK_RETRY;
418         }
419 
420         @Override
handleMessage(Message message)421         public void handleMessage(Message message) {
422             log("message what:" + message.what);
423             QnsAsyncResult ar = (QnsAsyncResult) message.obj;
424             switch (message.what) {
425                 case EVENT_IMS_STATE_CHANGED:
426                     if (ar != null) {
427                         QnsImsManager.ImsState state = (QnsImsManager.ImsState) ar.mResult;
428                         if (state.isImsAvailable()) {
429                             log("ImsState is changed to available");
430                             unregisterProvisioningCallback();
431                             resetRetryRegisterProvisioningCallbackCount();
432                             registerProvisioningCallback();
433                         } else {
434                             log("ImsState is changed to unavailable");
435                             clearLastProvisioningInfo();
436                         }
437                     }
438                     break;
439                 case EVENT_REGISTER_PROVISIONING_CALLBACK:
440                     registerProvisioningCallback();
441                     break;
442                 case EVENT_CALLBACK_REGISTERED:
443                     loadDefaultItems();
444                     break;
445                 case EVENT_NOTIFY_PROVISION_INFO_CHANGED:
446                     synchronized (mProvisioningInfo) {
447                         if (mProvisioningInfo.isUpdated()) {
448                             notifyProvisioningItemInfoChanged(
449                                     new QnsProvisioningInfo(mProvisioningInfo));
450                             mProvisioningInfo.markUpdated(false);
451                         }
452                     }
453                     break;
454                 default:
455                     break;
456             }
457         }
458 
sendRegisterProvisioningCallback()459         void sendRegisterProvisioningCallback() {
460             mRetryRegisterProvisioningCallbackCount--;
461             if (mRetryRegisterProvisioningCallbackCount > 0) {
462                 removeMessages(EVENT_REGISTER_PROVISIONING_CALLBACK);
463                 Message msg = obtainMessage(EVENT_REGISTER_PROVISIONING_CALLBACK);
464                 sendMessageDelayed(msg, REG_CALLBACK_DELAY);
465             }
466         }
467 
sendProvisioningCallbackRegistered()468         void sendProvisioningCallbackRegistered() {
469             removeMessages(EVENT_REGISTER_PROVISIONING_CALLBACK);
470             resetRetryRegisterProvisioningCallbackCount();
471             Message msg = obtainMessage(EVENT_CALLBACK_REGISTERED);
472             sendMessage(msg);
473         }
474 
sendNotifyProvisioningInfoChanged()475         void sendNotifyProvisioningInfoChanged() {
476             removeMessages(EVENT_NOTIFY_PROVISION_INFO_CHANGED);
477             Message msg = obtainMessage(EVENT_NOTIFY_PROVISION_INFO_CHANGED);
478             sendMessageDelayed(msg, 100);
479         }
480     }
481 
clearLastProvisioningInfo()482     private void clearLastProvisioningInfo() {
483         synchronized (mProvisioningInfo) {
484             mProvisioningInfo.clear();
485         }
486     }
487 }
488