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