• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1  /*
2   * Copyright (C) 2019 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  package com.android.internal.telephony.util;
17  
18  import static android.telephony.Annotation.DataState;
19  import static android.telephony.NetworkRegistrationInfo.FIRST_SERVICE_TYPE;
20  import static android.telephony.NetworkRegistrationInfo.LAST_SERVICE_TYPE;
21  
22  import android.annotation.NonNull;
23  import android.annotation.Nullable;
24  import android.app.ActivityManager;
25  import android.app.role.RoleManager;
26  import android.content.Context;
27  import android.content.pm.ComponentInfo;
28  import android.content.pm.PackageManager;
29  import android.content.pm.ResolveInfo;
30  import android.os.Binder;
31  import android.os.Bundle;
32  import android.os.PersistableBundle;
33  import android.os.RemoteException;
34  import android.os.SystemProperties;
35  import android.os.UserHandle;
36  import android.os.UserManager;
37  import android.provider.Telephony;
38  import android.provider.Telephony.Carriers.EditStatus;
39  import android.telephony.SubscriptionManager;
40  import android.telephony.TelephonyFrameworkInitializer;
41  import android.telephony.TelephonyManager;
42  import android.text.TextUtils;
43  import android.util.Log;
44  
45  import com.android.internal.telephony.ITelephony;
46  
47  import java.io.PrintWriter;
48  import java.util.Collections;
49  import java.util.List;
50  import java.util.concurrent.CountDownLatch;
51  import java.util.concurrent.Executor;
52  import java.util.concurrent.TimeUnit;
53  import java.util.function.Supplier;
54  import java.util.regex.Matcher;
55  import java.util.regex.Pattern;
56  
57  /**
58   * This class provides various util functions
59   */
60  public final class TelephonyUtils {
61      private static final String LOG_TAG = "TelephonyUtils";
62  
63      public static boolean IS_USER = "user".equals(android.os.Build.TYPE);
64      public static boolean IS_DEBUGGABLE = SystemProperties.getInt("ro.debuggable", 0) == 1;
65  
66      public static final Executor DIRECT_EXECUTOR = Runnable::run;
67  
68      /**
69       * Verify that caller holds {@link android.Manifest.permission#DUMP}.
70       *
71       * @return true if access should be granted.
72       */
checkDumpPermission(Context context, String tag, PrintWriter pw)73      public static boolean checkDumpPermission(Context context, String tag, PrintWriter pw) {
74          if (context.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
75                  != PackageManager.PERMISSION_GRANTED) {
76              pw.println("Permission Denial: can't dump " + tag + " from from pid="
77                      + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
78                      + " due to missing android.permission.DUMP permission");
79              return false;
80          } else {
81              return true;
82          }
83      }
84  
85      /** Returns an empty string if the input is {@code null}. */
emptyIfNull(@ullable String str)86      public static String emptyIfNull(@Nullable String str) {
87          return str == null ? "" : str;
88      }
89  
90      /** Returns an empty list if the input is {@code null}. */
emptyIfNull(@ullable List<T> cur)91      public static @NonNull <T> List<T> emptyIfNull(@Nullable List<T> cur) {
92          return cur == null ? Collections.emptyList() : cur;
93      }
94  
95      /**
96       * Returns a {@link ComponentInfo} from the {@link ResolveInfo},
97       * or throws an {@link IllegalStateException} if not available.
98       */
getComponentInfo(@onNull ResolveInfo resolveInfo)99      public static ComponentInfo getComponentInfo(@NonNull ResolveInfo resolveInfo) {
100          if (resolveInfo.activityInfo != null) return resolveInfo.activityInfo;
101          if (resolveInfo.serviceInfo != null) return resolveInfo.serviceInfo;
102          if (resolveInfo.providerInfo != null) return resolveInfo.providerInfo;
103          throw new IllegalStateException("Missing ComponentInfo!");
104      }
105  
106      /**
107       * Convenience method for running the provided action enclosed in
108       * {@link Binder#clearCallingIdentity}/{@link Binder#restoreCallingIdentity}
109       *
110       * Any exception thrown by the given action will need to be handled by caller.
111       *
112       */
runWithCleanCallingIdentity( @onNull Runnable action)113      public static void runWithCleanCallingIdentity(
114              @NonNull Runnable action) {
115          final long callingIdentity = Binder.clearCallingIdentity();
116          try {
117              action.run();
118          } finally {
119              Binder.restoreCallingIdentity(callingIdentity);
120          }
121      }
122  
123      /**
124       * Convenience method for running the provided action in the provided
125       * executor enclosed in
126       * {@link Binder#clearCallingIdentity}/{@link Binder#restoreCallingIdentity}
127       *
128       * Any exception thrown by the given action will need to be handled by caller.
129       *
130       */
runWithCleanCallingIdentity( @onNull Runnable action, @NonNull Executor executor)131      public static void runWithCleanCallingIdentity(
132              @NonNull Runnable action, @NonNull Executor executor) {
133          if (action != null) {
134              if (executor != null) {
135                  executor.execute(() -> runWithCleanCallingIdentity(action));
136              } else {
137                  runWithCleanCallingIdentity(action);
138              }
139          }
140      }
141  
142  
143      /**
144       * Convenience method for running the provided action enclosed in
145       * {@link Binder#clearCallingIdentity}/{@link Binder#restoreCallingIdentity} and return
146       * the result.
147       *
148       * Any exception thrown by the given action will need to be handled by caller.
149       *
150       */
runWithCleanCallingIdentity( @onNull Supplier<T> action)151      public static <T> T runWithCleanCallingIdentity(
152              @NonNull Supplier<T> action) {
153          final long callingIdentity = Binder.clearCallingIdentity();
154          try {
155              return action.get();
156          } finally {
157              Binder.restoreCallingIdentity(callingIdentity);
158          }
159      }
160  
161      /**
162       * Filter values in bundle to only basic types.
163       */
filterValues(Bundle bundle)164      public static Bundle filterValues(Bundle bundle) {
165          Bundle ret = new Bundle(bundle);
166          for (String key : bundle.keySet()) {
167              Object value = bundle.get(key);
168              if ((value instanceof Integer) || (value instanceof Long)
169                      || (value instanceof Double) || (value instanceof String)
170                      || (value instanceof int[]) || (value instanceof long[])
171                      || (value instanceof double[]) || (value instanceof String[])
172                      || (value instanceof PersistableBundle) || (value == null)
173                      || (value instanceof Boolean) || (value instanceof boolean[])) {
174                  continue;
175              }
176              if (value instanceof Bundle) {
177                  ret.putBundle(key, filterValues((Bundle) value));
178                  continue;
179              }
180              if (value.getClass().getName().startsWith("android.")) {
181                  continue;
182              }
183              ret.remove(key);
184          }
185          return ret;
186      }
187  
188      /** Wait for latch to trigger */
waitUntilReady(CountDownLatch latch, long timeoutMs)189      public static void waitUntilReady(CountDownLatch latch, long timeoutMs) {
190          try {
191              latch.await(timeoutMs, TimeUnit.MILLISECONDS);
192          } catch (InterruptedException ignored) {
193          }
194      }
195  
196      /**
197       * Convert data state to string
198       *
199       * @return The data state in string format.
200       */
dataStateToString(@ataState int state)201      public static String dataStateToString(@DataState int state) {
202          switch (state) {
203              case TelephonyManager.DATA_DISCONNECTED: return "DISCONNECTED";
204              case TelephonyManager.DATA_CONNECTING: return "CONNECTING";
205              case TelephonyManager.DATA_CONNECTED: return "CONNECTED";
206              case TelephonyManager.DATA_SUSPENDED: return "SUSPENDED";
207              case TelephonyManager.DATA_DISCONNECTING: return "DISCONNECTING";
208              case TelephonyManager.DATA_HANDOVER_IN_PROGRESS: return "HANDOVERINPROGRESS";
209              case TelephonyManager.DATA_UNKNOWN: return "UNKNOWN";
210          }
211          // This is the error case. The well-defined value for UNKNOWN is -1.
212          return "UNKNOWN(" + state + ")";
213      }
214  
215      /**
216       * Convert mobile data policy to string.
217       *
218       * @param mobileDataPolicy The mobile data policy.
219       * @return The mobile data policy in string format.
220       */
mobileDataPolicyToString( @elephonyManager.MobileDataPolicy int mobileDataPolicy)221      public static @NonNull String mobileDataPolicyToString(
222              @TelephonyManager.MobileDataPolicy int mobileDataPolicy) {
223          switch (mobileDataPolicy) {
224              case TelephonyManager.MOBILE_DATA_POLICY_DATA_ON_NON_DEFAULT_DURING_VOICE_CALL:
225                  return "DATA_ON_NON_DEFAULT_DURING_VOICE_CALL";
226              case TelephonyManager.MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED:
227                  return "MMS_ALWAYS_ALLOWED";
228              case TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH:
229                  return "AUTO_DATA_SWITCH";
230              default:
231                  return "UNKNOWN(" + mobileDataPolicy + ")";
232          }
233      }
234  
235      /**
236       * Convert APN edited status to string.
237       *
238       * @param apnEditStatus APN edited status.
239       * @return APN edited status in string format.
240       */
apnEditedStatusToString(@ditStatus int apnEditStatus)241      public static @NonNull String apnEditedStatusToString(@EditStatus int apnEditStatus) {
242          return switch (apnEditStatus) {
243              case Telephony.Carriers.UNEDITED -> "UNEDITED";
244              case Telephony.Carriers.USER_EDITED -> "USER_EDITED";
245              case Telephony.Carriers.USER_DELETED -> "USER_DELETED";
246              case Telephony.Carriers.CARRIER_EDITED -> "CARRIER_EDITED";
247              case Telephony.Carriers.CARRIER_DELETED -> "CARRIER_DELETED";
248              default -> "UNKNOWN(" + apnEditStatus + ")";
249          };
250      }
251  
252      /**
253       * Utility method to get user handle associated with this subscription.
254       *
255       * This method should be used internally as it returns null instead of throwing
256       * IllegalArgumentException or IllegalStateException.
257       *
258       * @param context Context object
259       * @param subId the subId of the subscription.
260       * @return userHandle associated with this subscription
261       * or {@code null} if:
262       * 1. subscription is not associated with any user
263       * 2. subId is invalid.
264       * 3. subscription service is not available.
265       *
266       * @throws SecurityException if the caller doesn't have permissions required.
267       */
268      @Nullable
getSubscriptionUserHandle(Context context, int subId)269      public static UserHandle getSubscriptionUserHandle(Context context, int subId) {
270          UserHandle userHandle = null;
271          SubscriptionManager subManager =  context.getSystemService(SubscriptionManager.class);
272          if ((subManager != null) && (SubscriptionManager.isValidSubscriptionId(subId))) {
273              userHandle = subManager.getSubscriptionUserHandle(subId);
274          }
275          return userHandle;
276      }
277  
278      /**
279       * Show switch to managed profile dialog if subscription is associated with managed profile.
280       *
281       * @param context Context object
282       * @param subId subscription id
283       * @param callingUid uid for the calling app
284       * @param callingPackage package name of the calling app
285       */
showSwitchToManagedProfileDialogIfAppropriate(Context context, int subId, int callingUid, String callingPackage)286      public static void showSwitchToManagedProfileDialogIfAppropriate(Context context,
287              int subId, int callingUid, String callingPackage) {
288          final long token = Binder.clearCallingIdentity();
289          try {
290              UserHandle callingUserHandle = UserHandle.getUserHandleForUid(callingUid);
291              // We only want to show this dialog, while user actually trying to send the message from
292              // a messaging app, in other cases this dialog don't make sense.
293              if (!TelephonyUtils.isUidForeground(context, callingUid)
294                      || !TelephonyUtils.isPackageSMSRoleHolderForUser(context, callingPackage,
295                      callingUserHandle)) {
296                  return;
297              }
298  
299              SubscriptionManager subscriptionManager = context.getSystemService(
300                      SubscriptionManager.class);
301              if (!subscriptionManager.isActiveSubscriptionId(subId)) {
302                  Log.e(LOG_TAG, "Tried to send message with an inactive subscription " + subId);
303                  return;
304              }
305              UserHandle associatedUserHandle = subscriptionManager.getSubscriptionUserHandle(subId);
306              UserManager um = context.getSystemService(UserManager.class);
307  
308              if (associatedUserHandle != null && um.isManagedProfile(
309                      associatedUserHandle.getIdentifier())) {
310  
311                  ITelephony iTelephony = ITelephony.Stub.asInterface(
312                          TelephonyFrameworkInitializer
313                                  .getTelephonyServiceManager()
314                                  .getTelephonyServiceRegisterer()
315                                  .get());
316                  if (iTelephony != null) {
317                      try {
318                          iTelephony.showSwitchToManagedProfileDialog();
319                      } catch (RemoteException e) {
320                          Log.e(LOG_TAG, "Failed to launch switch to managed profile dialog.");
321                      }
322                  }
323              }
324          } finally {
325              Binder.restoreCallingIdentity(token);
326          }
327      }
328  
isUidForeground(Context context, int uid)329      private static boolean isUidForeground(Context context, int uid) {
330          ActivityManager am = context.getSystemService(ActivityManager.class);
331          boolean result = am != null && am.getUidImportance(uid)
332                  == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
333          return result;
334      }
335  
isPackageSMSRoleHolderForUser(Context context, String callingPackage, UserHandle user)336      private static boolean isPackageSMSRoleHolderForUser(Context context, String callingPackage,
337              UserHandle user) {
338          RoleManager roleManager = context.getSystemService(RoleManager.class);
339          final List<String> smsRoleHolder = roleManager.getRoleHoldersAsUser(
340                  RoleManager.ROLE_SMS, user);
341  
342          // ROLE_SMS is an exclusive role per user, so there would just be one entry in the
343          // retuned list if not empty
344          if (!smsRoleHolder.isEmpty() && callingPackage.equals(smsRoleHolder.get(0))) {
345              return true;
346          }
347          return false;
348  
349      }
350  
351      /**
352       * @param input string that want to be compared.
353       * @param regex string that express regular expression
354       * @return {@code true} if matched  {@code false} otherwise.
355       */
isValidPattern(@ullable String input, @Nullable String regex)356      private static boolean isValidPattern(@Nullable String input, @Nullable String regex) {
357          if (TextUtils.isEmpty(input) || TextUtils.isEmpty(regex)) {
358              return false;
359          }
360          Pattern pattern = Pattern.compile(regex);
361          Matcher matcher = pattern.matcher(input);
362          if (!matcher.matches()) {
363              return false;
364          }
365          return true;
366      }
367  
368      /**
369       * @param countryCode two letters country code based on the ISO 3166-1.
370       * @return {@code true} if the countryCode is valid {@code false} otherwise.
371       */
isValidCountryCode(@ullable String countryCode)372      public static boolean isValidCountryCode(@Nullable String countryCode) {
373          return isValidPattern(countryCode, "^[A-Za-z]{2}$");
374      }
375  
376      /**
377       * @param plmn target plmn for validation.
378       * @return {@code true} if the target plmn is valid {@code false} otherwise.
379       */
isValidPlmn(@ullable String plmn)380      public static boolean isValidPlmn(@Nullable String plmn) {
381          return isValidPattern(plmn, "^(?:[0-9]{3})(?:[0-9]{2}|[0-9]{3})$");
382      }
383  
384      /**
385       * @param serviceType target serviceType for validation.
386       * @return {@code true} if the target serviceType is valid {@code false} otherwise.
387       */
isValidService(int serviceType)388      public static boolean isValidService(int serviceType) {
389          if (serviceType < FIRST_SERVICE_TYPE || serviceType > LAST_SERVICE_TYPE) {
390              return false;
391          }
392          return true;
393      }
394  }
395