1 /*
2  * Copyright (C) 2020 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.ims.rcs.uce.util;
18 
19 import android.content.Context;
20 import android.content.SharedPreferences;
21 import android.net.Uri;
22 import android.os.PersistableBundle;
23 import android.preference.PreferenceManager;
24 import android.provider.BlockedNumberContract;
25 import android.telephony.CarrierConfigManager;
26 import android.telephony.SubscriptionManager;
27 import android.telephony.ims.ImsRcsManager;
28 import android.telephony.ims.ProvisioningManager;
29 import android.telephony.ims.stub.ImsRegistrationImplBase;
30 import android.text.TextUtils;
31 import android.util.Log;
32 
33 import com.android.ims.rcs.uce.UceDeviceState.DeviceStateResult;
34 
35 import java.time.Instant;
36 import java.util.Optional;
37 import java.util.concurrent.TimeUnit;
38 
39 public class UceUtils {
40 
41     public static final int LOG_SIZE = 20;
42     private static final String LOG_PREFIX = "RcsUce.";
43     private static final String LOG_TAG = LOG_PREFIX + "UceUtils";
44 
45     private static final String SHARED_PREF_DEVICE_STATE_KEY = "UceDeviceState";
46 
47     private static final int DEFAULT_RCL_MAX_NUM_ENTRIES = 100;
48     private static final long DEFAULT_RCS_PUBLISH_SOURCE_THROTTLE_MS = 60000L;
49     private static final long DEFAULT_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC =
50             TimeUnit.DAYS.toSeconds(30);
51     private static final long DEFAULT_REQUEST_RETRY_INTERVAL_MS = TimeUnit.MINUTES.toMillis(20);
52     private static final long DEFAULT_MINIMUM_REQUEST_RETRY_AFTER_MS = TimeUnit.SECONDS.toMillis(3);
53 
54     // The default of the capabilities request timeout.
55     private static final long DEFAULT_CAP_REQUEST_TIMEOUT_AFTER_MS = TimeUnit.MINUTES.toMillis(3);
56     private static Optional<Long> OVERRIDE_CAP_REQUEST_TIMEOUT_AFTER_MS = Optional.empty();
57 
58     // The default value of the availability cache expiration.
59     private static final long DEFAULT_AVAILABILITY_CACHE_EXPIRATION_SEC = 60L;   // 60 seconds
60 
61     // The task ID of the UCE request
62     private static long TASK_ID = 0L;
63 
64     // The request coordinator ID
65     private static long REQUEST_COORDINATOR_ID = 0;
66 
67     /**
68      * Get the log prefix of RCS UCE
69      */
getLogPrefix()70     public static String getLogPrefix() {
71         return LOG_PREFIX;
72     }
73 
74     /**
75      * Generate the unique UCE request task id.
76      */
generateTaskId()77     public static synchronized long generateTaskId() {
78         return ++TASK_ID;
79     }
80 
81     /**
82      * Generate the unique request coordinator id.
83      */
generateRequestCoordinatorId()84     public static synchronized long generateRequestCoordinatorId() {
85         return ++REQUEST_COORDINATOR_ID;
86     }
87 
isEabProvisioned(Context context, int subId)88     public static boolean isEabProvisioned(Context context, int subId) {
89         boolean isProvisioned = false;
90         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
91             Log.w(LOG_TAG, "isEabProvisioned: invalid subscriptionId " + subId);
92             return false;
93         }
94         CarrierConfigManager configManager = (CarrierConfigManager)
95                 context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
96         if (configManager != null) {
97             PersistableBundle config = configManager.getConfigForSubId(subId);
98             if (config != null && !config.getBoolean(
99                     CarrierConfigManager.KEY_CARRIER_VOLTE_PROVISIONED_BOOL)) {
100                 return true;
101             }
102         }
103         try {
104             ProvisioningManager manager = ProvisioningManager.createForSubscriptionId(subId);
105             isProvisioned = manager.getRcsProvisioningStatusForCapability(
106                     ImsRcsManager.CAPABILITY_TYPE_PRESENCE_UCE,
107                     ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
108         } catch (Exception e) {
109             Log.w(LOG_TAG, "isEabProvisioned: exception=" + e.getMessage());
110         }
111         return isProvisioned;
112     }
113 
114     /**
115      * Check whether or not this carrier supports the exchange of phone numbers with the carrier's
116      * presence server.
117      */
isPresenceCapExchangeEnabled(Context context, int subId)118     public static boolean isPresenceCapExchangeEnabled(Context context, int subId) {
119         CarrierConfigManager configManager = context.getSystemService(CarrierConfigManager.class);
120         if (configManager == null) {
121             return false;
122         }
123         PersistableBundle config = configManager.getConfigForSubId(subId);
124         if (config == null) {
125             return false;
126         }
127         return config.getBoolean(
128                 CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL);
129     }
130 
131     /**
132      * Check if Presence is supported by the carrier.
133      */
isPresenceSupported(Context context, int subId)134     public static boolean isPresenceSupported(Context context, int subId) {
135         CarrierConfigManager configManager = context.getSystemService(CarrierConfigManager.class);
136         if (configManager == null) {
137             return false;
138         }
139         PersistableBundle config = configManager.getConfigForSubId(subId);
140         if (config == null) {
141             return false;
142         }
143         return config.getBoolean(CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_PUBLISH_BOOL);
144     }
145 
146     /**
147      * Check if SIP OPTIONS is supported by the carrier.
148      */
isSipOptionsSupported(Context context, int subId)149     public static boolean isSipOptionsSupported(Context context, int subId) {
150         CarrierConfigManager configManager = context.getSystemService(CarrierConfigManager.class);
151         if (configManager == null) {
152             return false;
153         }
154         PersistableBundle config = configManager.getConfigForSubId(subId);
155         if (config == null) {
156             return false;
157         }
158         return config.getBoolean(CarrierConfigManager.KEY_USE_RCS_SIP_OPTIONS_BOOL);
159     }
160 
161     /**
162      * Check whether the PRESENCE group subscribe is enabled or not.
163      *
164      * @return true when the Presence group subscribe is enabled, false otherwise.
165      */
isPresenceGroupSubscribeEnabled(Context context, int subId)166     public static boolean isPresenceGroupSubscribeEnabled(Context context, int subId) {
167         CarrierConfigManager configManager = context.getSystemService(CarrierConfigManager.class);
168         if (configManager == null) {
169             return false;
170         }
171         PersistableBundle config = configManager.getConfigForSubId(subId);
172         if (config == null) {
173             return false;
174         }
175         return config.getBoolean(CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_GROUP_SUBSCRIBE_BOOL);
176     }
177 
178     /**
179      *  Returns {@code true} if {@code phoneNumber} is blocked.
180      *
181      * @param context the context of the caller.
182      * @param phoneNumber the number to check.
183      * @return true if the number is blocked, false otherwise.
184      */
isNumberBlocked(Context context, String phoneNumber)185     public static boolean isNumberBlocked(Context context, String phoneNumber) {
186         int blockStatus;
187         try {
188             blockStatus = BlockedNumberContract.SystemContract.shouldSystemBlockNumber(
189                     context, phoneNumber, null /*extras*/);
190         } catch (Exception e) {
191             return false;
192         }
193         return blockStatus != BlockedNumberContract.STATUS_NOT_BLOCKED;
194     }
195 
196     /**
197      * Check whether sip uri should be used for presence subscribe
198      */
isSipUriForPresenceSubscribeEnabled(Context context, int subId)199     public static boolean isSipUriForPresenceSubscribeEnabled(Context context, int subId) {
200         CarrierConfigManager configManager = context.getSystemService(CarrierConfigManager.class);
201         if (configManager == null) {
202             return false;
203         }
204         PersistableBundle config = configManager.getConfigForSubId(subId);
205         if (config == null) {
206             return false;
207         }
208         return config.getBoolean(
209                 CarrierConfigManager.Ims.KEY_USE_SIP_URI_FOR_PRESENCE_SUBSCRIBE_BOOL);
210     }
211 
212     /**
213      * Check whether tel uri should be used for pidf xml
214      */
isTelUriForPidfXmlEnabled(Context context, int subId)215     public static boolean isTelUriForPidfXmlEnabled(Context context, int subId) {
216         CarrierConfigManager configManager = context.getSystemService(CarrierConfigManager.class);
217         if (configManager == null) {
218             return false;
219         }
220         PersistableBundle config = configManager.getConfigForSubId(subId);
221         if (config == null) {
222             return false;
223         }
224         return config.getBoolean(
225                 CarrierConfigManager.Ims.KEY_USE_TEL_URI_FOR_PIDF_XML_BOOL);
226     }
227 
228     /**
229      * Get the minimum time that allow two PUBLISH requests can be executed continuously.
230      *
231      * @param subId The subscribe ID
232      * @return The milliseconds that allowed two consecutive publish request.
233      */
getRcsPublishThrottle(int subId)234     public static long getRcsPublishThrottle(int subId) {
235         long throttle = DEFAULT_RCS_PUBLISH_SOURCE_THROTTLE_MS;
236         try {
237             ProvisioningManager manager = ProvisioningManager.createForSubscriptionId(subId);
238             long provisioningValue = manager.getProvisioningIntValue(
239                     ProvisioningManager.KEY_RCS_PUBLISH_SOURCE_THROTTLE_MS);
240             if (provisioningValue > 0) {
241                 throttle = provisioningValue;
242             }
243         } catch (Exception e) {
244             Log.w(LOG_TAG, "getRcsPublishThrottle: exception=" + e.getMessage());
245         }
246         return throttle;
247     }
248 
249     /**
250      * Retrieve the maximum number of contacts that is in one Request Contained List(RCL)
251      *
252      * @param subId The subscribe ID
253      * @return The maximum number of contacts.
254      */
getRclMaxNumberEntries(int subId)255     public static int getRclMaxNumberEntries(int subId) {
256         int maxNumEntries = DEFAULT_RCL_MAX_NUM_ENTRIES;
257         try {
258             ProvisioningManager manager = ProvisioningManager.createForSubscriptionId(subId);
259             int provisioningValue = manager.getProvisioningIntValue(
260                     ProvisioningManager.KEY_RCS_MAX_NUM_ENTRIES_IN_RCL);
261             if (provisioningValue > 0) {
262                 maxNumEntries = provisioningValue;
263             }
264         } catch (Exception e) {
265             Log.w(LOG_TAG, "getRclMaxNumberEntries: exception=" + e.getMessage());
266         }
267         return maxNumEntries;
268     }
269 
getNonRcsCapabilitiesCacheExpiration(Context context, int subId)270     public static long getNonRcsCapabilitiesCacheExpiration(Context context, int subId) {
271         CarrierConfigManager configManager = context.getSystemService(CarrierConfigManager.class);
272         if (configManager == null) {
273             return DEFAULT_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC;
274         }
275         PersistableBundle config = configManager.getConfigForSubId(subId);
276         if (config == null) {
277             return DEFAULT_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC;
278         }
279         return config.getInt(
280                 CarrierConfigManager.Ims.KEY_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC_INT);
281     }
282 
isRequestForbiddenBySip489(Context context, int subId)283     public static boolean isRequestForbiddenBySip489(Context context, int subId) {
284         CarrierConfigManager configManager = context.getSystemService(CarrierConfigManager.class);
285         if (configManager == null) {
286             return false;
287         }
288         PersistableBundle config = configManager.getConfigForSubId(subId);
289         if (config == null) {
290             return false;
291         }
292         return config.getBoolean(
293                 CarrierConfigManager.Ims.KEY_RCS_REQUEST_FORBIDDEN_BY_SIP_489_BOOL);
294     }
295 
getRequestRetryInterval(Context context, int subId)296     public static long getRequestRetryInterval(Context context, int subId) {
297         CarrierConfigManager configManager = context.getSystemService(CarrierConfigManager.class);
298         if (configManager == null) {
299             return DEFAULT_REQUEST_RETRY_INTERVAL_MS;
300         }
301         PersistableBundle config = configManager.getConfigForSubId(subId);
302         if (config == null) {
303             return DEFAULT_REQUEST_RETRY_INTERVAL_MS;
304         }
305         return config.getLong(
306                 CarrierConfigManager.Ims.KEY_RCS_REQUEST_RETRY_INTERVAL_MILLIS_LONG);
307     }
308 
saveDeviceStateToPreference(Context context, int subId, DeviceStateResult deviceState)309     public static boolean saveDeviceStateToPreference(Context context, int subId,
310             DeviceStateResult deviceState) {
311         SharedPreferences sharedPreferences =
312                 PreferenceManager.getDefaultSharedPreferences(context);
313         SharedPreferences.Editor editor = sharedPreferences.edit();
314         editor.putString(getDeviceStateSharedPrefKey(subId),
315                 getDeviceStateSharedPrefValue(deviceState));
316         return editor.commit();
317     }
318 
restoreDeviceState(Context context, int subId)319     public static Optional<DeviceStateResult> restoreDeviceState(Context context, int subId) {
320         SharedPreferences sharedPreferences =
321                 PreferenceManager.getDefaultSharedPreferences(context);
322         final String sharedPrefKey = getDeviceStateSharedPrefKey(subId);
323         String sharedPrefValue = sharedPreferences.getString(sharedPrefKey, "");
324         if (TextUtils.isEmpty(sharedPrefValue)) {
325             return Optional.empty();
326         }
327         String[] valueAry = sharedPrefValue.split(",");
328         if (valueAry == null || valueAry.length != 4) {
329             return Optional.empty();
330         }
331         try {
332             int deviceState = Integer.valueOf(valueAry[0]);
333             Optional<Integer> errorCode = (Integer.valueOf(valueAry[1]) == -1L) ?
334                     Optional.empty() : Optional.of(Integer.valueOf(valueAry[1]));
335 
336             long retryTimeMillis = Long.valueOf(valueAry[2]);
337             Optional<Instant> retryTime = (retryTimeMillis == -1L) ?
338                     Optional.empty() : Optional.of(Instant.ofEpochMilli(retryTimeMillis));
339 
340             long exitStateTimeMillis = Long.valueOf(valueAry[3]);
341             Optional<Instant> exitStateTime = (exitStateTimeMillis == -1L) ?
342                     Optional.empty() : Optional.of(Instant.ofEpochMilli(exitStateTimeMillis));
343 
344             return Optional.of(new DeviceStateResult(deviceState, errorCode, retryTime,
345                     exitStateTime));
346         } catch (Exception e) {
347             Log.d(LOG_TAG, "restoreDeviceState: exception " + e);
348             return Optional.empty();
349         }
350     }
351 
removeDeviceStateFromPreference(Context context, int subId)352     public static boolean removeDeviceStateFromPreference(Context context, int subId) {
353         SharedPreferences sharedPreferences =
354                 PreferenceManager.getDefaultSharedPreferences(context);
355         SharedPreferences.Editor editor = sharedPreferences.edit();
356         editor.remove(getDeviceStateSharedPrefKey(subId));
357         return editor.commit();
358     }
359 
getDeviceStateSharedPrefKey(int subId)360     private static String getDeviceStateSharedPrefKey(int subId) {
361         return SHARED_PREF_DEVICE_STATE_KEY + subId;
362     }
363 
364     /**
365      * Build the device state preference value.
366      */
getDeviceStateSharedPrefValue(DeviceStateResult deviceState)367     private static String getDeviceStateSharedPrefValue(DeviceStateResult deviceState) {
368         StringBuilder builder = new StringBuilder();
369         builder.append(deviceState.getDeviceState())  // device state
370                 .append(",").append(deviceState.getErrorCode().orElse(-1));  // error code
371 
372         long retryTimeMillis = -1L;
373         Optional<Instant> retryTime = deviceState.getRequestRetryTime();
374         if (retryTime.isPresent()) {
375             retryTimeMillis = retryTime.get().toEpochMilli();
376         }
377         builder.append(",").append(retryTimeMillis);   // retryTime
378 
379         long exitStateTimeMillis = -1L;
380         Optional<Instant> exitStateTime = deviceState.getExitStateTime();
381         if (exitStateTime.isPresent()) {
382             exitStateTimeMillis = exitStateTime.get().toEpochMilli();
383         }
384         builder.append(",").append(exitStateTimeMillis);   // exit state time
385         return builder.toString();
386     }
387 
388     /**
389      * Get the minimum value of the capabilities request retry after.
390      */
getMinimumRequestRetryAfterMillis()391     public static long getMinimumRequestRetryAfterMillis() {
392         return DEFAULT_MINIMUM_REQUEST_RETRY_AFTER_MS;
393     }
394 
395     /**
396      * Override the capability request timeout to the millisecond value specified. Sending a
397      * value <= 0 will reset the capabilities.
398      */
setCapRequestTimeoutAfterMillis(long timeoutAfterMs)399     public static synchronized void setCapRequestTimeoutAfterMillis(long timeoutAfterMs) {
400         if (timeoutAfterMs <= 0L) {
401             OVERRIDE_CAP_REQUEST_TIMEOUT_AFTER_MS = Optional.empty();
402         } else {
403             OVERRIDE_CAP_REQUEST_TIMEOUT_AFTER_MS = Optional.of(timeoutAfterMs);
404         }
405     }
406 
407     /**
408      * Get the milliseconds of the capabilities request timed out.
409      * @return the time in milliseconds before a pending capabilities request will time out.
410      */
getCapRequestTimeoutAfterMillis()411     public static synchronized long getCapRequestTimeoutAfterMillis() {
412         if(OVERRIDE_CAP_REQUEST_TIMEOUT_AFTER_MS.isPresent()) {
413             return OVERRIDE_CAP_REQUEST_TIMEOUT_AFTER_MS.get();
414         } else {
415             return DEFAULT_CAP_REQUEST_TIMEOUT_AFTER_MS;
416         }
417     }
418 
419     /**
420      * Get the contact number from the given URI.
421      * @param contactUri The contact uri of the capabilities to request for.
422      * @return The number of the contact uri. NULL if the number cannot be retrieved.
423      */
getContactNumber(Uri contactUri)424     public static String getContactNumber(Uri contactUri) {
425         if (contactUri == null) {
426             return null;
427         }
428         String number = contactUri.getSchemeSpecificPart();
429         if (TextUtils.isEmpty(number)) {
430             return null;
431         }
432 
433         String numberParts[] = number.split("[@;:]");
434         if (numberParts.length == 0) {
435             Log.d(LOG_TAG, "getContactNumber: the length of numberPars is 0");
436             return contactUri.toString();
437         }
438         return numberParts[0];
439     }
440 
441     /**
442      * Get the availability expiration from provisioning manager.
443      * @param subId The subscription ID
444      * @return the number of seconds for the availability cache expiration.
445      */
getAvailabilityCacheExpiration(int subId)446     public static long getAvailabilityCacheExpiration(int subId) {
447         long value = -1;
448         try {
449             ProvisioningManager pm = ProvisioningManager.createForSubscriptionId(subId);
450             value = pm.getProvisioningIntValue(
451                     ProvisioningManager.KEY_RCS_AVAILABILITY_CACHE_EXPIRATION_SEC);
452         } catch (Exception e) {
453             Log.w(LOG_TAG, "Exception in getAvailabilityCacheExpiration: " + e);
454         }
455 
456         if (value <= 0) {
457             Log.w(LOG_TAG, "The availability expiration cannot be less than 0.");
458             value = DEFAULT_AVAILABILITY_CACHE_EXPIRATION_SEC;
459         }
460         return value;
461     }
462 
463     /**
464      * The time interval of milliseconds for the subscribe retry.
465      *
466      * @return The time interval of milliseconds for the subscribe retry.
467      */
getSubscribeRetryInterval(Context context, int subId)468     public static long getSubscribeRetryInterval(Context context, int subId) {
469         CarrierConfigManager configManager = context.getSystemService(CarrierConfigManager.class);
470         if (configManager == null) {
471             return -1L;
472         }
473         PersistableBundle config = configManager.getConfigForSubId(subId);
474         if (config == null) {
475             return -1L;
476         }
477 
478         return config.getLong(
479                 CarrierConfigManager.Ims.KEY_SUBSCRIBE_RETRY_DURATION_MILLIS_LONG, -1L);
480     }
481 }
482