admin 管理员组文章数量: 887006
Android app targetSdk从28升级到33问题汇总
文章目录
- 一.TelephonyManager#listen(PhoneStateListener listener, int events)
- 错误日志
- 1.调用入口
- 2.~~binder service之TelephonyRegistryManager~~ TelephonyRegistryManager(不是binder服务端!!!)
- 注册
- 使用
- 3.binder service之TelephonyRegistry
- 注册
- 注意:~~可以看到两个binder service注册位置不一样~~ 经确认,前者运行在调用进程(同一个jvm),后者运行在binder服务端进程,这里是system server进程,通过ServiceManager注册
- 使用
- 1.先看权限检查TelephonyPermissions.checkCallingOrSelfReadPhoneState( mContext, subId, callingPackage, callingFeatureId, message)
- 2.再看什么情况下需要权限
- 注意:注解@EnabledSince 要和注解@ChangeId搭配使用
- 为什么同时要求运行系统不低于12
- 二.TelephonyManager#getCallState()
- 错误日志
- 1.调用入口
- 2.TelecomManager
- 注册
- 使用
- 3.binder service之ITelecomService
- 注册
- 使用
一.TelephonyManager#listen(PhoneStateListener listener, int events)
问题说明:targetsdkversion升级到12或者以上,设备运行系统至少12的话,如果不动态申请READ PHONE STATE权限则报错SecurityException
错误日志
java.lang.SecurityException: listenat android.os.Parcel.createExceptionOrNull(Parcel.java:2442)at android.os.Parcel.createException(Parcel.java:2426)at android.os.Parcel.readException(Parcel.java:2409)at android.os.Parcel.readException(Parcel.java:2351)at com.android.internal.telephony.ITelephonyRegistry$Stub$Proxy.listenWithEventList(ITelephonyRegistry.java:1036)at android.telephony.TelephonyRegistryManager.listenFromListener(TelephonyRegistryManager.java:250)at android.telephony.TelephonyManager.listen(TelephonyManager.java:6064)
android12
过程概述:
1.调用入口
5956 /**
5957 * Registers a listener object to receive notification of changes
5958 * in specified telephony states.
5959 * <p>
5960 * To register a listener, pass a {@link PhoneStateListener} and specify at least one telephony
5961 * state of interest in the events argument.
5962 *
5963 * At registration, and when a specified telephony state changes, the telephony manager invokes
5964 * the appropriate callback method on the listener object and passes the current (updated)
5965 * values.
5966 * <p>
5967 * To un-register a listener, pass the listener object and set the events argument to
5968 * {@link PhoneStateListener#LISTEN_NONE LISTEN_NONE} (0).
5969 *
5970 * If this TelephonyManager object has been created with {@link #createForSubscriptionId},
5971 * applies to the given subId. Otherwise, applies to
5972 * {@link SubscriptionManager#getDefaultSubscriptionId()}. To listen events for multiple subIds,
5973 * pass a separate listener object to each TelephonyManager object created with
5974 * {@link #createForSubscriptionId}.
5975 *
5976 * Note: if you call this method while in the middle of a binder transaction, you <b>must</b>
5977 * call {@link android.os.Binder#clearCallingIdentity()} before calling this method. A
5978 * {@link SecurityException} will be thrown otherwise.
5979 *
5980 * This API should be used sparingly -- large numbers of listeners will cause system
5981 * instability. If a process has registered too many listeners without unregistering them, it
5982 * may encounter an {@link IllegalStateException} when trying to register more listeners.
5983 *
5984 * @param listener The {@link PhoneStateListener} object to register
5985 * (or unregister)
5986 * @param events The telephony state(s) of interest to the listener,
5987 * as a bitwise-OR combination of {@link PhoneStateListener}
5988 * LISTEN_ flags.
5989 * @deprecated Use {@link #registerTelephonyCallback(Executor, TelephonyCallback)}.
5990 */
5991 @Deprecated
5992 public void listen(PhoneStateListener listener, int events) {
5993 if (mContext == null) return;
5994 boolean notifyNow = (getITelephony() != null);
5995 TelephonyRegistryManager telephonyRegistry =
5996 (TelephonyRegistryManager)
5997 mContext.getSystemService(Context.TELEPHONY_REGISTRY_SERVICE);
5998 if (telephonyRegistry != null) {
5999 telephonyRegistry.listenFromListener(mSubId, getOpPackageName(),
6000 getAttributionTag(), listener, events, notifyNow);
6001 } else {
6002 Rlog.w(TAG, "telephony registry not ready.");
6003 }
6004 }
走到TelephonyRegistryManager#listenFromListener(int subId, @NonNull String pkg, @NonNull String featureId,@NonNull PhoneStateListener listener, @NonNull int events, boolean notifyNow)
2.binder service之TelephonyRegistryManager TelephonyRegistryManager(不是binder服务端!!!)
注册
源码位置/frameworks/base/core/java/android/app/SystemServiceRegistry.java
678 registerService(Context.TELEPHONY_REGISTRY_SERVICE, TelephonyRegistryManager.class,
679 new CachedServiceFetcher<TelephonyRegistryManager>() {
680 @Override
681 public TelephonyRegistryManager createService(ContextImpl ctx) {
682 return new TelephonyRegistryManager(ctx);
683 }});
使用
源码位置 /frameworks/base/core/java/android/telephony/TelephonyRegistryManager.java
216 /**
217 * To check the SDK version for {@link #listenFromListener}.
218 */
219 @ChangeId
220 @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.P)
221 private static final long LISTEN_CODE_CHANGE = 147600208L;
222
223 /**
224 * Listen for incoming subscriptions
225 * @param subId Subscription ID
226 * @param pkg Package name
227 * @param featureId Feature ID
228 * @param listener Listener providing callback
229 * @param events Events
230 * @param notifyNow Whether to notify instantly
231 */
232 public void listenFromListener(int subId, @NonNull String pkg, @NonNull String featureId,
233 @NonNull PhoneStateListener listener, @NonNull int events, boolean notifyNow) {
234 if (listener == null) {
235 throw new IllegalStateException("telephony service is null.");
236 }
237
238 try {
239 int[] eventsList = getEventsFromBitmask(events).stream().mapToInt(i -> i).toArray();
240 // subId from PhoneStateListener is deprecated Q on forward, use the subId from
241 // TelephonyManager instance. Keep using subId from PhoneStateListener for pre-Q.
242 if (Compatibility.isChangeEnabled(LISTEN_CODE_CHANGE)) {
243 // Since mSubId in PhoneStateListener is deprecated from Q on forward, this is
244 // the only place to set mSubId and its for "informational" only.
245 listener.mSubId = (eventsList.length == 0)
246 ? SubscriptionManager.INVALID_SUBSCRIPTION_ID : subId;
247 } else if (listener.mSubId != null) {
248 subId = listener.mSubId;
249 }
250 sRegistry.listenWithEventList(
251 subId, pkg, featureId, listener.callback, eventsList, notifyNow);
252 } catch (RemoteException e) {
253 throw e.rethrowFromSystemServer();
254 }
255 }
private @NonNull Set<Integer> getEventsFromBitmask(int eventMask) {
1003
1004 Set<Integer> eventList = new ArraySet<>();
。。。
1026 // Note: Legacy call state listeners can get the phone number which is not provided in the
1027 // new version in TelephonyCallback.
1028 if ((eventMask & PhoneStateListener.LISTEN_CALL_STATE) != 0) {
1029 eventList.add(TelephonyCallback.EVENT_LEGACY_CALL_STATE_CHANGED);
1030 }
1031
最后转换成EVENT_LEGACY_CALL_STATE_CHANGED
至此走到ITelephonyRegistry#listenWithEventList(int subId, String callingPackage, String callingFeatureId,IPhoneStateListener callback, int[] events, boolean notifyNow)
3.binder service之TelephonyRegistry
注册
源码位置 /frameworks/base/services/java/com/android/server/SystemServer.java
1410 t.traceBegin("StartTelephonyRegistry");
1411 telephonyRegistry = new TelephonyRegistry(
1412 context, new TelephonyRegistry.ConfigurationProvider());
1413 ServiceManager.addService("telephony.registry", telephonyRegistry);
1414 t.traceEnd();
注意:可以看到两个binder service注册位置不一样 经确认,前者运行在调用进程(同一个jvm),后者运行在binder服务端进程,这里是system server进程,通过ServiceManager注册
使用
源码位置:frameworks/base/services/core/java/com/android/server/TelephonyRegistry.java
992 @Override
993 public void listenWithEventList(int subId, String callingPackage, String callingFeatureId,
994 IPhoneStateListener callback, int[] events, boolean notifyNow) {
995 Set<Integer> eventList = Arrays.stream(events).boxed().collect(Collectors.toSet());
996 listen(callingPackage, callingFeatureId, callback, eventList, notifyNow, subId);
997 }private void listen(String callingPackage, @Nullable String callingFeatureId,
1000 IPhoneStateListener callback, Set<Integer> events, boolean notifyNow, int subId) {
1001 int callerUserId = UserHandle.getCallingUserId();
1002 mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
1003 String str = "listen: E pkg=" + pii(callingPackage) + " uid=" + Binder.getCallingUid()
1004 + " events=" + events + " notifyNow=" + notifyNow
1005 + " subId=" + subId + " myUserId=" + UserHandle.myUserId()
1006 + " callerUserId=" + callerUserId;
1007 mListenLog.log(str);
1008 if (VDBG) {
1009 log(str);
1010 }
1011
1012 if (events.isEmpty()) {
1013 if (DBG) {
1014 log("listen: Unregister");
1015 }
1016 events.clear();
1017 remove(callback.asBinder());
1018 return;
1019 }
1020
1021 // Checks permission and throws SecurityException for disallowed operations. For pre-M
1022 // apps whose runtime permission has been revoked, we return immediately to skip sending
1023 // events to the app without crashing it.
1024 if (!checkListenerPermission(events, subId, callingPackage, callingFeatureId, "listen")) {
1025 return;
1026 }//关键权限校验代码
1027 。。。
}private boolean checkListenerPermission(Set<Integer> events, int subId, String callingPackage,
3055 @Nullable String callingFeatureId, String message) {
。。。
3090
3091 if (isPhoneStatePermissionRequired(events, callingPackage, Binder.getCallingUserHandle())) {
3092 if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
3093 mContext, subId, callingPackage, callingFeatureId, message)) {
3094 isPermissionCheckSuccessful = false;
3095 }
3096 }
1.先看权限检查TelephonyPermissions.checkCallingOrSelfReadPhoneState( mContext, subId, callingPackage, callingFeatureId, message)
可以看到先校验READ_PRIVILEGED_PHONE_STATE,如果没有该权限,则校验READ_PHONE_STATE,如果都没有,则抛出异常SecurityException
91 public static boolean checkCallingOrSelfReadPhoneState(
92 Context context, int subId, String callingPackage, @Nullable String callingFeatureId,
93 String message) {
94 return checkReadPhoneState(context, subId, Binder.getCallingPid(), Binder.getCallingUid(),
95 callingPackage, callingFeatureId, message);
96 }
。。。132 public static boolean checkReadPhoneState(
133 Context context, int subId, int pid, int uid, String callingPackage,
134 @Nullable String callingFeatureId, String message) {
135 try {
136 context.enforcePermission(
137 android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, pid, uid, message);
138
139 // SKIP checking for run-time permission since caller has PRIVILEGED permission
140 return true;
141 } catch (SecurityException privilegedPhoneStateException) {
142 try {
143 context.enforcePermission(
144 android.Manifest.permission.READ_PHONE_STATE, pid, uid, message);
145 } catch (SecurityException phoneStateException) {
146 // If we don't have the runtime permission, but do have carrier privileges, that
147 // suffices for reading phone state.
148 if (SubscriptionManager.isValidSubscriptionId(subId)) {
149 enforceCarrierPrivilege(context, subId, uid, message);
150 return true;
151 }
152 throw phoneStateException;
153 }
154 }
155
156 // We have READ_PHONE_STATE permission, so return true as long as the AppOps bit hasn't been
157 // revoked.
158 AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
159 return appOps.noteOp(AppOpsManager.OPSTR_READ_PHONE_STATE, uid, callingPackage,
160 callingFeatureId, null) == AppOpsManager.MODE_ALLOWED;
161 }
2.再看什么情况下需要权限
isPhoneStatePermissionRequired(events, callingPackage, Binder.getCallingUserHandle())
private boolean isPhoneStatePermissionRequired(Set<Integer> events, String callingPackage,
465 UserHandle userHandle) {
。。。
471
472 // Only check READ_PHONE_STATE for CALL_STATE_CHANGED for Android 12 or above.
473 if ((events.contains(TelephonyCallback.EVENT_LEGACY_CALL_STATE_CHANGED)
474 || events.contains(TelephonyCallback.EVENT_CALL_STATE_CHANGED))
475 && mConfigurationProvider.isCallStateReadPhoneStateEnforcedInPlatformCompat(
476 callingPackage, userHandle)) {
477 return true;
478 }
。。。
504 }
因为events中包含TelephonyCallback.EVENT_LEGACY_CALL_STATE_CHANGED
194 /**
195 * Wrapper class to facilitate testing -- encapsulates bits of configuration that are
196 * normally fetched from static methods with many dependencies.
197 */
198 public static class ConfigurationProvider {
...
225 */
226 public boolean isCallStateReadPhoneStateEnforcedInPlatformCompat(String packageName,
227 UserHandle userHandle) {
228 return Binder.withCleanCallingIdentity(() -> CompatChanges.isChangeEnabled(
229 TelecomManager.ENABLE_GET_CALL_STATE_PERMISSION_PROTECTION, packageName,
230 userHandle));
231 }
源码位置:frameworks/base/telecomm/java/android/telecom/TelecomManager.java
1010 /**
1011 * Enable READ_PHONE_STATE protection on APIs querying and notifying call state, such as
1012 * {@code TelecomManager#getCallState}, {@link TelephonyManager#getCallStateForSubscription()},
1013 * and {@link android.telephony.TelephonyCallback.CallStateListener}.
1014 * @hide
1015 */
1016 @ChangeId
1017 @EnabledSince(targetSdkVersion = Build.VERSION_CODES.S)
1018 // this magic number is a bug ID
1019 public static final long ENABLE_GET_CALL_STATE_PERMISSION_PROTECTION = 157233955L;
到了这里,就可以确定targetsdk版本从android 12开始并且运行系统版本不低于12则这个权限READ PHONE STATE必须动态申请,否则binder通信会报错SecurityException
注意:注解@EnabledSince 要和注解@ChangeId搭配使用
源码位置 /tools/platform-compat/java/android/compat/annotation/EnabledSince.java
25 /**
26 * Used to indicate that a compatibility {@link ChangeId change} is enabled only for apps with a
27 * {@code targetSdkVersion} <em>greater or equal to</em> the given value.
28 *
29 * <p>This annotation should only be applied to change ID constants that are also annotated with
30 * {@link ChangeId}. In any other context, this annotation will have no effect.
31 *
32 * @hide
33 */
34 @Retention(SOURCE)
35 @Target({FIELD})
36 public @interface EnabledSince {
37 /**
38 * @return Theminimum {@code targetSdkVersion} for which this change is enabled. Apps with
39 * a {@code targetSdkVersion} greater or equal to this value will get the change.
40 */
41 int targetSdkVersion();
42 }
43
为什么同时要求运行系统不低于12
答:低于12的系统版本中没有这些逻辑要求,可以对照12以下系统源码
二.TelephonyManager#getCallState()
问题说明:targetsdkversion升级到12或者以上,设备运行系统至少12的话,如果不动态申请READ PHONE STATE权限则报错SecurityException
跟上一个问题类似
错误日志
java.lang.SecurityException: getCallState: Neither user 10495 nor current process has android.permission.READ_PHONE_STATE.at android.os.Parcel.createExceptionOrNull(Parcel.java:2442)at android.os.Parcel.createException(Parcel.java:2426)at android.os.Parcel.readException(Parcel.java:2409)at android.os.Parcel.readException(Parcel.java:2351)at com.android.internal.telecom.ITelecomService$Stub$Proxy.getCallStateUsingPackage(ITelecomService.java:2700)at android.telecom.TelecomManager.getCallState(TelecomManager.java:1825)
这里我们可以看到是android.os.Parcel.readException抛出的,因为涉及到binder跨进程通信,属于binder服务端权限校验异常,会发送异常数据到binder客户端,所以binder客户端才会通过Parcel读取到exception
还是跟android12源码
过程概述:
1.调用入口
光看这段代码是看不出有任何权限要求的
5680 /**
5681 * Returns the state of all calls on the device.
5682 * <p>
5683 * This method considers not only calls in the Telephony stack, but also calls via other
5684 * {@link android.telecom.ConnectionService} implementations.
5685 * <p>
5686 * Note: The call state returned via this method may differ from what is reported by
5687 * {@link PhoneStateListener#onCallStateChanged(int, String)}, as that callback only considers
5688 * Telephony (mobile) calls.
5689 * <p>
5690 * Requires Permission:
5691 * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} for applications
5692 * targeting API level 31+.
5693 *
5694 * @return the current call state.
5695 * @deprecated Use {@link #getCallStateForSubscription} to retrieve the call state for a
5696 * specific telephony subscription (which allows carrier privileged apps),
5697 * {@link TelephonyCallback.CallStateListener} for real-time call state updates, or
5698 * {@link TelecomManager#isInCall()}, which supplies an aggregate "in call" state for the entire
5699 * device.
5700 */
5701 @RequiresPermission(value = android.Manifest.permission.READ_PHONE_STATE, conditional = true)
5702 @Deprecated
5703 public @CallState int getCallState() {
5704 if (mContext != null) {
5705 TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
5706 if (telecomManager != null) {
5707 return telecomManager.getCallState();
5708 }
5709 }
5710 return CALL_STATE_IDLE;
5711 }
2.TelecomManager
注册
源码位置/frameworks/base/core/java/android/app/SystemServiceRegistry.java
685 registerService(Context.TELECOM_SERVICE, TelecomManager.class,
686 new CachedServiceFetcher<TelecomManager>() {
687 @Override
688 public TelecomManager createService(ContextImpl ctx) {
689 return new TelecomManager(ctx.getOuterContext());
690 }});
注意:这里注册逻辑是在同一个进程注册的,没有binder跨进程
源码位置: /frameworks/base/core/java/android/app/ContextImpl.java
2048 public Object getSystemService(String name) {
2049 if (vmIncorrectContextUseEnabled()) {
2050 // Check incorrect Context usage.
2051 if (WINDOW_SERVICE.equals(name) && !isUiContext()) {
2052 final String errorMessage = "Tried to access visual service "
2053 + SystemServiceRegistry.getSystemServiceClassName(name)
2054 + " from a non-visual Context:" + getOuterContext();
2055 final String message = "WindowManager should be accessed from Activity or other "
2056 + "visual Context. Use an Activity or a Context created with "
2057 + "Context#createWindowContext(int, Bundle), which are adjusted to "
2058 + "the configuration and visual bounds of an area on screen.";
2059 final Exception exception = new IllegalAccessException(errorMessage);
2060 StrictMode.onIncorrectContextUsed(message, exception);
2061 Log.e(TAG, errorMessage + " " + message, exception);
2062 }
2063 }
2064 return SystemServiceRegistry.getSystemService(this, name);
2065 }
使用
源码位置: /frameworks/base/telecomm/java/android/telecom/TelecomManager.java
1781 /**
1782 * Returns one of the following constants that represents the current state of Telecom:
1783 *
1784 * {@link TelephonyManager#CALL_STATE_RINGING}
1785 * {@link TelephonyManager#CALL_STATE_OFFHOOK}
1786 * {@link TelephonyManager#CALL_STATE_IDLE}
1787 *
1788 * Takes into consideration both managed and self-managed calls.
1789 * <p>
1790 * Requires Permission:
1791 * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} for applications
1792 * targeting API level 31+.
1793 *
1794 * @hide
1795 */
1796 @RequiresPermission(anyOf = {READ_PRIVILEGED_PHONE_STATE,
1797 android.Manifest.permission.READ_PHONE_STATE}, conditional = true)
1798 @SystemApi
1799 public @CallState int getCallState() {
1800 ITelecomService service = getTelecomService();
1801 if (service != null) {
1802 try {
1803 return service.getCallStateUsingPackage(mContext.getPackageName(),
1804 mContext.getAttributionTag());
1805 } catch (RemoteException e) {
1806 Log.d(TAG, "RemoteException calling getCallState().", e);
1807 }
1808 }
1809 return TelephonyManager.CALL_STATE_IDLE;
1810 }
1811
这里只是看到
@RequiresPermission(anyOf = {READ_PRIVILEGED_PHONE_STATE,android.Manifest.permission.READ_PHONE_STATE}, conditional = true)
但是其实并没有对targetsdk有要求,也不是硬性要求必须有READ_PHONE_STATE权限,没有则不能运行
这里继续看getTelecomService(),这时候才是真正有binder通信
2572 private ITelecomService getTelecomService() {
2573 if (mTelecomServiceOverride != null) {
2574 return mTelecomServiceOverride;
2575 }
2576 if (sTelecomService == null) {
2577 ITelecomService temp = ITelecomService.Stub.asInterface(
2578 ServiceManager.getService(Context.TELECOM_SERVICE));
2579 synchronized (CACHE_LOCK) {
2580 if (sTelecomService == null && temp != null) {
2581 try {
2582 sTelecomService = temp;
2583 sTelecomService.asBinder().linkToDeath(SERVICE_DEATH, 0);
2584 } catch (Exception e) {
2585 sTelecomService = null;
2586 }
2587 }
2588 }
2589 }
2590 return sTelecomService;
2591 }
3.binder service之ITelecomService
注册
这里注册逻辑比较深!!!
源码位置: /frameworks/base/telecomm/java/android/telecom/TelecomManager.java
144 private void connectToTelecom() {
145 synchronized (mLock) {
146 if (mServiceConnection != null) {
147 // TODO: Is unbinding worth doing or wait for system to rebind?
148 mContext.unbindService(mServiceConnection);
149 mServiceConnection = null;
150 }
151
152 TelecomServiceConnection serviceConnection = new TelecomServiceConnection();
153 Intent intent = new Intent(SERVICE_ACTION);
154 intent.setComponent(SERVICE_COMPONENT);
155 int flags = Context.BIND_IMPORTANT | Context.BIND_FOREGROUND_SERVICE
156 | Context.BIND_AUTO_CREATE;
157
158 // Bind to Telecom and register the service
159 if (mContext.bindServiceAsUser(intent, serviceConnection, flags, UserHandle.SYSTEM)) {
160 mServiceConnection = serviceConnection;
161 }
162 }
163 }
抽丝剥茧,真正的binder服务端是在
源码位置: /packages/services/Telecomm/src/com/android/server/telecom/TelecomServiceImpl.java
944 /**
945 * @see TelecomManager#getCallState()
946 */
947 @Override
948 public int getCallStateUsingPackage(String callingPackage, String callingFeatureId) {
949 try {
950 Log.startSession("TSI.getCallStateUsingPackage");
951 if (CompatChanges.isChangeEnabled(
952 TelecomManager.ENABLE_GET_CALL_STATE_PERMISSION_PROTECTION, callingPackage,
953 Binder.getCallingUserHandle())) {
954 // Bypass canReadPhoneState check if this is being called from SHELL UID
955 if (Binder.getCallingUid() != Process.SHELL_UID && !canReadPhoneState(
956 callingPackage, callingFeatureId, "getCallState")) {
957 throw new SecurityException("getCallState API requires READ_PHONE_STATE"
958 + " for API version 31+");
959 }
960 }
961 synchronized (mLock) {
962 return mCallsManager.getCallState();
963 }
964 } finally {
965 Log.endSession();
966 }
967 }
使用
继续看到
源码位置: /packages/services/Telecomm/src/com/android/server/telecom/TelecomServiceImpl.java
getCallStateUsingPackage这个方法,关键校验逻辑
CompatChanges.isChangeEnabled(
952 TelecomManager.ENABLE_GET_CALL_STATE_PERMISSION_PROTECTION, callingPackage,
953 Binder.getCallingUserHandle())
源码位置:frameworks/base/telecomm/java/android/telecom/TelecomManager.java
1010 /**
1011 * Enable READ_PHONE_STATE protection on APIs querying and notifying call state, such as
1012 * {@code TelecomManager#getCallState}, {@link TelephonyManager#getCallStateForSubscription()},
1013 * and {@link android.telephony.TelephonyCallback.CallStateListener}.
1014 * @hide
1015 */
1016 @ChangeId
1017 @EnabledSince(targetSdkVersion = Build.VERSION_CODES.S)
1018 // this magic number is a bug ID
1019 public static final long ENABLE_GET_CALL_STATE_PERMISSION_PROTECTION = 157233955L;
又回到注解EnabledSince
跟TelephonyManager#listen(PhoneStateListener listener, int events)
殊途同归
本文标签: Android app targetSdk从28升级到33问题汇总
版权声明:本文标题:Android app targetSdk从28升级到33问题汇总 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.freenas.com.cn/jishu/1732359943h1534931.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论