diff options
52 files changed, 1177 insertions, 162 deletions
diff --git a/api/current.txt b/api/current.txt index db91b9dd2c1d..4363b6e753f9 100755 --- a/api/current.txt +++ b/api/current.txt @@ -27854,6 +27854,7 @@ package android.net { method public android.net.VpnService.Builder setBlocking(boolean); method public android.net.VpnService.Builder setConfigureIntent(android.app.PendingIntent); method public android.net.VpnService.Builder setHttpProxy(android.net.ProxyInfo); + method public android.net.VpnService.Builder setMetered(boolean); method public android.net.VpnService.Builder setMtu(int); method public android.net.VpnService.Builder setSession(String); method public android.net.VpnService.Builder setUnderlyingNetworks(android.net.Network[]); @@ -41156,6 +41157,7 @@ package android.telecom { method public static String capabilitiesToString(int); method public android.telecom.PhoneAccountHandle getAccountHandle(); method public int getCallCapabilities(); + method public int getCallDirection(); method @Nullable public android.telecom.CallIdentification getCallIdentification(); method public int getCallProperties(); method public String getCallerDisplayName(); @@ -41192,6 +41194,9 @@ package android.telecom { field public static final int CAPABILITY_SUPPORT_DEFLECT = 16777216; // 0x1000000 field public static final int CAPABILITY_SUPPORT_HOLD = 2; // 0x2 field public static final int CAPABILITY_SWAP_CONFERENCE = 8; // 0x8 + field public static final int DIRECTION_INCOMING = 0; // 0x0 + field public static final int DIRECTION_OUTGOING = 1; // 0x1 + field public static final int DIRECTION_UNKNOWN = -1; // 0xffffffff field public static final int PROPERTY_CONFERENCE = 1; // 0x1 field public static final int PROPERTY_EMERGENCY_CALLBACK_MODE = 4; // 0x4 field public static final int PROPERTY_ENTERPRISE_CALL = 32; // 0x20 @@ -43092,8 +43097,10 @@ package android.telephony { method public boolean setVoiceMailNumber(String, String); method @Deprecated public void setVoicemailRingtoneUri(android.telecom.PhoneAccountHandle, android.net.Uri); method @Deprecated public void setVoicemailVibrationEnabled(android.telecom.PhoneAccountHandle, boolean); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void switchMultiSimConfig(int); method public boolean updateAvailableNetworks(java.util.List<android.telephony.AvailableNetworkInfo>); field public static final String ACTION_CONFIGURE_VOICEMAIL = "android.telephony.action.CONFIGURE_VOICEMAIL"; + field public static final String ACTION_NETWORK_COUNTRY_CHANGED = "android.telephony.action.NETWORK_COUNTRY_CHANGED"; field @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final String ACTION_PHONE_STATE_CHANGED = "android.intent.action.PHONE_STATE"; field public static final String ACTION_RESPOND_VIA_MESSAGE = "android.intent.action.RESPOND_VIA_MESSAGE"; field public static final String ACTION_SECRET_CODE = "android.telephony.action.SECRET_CODE"; @@ -43131,6 +43138,7 @@ package android.telephony { field public static final String EXTRA_INCOMING_NUMBER = "incoming_number"; field public static final String EXTRA_IS_REFRESH = "android.telephony.extra.IS_REFRESH"; field public static final String EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT = "android.telephony.extra.LAUNCH_VOICEMAIL_SETTINGS_INTENT"; + field public static final String EXTRA_NETWORK_COUNTRY = "android.telephony.extra.NETWORK_COUNTRY"; field public static final String EXTRA_NOTIFICATION_COUNT = "android.telephony.extra.NOTIFICATION_COUNT"; field public static final String EXTRA_PHONE_ACCOUNT_HANDLE = "android.telephony.extra.PHONE_ACCOUNT_HANDLE"; field public static final String EXTRA_PRECISE_CARRIER_ID = "android.telephony.extra.PRECISE_CARRIER_ID"; diff --git a/api/system-current.txt b/api/system-current.txt index 097cb70b911f..e17459bf4852 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -101,6 +101,7 @@ package android { field public static final String OBSERVE_APP_USAGE = "android.permission.OBSERVE_APP_USAGE"; field public static final String OVERRIDE_WIFI_CONFIG = "android.permission.OVERRIDE_WIFI_CONFIG"; field public static final String PACKAGE_VERIFICATION_AGENT = "android.permission.PACKAGE_VERIFICATION_AGENT"; + field public static final String PACKET_KEEPALIVE_OFFLOAD = "android.permission.PACKET_KEEPALIVE_OFFLOAD"; field public static final String PEERS_MAC_ADDRESS = "android.permission.PEERS_MAC_ADDRESS"; field public static final String PERFORM_CDMA_PROVISIONING = "android.permission.PERFORM_CDMA_PROVISIONING"; field public static final String PERFORM_SIM_ACTIVATION = "android.permission.PERFORM_SIM_ACTIVATION"; @@ -3058,6 +3059,7 @@ package android.net { public class CaptivePortal implements android.os.Parcelable { ctor public CaptivePortal(android.os.IBinder); + method public void logEvent(int, String); method public void useNetwork(); field public static final int APP_RETURN_DISMISSED = 0; // 0x0 field public static final int APP_RETURN_UNWANTED = 1; // 0x1 @@ -3065,9 +3067,10 @@ package android.net { } public class ConnectivityManager { - method @RequiresPermission("android.permission.PACKET_KEEPALIVE_OFFLOAD") public android.net.SocketKeepalive createNattKeepalive(@NonNull android.net.Network, @NonNull java.io.FileDescriptor, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback); + method @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) public android.net.SocketKeepalive createNattKeepalive(@NonNull android.net.Network, @NonNull java.io.FileDescriptor, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback); method public boolean getAvoidBadWifi(); method @RequiresPermission(android.Manifest.permission.LOCAL_MAC_ADDRESS) public String getCaptivePortalServerUrl(); + method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void getLatestTetheringEntitlementValue(int, boolean, @NonNull android.net.ConnectivityManager.TetheringEntitlementValueListener, @Nullable android.os.Handler); method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported(); method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void setAirplaneMode(boolean); method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback); @@ -3078,6 +3081,9 @@ package android.net { field public static final int TETHERING_BLUETOOTH = 2; // 0x2 field public static final int TETHERING_USB = 1; // 0x1 field public static final int TETHERING_WIFI = 0; // 0x0 + field public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN = 13; // 0xd + field public static final int TETHER_ERROR_NO_ERROR = 0; // 0x0 + field public static final int TETHER_ERROR_PROVISION_FAILED = 11; // 0xb } public abstract static class ConnectivityManager.OnStartTetheringCallback { @@ -3086,6 +3092,11 @@ package android.net { method public void onTetheringStarted(); } + public abstract static class ConnectivityManager.TetheringEntitlementValueListener { + ctor public ConnectivityManager.TetheringEntitlementValueListener(); + method public void onEntitlementResult(int); + } + public final class IpPrefix implements android.os.Parcelable { ctor public IpPrefix(java.net.InetAddress, int); ctor public IpPrefix(String); @@ -4303,6 +4314,7 @@ package android.os { } public final class UserHandle implements android.os.Parcelable { + method public static int getAppId(int); method public int getIdentifier(); method @Deprecated public boolean isOwner(); method public boolean isSystem(); @@ -6049,6 +6061,7 @@ package android.telephony { public class PhoneStateListener { method public void onCallAttributesChanged(@NonNull android.telephony.CallAttributes); method public void onCallDisconnectCauseChanged(int, int); + method public void onImsCallDisconnectCauseChanged(@NonNull android.telephony.ims.ImsReasonInfo); method public void onPreciseCallStateChanged(android.telephony.PreciseCallState); method public void onPreciseDataConnectionStateChanged(android.telephony.PreciseDataConnectionState); method public void onRadioPowerStateChanged(int); @@ -6056,6 +6069,7 @@ package android.telephony { method public void onVoiceActivationStateChanged(int); field public static final int LISTEN_CALL_ATTRIBUTES_CHANGED = 67108864; // 0x4000000 field public static final int LISTEN_CALL_DISCONNECT_CAUSES = 33554432; // 0x2000000 + field public static final int LISTEN_IMS_CALL_DISCONNECT_CAUSES = 134217728; // 0x8000000 field public static final int LISTEN_PRECISE_CALL_STATE = 2048; // 0x800 field public static final int LISTEN_PRECISE_DATA_CONNECTION_STATE = 4096; // 0x1000 field public static final int LISTEN_RADIO_POWER_STATE_CHANGED = 8388608; // 0x800000 diff --git a/api/test-current.txt b/api/test-current.txt index af455fb4abb6..1384f0b7b41e 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -600,6 +600,7 @@ package android.net { public class CaptivePortal implements android.os.Parcelable { ctor public CaptivePortal(android.os.IBinder); + method public void logEvent(int, String); method public void useNetwork(); field public static final int APP_RETURN_DISMISSED = 0; // 0x0 field public static final int APP_RETURN_UNWANTED = 1; // 0x1 @@ -1790,6 +1791,10 @@ package android.view { method public boolean isSystemGroup(); } + public abstract class LayoutInflater { + method public void setPrecompiledLayoutsEnabledForTesting(boolean); + } + public final class MotionEvent extends android.view.InputEvent implements android.os.Parcelable { method public void setActionButton(int); method public void setButtonState(int); diff --git a/core/java/android/net/CaptivePortal.java b/core/java/android/net/CaptivePortal.java index 3b0126673779..3ab35e1eebf0 100644 --- a/core/java/android/net/CaptivePortal.java +++ b/core/java/android/net/CaptivePortal.java @@ -117,4 +117,17 @@ public class CaptivePortal implements Parcelable { } catch (RemoteException e) { } } + + /** + * Log a captive portal login event. + * @hide + */ + @SystemApi + @TestApi + public void logEvent(int eventId, String packageName) { + try { + ICaptivePortal.Stub.asInterface(mBinder).logEvent(eventId, packageName); + } catch (RemoteException e) { + } + } } diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 160157771b18..1240ef87451c 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -1033,34 +1033,6 @@ public class ConnectivityManager { } } - /** - * Configures an always-on VPN connection through a specific application. - * This connection is automatically granted and persisted after a reboot. - * - * <p>The designated package should declare a {@link VpnService} in its - * manifest guarded by {@link android.Manifest.permission.BIND_VPN_SERVICE}, - * otherwise the call will fail. - * - * @param userId The identifier of the user to set an always-on VPN for. - * @param vpnPackage The package name for an installed VPN app on the device, or {@code null} - * to remove an existing always-on VPN configuration. - * @param lockdownEnabled {@code true} to disallow networking when the VPN is not connected or - * {@code false} otherwise. - * @return {@code true} if the package is set as always-on VPN controller; - * {@code false} otherwise. - * @hide - */ - @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) - public boolean setAlwaysOnVpnPackageForUser(int userId, @Nullable String vpnPackage, - boolean lockdownEnabled) { - try { - return mService.setAlwaysOnVpnPackage( - userId, vpnPackage, lockdownEnabled, /* whitelist */ null); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - /** * Returns the package name of the currently set always-on VPN application. * If there is no always-on VPN set, or the VPN is provided by the system instead @@ -2609,6 +2581,7 @@ public class ConnectivityManager { } /** {@hide} */ + @SystemApi public static final int TETHER_ERROR_NO_ERROR = 0; /** {@hide} */ public static final int TETHER_ERROR_UNKNOWN_IFACE = 1; @@ -2631,9 +2604,13 @@ public class ConnectivityManager { /** {@hide} */ public static final int TETHER_ERROR_IFACE_CFG_ERROR = 10; /** {@hide} */ + @SystemApi public static final int TETHER_ERROR_PROVISION_FAILED = 11; /** {@hide} */ public static final int TETHER_ERROR_DHCPSERVER_ERROR = 12; + /** {@hide} */ + @SystemApi + public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN = 13; /** * Get a more detailed error code after a Tethering or Untethering @@ -2656,6 +2633,65 @@ public class ConnectivityManager { } /** + * Callback for use with {@link #getLatestTetheringEntitlementValue} to find out whether + * entitlement succeeded. + * @hide + */ + @SystemApi + public abstract static class TetheringEntitlementValueListener { + /** + * Called to notify entitlement result. + * + * @param resultCode a int value of entitlement result. It may be one of + * {@link #TETHER_ERROR_NO_ERROR}, + * {@link #TETHER_ERROR_PROVISION_FAILED}, or + * {@link #TETHER_ERROR_ENTITLEMENT_UNKONWN}. + */ + public void onEntitlementResult(int resultCode) {} + } + + /** + * Get the last value of the entitlement check on this downstream. If the cached value is + * {@link #TETHER_ERROR_NO_ERROR} or showEntitlementUi argument is false, it just return the + * cached value. Otherwise, a UI-based entitlement check would be performed. It is not + * guaranteed that the UI-based entitlement check will complete in any specific time period + * and may in fact never complete. Any successful entitlement check the platform performs for + * any reason will update the cached value. + * + * @param type the downstream type of tethering. Must be one of + * {@link #TETHERING_WIFI}, + * {@link #TETHERING_USB}, or + * {@link #TETHERING_BLUETOOTH}. + * @param showEntitlementUi a boolean indicating whether to run UI-based entitlement check. + * @param listener an {@link TetheringEntitlementValueListener} which will be called to notify + * the caller of the result of entitlement check. The listener may be called zero or + * one time. + * @param handler {@link Handler} to specify the thread upon which the listener will be invoked. + * {@hide} + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) + public void getLatestTetheringEntitlementValue(int type, boolean showEntitlementUi, + @NonNull final TetheringEntitlementValueListener listener, @Nullable Handler handler) { + Preconditions.checkNotNull(listener, "TetheringEntitlementValueListener cannot be null."); + ResultReceiver wrappedListener = new ResultReceiver(handler) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + listener.onEntitlementResult(resultCode); + } + }; + + try { + String pkgName = mContext.getOpPackageName(); + Log.i(TAG, "getLatestTetheringEntitlementValue:" + pkgName); + mService.getLatestTetheringEntitlementValue(type, wrappedListener, + showEntitlementUi, pkgName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Report network connectivity status. This is currently used only * to alter status bar UI. * <p>This method requires the caller to hold the permission diff --git a/core/java/android/net/ICaptivePortal.aidl b/core/java/android/net/ICaptivePortal.aidl index 56ae57dc0e8d..707b4f699873 100644 --- a/core/java/android/net/ICaptivePortal.aidl +++ b/core/java/android/net/ICaptivePortal.aidl @@ -22,4 +22,5 @@ package android.net; */ oneway interface ICaptivePortal { void appResponse(int response); + void logEvent(int eventId, String packageName); } diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index fd7360fd4c17..78fafebc4f37 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -197,4 +197,7 @@ interface IConnectivityManager int getConnectionOwnerUid(in ConnectionInfo connectionInfo); boolean isCallerCurrentAlwaysOnVpnApp(); boolean isCallerCurrentAlwaysOnVpnLockdownApp(); + + void getLatestTetheringEntitlementValue(int type, in ResultReceiver receiver, + boolean showEntitlementUi, String callerPkg); } diff --git a/core/java/android/net/INetworkMonitorCallbacks.aidl b/core/java/android/net/INetworkMonitorCallbacks.aidl index 0bc25750129b..a8682f9ddd3b 100644 --- a/core/java/android/net/INetworkMonitorCallbacks.aidl +++ b/core/java/android/net/INetworkMonitorCallbacks.aidl @@ -26,4 +26,5 @@ oneway interface INetworkMonitorCallbacks { void notifyPrivateDnsConfigResolved(in PrivateDnsConfigParcel config); void showProvisioningNotification(String action); void hideProvisioningNotification(); + void logCaptivePortalLoginEvent(int eventId, String packageName); }
\ No newline at end of file diff --git a/core/java/android/net/VpnService.java b/core/java/android/net/VpnService.java index dc099a46aa2a..784f23311103 100644 --- a/core/java/android/net/VpnService.java +++ b/core/java/android/net/VpnService.java @@ -791,6 +791,27 @@ public class VpnService extends Service { } /** + * Marks the VPN network as metered. A VPN network is classified as metered when the user is + * sensitive to heavy data usage due to monetary costs and/or data limitations. In such + * cases, you should set this to {@code true} so that apps on the system can avoid doing + * large data transfers. Otherwise, set this to {@code false}. Doing so would cause VPN + * network to inherit its meteredness from its underlying networks. + * + * <p>VPN apps targeting {@link android.os.Build.VERSION_CODES#Q} or above will be + * considered metered by default. + * + * @param isMetered {@code true} if VPN network should be treated as metered regardless of + * underlying network meteredness + * @return this {@link Builder} object to facilitate chaining method calls + * @see #setUnderlyingNetworks(Networks[]) + * @see ConnectivityManager#isActiveNetworkMetered() + */ + public Builder setMetered(boolean isMetered) { + mConfig.isMetered = isMetered; + return this; + } + + /** * Create a VPN interface using the parameters supplied to this * builder. The interface works on IP packets, and a file descriptor * is returned for the application to access them. Each read diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS new file mode 100644 index 000000000000..b568f157c01d --- /dev/null +++ b/core/java/android/os/OWNERS @@ -0,0 +1,2 @@ +# Zygote +per-file ZygoteProcess.java = chriswailes@google.com, ngeoffray@google.com, sehr@google.com, narayan@google.com, maco@google.com diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java index 648c022d8673..de41ce2e08c5 100644 --- a/core/java/android/os/UserHandle.java +++ b/core/java/android/os/UserHandle.java @@ -226,6 +226,7 @@ public final class UserHandle implements Parcelable { * @hide */ @TestApi + @SystemApi public static @AppIdInt int getAppId(int uid) { return uid % PER_USER_RANGE; } diff --git a/core/java/android/service/dreams/OWNERS b/core/java/android/service/dreams/OWNERS index 3c9bbf8797ea..426f002ad236 100644 --- a/core/java/android/service/dreams/OWNERS +++ b/core/java/android/service/dreams/OWNERS @@ -1,3 +1,3 @@ -dsandler@google.com +dsandler@android.com michaelwr@google.com roosa@google.com diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java index f2259b045c0a..2ee72bffc9ec 100644 --- a/core/java/android/view/LayoutInflater.java +++ b/core/java/android/view/LayoutInflater.java @@ -20,6 +20,7 @@ import android.annotation.LayoutRes; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemService; +import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.Resources; @@ -389,9 +390,13 @@ public abstract class LayoutInflater { } private void initPrecompiledViews() { + initPrecompiledViews( + SystemProperties.getBoolean(USE_PRECOMPILED_LAYOUT_SYSTEM_PROPERTY, false)); + } + + private void initPrecompiledViews(boolean enablePrecompiledViews) { + mUseCompiledView = enablePrecompiledViews; try { - mUseCompiledView = - SystemProperties.getBoolean(USE_PRECOMPILED_LAYOUT_SYSTEM_PROPERTY, false); if (mUseCompiledView) { mPrecompiledClassLoader = mContext.getClassLoader(); String dexFile = mContext.getCodeCacheDir() + COMPILED_VIEW_DEX_FILE_NAME; @@ -409,6 +414,17 @@ public abstract class LayoutInflater { } mUseCompiledView = false; } + if (!mUseCompiledView) { + mPrecompiledClassLoader = null; + } + } + + /** + * @hide for use by CTS tests + */ + @TestApi + public void setPrecompiledLayoutsEnabledForTesting(boolean enablePrecompiledLayouts) { + initPrecompiledViews(enablePrecompiledLayouts); } /** diff --git a/core/java/com/android/internal/net/VpnConfig.java b/core/java/com/android/internal/net/VpnConfig.java index da8605e645b4..65b974ba8b42 100644 --- a/core/java/com/android/internal/net/VpnConfig.java +++ b/core/java/com/android/internal/net/VpnConfig.java @@ -104,6 +104,7 @@ public class VpnConfig implements Parcelable { public boolean allowBypass; public boolean allowIPv4; public boolean allowIPv6; + public boolean isMetered = true; public Network[] underlyingNetworks; public ProxyInfo proxyInfo; @@ -165,6 +166,7 @@ public class VpnConfig implements Parcelable { out.writeInt(allowBypass ? 1 : 0); out.writeInt(allowIPv4 ? 1 : 0); out.writeInt(allowIPv6 ? 1 : 0); + out.writeInt(isMetered ? 1 : 0); out.writeTypedArray(underlyingNetworks, flags); out.writeParcelable(proxyInfo, flags); } @@ -191,6 +193,7 @@ public class VpnConfig implements Parcelable { config.allowBypass = in.readInt() != 0; config.allowIPv4 = in.readInt() != 0; config.allowIPv6 = in.readInt() != 0; + config.isMetered = in.readInt() != 0; config.underlyingNetworks = in.createTypedArray(Network.CREATOR); config.proxyInfo = in.readParcelable(null); return config; diff --git a/core/java/com/android/internal/os/OWNERS b/core/java/com/android/internal/os/OWNERS new file mode 100644 index 000000000000..928310549e6e --- /dev/null +++ b/core/java/com/android/internal/os/OWNERS @@ -0,0 +1 @@ +per-file ZygoteArguments.java,ZygoteConnection.java,ZygoteInit.java,Zygote.java,ZygoteServer.java = chriswailes@google.com, ngeoffray@google.com, sehr@google.com, narayan@google.com, maco@google.com diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java index ab356a6f9a99..2bb075989f35 100644 --- a/core/java/com/android/internal/os/ZygoteConnection.java +++ b/core/java/com/android/internal/os/ZygoteConnection.java @@ -26,6 +26,7 @@ import static android.system.OsConstants.STDOUT_FILENO; import static com.android.internal.os.ZygoteConnectionConstants.CONNECTION_TIMEOUT_MILLIS; import static com.android.internal.os.ZygoteConnectionConstants.WRAPPED_PID_TIMEOUT_MILLIS; +import android.metrics.LogMaker; import android.net.Credentials; import android.net.LocalSocket; import android.os.Process; @@ -35,6 +36,9 @@ import android.system.Os; import android.system.StructPollfd; import android.util.Log; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; + import dalvik.system.VMRuntime; import libcore.io.IoUtils; @@ -311,9 +315,43 @@ class ZygoteConnection { } } - private void handleHiddenApiAccessLogSampleRate(int percent) { + private class HiddenApiUsageLogger implements VMRuntime.HiddenApiUsageLogger { + + private final MetricsLogger mMetricsLogger = new MetricsLogger(); + + public void hiddenApiUsed(String packageName, String signature, + int accessMethod, boolean accessDenied) { + int accessMethodMetric = HiddenApiUsageLogger.ACCESS_METHOD_NONE; + switch(accessMethod) { + case HiddenApiUsageLogger.ACCESS_METHOD_NONE: + accessMethodMetric = MetricsEvent.ACCESS_METHOD_NONE; + break; + case HiddenApiUsageLogger.ACCESS_METHOD_REFLECTION: + accessMethodMetric = MetricsEvent.ACCESS_METHOD_REFLECTION; + break; + case HiddenApiUsageLogger.ACCESS_METHOD_JNI: + accessMethodMetric = MetricsEvent.ACCESS_METHOD_JNI; + break; + case HiddenApiUsageLogger.ACCESS_METHOD_LINKING: + accessMethodMetric = MetricsEvent.ACCESS_METHOD_LINKING; + break; + } + LogMaker logMaker = new LogMaker(MetricsEvent.ACTION_HIDDEN_API_ACCESSED) + .setPackageName(packageName) + .addTaggedData(MetricsEvent.FIELD_HIDDEN_API_SIGNATURE, signature) + .addTaggedData(MetricsEvent.FIELD_HIDDEN_API_ACCESS_METHOD, + accessMethodMetric); + if (accessDenied) { + logMaker.addTaggedData(MetricsEvent.FIELD_HIDDEN_API_ACCESS_DENIED, 1); + } + mMetricsLogger.write(logMaker); + } + } + + private void handleHiddenApiAccessLogSampleRate(int samplingRate) { try { - ZygoteInit.setHiddenApiAccessLogSampleRate(percent); + ZygoteInit.setHiddenApiAccessLogSampleRate(samplingRate); + ZygoteInit.setHiddenApiUsageLogger(new HiddenApiUsageLogger()); mSocketOutStream.writeInt(0); } catch (IOException ioe) { throw new IllegalStateException("Error writing to command socket", ioe); diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index e3e55ed28c6f..9f23797d6ccc 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -533,6 +533,14 @@ public class ZygoteInit { } /** + * Sets the implementation to be used for logging hidden API accesses + * @param logger the implementation of the VMRuntime.HiddenApiUsageLogger interface + */ + public static void setHiddenApiUsageLogger(VMRuntime.HiddenApiUsageLogger logger) { + VMRuntime.getRuntime().setHiddenApiUsageLogger(logger); + } + + /** * Creates a PathClassLoader for the given class path that is associated with a shared * namespace, i.e., this classloader can access platform-private native libraries. The * classloader will use java.library.path as the native library path. diff --git a/core/jni/OWNERS b/core/jni/OWNERS index a365a566f038..86342c47c455 100644 --- a/core/jni/OWNERS +++ b/core/jni/OWNERS @@ -4,3 +4,6 @@ per-file *Camera*,*camera* = shuzhenwang@google.com, yinchiayeh@google.com, zhij # Connectivity per-file android_net_* = ek@google.com, lorenzo@google.com, satk@google.com + +# Zygote +per-file com_android_inernal_os_Zygote.*,fd_utils.* = chriswailes@google.com, ngeoffray@google.com, sehr@google.com, narayan@google.com, maco@google.com diff --git a/core/jni/android_os_VintfObject.cpp b/core/jni/android_os_VintfObject.cpp index e64d2afe7bf3..ee11b6162db0 100644 --- a/core/jni/android_os_VintfObject.cpp +++ b/core/jni/android_os_VintfObject.cpp @@ -96,7 +96,7 @@ static jobjectArray android_os_VintfObject_report(JNIEnv* env, jclass) return toJavaStringArray(env, cStrings); } -static jint verify(JNIEnv* env, jobjectArray packageInfo, android::vintf::CheckFlags::Type checks) { +static jint android_os_VintfObject_verify(JNIEnv* env, jclass, jobjectArray packageInfo) { std::vector<std::string> cPackageInfo; if (packageInfo) { size_t count = env->GetArrayLength(packageInfo); @@ -109,18 +109,19 @@ static jint verify(JNIEnv* env, jobjectArray packageInfo, android::vintf::CheckF } } std::string error; - int32_t status = VintfObject::CheckCompatibility(cPackageInfo, &error, checks); + int32_t status = VintfObject::CheckCompatibility(cPackageInfo, &error); if (status) LOG(WARNING) << "VintfObject.verify() returns " << status << ": " << error; return status; } -static jint android_os_VintfObject_verify(JNIEnv* env, jclass, jobjectArray packageInfo) { - return verify(env, packageInfo, ::android::vintf::CheckFlags::ENABLE_ALL_CHECKS); -} - static jint android_os_VintfObject_verifyWithoutAvb(JNIEnv* env, jclass) { - return verify(env, nullptr, ::android::vintf::CheckFlags::DISABLE_AVB_CHECK); + std::string error; + int32_t status = VintfObject::CheckCompatibility({}, &error, + ::android::vintf::CheckFlags::DISABLE_AVB_CHECK); + if (status) + LOG(WARNING) << "VintfObject.verifyWithoutAvb() returns " << status << ": " << error; + return status; } static jobjectArray android_os_VintfObject_getHalNamesAndVersions(JNIEnv* env, jclass) { diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 2f3c1db4f775..bc5ec4938256 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1554,7 +1554,7 @@ <permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS" android:protectionLevel="signature|privileged" /> - <!-- Allows a system application to access hardware packet offload capabilities. + <!-- @SystemApi Allows a system application to access hardware packet offload capabilities. @hide --> <permission android:name="android.permission.PACKET_KEEPALIVE_OFFLOAD" android:protectionLevel="signature|privileged" /> diff --git a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java index d03e5e3102ed..08b22c7d518a 100644 --- a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java +++ b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java @@ -59,7 +59,6 @@ import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.TextView; -import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import java.io.IOException; @@ -106,11 +105,11 @@ public class CaptivePortalLoginActivity extends Activity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + mCaptivePortal = getIntent().getParcelableExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL); logMetricsEvent(MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_ACTIVITY); mCm = ConnectivityManager.from(this); mNetwork = getIntent().getParcelableExtra(ConnectivityManager.EXTRA_NETWORK); - mCaptivePortal = getIntent().getParcelableExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL); mUserAgent = getIntent().getStringExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_USER_AGENT); mUrl = getUrl(); @@ -636,7 +635,7 @@ public class CaptivePortalLoginActivity extends Activity { } private void logMetricsEvent(int event) { - MetricsLogger.action(this, event, getPackageName()); + mCaptivePortal.logEvent(event, getPackageName()); } private static final SparseArray<String> SSL_ERRORS = new SparseArray<>(); diff --git a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java index f21561ff4c69..b34efc46f3b4 100644 --- a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java +++ b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java @@ -687,6 +687,15 @@ public class NetworkMonitor extends StateMachine { } sendMessage(CMD_CAPTIVE_PORTAL_APP_FINISHED, response); } + + @Override + public void logEvent(int eventId, String packageName) + throws RemoteException { + mContext.enforceCallingPermission( + android.Manifest.permission.CONNECTIVITY_INTERNAL, + "CaptivePortal"); + mCallback.logCaptivePortalLoginEvent(eventId, packageName); + } })); final CaptivePortalProbeResult probeRes = mLastPortalProbeResult; intent.putExtra(EXTRA_CAPTIVE_PORTAL_URL, probeRes.detectUrl); diff --git a/packages/SettingsLib/OWNERS b/packages/SettingsLib/OWNERS index d188c65be883..d87908738b56 100644 --- a/packages/SettingsLib/OWNERS +++ b/packages/SettingsLib/OWNERS @@ -4,7 +4,7 @@ asargent@google.com dehboxturtle@google.com dhnishi@google.com dling@google.com -dsandler@google.com +dsandler@android.com evanlaird@google.com jackqdyulei@google.com jmonk@google.com diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS index 2a3cf3ebeedf..3f6ebf0fe09d 100644 --- a/packages/SystemUI/OWNERS +++ b/packages/SystemUI/OWNERS @@ -1,6 +1,6 @@ set noparent -dsandler@google.com +dsandler@android.com adamcohen@google.com asc@google.com diff --git a/packages/VpnDialogs/AndroidManifest.xml b/packages/VpnDialogs/AndroidManifest.xml index 8172e717850b..1d0b9b6d83db 100644 --- a/packages/VpnDialogs/AndroidManifest.xml +++ b/packages/VpnDialogs/AndroidManifest.xml @@ -20,6 +20,7 @@ package="com.android.vpndialogs"> <uses-permission android:name="android.permission.CONTROL_VPN" /> + <uses-permission android:name="android.permission.CONTROL_ALWAYS_ON_VPN" /> <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" /> <application android:label="VpnDialogs" diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index c8e5d2bdb769..08e49034aa6c 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -145,6 +145,7 @@ import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; +import com.android.internal.logging.MetricsLogger; import com.android.internal.net.LegacyVpnInfo; import com.android.internal.net.VpnConfig; import com.android.internal.net.VpnInfo; @@ -2684,6 +2685,11 @@ public class ConnectivityService extends IConnectivityManager.Stub EVENT_PROVISIONING_NOTIFICATION, PROVISIONING_NOTIFICATION_HIDE, mNai.network.netId)); } + + @Override + public void logCaptivePortalLoginEvent(int eventId, String packageName) { + new MetricsLogger().action(eventId, packageName); + } } private boolean networkRequiresValidation(NetworkAgentInfo nai) { @@ -3637,6 +3643,20 @@ public class ConnectivityService extends IConnectivityManager.Stub mTethering.stopTethering(type); } + /** + * Get the latest value of the tethering entitlement check. + * + * Note: Allow privileged apps who have TETHER_PRIVILEGED permission to access. If it turns + * out some such apps are observed to abuse this API, change to per-UID limits on this API + * if it's really needed. + */ + @Override + public void getLatestTetheringEntitlementValue(int type, ResultReceiver receiver, + boolean showEntitlementUi, String callerPkg) { + ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg); + mTethering.getLatestTetheringEntitlementValue(type, receiver, showEntitlementUi); + } + // Called when we lose the default network and have no replacement yet. // This will automatically be cleared after X seconds or a new default network // becomes CONNECTED, whichever happens first. The timer is started by the diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index cbfcd6035d5e..43af36f86f3d 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -52,6 +52,7 @@ import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.telephony.data.ApnSetting; import android.telephony.emergency.EmergencyNumber; +import android.telephony.ims.ImsReasonInfo; import android.util.LocalLog; import android.util.StatsLog; @@ -227,6 +228,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { private int mCallDisconnectCause = DisconnectCause.NOT_VALID; + private List<ImsReasonInfo> mImsReasonInfo = null; + private int mCallPreciseDisconnectCause = PreciseDisconnectCause.NOT_VALID; private boolean mCarrierNetworkChangeState = false; @@ -377,6 +380,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mCellLocation = new Bundle[numPhones]; mCellInfo = new ArrayList<List<CellInfo>>(); mSrvccState = new int[numPhones]; + mImsReasonInfo = new ArrayList<ImsReasonInfo>(); mPhysicalChannelConfigs = new ArrayList<List<PhysicalChannelConfig>>(); mEmergencyNumberList = new HashMap<>(); for (int i = 0; i < numPhones; i++) { @@ -394,6 +398,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mCallForwarding[i] = false; mCellLocation[i] = new Bundle(); mCellInfo.add(i, null); + mImsReasonInfo.add(i, null); mSrvccState[i] = TelephonyManager.SRVCC_STATE_HANDOVER_NONE; mPhysicalChannelConfigs.add(i, new ArrayList<PhysicalChannelConfig>()); } @@ -739,6 +744,13 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { remove(r.binder); } } + if ((events & PhoneStateListener.LISTEN_IMS_CALL_DISCONNECT_CAUSES) != 0) { + try { + r.callback.onImsCallDisconnectCauseChanged(mImsReasonInfo.get(phoneId)); + } catch (RemoteException ex) { + remove(r.binder); + } + } if ((events & PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE) != 0) { try { r.callback.onPreciseDataConnectionStateChanged( @@ -1591,6 +1603,34 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } + public void notifyImsDisconnectCause(int subId, ImsReasonInfo imsReasonInfo) { + if (!checkNotifyPermission("notifyImsCallDisconnectCause()")) { + return; + } + int phoneId = SubscriptionManager.getPhoneId(subId); + synchronized (mRecords) { + if (validatePhoneId(phoneId)) { + mImsReasonInfo.set(phoneId, imsReasonInfo); + for (Record r : mRecords) { + if (r.matchPhoneStateListenerEvent( + PhoneStateListener.LISTEN_IMS_CALL_DISCONNECT_CAUSES) + && idMatch(r.subId, subId, phoneId)) { + try { + if (DBG_LOC) { + log("notifyImsCallDisconnectCause: mImsReasonInfo=" + + imsReasonInfo + " r=" + r); + } + r.callback.onImsCallDisconnectCauseChanged(mImsReasonInfo.get(phoneId)); + } catch (RemoteException ex) { + mRemoveList.add(r.binder); + } + } + } + } + handleRemoveListLocked(); + } + } + public void notifyPreciseDataConnectionFailed(String apnType, String apn, @DataFailCause.FailCause int failCause) { if (!checkNotifyPermission("notifyPreciseDataConnectionFailed()")) { @@ -1627,7 +1667,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { int phoneId = SubscriptionManager.getPhoneId(subId); synchronized (mRecords) { if (validatePhoneId(phoneId)) { - mSrvccState[phoneId] = state; + mSrvccState[phoneId] = state; for (Record r : mRecords) { if (r.matchPhoneStateListenerEvent( PhoneStateListener.LISTEN_SRVCC_STATE_CHANGED) && @@ -1838,6 +1878,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { pw.println("mDataConnectionState=" + mDataConnectionState[i]); pw.println("mCellLocation=" + mCellLocation[i]); pw.println("mCellInfo=" + mCellInfo.get(i)); + pw.println("mImsCallDisconnectCause=" + mImsReasonInfo.get(i).toString()); pw.decreaseIndent(); } pw.println("mPreciseDataConnectionState=" + mPreciseDataConnectionState); @@ -2127,6 +2168,11 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, null); } + if ((events & PhoneStateListener.LISTEN_IMS_CALL_DISCONNECT_CAUSES) != 0) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.READ_PRECISE_PHONE_STATE, null); + } + return true; } diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index eb5be77e4a33..a14fd17209e8 100644 --- a/services/core/java/com/android/server/connectivity/Tethering.java +++ b/services/core/java/com/android/server/connectivity/Tethering.java @@ -121,7 +121,6 @@ import java.util.Collection; import java.util.HashSet; import java.util.Set; - /** * @hide * @@ -223,7 +222,8 @@ public class Tethering extends BaseNetworkObserver { IntentFilter filter = new IntentFilter(); filter.addAction(ACTION_CARRIER_CONFIG_CHANGED); - mEntitlementMgr = mDeps.getEntitlementManager(mContext, mLog, systemProperties); + mEntitlementMgr = mDeps.getEntitlementManager(mContext, mTetherMasterSM, + mLog, systemProperties); mCarrierConfigChange = new VersionedBroadcastListener( "CarrierConfigChangeListener", mContext, smHandler, filter, (Intent ignored) -> { @@ -470,6 +470,7 @@ public class Tethering extends BaseNetworkObserver { } else { sendTetherResult(receiver, resultCode); } + mEntitlementMgr.updateEntitlementCacheValue(type, resultCode); } }; @@ -1662,6 +1663,14 @@ public class Tethering extends BaseNetworkObserver { mUpstreamNetworkMonitor.startTrackDefaultNetwork(mDeps.getDefaultNetworkRequest()); } + /** Get the latest value of the tethering entitlement check. */ + public void getLatestTetheringEntitlementValue(int type, ResultReceiver receiver, + boolean showEntitlementUi) { + if (receiver != null) { + mEntitlementMgr.getLatestTetheringEntitlementValue(type, receiver, showEntitlementUi); + } + } + @Override public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { // Binder.java closes the resource for us. diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 250884431440..9141ccb387bd 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -165,6 +165,7 @@ public class Vpn { private final NetworkInfo mNetworkInfo; private String mPackage; private int mOwnerUID; + private boolean mIsPackageTargetingAtLeastQ; private String mInterface; private Connection mConnection; private LegacyVpnRunner mLegacyVpnRunner; @@ -226,6 +227,7 @@ public class Vpn { mPackage = VpnConfig.LEGACY_VPN; mOwnerUID = getAppUid(mPackage, mUserHandle); + mIsPackageTargetingAtLeastQ = doesPackageTargetAtLeastQ(mPackage); try { netService.registerObserver(mObserver); @@ -267,8 +269,11 @@ public class Vpn { public void updateCapabilities() { final Network[] underlyingNetworks = (mConfig != null) ? mConfig.underlyingNetworks : null; + // Only apps targeting Q and above can explicitly declare themselves as metered. + final boolean isAlwaysMetered = + mIsPackageTargetingAtLeastQ && (mConfig == null || mConfig.isMetered); updateCapabilities(mContext.getSystemService(ConnectivityManager.class), underlyingNetworks, - mNetworkCapabilities); + mNetworkCapabilities, isAlwaysMetered); if (mNetworkAgent != null) { mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities); @@ -277,11 +282,13 @@ public class Vpn { @VisibleForTesting public static void updateCapabilities(ConnectivityManager cm, Network[] underlyingNetworks, - NetworkCapabilities caps) { + NetworkCapabilities caps, boolean isAlwaysMetered) { int[] transportTypes = new int[] { NetworkCapabilities.TRANSPORT_VPN }; int downKbps = NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED; int upKbps = NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED; - boolean metered = false; + // VPN's meteredness is OR'd with isAlwaysMetered and meteredness of its underlying + // networks. + boolean metered = isAlwaysMetered; boolean roaming = false; boolean congested = false; @@ -724,6 +731,7 @@ public class Vpn { Log.i(TAG, "Switched from " + mPackage + " to " + newPackage); mPackage = newPackage; mOwnerUID = getAppUid(newPackage, mUserHandle); + mIsPackageTargetingAtLeastQ = doesPackageTargetAtLeastQ(newPackage); try { mNetd.allowProtect(mOwnerUID); } catch (Exception e) { @@ -789,6 +797,21 @@ public class Vpn { return result; } + private boolean doesPackageTargetAtLeastQ(String packageName) { + if (VpnConfig.LEGACY_VPN.equals(packageName)) { + return true; + } + PackageManager pm = mContext.getPackageManager(); + try { + ApplicationInfo appInfo = + pm.getApplicationInfoAsUser(packageName, 0 /*flags*/, mUserHandle); + return appInfo.targetSdkVersion >= VERSION_CODES.Q; + } catch (NameNotFoundException unused) { + Log.w(TAG, "Can't find \"" + packageName + "\""); + return false; + } + } + public NetworkInfo getNetworkInfo() { return mNetworkInfo; } @@ -1076,6 +1099,8 @@ public class Vpn { // as rules are deleted. This prevents data leakage as the rules are moved over. agentDisconnect(oldNetworkAgent); } + // Set up VPN's capabilities such as meteredness. + updateCapabilities(); if (oldConnection != null) { mContext.unbindService(oldConnection); @@ -1776,6 +1801,7 @@ public class Vpn { config.user = profile.key; config.interfaze = iface; config.session = profile.name; + config.isMetered = false; config.addLegacyRoutes(profile.routes); if (!profile.dnsServers.isEmpty()) { diff --git a/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java b/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java index a4e3e1d85bcb..75aac106e0e0 100644 --- a/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java +++ b/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java @@ -21,6 +21,9 @@ import static android.net.ConnectivityManager.EXTRA_PROVISION_CALLBACK; import static android.net.ConnectivityManager.EXTRA_REM_TETHER_TYPE; import static android.net.ConnectivityManager.EXTRA_RUN_PROVISION; import static android.net.ConnectivityManager.EXTRA_SET_ALARM; +import static android.net.ConnectivityManager.TETHER_ERROR_ENTITLEMENT_UNKONWN; +import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR; +import static android.net.ConnectivityManager.TETHER_ERROR_PROVISION_FAILED; import static com.android.internal.R.string.config_wifi_tether_enable; @@ -31,15 +34,21 @@ import android.content.Intent; import android.content.res.Resources; import android.net.util.SharedLog; import android.os.Binder; +import android.os.Bundle; +import android.os.Handler; +import android.os.Parcel; import android.os.PersistableBundle; import android.os.ResultReceiver; import android.os.UserHandle; import android.provider.Settings; import android.telephony.CarrierConfigManager; import android.util.ArraySet; +import android.util.Log; +import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.StateMachine; import com.android.server.connectivity.MockableSystemProperties; /** @@ -50,6 +59,7 @@ import com.android.server.connectivity.MockableSystemProperties; */ public class EntitlementManager { private static final String TAG = EntitlementManager.class.getSimpleName(); + private static final boolean DBG = false; // {@link ComponentName} of the Service used to run tether provisioning. private static final ComponentName TETHER_SERVICE = ComponentName.unflattenFromString( @@ -65,15 +75,19 @@ public class EntitlementManager { private final Context mContext; private final MockableSystemProperties mSystemProperties; private final SharedLog mLog; + private final Handler mMasterHandler; + private final SparseIntArray mEntitlementCacheValue; @Nullable private TetheringConfiguration mConfig; - public EntitlementManager(Context ctx, SharedLog log, + public EntitlementManager(Context ctx, StateMachine tetherMasterSM, SharedLog log, MockableSystemProperties systemProperties) { mContext = ctx; - mLog = log; + mLog = log.forSubComponent(TAG); mCurrentTethers = new ArraySet<Integer>(); mSystemProperties = systemProperties; + mEntitlementCacheValue = new SparseIntArray(); + mMasterHandler = tetherMasterSM.getHandler(); } /** @@ -128,6 +142,10 @@ public class EntitlementManager { * Reference ConnectivityManager.TETHERING_{@code *} for each tether type. */ public void reevaluateSimCardProvisioning() { + synchronized (mEntitlementCacheValue) { + mEntitlementCacheValue.clear(); + } + if (!mConfig.hasMobileHotspotProvisionApp()) return; if (carrierConfigAffirmsEntitlementCheckNotRequired()) return; @@ -175,6 +193,11 @@ public class EntitlementManager { } public void runUiTetherProvisioningAndEnable(int type, ResultReceiver receiver) { + runUiTetherProvisioning(type, receiver); + } + + @VisibleForTesting + protected void runUiTetherProvisioning(int type, ResultReceiver receiver) { Intent intent = new Intent(Settings.ACTION_TETHER_PROVISIONING); intent.putExtra(EXTRA_ADD_TETHER_TYPE, type); intent.putExtra(EXTRA_PROVISION_CALLBACK, receiver); @@ -221,4 +244,70 @@ public class EntitlementManager { Binder.restoreCallingIdentity(ident); } } + + private ResultReceiver buildProxyReceiver(int type, final ResultReceiver receiver) { + ResultReceiver rr = new ResultReceiver(mMasterHandler) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + int updatedCacheValue = updateEntitlementCacheValue(type, resultCode); + receiver.send(updatedCacheValue, null); + } + }; + + return writeToParcel(rr); + } + + private ResultReceiver writeToParcel(final ResultReceiver receiver) { + // This is necessary to avoid unmarshalling issues when sending the receiver + // across processes. + Parcel parcel = Parcel.obtain(); + receiver.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + ResultReceiver receiverForSending = ResultReceiver.CREATOR.createFromParcel(parcel); + parcel.recycle(); + return receiverForSending; + } + + /** + * Update the last entitlement value to internal cache + * + * @param type tethering type from ConnectivityManager.TETHERING_{@code *} + * @param resultCode last entitlement value + * @return the last updated entitlement value + */ + public int updateEntitlementCacheValue(int type, int resultCode) { + if (DBG) { + Log.d(TAG, "updateEntitlementCacheValue: " + type + ", result: " + resultCode); + } + synchronized (mEntitlementCacheValue) { + if (resultCode == TETHER_ERROR_NO_ERROR) { + mEntitlementCacheValue.put(type, resultCode); + return resultCode; + } else { + mEntitlementCacheValue.put(type, TETHER_ERROR_PROVISION_FAILED); + return TETHER_ERROR_PROVISION_FAILED; + } + } + } + + /** Get the last value of the tethering entitlement check. */ + public void getLatestTetheringEntitlementValue(int downstream, ResultReceiver receiver, + boolean showEntitlementUi) { + if (!isTetherProvisioningRequired()) { + receiver.send(TETHER_ERROR_NO_ERROR, null); + return; + } + + final int cacheValue; + synchronized (mEntitlementCacheValue) { + cacheValue = mEntitlementCacheValue.get( + downstream, TETHER_ERROR_ENTITLEMENT_UNKONWN); + } + if (cacheValue == TETHER_ERROR_NO_ERROR || !showEntitlementUi) { + receiver.send(cacheValue, null); + } else { + ResultReceiver proxy = buildProxyReceiver(downstream, receiver); + runUiTetherProvisioning(downstream, proxy); + } + } } diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java b/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java index a42efe960ff9..6d6f81eb98e6 100644 --- a/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java +++ b/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java @@ -81,8 +81,8 @@ public class TetheringDependencies { /** * Get a reference to the EntitlementManager to be used by tethering. */ - public EntitlementManager getEntitlementManager(Context ctx, SharedLog log, - MockableSystemProperties systemProperties) { - return new EntitlementManager(ctx, log, systemProperties); + public EntitlementManager getEntitlementManager(Context ctx, StateMachine target, + SharedLog log, MockableSystemProperties systemProperties) { + return new EntitlementManager(ctx, target, log, systemProperties); } } diff --git a/services/core/java/com/android/server/dreams/OWNERS b/services/core/java/com/android/server/dreams/OWNERS index 3c9bbf8797ea..426f002ad236 100644 --- a/services/core/java/com/android/server/dreams/OWNERS +++ b/services/core/java/com/android/server/dreams/OWNERS @@ -1,3 +1,3 @@ -dsandler@google.com +dsandler@android.com michaelwr@google.com roosa@google.com diff --git a/services/core/java/com/android/server/pm/OWNERS b/services/core/java/com/android/server/pm/OWNERS index 60d792530101..33b8641c145e 100644 --- a/services/core/java/com/android/server/pm/OWNERS +++ b/services/core/java/com/android/server/pm/OWNERS @@ -51,6 +51,8 @@ per-file UserManagerService.java = omakoto@google.com per-file UserManagerService.java = yamasani@google.com per-file UserRestrictionsUtils.java = omakoto@google.com per-file UserRestrictionsUtils.java = yamasani@google.com +per-file UserRestrictionsUtils.java = rubinxu@google.com +per-file UserRestrictionsUtils.java = sandness@google.com # security per-file KeySetHandle.java = cbrubaker@google.com diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java index 84611667d113..39fc715c7770 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java @@ -124,6 +124,7 @@ public class AppStandbyControllerTests { static class MyInjector extends AppStandbyController.Injector { long mElapsedRealtime; + boolean mIsAppIdleEnabled = true; boolean mIsCharging; List<String> mPowerSaveWhitelistExceptIdle = new ArrayList<>(); boolean mDisplayOn; @@ -155,7 +156,7 @@ public class AppStandbyControllerTests { @Override boolean isAppIdleEnabled() { - return true; + return mIsAppIdleEnabled; } @Override @@ -266,6 +267,13 @@ public class AppStandbyControllerTests { } } + private void setAppIdleEnabled(AppStandbyController controller, boolean enabled) { + mInjector.mIsAppIdleEnabled = enabled; + if (controller != null) { + controller.setAppIdleEnabled(enabled); + } + } + private AppStandbyController setupController() throws Exception { mInjector.mElapsedRealtime = 0; setupPm(mInjector.getContext().getPackageManager()); @@ -335,7 +343,7 @@ public class AppStandbyControllerTests { public void onParoleStateChanged(boolean isParoleOn) { synchronized (this) { // Only record information if it is being looked for - if (mLatch.getCount() > 0) { + if (mLatch != null && mLatch.getCount() > 0) { mOnParole = isParoleOn; mLastParoleChangeTime = getCurrentTime(); mLatch.countDown(); @@ -396,6 +404,74 @@ public class AppStandbyControllerTests { marginOfError); } + @Test + public void testEnabledState() throws Exception { + TestParoleListener paroleListener = new TestParoleListener(); + mController.addListener(paroleListener); + long lastUpdateTime; + + // Test that listeners are notified if enabled changes when the device is not in parole. + setChargingState(mController, false); + + // Start off not enabled. Device is effectively on permanent parole. + setAppIdleEnabled(mController, false); + + // Enable controller + paroleListener.rearmLatch(); + setAppIdleEnabled(mController, true); + paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2); + assertFalse(paroleListener.mOnParole); + lastUpdateTime = paroleListener.getLastParoleChangeTime(); + + paroleListener.rearmLatch(); + setAppIdleEnabled(mController, true); + paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2); + assertFalse(paroleListener.mOnParole); + // Make sure AppStandbyController doesn't notify listeners when there's no change. + assertEquals(lastUpdateTime, paroleListener.getLastParoleChangeTime()); + + // Disable controller + paroleListener.rearmLatch(); + setAppIdleEnabled(mController, false); + paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2); + assertTrue(paroleListener.mOnParole); + lastUpdateTime = paroleListener.getLastParoleChangeTime(); + + paroleListener.rearmLatch(); + setAppIdleEnabled(mController, false); + paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2); + assertTrue(paroleListener.mOnParole); + // Make sure AppStandbyController doesn't notify listeners when there's no change. + assertEquals(lastUpdateTime, paroleListener.getLastParoleChangeTime()); + + + // Test that listeners aren't notified if enabled status changes when the device is already + // in parole. + + // A device is in parole whenever it's charging. + setChargingState(mController, true); + + // Start off not enabled. + paroleListener.rearmLatch(); + setAppIdleEnabled(mController, false); + paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2); + assertTrue(paroleListener.mOnParole); + lastUpdateTime = paroleListener.getLastParoleChangeTime(); + + // Test that toggling doesn't notify the listener. + paroleListener.rearmLatch(); + setAppIdleEnabled(mController, true); + paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2); + assertTrue(paroleListener.mOnParole); + assertEquals(lastUpdateTime, paroleListener.getLastParoleChangeTime()); + + paroleListener.rearmLatch(); + setAppIdleEnabled(mController, false); + paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2); + assertTrue(paroleListener.mOnParole); + assertEquals(lastUpdateTime, paroleListener.getLastParoleChangeTime()); + } + private void assertTimeout(AppStandbyController controller, long elapsedTime, int bucket) { mInjector.mElapsedRealtime = elapsedTime; controller.checkIdleStates(USER_ID); diff --git a/services/usage/java/com/android/server/usage/AppStandbyController.java b/services/usage/java/com/android/server/usage/AppStandbyController.java index 9c62700a9118..e380d7a6808b 100644 --- a/services/usage/java/com/android/server/usage/AppStandbyController.java +++ b/services/usage/java/com/android/server/usage/AppStandbyController.java @@ -30,18 +30,19 @@ import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_EXEMPTED_SYNC import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_MOVE_TO_BACKGROUND; import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_MOVE_TO_FOREGROUND; import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_NOTIFICATION_SEEN; +import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_SLICE_PINNED; +import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_SLICE_PINNED_PRIV; import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_SYNC_ADAPTER; import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_SYSTEM_INTERACTION; import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_SYSTEM_UPDATE; import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_USER_INTERACTION; -import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_SLICE_PINNED; -import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_SLICE_PINNED_PRIV; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_EXEMPTED; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_FREQUENT; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_NEVER; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET; + import static com.android.server.SystemService.PHASE_BOOT_COMPLETED; import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY; @@ -340,14 +341,21 @@ public class AppStandbyController { } void setAppIdleEnabled(boolean enabled) { - mAppIdleEnabled = enabled; + synchronized (mAppIdleLock) { + if (mAppIdleEnabled != enabled) { + final boolean oldParoleState = isParoledOrCharging(); + mAppIdleEnabled = enabled; + if (isParoledOrCharging() != oldParoleState) { + postParoleStateChanged(); + } + } + } } public void onBootPhase(int phase) { mInjector.onBootPhase(phase); if (phase == PHASE_SYSTEM_SERVICES_READY) { Slog.d(TAG, "Setting app idle enabled state"); - setAppIdleEnabled(mInjector.isAppIdleEnabled()); // Observe changes to the threshold SettingsObserver settingsObserver = new SettingsObserver(mHandler); settingsObserver.registerObserver(); @@ -1807,8 +1815,6 @@ public class AppStandbyController { mContext.getContentResolver(), Global.APP_IDLE_CONSTANTS)); } - // Check if app_idle_enabled has changed - setAppIdleEnabled(mInjector.isAppIdleEnabled()); // Look at global settings for this. // TODO: Maybe apply different thresholds for different users. @@ -1880,6 +1886,10 @@ public class AppStandbyController { (KEY_STABLE_CHARGING_THRESHOLD, COMPRESS_TIME ? ONE_MINUTE : DEFAULT_STABLE_CHARGING_THRESHOLD); } + + // Check if app_idle_enabled has changed. Do this after getting the rest of the settings + // in case we need to change something based on the new values. + setAppIdleEnabled(mInjector.isAppIdleEnabled()); } long[] parseLongArray(String values, long[] defaults) { diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java index 2820836282a1..dcaa49996d0b 100644 --- a/telecomm/java/android/telecom/Call.java +++ b/telecomm/java/android/telecom/Call.java @@ -239,6 +239,30 @@ public final class Call { "android.telecom.event.HANDOVER_FAILED"; public static class Details { + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef( + prefix = { "DIRECTION_" }, + value = {DIRECTION_UNKNOWN, DIRECTION_INCOMING, DIRECTION_OUTGOING}) + public @interface CallDirection {} + + /** + * Indicates that the call is neither and incoming nor an outgoing call. This can be the + * case for calls reported directly by a {@link ConnectionService} in special cases such as + * call handovers. + */ + public static final int DIRECTION_UNKNOWN = -1; + + /** + * Indicates that the call is an incoming call. + */ + public static final int DIRECTION_INCOMING = 0; + + /** + * Indicates that the call is an outgoing call. + */ + public static final int DIRECTION_OUTGOING = 1; + /** Call can currently be put on hold or unheld. */ public static final int CAPABILITY_HOLD = 0x00000001; @@ -519,6 +543,7 @@ public final class Call { private final Bundle mIntentExtras; private final long mCreationTimeMillis; private final CallIdentification mCallIdentification; + private final @CallDirection int mCallDirection; /** * Whether the supplied capabilities supports the specified capability. @@ -838,6 +863,14 @@ public final class Call { return mCallIdentification; } + /** + * Indicates whether the call is an incoming or outgoing call. + * @return The call's direction. + */ + public @CallDirection int getCallDirection() { + return mCallDirection; + } + @Override public boolean equals(Object o) { if (o instanceof Details) { @@ -859,7 +892,8 @@ public final class Call { areBundlesEqual(mExtras, d.mExtras) && areBundlesEqual(mIntentExtras, d.mIntentExtras) && Objects.equals(mCreationTimeMillis, d.mCreationTimeMillis) && - Objects.equals(mCallIdentification, d.mCallIdentification); + Objects.equals(mCallIdentification, d.mCallIdentification) && + Objects.equals(mCallDirection, d.mCallDirection); } return false; } @@ -881,7 +915,8 @@ public final class Call { mExtras, mIntentExtras, mCreationTimeMillis, - mCallIdentification); + mCallIdentification, + mCallDirection); } /** {@hide} */ @@ -902,7 +937,8 @@ public final class Call { Bundle extras, Bundle intentExtras, long creationTimeMillis, - CallIdentification callIdentification) { + CallIdentification callIdentification, + int callDirection) { mTelecomCallId = telecomCallId; mHandle = handle; mHandlePresentation = handlePresentation; @@ -920,6 +956,7 @@ public final class Call { mIntentExtras = intentExtras; mCreationTimeMillis = creationTimeMillis; mCallIdentification = callIdentification; + mCallDirection = callDirection; } /** {@hide} */ @@ -941,7 +978,8 @@ public final class Call { parcelableCall.getExtras(), parcelableCall.getIntentExtras(), parcelableCall.getCreationTimeMillis(), - parcelableCall.getCallIdentification()); + parcelableCall.getCallIdentification(), + parcelableCall.getCallDirection()); } @Override diff --git a/telecomm/java/android/telecom/CallIdentification.java b/telecomm/java/android/telecom/CallIdentification.java index 97af06c1d64c..87834fd5109d 100644 --- a/telecomm/java/android/telecom/CallIdentification.java +++ b/telecomm/java/android/telecom/CallIdentification.java @@ -250,8 +250,8 @@ public final class CallIdentification implements Parcelable { mDetails = details; mPhoto = photo; mNuisanceConfidence = nuisanceConfidence; - mCallScreeningAppName = callScreeningPackageName; - mCallScreeningPackageName = callScreeningAppName; + mCallScreeningAppName = callScreeningAppName; + mCallScreeningPackageName = callScreeningPackageName; } private String mName; @@ -430,4 +430,22 @@ public final class CallIdentification implements Parcelable { return Objects.hash(mName, mDescription, mDetails, mPhoto, mNuisanceConfidence, mCallScreeningAppName, mCallScreeningPackageName); } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("[CallId mName="); + sb.append(Log.pii(mName)); + sb.append(", mDesc="); + sb.append(mDescription); + sb.append(", mDet="); + sb.append(mDetails); + sb.append(", conf="); + sb.append(mNuisanceConfidence); + sb.append(", appName="); + sb.append(mCallScreeningAppName); + sb.append(", pkgName="); + sb.append(mCallScreeningPackageName); + return sb.toString(); + } } diff --git a/telecomm/java/android/telecom/CallScreeningService.java b/telecomm/java/android/telecom/CallScreeningService.java index be96b3cac6f6..826ad82dfbb2 100644 --- a/telecomm/java/android/telecom/CallScreeningService.java +++ b/telecomm/java/android/telecom/CallScreeningService.java @@ -21,6 +21,7 @@ import android.annotation.SdkConstant; import android.app.Service; import android.content.ComponentName; import android.content.Intent; +import android.net.Uri; import android.os.Handler; import android.os.IBinder; import android.os.Looper; @@ -33,8 +34,9 @@ import com.android.internal.telecom.ICallScreeningService; /** * This service can be implemented by the default dialer (see - * {@link TelecomManager#getDefaultDialerPackage()}) to allow or disallow incoming calls before - * they are shown to a user. + * {@link TelecomManager#getDefaultDialerPackage()}) or a third party app to allow or disallow + * incoming calls before they are shown to a user. This service can also provide + * {@link CallIdentification} information for calls. * <p> * Below is an example manifest registration for a {@code CallScreeningService}. * <pre> @@ -56,6 +58,34 @@ import com.android.internal.telecom.ICallScreeningService; * information about a {@link Call.Details call} which will be shown to the user in the * Dialer app.</li> * </ol> + * <p> + * <h2>Becoming the {@link CallScreeningService}</h2> + * Telecom will bind to a single app chosen by the user which implements the + * {@link CallScreeningService} API when there are new incoming and outgoing calls. + * <p> + * The code snippet below illustrates how your app can request that it fills the call screening + * role. + * <pre> + * {@code + * private static final int REQUEST_ID = 1; + * + * public void requestRole() { + * RoleManager roleManager = (RoleManager) getSystemService(ROLE_SERVICE); + * Intent intent = roleManager.createRequestRoleIntent("android.app.role.CALL_SCREENING_APP"); + * startActivityForResult(intent, REQUEST_ID); + * } + * + * @Override + * public void onActivityResult(int requestCode, int resultCode, Intent data) { + * if (requestCode == REQUEST_ID) { + * if (resultCode == android.app.Activity.RESULT_OK) { + * // Your app is now the call screening app + * } else { + * // Your app is not the call screening app + * } + * } + * } + * </pre> */ public abstract class CallScreeningService extends Service { /** @@ -222,30 +252,46 @@ public abstract class CallScreeningService extends Service { } /** - * Called when a new incoming call is added. - * {@link CallScreeningService#respondToCall(Call.Details, CallScreeningService.CallResponse)} - * should be called to allow or disallow the call. + * Called when a new incoming or outgoing call is added which is not in the user's contact list. + * <p> + * A {@link CallScreeningService} must indicate whether an incoming call is allowed or not by + * calling + * {@link CallScreeningService#respondToCall(Call.Details, CallScreeningService.CallResponse)}. + * Your app can tell if a call is an incoming call by checking to see if + * {@link Call.Details#getCallDirection()} is {@link Call.Details#DIRECTION_INCOMING}. + * <p> + * For incoming or outgoing calls, the {@link CallScreeningService} can call + * {@link #provideCallIdentification(Call.Details, CallIdentification)} in order to provide + * {@link CallIdentification} for the call. * <p> * Note: The {@link Call.Details} instance provided to a call screening service will only have * the following properties set. The rest of the {@link Call.Details} properties will be set to * their default value or {@code null}. * <ul> - * <li>{@link Call.Details#getState()}</li> + * <li>{@link Call.Details#getCallDirection()}</li> * <li>{@link Call.Details#getConnectTimeMillis()}</li> * <li>{@link Call.Details#getCreationTimeMillis()}</li> * <li>{@link Call.Details#getHandle()}</li> * <li>{@link Call.Details#getHandlePresentation()}</li> * </ul> + * <p> + * Only calls where the {@link Call.Details#getHandle() handle} {@link Uri#getScheme() scheme} + * is {@link PhoneAccount#SCHEME_TEL} are passed for call + * screening. Further, only calls which are not in the user's contacts are passed for + * screening. For outgoing calls, no post-dial digits are passed. * - * @param callDetails Information about a new incoming call, see {@link Call.Details}. + * @param callDetails Information about a new call, see {@link Call.Details}. */ public abstract void onScreenCall(@NonNull Call.Details callDetails); /** - * Responds to the given call, either allowing it or disallowing it. + * Responds to the given incoming call, either allowing it or disallowing it. * <p> * The {@link CallScreeningService} calls this method to inform the system whether the call * should be silently blocked or not. + * <p> + * Calls to this method are ignored unless the {@link Call.Details#getCallDirection()} is + * {@link Call.Details#DIRECTION_INCOMING}. * * @param callDetails The call to allow. * <p> diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java index 1aeeca73c0b9..cea2fbfa588c 100644 --- a/telecomm/java/android/telecom/InCallService.java +++ b/telecomm/java/android/telecom/InCallService.java @@ -40,11 +40,30 @@ import java.util.Collections; import java.util.List; /** - * This service is implemented by any app that wishes to provide the user-interface for managing - * phone calls. Telecom binds to this service while there exists a live (active or incoming) call, - * and uses it to notify the in-call app of any live and recently disconnected calls. An app must - * first be set as the default phone app (See {@link TelecomManager#getDefaultDialerPackage()}) - * before the telecom service will bind to its {@code InCallService} implementation. + * This service is implemented by an app that wishes to provide functionality for managing + * phone calls. + * <p> + * There are three types of apps which Telecom can bind to when there exists a live (active or + * incoming) call: + * <ol> + * <li>Default Dialer/Phone app - the default dialer/phone app is one which provides the + * in-call user interface while the device is in a call. A device is bundled with a system + * provided default dialer/phone app. The user may choose a single app to take over this role + * from the system app.</li> + * <li>Default Car-mode Dialer/Phone app - the default car-mode dialer/phone app is one which + * provides the in-call user interface while the device is in a call and the device is in car + * mode. The user may choose a single app to fill this role.</li> + * <li>Call Companion app - a call companion app is one which provides no user interface itself, + * but exposes call information to another display surface, such as a wearable device. The + * user may choose multiple apps to fill this role.</li> + * </ol> + * <p> + * Apps which wish to fulfill one of the above roles use the {@code android.app.role.RoleManager} + * to request that they fill the desired role. + * + * <h2>Becoming the Default Phone App</h2> + * An app filling the role of the default phone app provides a user interface while the device is in + * a call, and the device is not in car mode. * <p> * Below is an example manifest registration for an {@code InCallService}. The meta-data * {@link TelecomManager#METADATA_IN_CALL_SERVICE_UI} indicates that this particular @@ -82,12 +101,34 @@ import java.util.List; * } * </pre> * <p> - * When a user installs your application and runs it for the first time, you should prompt the user - * to see if they would like your application to be the new default phone app. See the - * {@link TelecomManager#ACTION_CHANGE_DEFAULT_DIALER} intent documentation for more information on - * how to do this. + * When a user installs your application and runs it for the first time, you should use the + * {@code android.app.role.RoleManager} to prompt the user to see if they would like your app to + * be the new default phone app. + * <p id="requestRole"> + * The code below shows how your app can request to become the default phone/dialer app: + * <pre> + * {@code + * private static final int REQUEST_ID = 1; + * + * public void requestRole() { + * RoleManager roleManager = (RoleManager) getSystemService(ROLE_SERVICE); + * Intent intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_DIALER); + * startActivityForResult(intent, REQUEST_ID); + * } + * + * @Override + * public void onActivityResult(int requestCode, int resultCode, Intent data) { + * if (requestCode == REQUEST_ID) { + * if (resultCode == android.app.Activity.RESULT_OK) { + * // Your app is now the default dialer app + * } else { + * // Your app is not the default dialer app + * } + * } + * } + * </pre> * <p id="incomingCallNotification"> - * <h2>Showing the Incoming Call Notification</h2> + * <h3>Showing the Incoming Call Notification</h3> * When your app receives a new incoming call via {@link InCallService#onCallAdded(Call)}, it is * responsible for displaying an incoming call UI for the incoming call. It should do this using * {@link android.app.NotificationManager} APIs to post a new incoming call notification. @@ -121,7 +162,7 @@ import java.util.List; * heads-up notification if the user is actively using the phone. When the user is not using the * phone, your full-screen incoming call UI is used instead. * For example: - * <pre><code> + * <pre><code>{@code * // Create an intent which triggers your fullscreen incoming call user interface. * Intent intent = new Intent(Intent.ACTION_MAIN, null); * intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_NEW_TASK); @@ -151,7 +192,49 @@ import java.util.List; * NotificationManager notificationManager = mContext.getSystemService( * NotificationManager.class); * notificationManager.notify(YOUR_CHANNEL_ID, YOUR_TAG, YOUR_ID, builder.build()); - * </code></pre> + * }</pre> + * <p> + * <h2>Becoming the Default Car-mode Phone App</h2> + * An app filling the role of the default car-mode dialer/phone app provides a user interface while + * the device is in a call, and in car mode. See + * {@link android.app.UiModeManager#ACTION_ENTER_CAR_MODE} for more information about car mode. + * When the device is in car mode, Telecom binds to the default car-mode dialer/phone app instead + * of the usual dialer/phone app. + * <p> + * Similar to the requirements for becoming the default dialer/phone app, your app must declare a + * manifest entry for its {@link InCallService} implementation. Your manifest entry should ensure + * the following conditions are met: + * <ul> + * <li>Do NOT declare the {@link TelecomManager#METADATA_IN_CALL_SERVICE_UI} metadata.</li> + * <li>Set the {@link TelecomManager#METADATA_IN_CALL_SERVICE_CAR_MODE_UI} metadata to + * {@code true}<li> + * <li>Your app must request the permission + * {@link android.Manifest.permission.CALL_COMPANION_APP}.</li> + * </ul> + * <p> + * Your app should request to fill the role {@code android.app.role.CAR_MODE_DIALER_APP} in order to + * become the default (see <a href="#requestRole">above</a> for how to request your app fills this + * role). + * + * <h2>Becoming a Call Companion App</h2> + * An app which fills the companion app role does not directly provide a user interface while the + * device is in a call. Instead, it is typically used to relay information about calls to another + * display surface, such as a wearable device. + * <p> + * Similar to the requirements for becoming the default dialer/phone app, your app must declare a + * manifest entry for its {@link InCallService} implementation. Your manifest entry should + * ensure the following conditions are met: + * <ul> + * <li>Do NOT declare the {@link TelecomManager#METADATA_IN_CALL_SERVICE_UI} metadata.</li> + * <li>Do NOT declare the {@link TelecomManager#METADATA_IN_CALL_SERVICE_CAR_MODE_UI} + * metadata.</li> + * <li>Your app must request the permission + * {@link android.Manifest.permission.CALL_COMPANION_APP}.</li> + * </ul> + * <p> + * Your app should request to fill the role {@code android.app.role.CALL_COMPANION_APP} in order to + * become a call companion app (see <a href="#requestRole">above</a> for how to request your app + * fills this role). */ public abstract class InCallService extends Service { diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java index 911786e455c2..f7dec83c3ace 100644 --- a/telecomm/java/android/telecom/ParcelableCall.java +++ b/telecomm/java/android/telecom/ParcelableCall.java @@ -24,6 +24,7 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; +import android.telecom.Call.Details.CallDirection; import java.util.ArrayList; import java.util.Collections; @@ -64,6 +65,7 @@ public final class ParcelableCall implements Parcelable { private final Bundle mExtras; private final long mCreationTimeMillis; private final CallIdentification mCallIdentification; + private final int mCallDirection; public ParcelableCall( String id, @@ -92,7 +94,8 @@ public final class ParcelableCall implements Parcelable { Bundle intentExtras, Bundle extras, long creationTimeMillis, - CallIdentification callIdentification) { + CallIdentification callIdentification, + int callDirection) { mId = id; mState = state; mDisconnectCause = disconnectCause; @@ -120,6 +123,7 @@ public final class ParcelableCall implements Parcelable { mExtras = extras; mCreationTimeMillis = creationTimeMillis; mCallIdentification = callIdentification; + mCallDirection = callDirection; } /** The unique ID of the call. */ @@ -318,6 +322,13 @@ public final class ParcelableCall implements Parcelable { return mCallIdentification; } + /** + * Indicates whether the call is an incoming or outgoing call. + */ + public @CallDirection int getCallDirection() { + return mCallDirection; + } + /** Responsible for creating ParcelableCall objects for deserialized Parcels. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public static final Parcelable.Creator<ParcelableCall> CREATOR = @@ -356,6 +367,7 @@ public final class ParcelableCall implements Parcelable { ParcelableRttCall rttCall = source.readParcelable(classLoader); long creationTimeMillis = source.readLong(); CallIdentification callIdentification = source.readParcelable(classLoader); + int callDirection = source.readInt(); return new ParcelableCall( id, state, @@ -383,7 +395,8 @@ public final class ParcelableCall implements Parcelable { intentExtras, extras, creationTimeMillis, - callIdentification); + callIdentification, + callDirection); } @Override @@ -429,6 +442,7 @@ public final class ParcelableCall implements Parcelable { destination.writeParcelable(mRttCall, 0); destination.writeLong(mCreationTimeMillis); destination.writeParcelable(mCallIdentification, 0); + destination.writeInt(mCallDirection); } @Override diff --git a/telephony/java/android/telephony/NetworkService.java b/telephony/java/android/telephony/NetworkService.java index 4bca404d9444..6c45cc4ef3b8 100644 --- a/telephony/java/android/telephony/NetworkService.java +++ b/telephony/java/android/telephony/NetworkService.java @@ -16,6 +16,7 @@ package android.telephony; +import android.annotation.NonNull; import android.annotation.SystemApi; import android.app.Service; import android.content.Intent; @@ -112,13 +113,13 @@ public abstract class NetworkService extends Service { mSlotId, 0, null).sendToTarget(); } - private void registerForStateChanged(INetworkServiceCallback callback) { + private void registerForStateChanged(@NonNull INetworkServiceCallback callback) { synchronized (mNetworkRegistrationStateChangedCallbacks) { mNetworkRegistrationStateChangedCallbacks.add(callback); } } - private void unregisterForStateChanged(INetworkServiceCallback callback) { + private void unregisterForStateChanged(@NonNull INetworkServiceCallback callback) { synchronized (mNetworkRegistrationStateChangedCallbacks) { mNetworkRegistrationStateChangedCallbacks.remove(callback); } diff --git a/telephony/java/android/telephony/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java index af324debbd57..fea1b7b08a20 100644 --- a/telephony/java/android/telephony/PhoneStateListener.java +++ b/telephony/java/android/telephony/PhoneStateListener.java @@ -28,6 +28,7 @@ import android.os.Handler; import android.os.HandlerExecutor; import android.os.Looper; import android.telephony.emergency.EmergencyNumber; +import android.telephony.ims.ImsReasonInfo; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.IPhoneStateListener; @@ -347,6 +348,20 @@ public class PhoneStateListener { @SystemApi public static final int LISTEN_CALL_ATTRIBUTES_CHANGED = 0x04000000; + /** + * Listen for IMS call disconnect causes which contains + * {@link android.telephony.ims.ImsReasonInfo} + * + * {@more} + * Requires Permission: {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE + * READ_PRECISE_PHONE_STATE} + * + * @see #onImsCallDisconnectCauseChanged(ImsReasonInfo) + * @hide + */ + @SystemApi + public static final int LISTEN_IMS_CALL_DISCONNECT_CAUSES = 0x08000000; + /* * Subscription used to listen to the phone state changes * @hide @@ -578,6 +593,17 @@ public class PhoneStateListener { } /** + * Callback invoked when Ims call disconnect cause changes. + * @param imsReasonInfo {@link ImsReasonInfo} contains details on why IMS call failed. + * + * @hide + */ + @SystemApi + public void onImsCallDisconnectCauseChanged(@NonNull ImsReasonInfo imsReasonInfo) { + // default implementation empty + } + + /** * Callback invoked when data connection state changes with precise information. * @param dataConnectionState {@link PreciseDataConnectionState} * @@ -981,6 +1007,16 @@ public class PhoneStateListener { Binder.withCleanCallingIdentity( () -> mExecutor.execute(() -> psl.onPreferredDataSubIdChanged(subId))); } + + public void onImsCallDisconnectCauseChanged(ImsReasonInfo disconnectCause) { + PhoneStateListener psl = mPhoneStateListenerWeakRef.get(); + if (psl == null) return; + + Binder.withCleanCallingIdentity( + () -> mExecutor.execute( + () -> psl.onImsCallDisconnectCauseChanged(disconnectCause))); + + } } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 5c86efb2d678..24d790a663c9 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -349,41 +349,30 @@ public class TelephonyManager { * Returns 0 if none of voice, sms, data is not supported * Returns 1 for Single standby mode (Single SIM functionality) * Returns 2 for Dual standby mode.(Dual SIM functionality) + * Returns 3 for Tri standby mode.(Tri SIM functionality) */ public int getPhoneCount() { - int phoneCount = 1; - switch (getMultiSimConfiguration()) { - case UNKNOWN: - // if voice or sms or data is supported, return 1 otherwise 0 - if (isVoiceCapable() || isSmsCapable()) { - phoneCount = 1; - } else { - // todo: try to clean this up further by getting rid of the nested conditions - if (mContext == null) { - phoneCount = 1; - } else { - // check for data support - ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService( - Context.CONNECTIVITY_SERVICE); - if (cm == null) { - phoneCount = 1; - } else { - if (cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)) { - phoneCount = 1; - } else { - phoneCount = 0; - } - } - } + int phoneCount = 0; + + // check for voice and data support, 0 if not supported + if (!isVoiceCapable() && !isSmsCapable()) { + ConnectivityManager cm = (ConnectivityManager) mContext.getSystemService( + Context.CONNECTIVITY_SERVICE); + if (cm != null) { + if (!cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)) { + return phoneCount; } - break; - case DSDS: - case DSDA: - phoneCount = PhoneConstants.MAX_PHONE_COUNT_DUAL_SIM; - break; - case TSTS: - phoneCount = PhoneConstants.MAX_PHONE_COUNT_TRI_SIM; - break; + } + } + + phoneCount = 1; + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + phoneCount = telephony.getNumOfActiveSims(); + } + } catch (RemoteException ex) { + Rlog.e(TAG, "getNumOfActiveSims RemoteException", ex); } return phoneCount; } @@ -10029,4 +10018,72 @@ public class TelephonyManager { } return true; } + + /** + * Broadcast intent action for network country code changes. + * + * <p> + * The {@link #EXTRA_NETWORK_COUNTRY} extra indicates the country code of the current + * network returned by {@link #getNetworkCountryIso()}. + * + * @see #EXTRA_NETWORK_COUNTRY + * @see #getNetworkCountryIso() + */ + public static final String ACTION_NETWORK_COUNTRY_CHANGED = + "android.telephony.action.NETWORK_COUNTRY_CHANGED"; + + /** + * The extra used with an {@link #ACTION_NETWORK_COUNTRY_CHANGED} to specify the + * the country code in ISO 3166 format. + * <p class="note"> + * Retrieve with {@link android.content.Intent#getStringExtra(String)}. + */ + public static final String EXTRA_NETWORK_COUNTRY = + "android.telephony.extra.NETWORK_COUNTRY"; + /** + * Switch configs to enable multi-sim or switch back to single-sim + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the + * calling app has carrier privileges (see {@link #hasCarrierPrivileges}). + * @param numOfSims number of live SIMs we want to switch to + * @throws android.os.RemoteException + */ + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public void switchMultiSimConfig(int numOfSims) { + //only proceed if multi-sim is not restricted + if (isMultisimCarrierRestricted()) { + Rlog.e(TAG, "switchMultiSimConfig not possible. It is restricted."); + return; + } + + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + telephony.switchMultiSimConfig(numOfSims); + } + } catch (RemoteException ex) { + Rlog.e(TAG, "switchMultiSimConfig RemoteException", ex); + } + } + + /** + * Get whether reboot is required or not after making changes to modem configurations. + * @Return {@code True} if reboot is required after making changes to modem configurations, + * otherwise return {@code False}. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public boolean isRebootRequiredForModemConfigChange() { + try { + ITelephony service = getITelephony(); + if (service != null) { + return service.isRebootRequiredForModemConfigChange(); + } + } catch (RemoteException e) { + Log.e(TAG, "isRebootRequiredForModemConfigChange RemoteException", e); + } + return false; + } } diff --git a/telephony/java/android/telephony/data/DataService.java b/telephony/java/android/telephony/data/DataService.java index 74d1e838f186..79572b9706a9 100644 --- a/telephony/java/android/telephony/data/DataService.java +++ b/telephony/java/android/telephony/data/DataService.java @@ -157,7 +157,10 @@ public abstract class DataService extends Service { @Nullable LinkProperties linkProperties, @Nullable DataServiceCallback callback) { // The default implementation is to return unsupported. - callback.onSetupDataCallComplete(DataServiceCallback.RESULT_ERROR_UNSUPPORTED, null); + if (callback != null) { + callback.onSetupDataCallComplete(DataServiceCallback.RESULT_ERROR_UNSUPPORTED, + null); + } } /** @@ -176,7 +179,9 @@ public abstract class DataService extends Service { public void deactivateDataCall(int cid, @DeactivateDataReason int reason, @Nullable DataServiceCallback callback) { // The default implementation is to return unsupported. - callback.onDeactivateDataCallComplete(DataServiceCallback.RESULT_ERROR_UNSUPPORTED); + if (callback != null) { + callback.onDeactivateDataCallComplete(DataServiceCallback.RESULT_ERROR_UNSUPPORTED); + } } /** @@ -190,7 +195,10 @@ public abstract class DataService extends Service { public void setInitialAttachApn(DataProfile dataProfile, boolean isRoaming, @Nullable DataServiceCallback callback) { // The default implementation is to return unsupported. - callback.onSetInitialAttachApnComplete(DataServiceCallback.RESULT_ERROR_UNSUPPORTED); + if (callback != null) { + callback.onSetInitialAttachApnComplete( + DataServiceCallback.RESULT_ERROR_UNSUPPORTED); + } } /** @@ -206,7 +214,9 @@ public abstract class DataService extends Service { public void setDataProfile(List<DataProfile> dps, boolean isRoaming, @Nullable DataServiceCallback callback) { // The default implementation is to return unsupported. - callback.onSetDataProfileComplete(DataServiceCallback.RESULT_ERROR_UNSUPPORTED); + if (callback != null) { + callback.onSetDataProfileComplete(DataServiceCallback.RESULT_ERROR_UNSUPPORTED); + } } /** diff --git a/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl b/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl index 3dbebe832fac..322ce45797ec 100644 --- a/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl +++ b/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl @@ -27,6 +27,7 @@ import android.telephony.PreciseDataConnectionState; import android.telephony.ServiceState; import android.telephony.SignalStrength; import android.telephony.emergency.EmergencyNumber; +import android.telephony.ims.ImsReasonInfo; oneway interface IPhoneStateListener { void onServiceStateChanged(in ServiceState serviceState); @@ -58,5 +59,6 @@ oneway interface IPhoneStateListener { void onCallAttributesChanged(in CallAttributes callAttributes); void onEmergencyNumberListChanged(in Map emergencyNumberList); void onCallDisconnectCauseChanged(in int disconnectCause, in int preciseDisconnectCause); + void onImsCallDisconnectCauseChanged(in ImsReasonInfo imsReasonInfo); } diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index a12792a8cfa6..c7061dfe25f1 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -1829,4 +1829,19 @@ interface ITelephony { * @hide */ boolean isMultisimCarrierRestricted(); + + /** + * Switch configs to enable multi-sim or switch back to single-sim + * @hide + */ + void switchMultiSimConfig(int numOfSims); + /** + * Get how many modems have been activated on the phone + * @hide + */ + int getNumOfActiveSims(); + /** + * Get if reboot is required upon altering modems configurations + */ + boolean isRebootRequiredForModemConfigChange(); } diff --git a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl index 2be1f419db4d..e9eba324acb0 100644 --- a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl @@ -22,6 +22,7 @@ import android.net.NetworkCapabilities; import android.os.Bundle; import android.telephony.CallQuality; import android.telephony.CellInfo; +import android.telephony.ims.ImsReasonInfo; import android.telephony.PhoneCapability; import android.telephony.PhysicalChannelConfig; import android.telephony.ServiceState; @@ -84,4 +85,5 @@ interface ITelephonyRegistry { void notifyRadioPowerStateChanged(in int state); void notifyEmergencyNumberList(); void notifyCallQualityChanged(in CallQuality callQuality, int phoneId); + void notifyImsDisconnectCause(int subId, in ImsReasonInfo imsReasonInfo); } diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java index f901c0e29f9b..08e30dedca74 100644 --- a/telephony/java/com/android/internal/telephony/RILConstants.java +++ b/telephony/java/com/android/internal/telephony/RILConstants.java @@ -404,6 +404,7 @@ public interface RILConstants { int RIL_REQUEST_SET_PREFERRED_DATA_MODEM = 204; int RIL_REQUEST_EMERGENCY_DIAL = 205; int RIL_REQUEST_GET_PHONE_CAPABILITY = 206; + int RIL_REQUEST_SWITCH_DUAL_SIM_CONFIG = 207; /* Responses begin */ int RIL_RESPONSE_ACKNOWLEDGEMENT = 800; diff --git a/telephony/java/com/android/internal/telephony/TelephonyProperties.java b/telephony/java/com/android/internal/telephony/TelephonyProperties.java index 6567ea764b50..603c4c2870d7 100644 --- a/telephony/java/com/android/internal/telephony/TelephonyProperties.java +++ b/telephony/java/com/android/internal/telephony/TelephonyProperties.java @@ -194,6 +194,13 @@ public interface TelephonyProperties */ static final String PROPERTY_MULTI_SIM_CONFIG = "persist.radio.multisim.config"; + /** + * Property to indicate if reboot is required when changing modems configurations + * Type: String(true, false) default is false; most devices don't need reboot + */ + String PROPERTY_REBOOT_REQUIRED_ON_MODEM_CHANGE = + "persist.radio.reboot_on_modem_change"; + /** * Property to store default subscription. */ diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 036c5dcc8926..ebfb69e1ad77 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -906,6 +906,7 @@ public class ConnectivityServiceTest { mNetworkCapabilities.set(mMockNetworkAgent.getNetworkCapabilities()); mConnected = true; mConfig = new VpnConfig(); + mConfig.isMetered = false; } @Override diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java index 5b17224e41e5..46de3d0608ff 100644 --- a/tests/net/java/com/android/server/connectivity/VpnTest.java +++ b/tests/net/java/com/android/server/connectivity/VpnTest.java @@ -168,6 +168,8 @@ public class VpnTest { ApplicationInfo applicationInfo = new ApplicationInfo(); applicationInfo.targetSdkVersion = VERSION_CODES.CUR_DEVELOPMENT; when(mContext.getApplicationInfo()).thenReturn(applicationInfo); + when(mPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt())) + .thenReturn(applicationInfo); doNothing().when(mNetService).registerObserver(any()); } @@ -544,23 +546,28 @@ public class VpnTest { final Network wifi = new Network(2); final Map<Network, NetworkCapabilities> networks = new HashMap<>(); - networks.put(mobile, new NetworkCapabilities() - .addTransportType(TRANSPORT_CELLULAR) - .addCapability(NET_CAPABILITY_INTERNET) - .addCapability(NET_CAPABILITY_NOT_METERED) - .addCapability(NET_CAPABILITY_NOT_CONGESTED) - .setLinkDownstreamBandwidthKbps(10)); - networks.put(wifi, new NetworkCapabilities() - .addTransportType(TRANSPORT_WIFI) - .addCapability(NET_CAPABILITY_INTERNET) - .addCapability(NET_CAPABILITY_NOT_ROAMING) - .addCapability(NET_CAPABILITY_NOT_CONGESTED) - .setLinkUpstreamBandwidthKbps(20)); + networks.put( + mobile, + new NetworkCapabilities() + .addTransportType(TRANSPORT_CELLULAR) + .addCapability(NET_CAPABILITY_INTERNET) + .addCapability(NET_CAPABILITY_NOT_CONGESTED) + .setLinkDownstreamBandwidthKbps(10)); + networks.put( + wifi, + new NetworkCapabilities() + .addTransportType(TRANSPORT_WIFI) + .addCapability(NET_CAPABILITY_INTERNET) + .addCapability(NET_CAPABILITY_NOT_METERED) + .addCapability(NET_CAPABILITY_NOT_ROAMING) + .addCapability(NET_CAPABILITY_NOT_CONGESTED) + .setLinkUpstreamBandwidthKbps(20)); setMockedNetworks(networks); final NetworkCapabilities caps = new NetworkCapabilities(); - Vpn.updateCapabilities(mConnectivityManager, new Network[] { }, caps); + Vpn.updateCapabilities( + mConnectivityManager, new Network[] {}, caps, false /* isAlwaysMetered */); assertTrue(caps.hasTransport(TRANSPORT_VPN)); assertFalse(caps.hasTransport(TRANSPORT_CELLULAR)); assertFalse(caps.hasTransport(TRANSPORT_WIFI)); @@ -570,17 +577,33 @@ public class VpnTest { assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_ROAMING)); assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_CONGESTED)); - Vpn.updateCapabilities(mConnectivityManager, new Network[] { mobile }, caps); + Vpn.updateCapabilities( + mConnectivityManager, + new Network[] {mobile}, + caps, + false /* isAlwaysMetered */); assertTrue(caps.hasTransport(TRANSPORT_VPN)); assertTrue(caps.hasTransport(TRANSPORT_CELLULAR)); assertFalse(caps.hasTransport(TRANSPORT_WIFI)); assertEquals(10, caps.getLinkDownstreamBandwidthKbps()); assertEquals(LINK_BANDWIDTH_UNSPECIFIED, caps.getLinkUpstreamBandwidthKbps()); - assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_METERED)); + assertFalse(caps.hasCapability(NET_CAPABILITY_NOT_METERED)); assertFalse(caps.hasCapability(NET_CAPABILITY_NOT_ROAMING)); assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_CONGESTED)); - Vpn.updateCapabilities(mConnectivityManager, new Network[] { wifi }, caps); + Vpn.updateCapabilities( + mConnectivityManager, new Network[] {wifi}, caps, false /* isAlwaysMetered */); + assertTrue(caps.hasTransport(TRANSPORT_VPN)); + assertFalse(caps.hasTransport(TRANSPORT_CELLULAR)); + assertTrue(caps.hasTransport(TRANSPORT_WIFI)); + assertEquals(LINK_BANDWIDTH_UNSPECIFIED, caps.getLinkDownstreamBandwidthKbps()); + assertEquals(20, caps.getLinkUpstreamBandwidthKbps()); + assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_METERED)); + assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_ROAMING)); + assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_CONGESTED)); + + Vpn.updateCapabilities( + mConnectivityManager, new Network[] {wifi}, caps, true /* isAlwaysMetered */); assertTrue(caps.hasTransport(TRANSPORT_VPN)); assertFalse(caps.hasTransport(TRANSPORT_CELLULAR)); assertTrue(caps.hasTransport(TRANSPORT_WIFI)); @@ -590,7 +613,11 @@ public class VpnTest { assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_ROAMING)); assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_CONGESTED)); - Vpn.updateCapabilities(mConnectivityManager, new Network[] { mobile, wifi }, caps); + Vpn.updateCapabilities( + mConnectivityManager, + new Network[] {mobile, wifi}, + caps, + false /* isAlwaysMetered */); assertTrue(caps.hasTransport(TRANSPORT_VPN)); assertTrue(caps.hasTransport(TRANSPORT_CELLULAR)); assertTrue(caps.hasTransport(TRANSPORT_WIFI)); diff --git a/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java b/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java index 0f72229d38e6..ec286759354a 100644 --- a/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java +++ b/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java @@ -16,8 +16,16 @@ package com.android.server.connectivity.tethering; +import static android.net.ConnectivityManager.TETHERING_USB; +import static android.net.ConnectivityManager.TETHERING_WIFI; +import static android.net.ConnectivityManager.TETHER_ERROR_ENTITLEMENT_UNKONWN; +import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR; +import static android.net.ConnectivityManager.TETHER_ERROR_PROVISION_FAILED; + +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; @@ -27,12 +35,22 @@ import android.content.ContentResolver; import android.content.Context; import android.content.res.Resources; import android.net.util.SharedLog; +import android.os.Bundle; +import android.os.Message; import android.os.PersistableBundle; +import android.os.ResultReceiver; +import android.os.test.TestLooper; +import android.provider.Settings; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.telephony.CarrierConfigManager; +import android.test.mock.MockContentResolver; import com.android.internal.R; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; +import com.android.internal.util.test.BroadcastInterceptingContext; +import com.android.internal.util.test.FakeSettingsProvider; import com.android.server.connectivity.MockableSystemProperties; import org.junit.After; @@ -42,6 +60,10 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + @RunWith(AndroidJUnit4.class) @SmallTest public final class EntitlementManagerTest { @@ -51,7 +73,6 @@ public final class EntitlementManagerTest { @Mock private CarrierConfigManager mCarrierConfigManager; @Mock private Context mContext; - @Mock private ContentResolver mContent; @Mock private MockableSystemProperties mSystemProperties; @Mock private Resources mResources; @Mock private SharedLog mLog; @@ -59,15 +80,49 @@ public final class EntitlementManagerTest { // Like so many Android system APIs, these cannot be mocked because it is marked final. // We have to use the real versions. private final PersistableBundle mCarrierConfig = new PersistableBundle(); + private final TestLooper mLooper = new TestLooper(); + private Context mMockContext; + private MockContentResolver mContentResolver; + + private TestStateMachine mSM; + private WrappedEntitlementManager mEnMgr; - private EntitlementManager mEnMgr; + private class MockContext extends BroadcastInterceptingContext { + MockContext(Context base) { + super(base); + } + + @Override + public Resources getResources() { + return mResources; + } + + @Override + public ContentResolver getContentResolver() { + return mContentResolver; + } + } + + public class WrappedEntitlementManager extends EntitlementManager { + public int fakeEntitlementResult = TETHER_ERROR_ENTITLEMENT_UNKONWN; + public boolean everRunUiEntitlement = false; + + public WrappedEntitlementManager(Context ctx, StateMachine target, + SharedLog log, MockableSystemProperties systemProperties) { + super(ctx, target, log, systemProperties); + } + + @Override + protected void runUiTetherProvisioning(int type, ResultReceiver receiver) { + everRunUiEntitlement = true; + receiver.send(fakeEntitlementResult, null); + } + } @Before public void setUp() { MockitoAnnotations.initMocks(this); - when(mContext.getResources()).thenReturn(mResources); - when(mContext.getContentResolver()).thenReturn(mContent); when(mResources.getStringArray(R.array.config_tether_dhcp_range)) .thenReturn(new String[0]); when(mResources.getStringArray(R.array.config_tether_usb_regexs)) @@ -80,12 +135,21 @@ public final class EntitlementManagerTest { .thenReturn(new int[0]); when(mLog.forSubComponent(anyString())).thenReturn(mLog); - mEnMgr = new EntitlementManager(mContext, mLog, mSystemProperties); - mEnMgr.updateConfiguration(new TetheringConfiguration(mContext, mLog)); + mContentResolver = new MockContentResolver(); + mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); + mMockContext = new MockContext(mContext); + mSM = new TestStateMachine(); + mEnMgr = new WrappedEntitlementManager(mMockContext, mSM, mLog, mSystemProperties); + mEnMgr.updateConfiguration(new TetheringConfiguration(mMockContext, mLog)); } @After - public void tearDown() throws Exception {} + public void tearDown() throws Exception { + if (mSM != null) { + mSM.quit(); + mSM = null; + } + } private void setupForRequiredProvisioning() { // Produce some acceptable looking provision app setting if requested. @@ -104,7 +168,7 @@ public final class EntitlementManagerTest { @Test public void canRequireProvisioning() { setupForRequiredProvisioning(); - mEnMgr.updateConfiguration(new TetheringConfiguration(mContext, mLog)); + mEnMgr.updateConfiguration(new TetheringConfiguration(mMockContext, mLog)); assertTrue(mEnMgr.isTetherProvisioningRequired()); } @@ -113,7 +177,7 @@ public final class EntitlementManagerTest { setupForRequiredProvisioning(); when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE)) .thenReturn(null); - mEnMgr.updateConfiguration(new TetheringConfiguration(mContext, mLog)); + mEnMgr.updateConfiguration(new TetheringConfiguration(mMockContext, mLog)); // Couldn't get the CarrierConfigManager, but still had a declared provisioning app. // Therefore provisioning still be required. assertTrue(mEnMgr.isTetherProvisioningRequired()); @@ -123,7 +187,7 @@ public final class EntitlementManagerTest { public void toleratesCarrierConfigMissing() { setupForRequiredProvisioning(); when(mCarrierConfigManager.getConfig()).thenReturn(null); - mEnMgr.updateConfiguration(new TetheringConfiguration(mContext, mLog)); + mEnMgr.updateConfiguration(new TetheringConfiguration(mMockContext, mLog)); // We still have a provisioning app configured, so still require provisioning. assertTrue(mEnMgr.isTetherProvisioningRequired()); } @@ -133,12 +197,143 @@ public final class EntitlementManagerTest { setupForRequiredProvisioning(); when(mResources.getStringArray(R.array.config_mobile_hotspot_provision_app)) .thenReturn(null); - mEnMgr.updateConfiguration(new TetheringConfiguration(mContext, mLog)); + mEnMgr.updateConfiguration(new TetheringConfiguration(mMockContext, mLog)); assertFalse(mEnMgr.isTetherProvisioningRequired()); when(mResources.getStringArray(R.array.config_mobile_hotspot_provision_app)) .thenReturn(new String[] {"malformedApp"}); - mEnMgr.updateConfiguration(new TetheringConfiguration(mContext, mLog)); + mEnMgr.updateConfiguration(new TetheringConfiguration(mMockContext, mLog)); assertFalse(mEnMgr.isTetherProvisioningRequired()); } + @Test + public void testGetLastEntitlementCacheValue() throws Exception { + final CountDownLatch mCallbacklatch = new CountDownLatch(1); + // 1. Entitlement check is not required. + mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; + mEnMgr.everRunUiEntitlement = false; + ResultReceiver receiver = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + assertEquals(TETHER_ERROR_NO_ERROR, resultCode); + mCallbacklatch.countDown(); + } + }; + mEnMgr.getLatestTetheringEntitlementValue(TETHERING_WIFI, receiver, true); + callbackTimeoutHelper(mCallbacklatch); + assertFalse(mEnMgr.everRunUiEntitlement); + + setupForRequiredProvisioning(); + mEnMgr.updateConfiguration(new TetheringConfiguration(mMockContext, mLog)); + // 2. No cache value and don't need to run entitlement check. + mEnMgr.everRunUiEntitlement = false; + receiver = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + assertEquals(TETHER_ERROR_ENTITLEMENT_UNKONWN, resultCode); + mCallbacklatch.countDown(); + } + }; + mEnMgr.getLatestTetheringEntitlementValue(TETHERING_WIFI, receiver, false); + callbackTimeoutHelper(mCallbacklatch); + assertFalse(mEnMgr.everRunUiEntitlement); + // 3. No cache value and ui entitlement check is needed. + mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISION_FAILED; + mEnMgr.everRunUiEntitlement = false; + receiver = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + assertEquals(TETHER_ERROR_PROVISION_FAILED, resultCode); + mCallbacklatch.countDown(); + } + }; + mEnMgr.getLatestTetheringEntitlementValue(TETHERING_WIFI, receiver, true); + mLooper.dispatchAll(); + callbackTimeoutHelper(mCallbacklatch); + assertTrue(mEnMgr.everRunUiEntitlement); + // 4. Cache value is TETHER_ERROR_PROVISION_FAILED and don't need to run entitlement check. + mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; + mEnMgr.everRunUiEntitlement = false; + receiver = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + assertEquals(TETHER_ERROR_PROVISION_FAILED, resultCode); + mCallbacklatch.countDown(); + } + }; + mEnMgr.getLatestTetheringEntitlementValue(TETHERING_WIFI, receiver, false); + callbackTimeoutHelper(mCallbacklatch); + assertFalse(mEnMgr.everRunUiEntitlement); + // 5. Cache value is TETHER_ERROR_PROVISION_FAILED and ui entitlement check is needed. + mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; + mEnMgr.everRunUiEntitlement = false; + receiver = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + assertEquals(TETHER_ERROR_NO_ERROR, resultCode); + mCallbacklatch.countDown(); + } + }; + mEnMgr.getLatestTetheringEntitlementValue(TETHERING_WIFI, receiver, true); + mLooper.dispatchAll(); + callbackTimeoutHelper(mCallbacklatch); + assertTrue(mEnMgr.everRunUiEntitlement); + // 6. Cache value is TETHER_ERROR_NO_ERROR. + mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; + mEnMgr.everRunUiEntitlement = false; + receiver = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + assertEquals(TETHER_ERROR_NO_ERROR, resultCode); + mCallbacklatch.countDown(); + } + }; + mEnMgr.getLatestTetheringEntitlementValue(TETHERING_WIFI, receiver, true); + callbackTimeoutHelper(mCallbacklatch); + assertFalse(mEnMgr.everRunUiEntitlement); + // 7. Test get value for other downstream type. + mEnMgr.everRunUiEntitlement = false; + receiver = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + assertEquals(TETHER_ERROR_ENTITLEMENT_UNKONWN, resultCode); + mCallbacklatch.countDown(); + } + }; + mEnMgr.getLatestTetheringEntitlementValue(TETHERING_USB, receiver, false); + callbackTimeoutHelper(mCallbacklatch); + assertFalse(mEnMgr.everRunUiEntitlement); + } + + void callbackTimeoutHelper(final CountDownLatch latch) throws Exception { + if (!latch.await(1, TimeUnit.SECONDS)) { + fail("Timout, fail to recieve callback"); + } + } + public class TestStateMachine extends StateMachine { + public final ArrayList<Message> messages = new ArrayList<>(); + private final State mLoggingState = + new EntitlementManagerTest.TestStateMachine.LoggingState(); + + class LoggingState extends State { + @Override public void enter() { + messages.clear(); + } + + @Override public void exit() { + messages.clear(); + } + + @Override public boolean processMessage(Message msg) { + messages.add(msg); + return false; + } + } + + public TestStateMachine() { + super("EntitlementManagerTest.TestStateMachine", mLooper.getLooper()); + addState(mLoggingState); + setInitialState(mLoggingState); + super.start(); + } + } } |