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