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